From 086c60e5b98463e88936d6e159c667f4ac203375 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Mon, 11 Sep 2023 10:14:39 +1000 Subject: [PATCH 01/40] add slash commands guide --- docs/guide/interactions/slash_commands.rst | 928 ++++++++++++++++++ .../avatar_command_optional_preview.png | Bin 0 -> 15152 bytes .../app_commands/avatar_command_preview.png | Bin 0 -> 13979 bytes .../app_commands/colour_command_preview.png | Bin 0 -> 19642 bytes .../guide/app_commands/interaction_failed.png | Bin 0 -> 22530 bytes .../app_commands/meow_command_preview.png | Bin 0 -> 15572 bytes .../app_commands/repeat_command_described.png | Bin 0 -> 11974 bytes .../app_commands/repeat_command_preview.png | Bin 0 -> 7797 bytes .../repeat_command_wrong_type.png | Bin 0 -> 12344 bytes .../todo_group_nested_preview.png | Bin 0 -> 23355 bytes .../guide/app_commands/todo_group_preview.png | Bin 0 -> 14244 bytes 11 files changed, 928 insertions(+) create mode 100644 docs/guide/interactions/slash_commands.rst create mode 100644 docs/images/guide/app_commands/avatar_command_optional_preview.png create mode 100644 docs/images/guide/app_commands/avatar_command_preview.png create mode 100644 docs/images/guide/app_commands/colour_command_preview.png create mode 100644 docs/images/guide/app_commands/interaction_failed.png create mode 100644 docs/images/guide/app_commands/meow_command_preview.png create mode 100644 docs/images/guide/app_commands/repeat_command_described.png create mode 100644 docs/images/guide/app_commands/repeat_command_preview.png create mode 100644 docs/images/guide/app_commands/repeat_command_wrong_type.png create mode 100644 docs/images/guide/app_commands/todo_group_nested_preview.png create mode 100644 docs/images/guide/app_commands/todo_group_preview.png diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst new file mode 100644 index 000000000000..c9f6d4cb0fe7 --- /dev/null +++ b/docs/guide/interactions/slash_commands.rst @@ -0,0 +1,928 @@ +.. _discord_slash_commands: + +Slash commands +=============== + +Slash commands are one of Discord's primary methods of implementing a user-interface for bots. +Analogous to their name, they're previewed and invoked in the Discord client by beginning your message with a forward-slash. + +.. image:: /images/guide/app_commands/meow_command_preview.png + :width: 400 + +App commands are implemented within the :ref:`discord.app_commands ` package. +Code examples in this page will always assume the following two imports: + +.. code-block:: python3 + + import discord + from discord import app_commands + +Setting up +----------- + +To work with app commands, bots need the ``applications.commands`` scope. + +You can enable this scope when generating an OAuth2 URL for your bot, shown :ref:`here `. + +Defining a Tree +++++++++++++++++ + +First, a :class:`.app_commands.CommandTree` needs to be instaniated +- which is a container holding all of the bot's application commands. + +This class contains any relevant methods for managing app commands: + +- :meth:`.CommandTree.add_command` to add a command +- :meth:`.CommandTree.remove_command` to remove a command +- :meth:`.CommandTree.get_command` to find a specific command +- :meth:`.CommandTree.get_commands` to return all commands + +Preferably, this tree should be attached to the client instance to +play well with type checkers and to allow for easy access from anywhere in the code: + +.. code-block:: python3 + + class MyClient(discord.Client): + def __init__(self): + super().__init__(intents=discord.Intents.default()) + self.tree = app_commands.CommandTree(self) + + client = MyClient() + +.. note:: + + If your project instead uses :class:`.ext.commands.Bot` as the client instance, + a :class:`~discord.app_commands.CommandTree` has already been defined at :attr:`.Bot.tree`, + so this step is largely skipped. + +Creating a command +------------------- + +Slash commands are created by decorating an async function. +This function is then called whenever the slash command is invoked. + +For example, the following code responds with "meow" on invocation: + +.. code-block:: python3 + + @client.tree.command() + async def meow(interaction: discord.Interaction): + """Meow meow meow""" + + await interaction.response.send_message("meow") + +Functions of this pattern are called callbacks, since their execution is +relinquished to the library to be called later. + +Callbacks always have one parameter, ``interaction``, +representing the :class:`discord.Interaction` that is received when the command is invoked. + +There are two main decorators to use when creating a command: + +- :meth:`tree.command() <.CommandTree.command>` (as seen above) +- :func:`.app_commands.command` + +Both decorators wrap an async function into a :class:`~.app_commands.Command`, however, +the former also adds the command to the tree, +which skips the step of having to add it manually using :meth:`.CommandTree.add_command()`. + +For example, these two are functionally equivalent: + +.. code-block:: python3 + + @app_commands.command() + async def meow(interaction: discord.Interaction): + pass + + client.tree.add_command(meow) + + # versus. + + @client.tree.command() + async def meow(interaction: discord.Interaction): + pass + +Since ``tree.command()`` is more concise and easier to understand, +it'll be the main method used to create slash commands in this guide. + +Some information is logically inferred from the function to populate the slash command's fields: + +- The :attr:`~.app_commands.Command.name` takes after the function name "meow" +- The :attr:`~.app_commands.Command.description` takes after the docstring "Meow meow meow" + +To change them to something else, ``tree.command()`` accepts ``name`` and ``description`` as keyword arguments. + +If a description isn't provided, an ellipsis "..." is used instead. + +.. code-block:: python3 + + @client.tree.command(name="woof", description="Woof woof woof") + async def meow(interaction: discord.Interaction): + pass + +Interaction +++++++++++++ + +App commands always reserve the first parameter for an :class:`~discord.Interaction`, +a Discord model used for both app commands and UI message components. + +When an interaction is created on command invoke, some information about the surrounding context is given, such as: + +- :class:`discord.Interaction.channel` - the channel it was invoked in +- :class:`discord.Interaction.guild` - the guild it was invoked in, if any +- :class:`discord.Interaction.user` - the user or member who invoked the command + +When it comes to responding to an interaction, by sending a message or otherwise, +the methods from :attr:`.Interaction.response` need to be used. + +A response needs to occur within 3 seconds, otherwise an interaction fails with this error on Discord: + +.. image:: /images/guide/app_commands/interaction_failed.png + +In practice, it's common to use either of the following two methods: + +- :meth:`.InteractionResponse.send_message` to send a message +- :meth:`.InteractionResponse.defer` to defer a response + +In this case of deferring, a follow-up message needs to be sent within 15 minutes for app commands. + +For example, to send a deferred ephemeral message: + +.. code-block:: python3 + + import asyncio + import random + + @client.tree.command() + async def weather(self, interaction: discord.Interaction): + await interaction.response.defer(ephemeral=True) # indicates the follow-up message will be ephemeral + + weathers = ["clear", "cloudy", "rainy", "stormy"] + await asyncio.sleep(5) # an expensive operation... (no more than 15 minutes!) + result = random.choice(weathers) + + await interaction.followup.send(f"the weather today is {result}!") + +Syncing +++++++++ + +In order for this command to show up on Discord, the API needs some information regarding it, namely: + +- The name and description +- Any parameter names, types, descriptions (covered later) +- Any checks attached (covered later) +- Whether this command is a group (covered later) +- Whether this is a global or local command (covered later) + +Syncing is the process of sending this information, which is done by +calling the :meth:`.CommandTree.sync` method, typically in :meth:`.Client.setup_hook`: + +.. code-block:: python3 + + class MyClient(discord.Client): + def __init__(self): + super().__init__(intents=discord.Intents.default()) + self.tree = app_commands.CommandTree(self) + + async def setup_hook(self): + await self.tree.sync() + +Commands need to be synced again each time a new command is added or removed, or if any of the above properties change. + +Reloading your own client is sometimes also needed for new changes to be visible. + +discord.py will log warnings if there's a mismatch with what Discord provides and +what the bot defines in code during invocation. + +Parameters +----------- + +Since slash commands are defined by making Python functions, parameters are similarly defined with function parameters. + +Each parameter must have an assiociated type. This restricts what type of value a user can and cannot input. +Types are specified in code through :pep:`526` function annotations. + +For example, the following code implements a repeat command that repeats text a +certain number of times using a ``content`` and an ``n_times`` parameter: + +.. code-block:: python3 + + import textwrap + + @client.tree.command() + async def repeat(interaction: discord.Interaction, content: str, n_times: int): + to_send = textwrap.shorten(f"{content} " * n_times, width=2000) + await interaction.response.send_message(to_send) + +On the client, these parameters show up as "black boxes" that need to be filled out during invocation: + +.. image:: /images/guide/app_commands/repeat_command_preview.png + :width: 300 + +Parameters cannot have a value that doesn't match their type; trying to enter a non-numeric character for ``n_times`` will result in an error: + +.. image:: /images/guide/app_commands/repeat_command_wrong_type.png + :width: 300 + +Some types of parameters require different modes of input. For example, +annotating to :class:`discord.Member` will show a selection of members to pick from in the current guild. + +.. image:: /images/guide/app_commands/avatar_command_preview.png + :width: 300 + +A full list of available parameter types can be seen in the :ref:`type conversion table `. + +User parameter ++++++++++++++++ + +Annotating to either :class:`discord.User` or :class:`discord.Member` both point to a ``USER`` Discord-type. + +The actual type given by Discord is dependent on whether the command was invoked in DM-messages or in a guild. +It is important to annotate correctly based on this. + +For example, if a parameter annotates to :class:`~discord.Member`, and the command is invoked in a guild, +discord.py will raise an error since the actual type given by Discord, +:class:`~discord.User`, is incompatible with :class:`~discord.Member`. + +discord.py doesn't raise an error for the other way around, ie. a parameter annotated to :class:`~discord.User` invoked in a guild. +This is because :class:`~discord.Member` is compatible with :class:`~discord.User`. + +It is still important to be aware of this, as it can cause unexpected behaviour in your code. + +typing.Optional +++++++++++++++++ + +Discord also supports optional parameters, wherein a user doesn't need to provide a value during invocation. + +To do this, a parameter should annotate to :obj:`~typing.Optional`. + +``None`` will be passed instead if a user didn't submit anything, or the parameter's default value. + +For example, this command displays a given user's avatar, or the current user's avatar: + +.. code-block:: python3 + + from typing import Optional + + @client.tree.command() + async def avatar(interaction: discord.Interaction, user: Optional[discord.User] = None): + avatar = (user or interaction.user).display_avatar + await interaction.response.send_message(avatar.url) + +On Discord: + +.. image:: /images/guide/app_commands/avatar_command_optional_preview.png + +`Python version 3.10+ union types `_ are also supported instead of :obj:`typing.Optional`. + +typing.Union ++++++++++++++ + +Some types comprise of multiple other types. For example, a ``MENTIONABLE`` type parameter can point to any of these: + +- :class:`discord.User` +- :class:`discord.Member` +- :class:`discord.Role` + +To specify in code, a parameter should annotate to a :obj:`typing.Union` with all the different models: + +.. code-block:: python3 + + from typing import Union + + @client.tree.command() + async def something( + interaction: discord.Interaction, + mentionable: Union[discord.User, discord.Member, discord.Role] + ): + await interaction.response.send_message( + f"i got: {mentionable}, of type: {mentionable.__class__.__name__}" + ) + +Types that point to other types also don't have to include everything. +For example, a ``CHANNEL`` type parameter can point to any channel in a guild, +but can be narrowed down to a specific set of channels: + +.. code-block:: python3 + + from typing import Union + + @client.tree.command() + async def channel_info(interaction: discord.Interaction, channel: discord.abc.GuildChannel): + # Everything except threads + pass + + @client.tree.command() + async def channel_info(interaction: discord.Interaction, channel: discord.TextChannel): + # Only text channels + pass + + @client.tree.command() + async def channel_info(interaction: discord.Interaction, channel: Union[discord.Thread, discord.VoiceChannel]): + # Threads and voice channels only + pass + +.. note:: + + Union types can't mix Discord types. + + Something like ``Union[discord.Member, discord.TextChannel]`` isn't possible. + +Refer to the :ref:`type conversion table ` for full information on sub-types. + +Describing ++++++++++++ + +Descriptions are added to parameters using the :func:`.app_commands.describe` decorator, +where each keyword is treated as a parameter name. + +.. code-block:: python3 + + @client.tree.command() + @app_commands.describe( + content="the text to repeat", + n_times="the number of times to repeat the text" + ) + async def repeat(interaction: discord.Interaction, content: str, n_times: int): + to_send = textwrap.shorten(f"{content} " * n_times, width=2000) + await interaction.response.send_message(to_send) + +These show up on Discord just beside the parameter's name: + +.. image:: /images/guide/app_commands/repeat_command_described.png + +In addition to the decorator, parameter descriptions can also be added using +Google, Sphinx or Numpy style docstrings. + +.. code-block:: python3 + + @client.tree.command() # numpy + async def addition(interaction: discord.Interaction, a: int, b: int): + """adds 2 numbers together. + + Parameters + ----------- + a: int + left operand + b: int + right operand + """ + + await interaction.response.send_message(f"{a} + {b} is {a + b}!") + + @client.tree.command() # google + async def addition(interaction: discord.Interaction, a: int, b: int): + """adds 2 numbers together. + + Args: + a (int): left operand + b (int): right operand + """ + + @client.tree.command() # sphinx + async def addition(interaction: discord.Interaction, a: int, b: int): + """adds 2 numbers together. + + :param a: left operand + :param b: right operand + """ + +If both are used, :func:`.app_commands.describe` always takes precedence. + +Naming +^^^^^^^ + +Since parameter names are confined to the rules of Python's syntax, +the library offers a method to rename them with the :func:`.app_commands.rename` decorator. + +In use: + +.. code-block:: python3 + + @client.tree.command() + @app_commands.rename(n_times="number-of-times") + async def repeat(interaction: discord.Interaction, content: str, n_times: int): + to_send = textwrap.shorten(f"{content} " * n_times, width=2000) + await interaction.response.send_message(to_send) + + +When referring to a renamed parameter in other decorators, the original parameter name should be used. +For example, to use :func:`~.app_commands.describe` and :func:`~.app_commands.rename` together: + +.. code-block:: python3 + + @client.tree.command() + @app_commands.describe( + content="the text to repeat", + n_times="the number of times to repeat the text" + ) + @app_commands.rename(n_times="number-of-times") + async def repeat(interaction: discord.Interaction, content: str, n_times: int): + to_send = textwrap.shorten(f"{content} " * n_times, width=2000) + await interaction.response.send_message(to_send) + +Choices +++++++++ + +To provide the user with a list of options to choose from for an argument, the :func:`.app_commands.choices` decorator can be applied. + +A user is restricted to selecting a choice and can't type something else. + +Each individual choice contains 2 fields: + +- A name, which is what the user sees +- A value, which is hidden to the user and only visible to the API. Typically, this is either the same as the name or something more developer-friendly. Value types are limited to either a :class:`str`, :class:`int` or :class:`float`. + +To illustrate, the following command has a selection of 3 colours with each value being the colour code: + +.. code-block:: python3 + + @client.tree.command() + @app_commands.describe(colour="pick your favourite colour") + @app_commands.choices(colour=[ + app_commands.Choice(name="Red", value=0xFF0000), + app_commands.Choice(name="Green", value=0x00FF00), + app_commands.Choice(name="Blue", value=0x0000FF) + ]) + async def colour(interaction: discord.Interaction, colour: app_commands.Choice[int]): + """show a colour""" + + embed = discord.Embed(title=colour.name, colour=colour.value) + await interaction.response.send_message(embed=embed) + +On the client: + +.. image:: /images/guide/app_commands/colour_command_preview.png + :width: 400 + +discord.py also supports 2 other pythonic ways of adding choices to a command, +shown :func:`here ` in the reference. + +Autocompletion ++++++++++++++++ + +waiting to be written + +i mostly just want to link to the reference :meth:`~.app_commands.Command.autocomplete` + +Range +++++++ + +:class:`str`, :class:`int` and :class:`float` type parameters can optionally set a minimum and maximum value. +For strings, this limits the character count, whereas for numeric types this limits the magnitude. + +To set a range, a parameter should annotate to :class:`.app_commands.Range`. + +Transformers ++++++++++++++ + +Sometimes additional logic for parsing arguments is wanted. +For instance, to parse a date string into a :class:`datetime.datetime` we might do: + +.. code-block:: python3 + + import datetime + + @client.tree.command() + async def date(interaction: discord.Interaction, date: str): + when = datetime.datetime.strptime(date, "%d/%m/%Y") # dd/mm/yyyy format + when = when.replace(tzinfo=datetime.timezone.utc) # attach timezone information + + # do something with 'when'... + +However, this can get verbose pretty quickly if the parsing is more complex or we need to do this parsing in multiple commands. +It helps to isolate this code into it's own place, which we can do with transformers. + +Transformers are effectively classes containing a ``transform`` method that "transforms" a raw argument value into a new value. +Making one is done by inherting from :class:`.app_commands.Transformer` and overriding the :meth:`~.Transformer.transform` method. + +.. code-block:: python3 + + # the above example adapted to a transformer + + class DateTransformer(app_commands.Transformer): + async def transform(self, interaction: discord.Interaction, value: str) -> datetime.datetime: + when = datetime.datetime.strptime(date, "%d/%m/%Y") + when = when.replace(tzinfo=datetime.timezone.utc) + return when + +If you're familar with ``ext.commands``, a lot of similarities can be drawn between transformers and converters. + +To use this transformer in a command, a paramater needs to annotate to :class:`.app_commands.Transform`, +passing the transformed type and transformer respectively. + +.. code-block:: python3 + + @client.tree.command() + async def date(interaction: discord.Interaction, when: app_commands.Transform[datetime.datetime, DateTransformer]): + # do something with 'when'... + +It's also possible to instead pass an instance of the transformer instead of the class directly, +which opens up the possibility of setting up some state in :meth:`~object.__init__`. + +Since the parameter's type annotation is replaced with :class:`~.app_commands.Transform`, +the underlying type and other information must now be provided through the :class:`~.app_commands.Transformer` itself. + +These can be provided by overriding the following properties: + +- :attr:`~.Transformer.type` +- :attr:`~.Transformer.min_value` +- :attr:`~.Transformer.max_value` +- :attr:`~.Transformer.choices` +- :attr:`~.Transformer.channel_types` + +Since these are properties, they must be decorated with :class:`property`. +For example, to change the underlying type to :class:`~discord.User`: + +.. code-block:: python3 + + class UserAvatar(app_commands.Transformer): + async def transform(self, interaction: discord.Interaction, user: discord.User) -> discord.Asset: + return user.display_avatar + + @property + def type(self) -> discord.AppCommandOptionType: + return discord.AppCommandOptionType.user + +.. _type_conversion: + +Type conversion +++++++++++++++++ + +The table below outlines the relationship between Discord and Python types. + ++-----------------+------------------------------------------------------------------------------------+ +| Discord Type | Python Type | ++=================+====================================================================================+ +| ``STRING`` | :class:`str` | ++-----------------+------------------------------------------------------------------------------------+ +| ``INTEGER`` | :class:`int` | ++-----------------+------------------------------------------------------------------------------------+ +| ``BOOLEAN`` | :class:`bool` | ++-----------------+------------------------------------------------------------------------------------+ +| ``NUMBER`` | :class:`float` | ++-----------------+------------------------------------------------------------------------------------+ +| ``USER`` | :class:`~discord.User` or :class:`~discord.Member` | ++-----------------+------------------------------------------------------------------------------------+ +| ``CHANNEL`` | :class:`~discord.abc.GuildChannel` and all subclasses, or :class:`~discord.Thread` | ++-----------------+------------------------------------------------------------------------------------+ +| ``ROLE`` | :class:`~discord.Role` | ++-----------------+------------------------------------------------------------------------------------+ +| ``MENTIONABLE`` | :class:`~discord.User` or :class:`~discord.Member`, or :class:`~discord.Role` | ++-----------------+------------------------------------------------------------------------------------+ +| ``ATTACHMENT`` | :class:`~discord.Attachment` | ++-----------------+------------------------------------------------------------------------------------+ + +:ddocs:`Application command option types ` as documented by Discord. + +Checks +------- + +Checks refer to the restrictions an app command can have for invocation. +A user needs to pass all checks on a command in order to be able to invoke and see the command on their client. + +The following checks are supported: + +Age-restriction +++++++++++++++++ + +Indicates whether this command can only be used in NSFW channels or not. + +This can be configured by passing the ``nsfw`` keyword argument within the command decorator: + +.. code-block:: python3 + + @client.tree.command(nsfw=True) + async def evil(interaction: discord.Interaction): + await interaction.response.send_message("******") # very explicit text! + +Guild-only ++++++++++++ + +Indicates whether this command can only be used in guilds or not. + +Enabled by adding the :func:`.app_commands.guild_only` decorator when defining a slash command: + +.. code-block:: python3 + + @client.tree.command() + @app_commands.guild_only() + async def serverinfo(interaction: discord.Interaction): + assert interaction.guild + await interaction.response.send_message(interaction.guild.name) + +Default permissions +++++++++++++++++++++ + +This sets the default permissions a user needs in order to be able to see and invoke a slash command. + +Configured by adding the :func:`.app_commands.default_permissions` decorator when defining a slash command: + +.. code-block:: python3 + + import datetime + + @client.tree.command() + @app_commands.default_permissions(moderate_members=True) + async def timeout(interaction: discord.Interaction, member: discord.Member, days: app_commands.Range[int, 1, 28]): + await member.timeout(datetime.timedelta(days=days)) + +.. warning:: + + This can be overriden to a different set of permissions by server administrators through the "Integrations" tab on the official client, + meaning, an invoking user might not actually have the permissions specified in the decorator. + +Custom checks +++++++++++++++ + +waiting to be written + +cover: + +- how to make a check, what it should return, default behaviours +- builtin common checks and exceptions + +Custom checks come in two forms: + +- A local check, which runs for a single command +- A global check, which runs before all commands, and before any local checks + +Global check +^^^^^^^^^^^^^ + +To define a global check, override :meth:`.CommandTree.interaction_check` in a :class:`~.app_commands.CommandTree` subclass. +This method is called before every command invoke. + +For example: + +.. code-block:: python3 + + whitelist = {236802254298939392, 402159684724719617} # cool people only + + class MyCommandTree(app_commands.CommandTree): + async def interaction_check(self, interaction: discord.Interaction) -> bool: + return interaction.user.id in whitelist + +.. note:: + + If your project uses :class:`.ext.commands.Bot` as the client instance, + the :class:`.CommandTree` class can be configured via the ``tree_cls`` keyword argument in the bot constructor. + + .. code-block:: python3 + + from discord.ext import commands + + bot = commands.Bot( + command_prefix="?", + intents=discord.Intents.default(), + tree_cls=MyCommandTree + ) + +Error handling +--------------- + +So far, any exceptions raised within a command callback, any custom checks or in a transformer should just be +printed out in the program's ``stderr`` or through any custom logging handlers. + +In order to catch exceptions, the library uses something called error handlers. + +There are 3 handlers available: + +1. A local handler, which only catches errors for a specific command +2. A group handler, which catches errors only for a certain group's subcommands +3. A global handler, which catches all errors in all commands + +If an exception is raised, the library calls all 3 of these handlers in that order. + +If a subcommand has multiple parents, +the subcommand's parent handler is called first, followed by it's parent handler. + + +waiting to be written further: + +- code examples for each of the error handler types +- CommandInvokeError, TransformerError, __cause__ +- creating custom erors to know which check/transformer raised what +- an example logging setup + +Command groups +--------------- + +To make a more organised and complex tree of commands, Discord implements command groups and subcommands. +A group can contain up to 25 subcommands, with up to 1 level of nesting supported. + +Meaning, a structure like this is possible: + +.. code-block:: + + highlight + ├── blocks + │ ├── /highlight blocks add + │ └── /highlight blocks remove + ├── /highlight add + └── /highlight delete + +Command groups **are not invocable** on their own. + +Therefore, instead of creating a command the standard way by decorating an async function, +groups are created by using :class:`.app_commands.Group`. + +This class is customisable by subclassing and passing in any relevant fields at inheritance: + +.. code-block:: python3 + + class Todo(app_commands.Group, name="todo", description="manages a todolist"): + ... + + client.tree.add_command(Todo()) # required! + +.. note:: + + Groups need to be added to the command tree manually with :meth:`.CommandTree.add_command`, + since we lose the shortcut decorator :meth:`.CommandTree.command` with this class approach. + +If ``name`` or ``description`` are omitted, the class defaults to using a lower-case kebab-case +version of the class name, and the class's docstring shortened to 100 characters for the description. + +Subcommands can be made in-line by decorating bound methods in the class: + +.. code-block:: python3 + + class Todo(app_commands.Group, name="todo", description="manages a todolist"): + @app_commands.command(name="add", description="add a todo") + async def todo_add(self, interaction: discord.Interaction): + await interaction.response.send_message("added something to your todolist...!") + + client.tree.add_command(Todo()) + +After syncing: + +.. image:: /images/guide/app_commands/todo_group_preview.png + :width: 400 + +To add 1-level of nesting, create another :class:`~.app_commands.Group` in the class: + +.. code-block:: python3 + + class Todo(app_commands.Group, name="todo", description="manages a todolist"): + @app_commands.command(name="add", description="add a todo") + async def todo_add(self, interaction: discord.Interaction): + await interaction.response.send_message("added something to your todolist...!") + + todo_lists = app_commands.Group( + name="lists", + description="commands for managing different todolists for different purposes" + ) + + @todo_lists.command(name="switch", description="switch to a different todolist") + async def todo_lists_switch(self, interaction: discord.Interaction): + ... # /todo lists switch + +.. image:: /images/guide/app_commands/todo_group_nested_preview.png + :width: 400 + +Decorators like :func:`.app_commands.default_permissions` and :func:`.app_commands.guild_only` +can be added on top of the class to apply to the group, for example: + +.. code-block:: python3 + + @app_commands.default_permissions(manage_guild=True) + class Moderation(app_commands.Group): + ... + +Due to a Discord limitation, individual subcommands cannot have differing official-checks. + +Guild Specific Commands +------------------------ + +So far, all the command examples in this page have been global commands, +which every guild your bot is in can see and use. + +In contrast, guild-specific commands are only seeable and usable by members of a certain guild. + +There are 2 main ways to specify which guilds a command should sync to: + +- Via the :func:`.app_commands.guilds` decorator, which takes a variadic amount of guilds +- By passing in ``guild`` or ``guilds`` when adding a command to a :class:`~.app_commands.CommandTree` + +To demonstrate: + +.. code-block:: python3 + + @client.tree.command() + @app_commands.guilds(discord.Object(336642139381301249)) + async def support(interaction: discord.Interaction): + await interaction.response.send_message("hello, welcome to the discord.py server!") + + # or: + + @app_commands.command() + async def support(interaction: discord.Interaction): + await interaction.response.send_message("hello, welcome to the discord.py server!") + + client.tree.add_command(support, guild=discord.Object(336642139381301249)) + +.. note:: + + For these to show up, :meth:`.CommandTree.sync` needs to be called for **each** guild + using the ``guild`` keyword-argument. + +Since local commands can be useful in a development scenario, as often we don't want unfinished commands +to propagate to all guilds, the library offers a helper method :meth:`.CommandTree.copy_global_to` +to copy all global commands to a certain guild for syncing: + +.. code-block:: python3 + + class MyClient(discord.Client): + def __init__(self): + super().__init__(intents=discord.Intents.default()) + self.tree = app_commands.CommandTree(self) + + async def setup_hook(self): + guild = discord.Object(695868929154744360) # a testing server + self.tree.copy_global_to(guild) + await self.tree.sync(guild=guild) + +You'll typically find this syncing paradigm in some of the examples in the repository. + +Translating +------------ + +waiting to be written + +Recipes +-------- + +This section covers some common use-cases for slash commands. + +Manually syncing ++++++++++++++++++ + +Syncing app commands on startup, such as inside :meth:`.Client.setup_hook` can often be spammy +and incur the heavy ratelimits set by Discord. +Therefore, it's helpful to control the syncing process manually. + +A common and recommended approach is to create an owner-only traditional message command to do this. + +The ``ext.commands`` extension makes this easy: + +.. code-block:: python3 + + from discord.ext import commands + + bot = commands.Bot("?", intents=discord.Intents.default()) + + @bot.command() + @commands.is_owner() + async def sync(ctx: commands.Context): + synced = await bot.tree.sync() + await ctx.reply(f"synced {len(synced)} global commands") + + # invocable only by yourself on discord using ?sync + +A more complex command that offers higher granularity using arguments: + +.. code-block:: python3 + + from typing import Literal, Optional + + import discord + from discord.ext import commands + + # requires the `message_content` intent to work! + + # authored by Umbra + # https://about.abstractumbra.dev/discord.py/2023/01/29/sync-command-example.html + + @bot.command() + @commands.guild_only() + @commands.is_owner() + async def sync(ctx: commands.Context, guilds: commands.Greedy[discord.Object], spec: Optional[Literal["~", "*", "^"]] = None) -> None: + if not guilds: + if spec == "~": + synced = await ctx.bot.tree.sync(guild=ctx.guild) + elif spec == "*": + ctx.bot.tree.copy_global_to(guild=ctx.guild) + synced = await ctx.bot.tree.sync(guild=ctx.guild) + elif spec == "^": + ctx.bot.tree.clear_commands(guild=ctx.guild) + await ctx.bot.tree.sync(guild=ctx.guild) + synced = [] + else: + synced = await ctx.bot.tree.sync() + + await ctx.send( + f"Synced {len(synced)} commands {'globally' if spec is None else 'to the current guild.'}" + ) + return + + ret = 0 + for guild in guilds: + try: + await ctx.bot.tree.sync(guild=guild) + except discord.HTTPException: + pass + else: + ret += 1 + + await ctx.send(f"Synced the tree to {ret}/{len(guilds)}.") \ No newline at end of file diff --git a/docs/images/guide/app_commands/avatar_command_optional_preview.png b/docs/images/guide/app_commands/avatar_command_optional_preview.png new file mode 100644 index 0000000000000000000000000000000000000000..43480c5d846c5eca7ea9c3c7361461aa0e3aae21 GIT binary patch literal 15152 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 NSJqT2Q+N^f-vC}~(c%CA literal 0 HcmV?d00001 diff --git a/docs/images/guide/app_commands/colour_command_preview.png b/docs/images/guide/app_commands/colour_command_preview.png new file mode 100644 index 0000000000000000000000000000000000000000..7c0ff91dcfb59b2241715e51eef0a031478fe1e0 GIT binary patch literal 19642 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 S{dqjtp#~O1K=VB#&i@ODyFxAi literal 0 HcmV?d00001 diff --git a/docs/images/guide/app_commands/interaction_failed.png b/docs/images/guide/app_commands/interaction_failed.png new file mode 100644 index 0000000000000000000000000000000000000000..030bdd074dcf744441811a3be46b964c6cb207aa GIT binary patch literal 22530 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

dEXI&wJ@uI;%v7xRN#1=VL{Z!aEk1+tZa|Kc-t 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 n?|;bE|Ms*053(%D_7#Zk{|FnU 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 z z^wH&8j!@qX1*cg}16Q@= z&FK#4|BZTDwk1S4Vy95__0rlXpwQk_gxylrMBIPpR`B zPvJz0Vj|9lw0LY&`(K{|u96B)bSf)YuQx)qK;~mfI?k{d!RRclF*1p0hUW%by+9I_ z$#MCYS9_oLESJe_-Fn^U-Vn+mxsFxYjlqJRe>w%}Rkp=f5HP zu6r8CJxOth1C1s3>FnchF=3kSFr1`pA(fGucy|eUaK&R>3A>JR6W=8CD`N; z3N=LC98P?@zXx8-qIt-;%ixs<$Q)^*a4e3CbR8hLTlHT_tSrv;nNnX``WU{2&FMX0 zCJ;EUee{wDTAUkT@%38Sob9NNj)5);PDop+5SpE(4w6*jBy{bKe4%FInSzXtv%?AC zf>%g48o9i;R=xiq1(LqZ>W(S1A$Ssla$=_ARjl1Yi^lzFmr+H&B^bQXmVdETA@g{O z&kYr;#O{b39)o(~Q05edbcB40PtT1<04yAuWn_}W#+(?uu}dY#n?9b5rx0#hwyM;u zL0C#ii1Z^c$i{jKiG&luSXZGkBwr8MjS}i>A~PSM&X2F4H!9AMCefwKP=LF6R2Tq6 z!<2`#8FiF7)M$S!^KO-g{r*Z>(FX?L-)9*b1YogCRHoC@UULnv=u88XRSF~GZ;o`P zZ_%-Noq<8mn?>j55d-2XLDEN<5J6Tln;i^5UmNWI!)eri1y&^{$SqBsf+0Zkl08Yn zop%I(g#GG)SR^CAe^Vd;aELc48;s_riubRhncQBe25XFUIly&Q10FjDj)Ic-M+2I= z5f;Tr$&csHpZ^nNsS^Q772xovL+BLOBX+Hn!LWME!eU1Ec&}{CtC66Of?{I1Y5#kn;eR=T h`~RIqcI^RaT0!E-u+j(*IEnm3?!A(9m89|4e*>4gfmZ+k literal 0 HcmV?d00001 diff --git a/docs/images/guide/app_commands/repeat_command_described.png b/docs/images/guide/app_commands/repeat_command_described.png new file mode 100644 index 0000000000000000000000000000000000000000..a7bf789cef029937c81c6813ff699add58f0aab2 GIT binary patch literal 11974 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

d=?mzrTSebq^X@*iod_ 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 literal 0 HcmV?d00001 diff --git a/docs/images/guide/app_commands/todo_group_nested_preview.png b/docs/images/guide/app_commands/todo_group_nested_preview.png new file mode 100644 index 0000000000000000000000000000000000000000..1f134a5f5c75e504eec4e6b684d8c1d776803523 GIT binary patch literal 23355 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 literal 0 HcmV?d00001 From 558db50c2711636c3c11aa87a3cac9fbc60a9d49 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Mon, 11 Sep 2023 12:29:21 +1000 Subject: [PATCH 02/40] touch on message content intent availability --- docs/guide/interactions/slash_commands.rst | 33 ++++++++++++++-------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index c9f6d4cb0fe7..774ab6a6dc2e 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -72,10 +72,7 @@ For example, the following code responds with "meow" on invocation: await interaction.response.send_message("meow") Functions of this pattern are called callbacks, since their execution is -relinquished to the library to be called later. - -Callbacks always have one parameter, ``interaction``, -representing the :class:`discord.Interaction` that is received when the command is invoked. +left to the library to be called later. There are two main decorators to use when creating a command: @@ -112,18 +109,18 @@ Some information is logically inferred from the function to populate the slash c To change them to something else, ``tree.command()`` accepts ``name`` and ``description`` as keyword arguments. -If a description isn't provided, an ellipsis "..." is used instead. - .. code-block:: python3 @client.tree.command(name="woof", description="Woof woof woof") async def meow(interaction: discord.Interaction): pass +If a description isn't provided, an ellipsis "..." is used instead. + Interaction ++++++++++++ -App commands always reserve the first parameter for an :class:`~discord.Interaction`, +As shown above, app commands always keep the first parameter for an :class:`~discord.Interaction`, a Discord model used for both app commands and UI message components. When an interaction is created on command invoke, some information about the surrounding context is given, such as: @@ -159,9 +156,9 @@ For example, to send a deferred ephemeral message: weathers = ["clear", "cloudy", "rainy", "stormy"] await asyncio.sleep(5) # an expensive operation... (no more than 15 minutes!) - result = random.choice(weathers) + forecast = random.choice(weathers) - await interaction.followup.send(f"the weather today is {result}!") + await interaction.followup.send(f"the weather today is {forecast}!") Syncing ++++++++ @@ -870,7 +867,9 @@ The ``ext.commands`` extension makes this easy: from discord.ext import commands - bot = commands.Bot("?", intents=discord.Intents.default()) + # requires the `message_content` intent to work! + + bot = commands.Bot(command_prefix="?", intents=discord.Intents.default()) @bot.command() @commands.is_owner() @@ -925,4 +924,16 @@ A more complex command that offers higher granularity using arguments: else: ret += 1 - await ctx.send(f"Synced the tree to {ret}/{len(guilds)}.") \ No newline at end of file + await ctx.send(f"Synced the tree to {ret}/{len(guilds)}.") + +If your bot isn't able to use the message content intent, due to verificaiton requirements or otherwise, +bots can still read message content for direct-messages and for messages that mention the bot. + +:func:`.commands.when_mentioned` can be used to apply a mention prefix to your bot: + +.. code-block:: python3 + + bot = commands.Bot( + command_prefix=commands.when_mentioned, + intents=discord.Intents.default() + ) \ No newline at end of file From 61f3ff046168190e25c1836139cb953980457fe5 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Mon, 11 Sep 2023 12:36:35 +1000 Subject: [PATCH 03/40] actually enable the intent and fix typo --- docs/guide/interactions/slash_commands.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index 774ab6a6dc2e..5a6375758883 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -869,7 +869,9 @@ The ``ext.commands`` extension makes this easy: # requires the `message_content` intent to work! - bot = commands.Bot(command_prefix="?", intents=discord.Intents.default()) + intents = discord.Intents.default() + intents.message_content = True + bot = commands.Bot(command_prefix="?", intents=intents) @bot.command() @commands.is_owner() @@ -926,7 +928,7 @@ A more complex command that offers higher granularity using arguments: await ctx.send(f"Synced the tree to {ret}/{len(guilds)}.") -If your bot isn't able to use the message content intent, due to verificaiton requirements or otherwise, +If your bot isn't able to use the message content intent, due to verification requirements or otherwise, bots can still read message content for direct-messages and for messages that mention the bot. :func:`.commands.when_mentioned` can be used to apply a mention prefix to your bot: From c85e8e9ce5f81dd2ed76e7b37a615476ce5c1cc8 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Fri, 15 Sep 2023 04:55:23 +1000 Subject: [PATCH 04/40] autocomplete and translating (wip) --- docs/guide/interactions/slash_commands.rst | 426 ++++++++++++++---- .../app_commands/apple_command_english.png | Bin 0 -> 12471 bytes .../app_commands/apple_command_japanese.png | Bin 0 -> 11508 bytes 3 files changed, 344 insertions(+), 82 deletions(-) create mode 100644 docs/images/guide/app_commands/apple_command_english.png create mode 100644 docs/images/guide/app_commands/apple_command_japanese.png diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index 5a6375758883..6c5891b5d654 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -1,3 +1,5 @@ +:orphan: + .. _discord_slash_commands: Slash commands @@ -9,10 +11,10 @@ Analogous to their name, they're previewed and invoked in the Discord client by .. image:: /images/guide/app_commands/meow_command_preview.png :width: 400 -App commands are implemented within the :ref:`discord.app_commands ` package. +Application commands are implemented within the :ref:`discord.app_commands ` package. Code examples in this page will always assume the following two imports: -.. code-block:: python3 +.. code-block:: python import discord from discord import app_commands @@ -27,10 +29,10 @@ You can enable this scope when generating an OAuth2 URL for your bot, shown :ref Defining a Tree ++++++++++++++++ -First, a :class:`.app_commands.CommandTree` needs to be instaniated -- which is a container holding all of the bot's application commands. +First, an :class:`.app_commands.CommandTree` needs to be created +- which acts as a container holding all of the bot's application commands. -This class contains any relevant methods for managing app commands: +This class contains a few dedicated methods for managing and viewing app commands: - :meth:`.CommandTree.add_command` to add a command - :meth:`.CommandTree.remove_command` to remove a command @@ -40,7 +42,10 @@ This class contains any relevant methods for managing app commands: Preferably, this tree should be attached to the client instance to play well with type checkers and to allow for easy access from anywhere in the code: -.. code-block:: python3 +.. code-block:: python + + import discord + from discord import app_commands class MyClient(discord.Client): def __init__(self): @@ -63,7 +68,7 @@ This function is then called whenever the slash command is invoked. For example, the following code responds with "meow" on invocation: -.. code-block:: python3 +.. code-block:: python @client.tree.command() async def meow(interaction: discord.Interaction): @@ -76,8 +81,8 @@ left to the library to be called later. There are two main decorators to use when creating a command: -- :meth:`tree.command() <.CommandTree.command>` (as seen above) -- :func:`.app_commands.command` +1. :meth:`tree.command() <.CommandTree.command>` (as seen above) +2. :func:`.app_commands.command` Both decorators wrap an async function into a :class:`~.app_commands.Command`, however, the former also adds the command to the tree, @@ -85,7 +90,7 @@ which skips the step of having to add it manually using :meth:`.CommandTree.add_ For example, these two are functionally equivalent: -.. code-block:: python3 +.. code-block:: python @app_commands.command() async def meow(interaction: discord.Interaction): @@ -109,13 +114,13 @@ Some information is logically inferred from the function to populate the slash c To change them to something else, ``tree.command()`` accepts ``name`` and ``description`` as keyword arguments. -.. code-block:: python3 +.. code-block:: python @client.tree.command(name="woof", description="Woof woof woof") async def meow(interaction: discord.Interaction): pass -If a description isn't provided, an ellipsis "..." is used instead. +If a description isn't provided through ``description`` or by the docstring, an ellipsis "..." is used instead. Interaction ++++++++++++ @@ -129,10 +134,10 @@ When an interaction is created on command invoke, some information about the sur - :class:`discord.Interaction.guild` - the guild it was invoked in, if any - :class:`discord.Interaction.user` - the user or member who invoked the command -When it comes to responding to an interaction, by sending a message or otherwise, -the methods from :attr:`.Interaction.response` need to be used. +Attributes like these and others are a given, however when it comes to responding to an interaction, +by sending a message or otherwise, the methods from :attr:`.Interaction.response` need to be used. -A response needs to occur within 3 seconds, otherwise an interaction fails with this error on Discord: +A response needs to occur within 3 seconds, otherwise this message pops up on Discord in red: .. image:: /images/guide/app_commands/interaction_failed.png @@ -141,11 +146,11 @@ In practice, it's common to use either of the following two methods: - :meth:`.InteractionResponse.send_message` to send a message - :meth:`.InteractionResponse.defer` to defer a response -In this case of deferring, a follow-up message needs to be sent within 15 minutes for app commands. +In the case of deferring, a follow-up message needs to be sent within 15 minutes for app commands. For example, to send a deferred ephemeral message: -.. code-block:: python3 +.. code-block:: python import asyncio import random @@ -170,11 +175,12 @@ In order for this command to show up on Discord, the API needs some information - Any checks attached (covered later) - Whether this command is a group (covered later) - Whether this is a global or local command (covered later) +- Any localisations for the above (covered later) Syncing is the process of sending this information, which is done by calling the :meth:`.CommandTree.sync` method, typically in :meth:`.Client.setup_hook`: -.. code-block:: python3 +.. code-block:: python class MyClient(discord.Client): def __init__(self): @@ -202,7 +208,7 @@ Types are specified in code through :pep:`526` function annotations. For example, the following code implements a repeat command that repeats text a certain number of times using a ``content`` and an ``n_times`` parameter: -.. code-block:: python3 +.. code-block:: python import textwrap @@ -229,35 +235,17 @@ annotating to :class:`discord.Member` will show a selection of members to pick f A full list of available parameter types can be seen in the :ref:`type conversion table `. -User parameter -+++++++++++++++ - -Annotating to either :class:`discord.User` or :class:`discord.Member` both point to a ``USER`` Discord-type. - -The actual type given by Discord is dependent on whether the command was invoked in DM-messages or in a guild. -It is important to annotate correctly based on this. - -For example, if a parameter annotates to :class:`~discord.Member`, and the command is invoked in a guild, -discord.py will raise an error since the actual type given by Discord, -:class:`~discord.User`, is incompatible with :class:`~discord.Member`. - -discord.py doesn't raise an error for the other way around, ie. a parameter annotated to :class:`~discord.User` invoked in a guild. -This is because :class:`~discord.Member` is compatible with :class:`~discord.User`. - -It is still important to be aware of this, as it can cause unexpected behaviour in your code. - typing.Optional ++++++++++++++++ -Discord also supports optional parameters, wherein a user doesn't need to provide a value during invocation. - -To do this, a parameter should annotate to :obj:`~typing.Optional`. +Discord supports optional parameters, wherein a user doesn't need to provide a value during invocation. -``None`` will be passed instead if a user didn't submit anything, or the parameter's default value. +A parameter is considered optional if its assigned a default value and/or annotated +to :obj:`~typing.Optional`. For example, this command displays a given user's avatar, or the current user's avatar: -.. code-block:: python3 +.. code-block:: python from typing import Optional @@ -283,7 +271,7 @@ Some types comprise of multiple other types. For example, a ``MENTIONABLE`` type To specify in code, a parameter should annotate to a :obj:`typing.Union` with all the different models: -.. code-block:: python3 +.. code-block:: python from typing import Union @@ -300,7 +288,7 @@ Types that point to other types also don't have to include everything. For example, a ``CHANNEL`` type parameter can point to any channel in a guild, but can be narrowed down to a specific set of channels: -.. code-block:: python3 +.. code-block:: python from typing import Union @@ -333,7 +321,8 @@ Describing Descriptions are added to parameters using the :func:`.app_commands.describe` decorator, where each keyword is treated as a parameter name. -.. code-block:: python3 +.. code-block:: python + :emphasize-lines: 2-5 @client.tree.command() @app_commands.describe( @@ -351,7 +340,9 @@ These show up on Discord just beside the parameter's name: In addition to the decorator, parameter descriptions can also be added using Google, Sphinx or Numpy style docstrings. -.. code-block:: python3 +Examples using a command to add 2 numbers together: + +.. code-block:: python @client.tree.command() # numpy async def addition(interaction: discord.Interaction, a: int, b: int): @@ -394,7 +385,8 @@ the library offers a method to rename them with the :func:`.app_commands.rename` In use: -.. code-block:: python3 +.. code-block:: python + :emphasize-lines: 2 @client.tree.command() @app_commands.rename(n_times="number-of-times") @@ -402,11 +394,10 @@ In use: to_send = textwrap.shorten(f"{content} " * n_times, width=2000) await interaction.response.send_message(to_send) - When referring to a renamed parameter in other decorators, the original parameter name should be used. For example, to use :func:`~.app_commands.describe` and :func:`~.app_commands.rename` together: -.. code-block:: python3 +.. code-block:: python @client.tree.command() @app_commands.describe( @@ -432,7 +423,7 @@ Each individual choice contains 2 fields: To illustrate, the following command has a selection of 3 colours with each value being the colour code: -.. code-block:: python3 +.. code-block:: python @client.tree.command() @app_commands.describe(colour="pick your favourite colour") @@ -458,9 +449,39 @@ shown :func:`here ` in the reference. Autocompletion +++++++++++++++ -waiting to be written +Autocomplete callbacks allow the bot to dynamically return up to 25 choices +to a user as they type a parameter. + +In short: -i mostly just want to link to the reference :meth:`~.app_commands.Command.autocomplete` +- User starts typing. + +- After a brief debounced pause from typing, Discord requests a list of choices from the bot. + +- An autocomplete callback is called with the current user input. + +- Returned choices are sent back to Discord and shown in the user's client. + + - An empty list can be returned to denote no choices. + +Attaching an autocomplete function to a parameter can be done in 2 main ways: + +1. From the command, with the :meth:`~.app_commands.Command.autocomplete` decorator +2. With a separate decorator, :func:`.app_commands.autocomplete` + +Code examples for either method can be found in the corresponding reference page. + +.. note:: + + Unlike :func:`.app_commands.choices`, a user can still submit any value instead of + being limited to the bot's suggestions. + +.. warning:: + + Since exceptions raised from within an autocomplete callback are not considered handleable, + they're silently ignored and discarded. + + Instead, an empty list is returned to the user. Range ++++++ @@ -476,7 +497,7 @@ Transformers Sometimes additional logic for parsing arguments is wanted. For instance, to parse a date string into a :class:`datetime.datetime` we might do: -.. code-block:: python3 +.. code-block:: python import datetime @@ -493,7 +514,7 @@ It helps to isolate this code into it's own place, which we can do with transfor Transformers are effectively classes containing a ``transform`` method that "transforms" a raw argument value into a new value. Making one is done by inherting from :class:`.app_commands.Transformer` and overriding the :meth:`~.Transformer.transform` method. -.. code-block:: python3 +.. code-block:: python # the above example adapted to a transformer @@ -503,12 +524,12 @@ Making one is done by inherting from :class:`.app_commands.Transformer` and over when = when.replace(tzinfo=datetime.timezone.utc) return when -If you're familar with ``ext.commands``, a lot of similarities can be drawn between transformers and converters. +If you're familar with the commands extension :ref:`ext.commands `, a lot of similarities can be drawn between transformers and converters. To use this transformer in a command, a paramater needs to annotate to :class:`.app_commands.Transform`, passing the transformed type and transformer respectively. -.. code-block:: python3 +.. code-block:: python @client.tree.command() async def date(interaction: discord.Interaction, when: app_commands.Transform[datetime.datetime, DateTransformer]): @@ -531,7 +552,7 @@ These can be provided by overriding the following properties: Since these are properties, they must be decorated with :class:`property`. For example, to change the underlying type to :class:`~discord.User`: -.. code-block:: python3 +.. code-block:: python class UserAvatar(app_commands.Transformer): async def transform(self, interaction: discord.Interaction, user: discord.User) -> discord.Asset: @@ -572,6 +593,22 @@ The table below outlines the relationship between Discord and Python types. :ddocs:`Application command option types ` as documented by Discord. +User parameter +^^^^^^^^^^^^^^^ + +Annotating to either :class:`discord.User` or :class:`discord.Member` both point to a ``USER`` Discord-type. + +The actual type given by Discord is dependent on whether the command was invoked in DM-messages or in a guild. + +For example, if a parameter annotates to :class:`~discord.Member`, and the command is invoked in a guild, +discord.py will raise an error since the actual type given by Discord, +:class:`~discord.User`, is incompatible with :class:`~discord.Member`. + +discord.py doesn't raise an error for the other way around, ie. a parameter annotated to :class:`~discord.User` invoked in a guild. +This is because :class:`~discord.Member` is compatible with :class:`~discord.User`. + +It's still important to be aware of this, as it can cause unexpected behaviour in your code. + Checks ------- @@ -587,7 +624,7 @@ Indicates whether this command can only be used in NSFW channels or not. This can be configured by passing the ``nsfw`` keyword argument within the command decorator: -.. code-block:: python3 +.. code-block:: python @client.tree.command(nsfw=True) async def evil(interaction: discord.Interaction): @@ -600,12 +637,12 @@ Indicates whether this command can only be used in guilds or not. Enabled by adding the :func:`.app_commands.guild_only` decorator when defining a slash command: -.. code-block:: python3 +.. code-block:: python @client.tree.command() @app_commands.guild_only() async def serverinfo(interaction: discord.Interaction): - assert interaction.guild + assert interaction.guild is not None await interaction.response.send_message(interaction.guild.name) Default permissions @@ -615,7 +652,7 @@ This sets the default permissions a user needs in order to be able to see and in Configured by adding the :func:`.app_commands.default_permissions` decorator when defining a slash command: -.. code-block:: python3 +.. code-block:: python import datetime @@ -652,7 +689,7 @@ This method is called before every command invoke. For example: -.. code-block:: python3 +.. code-block:: python whitelist = {236802254298939392, 402159684724719617} # cool people only @@ -663,9 +700,11 @@ For example: .. note:: If your project uses :class:`.ext.commands.Bot` as the client instance, - the :class:`.CommandTree` class can be configured via the ``tree_cls`` keyword argument in the bot constructor. + the :class:`.CommandTree` class can be configured via + the ``tree_cls`` keyword argument in the bot constructor: - .. code-block:: python3 + .. code-block:: python + :emphasize-lines: 6 from discord.ext import commands @@ -706,18 +745,19 @@ Command groups --------------- To make a more organised and complex tree of commands, Discord implements command groups and subcommands. -A group can contain up to 25 subcommands, with up to 1 level of nesting supported. +A group can contain up to 25 subcommands or subgroups, with up to 1 level of nesting supported, +effectively making the command limit max out at 62500. Meaning, a structure like this is possible: .. code-block:: - highlight - ├── blocks - │ ├── /highlight blocks add - │ └── /highlight blocks remove - ├── /highlight add - └── /highlight delete + todo + ├── lists + │ ├── /todo lists create + │ └── /todo lists switch + ├── /todo add + └── /todo delete Command groups **are not invocable** on their own. @@ -726,7 +766,7 @@ groups are created by using :class:`.app_commands.Group`. This class is customisable by subclassing and passing in any relevant fields at inheritance: -.. code-block:: python3 +.. code-block:: python class Todo(app_commands.Group, name="todo", description="manages a todolist"): ... @@ -743,7 +783,7 @@ version of the class name, and the class's docstring shortened to 100 characters Subcommands can be made in-line by decorating bound methods in the class: -.. code-block:: python3 +.. code-block:: python class Todo(app_commands.Group, name="todo", description="manages a todolist"): @app_commands.command(name="add", description="add a todo") @@ -759,7 +799,7 @@ After syncing: To add 1-level of nesting, create another :class:`~.app_commands.Group` in the class: -.. code-block:: python3 +.. code-block:: python class Todo(app_commands.Group, name="todo", description="manages a todolist"): @app_commands.command(name="add", description="add a todo") @@ -781,7 +821,7 @@ To add 1-level of nesting, create another :class:`~.app_commands.Group` in the c Decorators like :func:`.app_commands.default_permissions` and :func:`.app_commands.guild_only` can be added on top of the class to apply to the group, for example: -.. code-block:: python3 +.. code-block:: python @app_commands.default_permissions(manage_guild=True) class Moderation(app_commands.Group): @@ -804,7 +844,7 @@ There are 2 main ways to specify which guilds a command should sync to: To demonstrate: -.. code-block:: python3 +.. code-block:: python @client.tree.command() @app_commands.guilds(discord.Object(336642139381301249)) @@ -828,7 +868,7 @@ Since local commands can be useful in a development scenario, as often we don't to propagate to all guilds, the library offers a helper method :meth:`.CommandTree.copy_global_to` to copy all global commands to a certain guild for syncing: -.. code-block:: python3 +.. code-block:: python class MyClient(discord.Client): def __init__(self): @@ -845,7 +885,230 @@ You'll typically find this syncing paradigm in some of the examples in the repos Translating ------------ -waiting to be written +heavy work-in-progress...! + +Discord supports localisation for the following fields: + +- Command names and descriptions +- Parameter names and descriptions +- Choice names (choices and autocomplete) + +This allows the above fields to appear differently according to a user client's language setting. + +Localisations can be done :ddocs:`partially ` - +when a locale doesn't have a translation for a given field, Discord will use the default/original string instead. + +Support for localisation is implemented in discord.py with the :class:`.app_commands.Translator` interface, +which are effectively classes containing a core ``transform`` method that +takes the following parameters: + +1. a ``string`` - the string to be translated according to ``locale`` +2. a ``locale`` - the locale to translate to +3. a ``context`` - the context of this translation (what type of string is being translated) + +When :meth:`.CommandTree.sync` is called, this method is called in a heavy loop for each +string for each locale. + +A wide variety of translation systems can be implemented using this interface, such as +`gettext `_ and +`Project Fluent `_. + +Only strings marked as ready for translation are passed to the method. +By default, every string is considered translatable and passed. + +Nonetheless, to specify a translatable string explicitly, +simply pass a string wrapped in :class:`~.app_commands.locale_str` in places you'd usually use :class:`str`: + +.. code-block:: python + + from discord.app_commands import locale_str as _ + + @client.tree.command(name=_("example"), description=_("an example command")) + async def example(interaction: discord.Interaction): + ... + +To toggle this behaviour, set the ``auto_locale_strings`` keyword-argument +to :obj:`False` when creating a command: + +.. code-block:: python + + @client.tree.command(name="example", description="an example command", auto_locale_strings=False) + async def example(interaction: discord.Interaction): + # i am not translated + +.. hint:: + + Additional keyword-arguments passed to the :class:`~.app_commands.locale_str` constructor are + inferred as "extra" information, which is kept untouched by the library in :attr:`~.locale_str.extras`. + + Utilise this field if additional info surrounding the string is required for translation. + +Next, to create a translator, inherit from :class:`.app_commands.Translator` and +override the :meth:`~.Translator.translate` method: + +.. code-block:: python + + class MyTranslator(app_commands.Translator): + async def translate( + self, + string: app_commands.locale_str, + locale: discord.Locale, + context: app_commands.TranslationContext + ) -> str: + ... + +A string should be returned according to the given ``locale``. If no translation is available, +:obj:`None` should be returned instead. + +:class:`~.app_commands.TranslationContext` provides contextual info for what is being translated. + +This contains 2 properties: + +- :attr:`~.app_commands.TranslationContext.location` - an enum representing what is being translated, eg. a command description. + +- :attr:`~.app_commands.TranslationContext.data` - can point to different things depending on the ``location``. + + - When translating a field for a command or group, such as the name, this points to the command in question. + + - When translating a parameter name, this points to the :class:`~.app_commands.Parameter`. + + - For choice names, this points to the :class:`~.app_commands.Choice`. + +Lastly, in order for a translator to be used, it needs to be attached to the tree +by calling :meth:`.CommandTree.set_translator`. + +Since this is an async method, it's ideal to call it in an async entry-point, such as :meth:`.Client.setup_hook`: + +.. code-block:: python + + class MyClient(discord.Client): + def __init__(self): + super().__init__(intents=discord.Intents.default()) + self.tree = app_commands.CommandTree(self) + + async def setup_hook(self): + await self.tree.set_translator(MyTranslator()) + +In summary: + +- Use :class:`~.app_commands.locale_str` in-place of :class:`str` in parts of a command you want translated. + + - Done by default, so this step is skipped in-practice. + +- Subclass :class:`.app_commands.Translator` and override the :meth:`.Translator.translate` method. + + - Return a translated string or :obj:`None`. + +- Call :meth:`.CommandTree.set_translator` with a translator instance. + +- Call :meth:`.CommandTree.sync`. + + - :meth:`.Translator.translate` will be called on all translatable strings. + +Following is a quick demo using the `Project Fluent `_ translation system +and the `Python fluent library `_. + +Relative to the bot's working directory is a translation resource +described in fluent's `FTL `_ format - containing +the Japanese (locale: ``ja``) translations for the bot: + +.. code-block:: + :caption: l10n/ja/commands.ftl + + # command metadata + apple-command-name = リンゴ + apple-command-description = ボットにリンゴを食べさせます。 + + # parameters + apple-command-amount = 食べさせるリンゴの数 + + # responses from the command body + apple-command-response = リンゴを{ $apple_count }個食べました。 + +In code, strings are only considered translatable if they have an +attached ``fluent_id`` identifier: + +.. code-block:: python + + @client.tree.command( + name=_("apple", fluent_id="apple-command-name"), + description=_("tell the bot to eat some apples", fluent_id="apple-command-description") + ) + @app_commands.describe(amount=_("how many apples?", fluent_id="apple-command-amount")) + async def apple(interaction: discord.Interaction, amount: int): + translator = client.tree.translator + + # plurals for the bots native language (english) are handled here in the code. + # fluent can handle plurals for secondary languages if needed. + # see: https://projectfluent.org/fluent/guide/selectors.html + + plural = "apple" if amount == 1 else "apples" + + translated = await translator.translate_string( + _(f"i ate {amount} {plural}", fluent_id="apple-command-response"), + interaction.locale, + apple_count=amount + ) + + await interaction.response.send_message(translated) + +.. code-block:: python + + from fluent.runtime import FluentLocalization, FluentResourceLoader + + class JapaneseTranslator(app_commands.Translator): + def __init__(self): + self.resources = FluentResourceLoader("l10n/{locale}") + self.mapping = { + discord.Locale.japanese: FluentLocalization(["ja"], ["commands.ftl"], self.resources), + # + additional locales as needed + } + + # translates a given string for a locale, + # subsituting any required parameters + async def translate_string( + self, + string: locale_str, + locale: discord.Locale, + **params: Any + ) -> str: + l10n = self.mapping.get(locale) + if not l10n: + # return the string untouched + return string.message + + fluent_id = string.extras["fluent_id"] + return l10n.format_value(fluent_id, params) + + # core translate method called by the library + async def translate( + self, + string: locale_str, + locale: discord.Locale, + context: app_commands.TranslationContext + ): + fluent_id = string.extras.get("fluent_id") + if not fluent_id: + # ignore strings without an attached fluent_id + return None + + l10n = self.mapping.get(locale) + if not l10n: + # no translation available for this locale + return None + + # otherwise, a translation is assumed to exist and is returned + return l10n.format_value(fluent_id) + +Viewing the command with an English (or any other) language setting: + +.. image:: /images/guide/app_commands/apple_command_english.png + :width: 300 + +With a Japanese language setting: + +.. image:: /images/guide/app_commands/apple_command_japanese.png + :width: 300 Recipes -------- @@ -861,9 +1124,9 @@ Therefore, it's helpful to control the syncing process manually. A common and recommended approach is to create an owner-only traditional message command to do this. -The ``ext.commands`` extension makes this easy: +The :ref:`commands extension ` makes this easy: -.. code-block:: python3 +.. code-block:: python from discord.ext import commands @@ -883,7 +1146,7 @@ The ``ext.commands`` extension makes this easy: A more complex command that offers higher granularity using arguments: -.. code-block:: python3 +.. code-block:: python from typing import Literal, Optional @@ -892,7 +1155,6 @@ A more complex command that offers higher granularity using arguments: # requires the `message_content` intent to work! - # authored by Umbra # https://about.abstractumbra.dev/discord.py/2023/01/29/sync-command-example.html @bot.command() @@ -933,7 +1195,7 @@ bots can still read message content for direct-messages and for messages that me :func:`.commands.when_mentioned` can be used to apply a mention prefix to your bot: -.. code-block:: python3 +.. code-block:: python bot = commands.Bot( command_prefix=commands.when_mentioned, diff --git a/docs/images/guide/app_commands/apple_command_english.png b/docs/images/guide/app_commands/apple_command_english.png new file mode 100644 index 0000000000000000000000000000000000000000..10da381481326de1cc618f749eaac31d2c806c72 GIT binary patch literal 12471 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 c>C2b=k_opi(HS||j}73IWh}t|7mmH?#Q*>R literal 0 HcmV?d00001 diff --git a/docs/images/guide/app_commands/apple_command_japanese.png b/docs/images/guide/app_commands/apple_command_japanese.png new file mode 100644 index 0000000000000000000000000000000000000000..4cf73104ee9c37175626489a930895996b7e04e4 GIT binary patch literal 11508 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 bo@hP!tB33>;@J?H%1FxcYH}4aVCeq>g3%zs literal 0 HcmV?d00001 From ae605b7061f7bad44e8af42bee597e845d05afeb Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Mon, 18 Sep 2023 21:40:35 +1000 Subject: [PATCH 05/40] union can be used to accept both user types --- docs/guide/interactions/slash_commands.rst | 32 ++++++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index 6c5891b5d654..77e7fcdf2ce2 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -549,8 +549,7 @@ These can be provided by overriding the following properties: - :attr:`~.Transformer.choices` - :attr:`~.Transformer.channel_types` -Since these are properties, they must be decorated with :class:`property`. -For example, to change the underlying type to :class:`~discord.User`: +Since these are properties, they must be decorated with :class:`property`: .. code-block:: python @@ -558,6 +557,7 @@ For example, to change the underlying type to :class:`~discord.User`: async def transform(self, interaction: discord.Interaction, user: discord.User) -> discord.Asset: return user.display_avatar + # changes the underlying type to discord.User @property def type(self) -> discord.AppCommandOptionType: return discord.AppCommandOptionType.user @@ -604,10 +604,30 @@ For example, if a parameter annotates to :class:`~discord.Member`, and the comma discord.py will raise an error since the actual type given by Discord, :class:`~discord.User`, is incompatible with :class:`~discord.Member`. -discord.py doesn't raise an error for the other way around, ie. a parameter annotated to :class:`~discord.User` invoked in a guild. -This is because :class:`~discord.Member` is compatible with :class:`~discord.User`. +discord.py doesn't raise an error for the other way around, ie. a parameter annotated to :class:`~discord.User` invoked in a guild - +this is because :class:`~discord.Member` is compatible with :class:`~discord.User`. -It's still important to be aware of this, as it can cause unexpected behaviour in your code. +To accept both types, regardless of where the command was invoked, place both types in a :obj:`~typing.Union`: + +.. code-block:: python + + from typing import Union + + @client.tree.command() + async def userinfo( + interaction: discord.Interaction, + user: Union[discord.User, discord.Member] + ): + info = user.name + + # add some extra info if this command was invoked in a guild + if isinstance(user, discord.Member): + joined = user.joined_at + if joined: + relative = discord.utils.format_dt(joined, "R") + info = f"{info} (joined this server {relative})" + + await interaction.response.send_message(info) Checks ------- @@ -1026,7 +1046,7 @@ the Japanese (locale: ``ja``) translations for the bot: apple-command-response = リンゴを{ $apple_count }個食べました。 In code, strings are only considered translatable if they have an -attached ``fluent_id`` identifier: +attached ``fluent_id`` extra: .. code-block:: python From 42d92ac35249a344b1b1857e5733fb0255feae2d Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Tue, 19 Sep 2023 07:25:36 +1000 Subject: [PATCH 06/40] group examples for checks and outdated commands --- docs/guide/interactions/slash_commands.rst | 31 +++++++++++++++++----- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index 77e7fcdf2ce2..cef9ae727680 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -192,9 +192,13 @@ calling the :meth:`.CommandTree.sync` method, typically in :meth:`.Client.setup_ Commands need to be synced again each time a new command is added or removed, or if any of the above properties change. -Reloading your own client is sometimes also needed for new changes to be visible. +Reloading your own client is sometimes also needed for new changes to be visible - +old commands tend to linger in the command preview if a client hasn't yet refreshed, but Discord +blocks invocation with this message in red: -discord.py will log warnings if there's a mismatch with what Discord provides and +.. image:: /images/guide/app_commands/outdated_command.png + +As another measure, discord.py will log warnings if there's a mismatch with what Discord provides and what the bot defines in code during invocation. Parameters @@ -607,7 +611,7 @@ discord.py will raise an error since the actual type given by Discord, discord.py doesn't raise an error for the other way around, ie. a parameter annotated to :class:`~discord.User` invoked in a guild - this is because :class:`~discord.Member` is compatible with :class:`~discord.User`. -To accept both types, regardless of where the command was invoked, place both types in a :obj:`~typing.Union`: +To accept member and user, regardless of where the command was invoked, place both types in a :obj:`~typing.Union`: .. code-block:: python @@ -650,12 +654,16 @@ This can be configured by passing the ``nsfw`` keyword argument within the comma async def evil(interaction: discord.Interaction): await interaction.response.send_message("******") # very explicit text! + # or, for a group: + class Evil(app_commands.Group, nsfw=True): + """very evil commands""" + Guild-only +++++++++++ Indicates whether this command can only be used in guilds or not. -Enabled by adding the :func:`.app_commands.guild_only` decorator when defining a slash command: +Enabled by adding the :func:`.app_commands.guild_only` decorator when defining an app command: .. code-block:: python @@ -665,12 +673,16 @@ Enabled by adding the :func:`.app_commands.guild_only` decorator when defining a assert interaction.guild is not None await interaction.response.send_message(interaction.guild.name) + # on a group: + class Server(app_commands.Group, guild_only=True): + """commands that can only be used in a server...""" + Default permissions ++++++++++++++++++++ -This sets the default permissions a user needs in order to be able to see and invoke a slash command. +This sets the default permissions a user needs in order to be able to see and invoke an app command. -Configured by adding the :func:`.app_commands.default_permissions` decorator when defining a slash command: +Configured by adding the :func:`.app_commands.default_permissions` decorator when defining an app command: .. code-block:: python @@ -681,6 +693,13 @@ Configured by adding the :func:`.app_commands.default_permissions` decorator whe async def timeout(interaction: discord.Interaction, member: discord.Member, days: app_commands.Range[int, 1, 28]): await member.timeout(datetime.timedelta(days=days)) + # groups need a permissions instance: + + default_perms = discord.Permissions(manage_emojis=True) + + class Emojis(app_commands.Group, default_permissions=default_perms): + """commands to do stuff with emojis""" + .. warning:: This can be overriden to a different set of permissions by server administrators through the "Integrations" tab on the official client, From 1df47407f0a169871764f34575e27c4b0638e0ee Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Tue, 19 Sep 2023 07:47:22 +1000 Subject: [PATCH 07/40] reorder topics and rename, change example --- docs/guide/interactions/slash_commands.rst | 300 +++++++++++---------- 1 file changed, 155 insertions(+), 145 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index cef9ae727680..79bd94caee44 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -633,6 +633,146 @@ To accept member and user, regardless of where the command was invoked, place bo await interaction.response.send_message(info) +Command groups +--------------- + +To make a more organised and complex tree of commands, Discord implements command groups and subcommands. +A group can contain up to 25 subcommands or subgroups, with up to 1 level of nesting supported. + +Meaning, a structure like this is possible: + +.. code-block:: + + todo + ├── lists + │ ├── /todo lists create + │ └── /todo lists switch + ├── /todo add + └── /todo delete + +Command groups **are not invocable** on their own. + +Therefore, instead of creating a command the standard way by decorating an async function, +groups are created by using :class:`.app_commands.Group`. + +This class is customisable by subclassing and passing in any relevant fields at inheritance: + +.. code-block:: python + + class Todo(app_commands.Group, name="todo", description="manages a todolist"): + ... + + client.tree.add_command(Todo()) # required! + +.. note:: + + Groups need to be added to the command tree manually with :meth:`.CommandTree.add_command`, + since we lose the shortcut decorator :meth:`.CommandTree.command` with this class approach. + +If ``name`` or ``description`` are omitted, the class defaults to using a lower-case kebab-case +version of the class name, and the class's docstring shortened to 100 characters for the description. + +Subcommands can be made in-line by decorating bound methods in the class: + +.. code-block:: python + + class Todo(app_commands.Group, name="todo", description="manages a todolist"): + @app_commands.command(name="add", description="add a todo") + async def todo_add(self, interaction: discord.Interaction): + await interaction.response.send_message("added something to your todolist...!") + + client.tree.add_command(Todo()) + +After syncing: + +.. image:: /images/guide/app_commands/todo_group_preview.png + :width: 400 + +To add 1-level of nesting, create another :class:`~.app_commands.Group` in the class: + +.. code-block:: python + + class Todo(app_commands.Group, name="todo", description="manages a todolist"): + @app_commands.command(name="add", description="add a todo") + async def todo_add(self, interaction: discord.Interaction): + await interaction.response.send_message("added something to your todolist...!") + + todo_lists = app_commands.Group( + name="lists", + description="commands for managing different todolists for different purposes" + ) + + @todo_lists.command(name="switch", description="switch to a different todolist") + async def todo_lists_switch(self, interaction: discord.Interaction): + ... # /todo lists switch + +.. image:: /images/guide/app_commands/todo_group_nested_preview.png + :width: 400 + +Decorators like :func:`.app_commands.default_permissions` and :func:`.app_commands.guild_only` +can be added on top of the class to apply to the group, for example: + +.. code-block:: python + + @app_commands.default_permissions(manage_guild=True) + class Moderation(app_commands.Group): + ... + +Due to a Discord limitation, individual subcommands cannot have differing official-checks. + +Guild commands +--------------- + +So far, all the command examples in this page have been global commands, +which every guild your bot is in can see and use. + +In contrast, guild commands are only seeable and usable by members of a certain guild. + +There are 2 main ways to specify which guilds a command should sync a copy to: + +- Via the :func:`.app_commands.guilds` decorator, which takes a variadic amount of guilds +- By passing in ``guild`` or ``guilds`` when adding a command to a :class:`~.app_commands.CommandTree` + +To demonstrate: + +.. code-block:: python + + @client.tree.command() + @app_commands.guilds(discord.Object(336642139381301249)) + async def support(interaction: discord.Interaction): + await interaction.response.send_message("hello, welcome to the discord.py server!") + + # or: + + @app_commands.command() + async def support(interaction: discord.Interaction): + await interaction.response.send_message("hello, welcome to the discord.py server!") + + client.tree.add_command(support, guild=discord.Object(336642139381301249)) + +.. note:: + + For these to show up, :meth:`.CommandTree.sync` needs to be called for **each** guild + using the ``guild`` keyword-argument. + +Since guild commands can be useful in a development scenario, as often we don't want unfinished commands +to propagate to all guilds, the library offers a helper method :meth:`.CommandTree.copy_global_to` +to copy all global commands to a certain guild for syncing: + +.. code-block:: python + + class MyClient(discord.Client): + def __init__(self): + super().__init__(intents=discord.Intents.default()) + self.tree = app_commands.CommandTree(self) + + async def setup_hook(self): + guild = discord.Object(695868929154744360) # a testing server + self.tree.copy_global_to(guild) + await self.tree.sync(guild=guild) + +You'll typically find this syncing paradigm in some of the examples in the repository. + Checks ------- @@ -686,12 +826,20 @@ Configured by adding the :func:`.app_commands.default_permissions` decorator whe .. code-block:: python - import datetime + import random @client.tree.command() - @app_commands.default_permissions(moderate_members=True) - async def timeout(interaction: discord.Interaction, member: discord.Member, days: app_commands.Range[int, 1, 28]): - await member.timeout(datetime.timedelta(days=days)) + @app_commands.guild_only() + @app_commands.default_permissions(manage_emojis=True) + async def emoji(interaction: discord.Interaction): + assert interaction.guild is not None + + emojis = interaction.guild.emojis + if not emojis: + await interaction.response.send_message("i don't see any emojis", ephemeral=True) + else: + emo = random.choice(interaction.guild.emojis) + await interaction.response.send_message(str(emo)) # groups need a permissions instance: @@ -700,6 +848,9 @@ Configured by adding the :func:`.app_commands.default_permissions` decorator whe class Emojis(app_commands.Group, default_permissions=default_perms): """commands to do stuff with emojis""" +Commands with this check are still visible in the bot's direct messages. +To avoid this, :func:`~.app_commands.guild_only` can also be applied. + .. warning:: This can be overriden to a different set of permissions by server administrators through the "Integrations" tab on the official client, @@ -780,147 +931,6 @@ waiting to be written further: - creating custom erors to know which check/transformer raised what - an example logging setup -Command groups ---------------- - -To make a more organised and complex tree of commands, Discord implements command groups and subcommands. -A group can contain up to 25 subcommands or subgroups, with up to 1 level of nesting supported, -effectively making the command limit max out at 62500. - -Meaning, a structure like this is possible: - -.. code-block:: - - todo - ├── lists - │ ├── /todo lists create - │ └── /todo lists switch - ├── /todo add - └── /todo delete - -Command groups **are not invocable** on their own. - -Therefore, instead of creating a command the standard way by decorating an async function, -groups are created by using :class:`.app_commands.Group`. - -This class is customisable by subclassing and passing in any relevant fields at inheritance: - -.. code-block:: python - - class Todo(app_commands.Group, name="todo", description="manages a todolist"): - ... - - client.tree.add_command(Todo()) # required! - -.. note:: - - Groups need to be added to the command tree manually with :meth:`.CommandTree.add_command`, - since we lose the shortcut decorator :meth:`.CommandTree.command` with this class approach. - -If ``name`` or ``description`` are omitted, the class defaults to using a lower-case kebab-case -version of the class name, and the class's docstring shortened to 100 characters for the description. - -Subcommands can be made in-line by decorating bound methods in the class: - -.. code-block:: python - - class Todo(app_commands.Group, name="todo", description="manages a todolist"): - @app_commands.command(name="add", description="add a todo") - async def todo_add(self, interaction: discord.Interaction): - await interaction.response.send_message("added something to your todolist...!") - - client.tree.add_command(Todo()) - -After syncing: - -.. image:: /images/guide/app_commands/todo_group_preview.png - :width: 400 - -To add 1-level of nesting, create another :class:`~.app_commands.Group` in the class: - -.. code-block:: python - - class Todo(app_commands.Group, name="todo", description="manages a todolist"): - @app_commands.command(name="add", description="add a todo") - async def todo_add(self, interaction: discord.Interaction): - await interaction.response.send_message("added something to your todolist...!") - - todo_lists = app_commands.Group( - name="lists", - description="commands for managing different todolists for different purposes" - ) - - @todo_lists.command(name="switch", description="switch to a different todolist") - async def todo_lists_switch(self, interaction: discord.Interaction): - ... # /todo lists switch - -.. image:: /images/guide/app_commands/todo_group_nested_preview.png - :width: 400 - -Decorators like :func:`.app_commands.default_permissions` and :func:`.app_commands.guild_only` -can be added on top of the class to apply to the group, for example: - -.. code-block:: python - - @app_commands.default_permissions(manage_guild=True) - class Moderation(app_commands.Group): - ... - -Due to a Discord limitation, individual subcommands cannot have differing official-checks. - -Guild Specific Commands ------------------------- - -So far, all the command examples in this page have been global commands, -which every guild your bot is in can see and use. - -In contrast, guild-specific commands are only seeable and usable by members of a certain guild. - -There are 2 main ways to specify which guilds a command should sync to: - -- Via the :func:`.app_commands.guilds` decorator, which takes a variadic amount of guilds -- By passing in ``guild`` or ``guilds`` when adding a command to a :class:`~.app_commands.CommandTree` - -To demonstrate: - -.. code-block:: python - - @client.tree.command() - @app_commands.guilds(discord.Object(336642139381301249)) - async def support(interaction: discord.Interaction): - await interaction.response.send_message("hello, welcome to the discord.py server!") - - # or: - - @app_commands.command() - async def support(interaction: discord.Interaction): - await interaction.response.send_message("hello, welcome to the discord.py server!") - - client.tree.add_command(support, guild=discord.Object(336642139381301249)) - -.. note:: - - For these to show up, :meth:`.CommandTree.sync` needs to be called for **each** guild - using the ``guild`` keyword-argument. - -Since local commands can be useful in a development scenario, as often we don't want unfinished commands -to propagate to all guilds, the library offers a helper method :meth:`.CommandTree.copy_global_to` -to copy all global commands to a certain guild for syncing: - -.. code-block:: python - - class MyClient(discord.Client): - def __init__(self): - super().__init__(intents=discord.Intents.default()) - self.tree = app_commands.CommandTree(self) - - async def setup_hook(self): - guild = discord.Object(695868929154744360) # a testing server - self.tree.copy_global_to(guild) - await self.tree.sync(guild=guild) - -You'll typically find this syncing paradigm in some of the examples in the repository. - Translating ------------ From 373933b53d42851a61a9e581841f0df69b65de74 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Tue, 19 Sep 2023 07:59:08 +1000 Subject: [PATCH 08/40] changed my mind --- docs/guide/interactions/slash_commands.rst | 30 ++++++---------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index 79bd94caee44..716f82f5f8e0 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -710,12 +710,12 @@ To add 1-level of nesting, create another :class:`~.app_commands.Group` in the c :width: 400 Decorators like :func:`.app_commands.default_permissions` and :func:`.app_commands.guild_only` -can be added on top of the class to apply to the group, for example: +can be added on top of a subclass to apply to the group, for example: .. code-block:: python - @app_commands.default_permissions(manage_guild=True) - class Moderation(app_commands.Group): + @app_commands.default_permissions(manage_emojis=True) + class Emojis(app_commands.Group): ... Due to a Discord limitation, individual subcommands cannot have differing official-checks. @@ -779,8 +779,6 @@ Checks Checks refer to the restrictions an app command can have for invocation. A user needs to pass all checks on a command in order to be able to invoke and see the command on their client. -The following checks are supported: - Age-restriction ++++++++++++++++ @@ -789,15 +787,12 @@ Indicates whether this command can only be used in NSFW channels or not. This can be configured by passing the ``nsfw`` keyword argument within the command decorator: .. code-block:: python + :emphasize-lines: 1 @client.tree.command(nsfw=True) async def evil(interaction: discord.Interaction): await interaction.response.send_message("******") # very explicit text! - # or, for a group: - class Evil(app_commands.Group, nsfw=True): - """very evil commands""" - Guild-only +++++++++++ @@ -806,6 +801,7 @@ Indicates whether this command can only be used in guilds or not. Enabled by adding the :func:`.app_commands.guild_only` decorator when defining an app command: .. code-block:: python + :emphasize-lines: 2 @client.tree.command() @app_commands.guild_only() @@ -813,10 +809,6 @@ Enabled by adding the :func:`.app_commands.guild_only` decorator when defining a assert interaction.guild is not None await interaction.response.send_message(interaction.guild.name) - # on a group: - class Server(app_commands.Group, guild_only=True): - """commands that can only be used in a server...""" - Default permissions ++++++++++++++++++++ @@ -825,15 +817,16 @@ This sets the default permissions a user needs in order to be able to see and in Configured by adding the :func:`.app_commands.default_permissions` decorator when defining an app command: .. code-block:: python + :emphasize-lines: 5 import random @client.tree.command() - @app_commands.guild_only() @app_commands.default_permissions(manage_emojis=True) async def emoji(interaction: discord.Interaction): assert interaction.guild is not None + # sends a random emoji emojis = interaction.guild.emojis if not emojis: await interaction.response.send_message("i don't see any emojis", ephemeral=True) @@ -841,15 +834,8 @@ Configured by adding the :func:`.app_commands.default_permissions` decorator whe emo = random.choice(interaction.guild.emojis) await interaction.response.send_message(str(emo)) - # groups need a permissions instance: - - default_perms = discord.Permissions(manage_emojis=True) - - class Emojis(app_commands.Group, default_permissions=default_perms): - """commands to do stuff with emojis""" - Commands with this check are still visible in the bot's direct messages. -To avoid this, :func:`~.app_commands.guild_only` can also be applied. +To prevent this, :func:`~.app_commands.guild_only` can also be added. .. warning:: From 06c704551f4afdd35b1eba2ef9c6c784c3fa2290 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Tue, 19 Sep 2023 08:07:14 +1000 Subject: [PATCH 09/40] kind of a skill issue --- docs/guide/interactions/slash_commands.rst | 26 +++++++++------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index 716f82f5f8e0..8a153f7012a7 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -787,7 +787,6 @@ Indicates whether this command can only be used in NSFW channels or not. This can be configured by passing the ``nsfw`` keyword argument within the command decorator: .. code-block:: python - :emphasize-lines: 1 @client.tree.command(nsfw=True) async def evil(interaction: discord.Interaction): @@ -801,7 +800,6 @@ Indicates whether this command can only be used in guilds or not. Enabled by adding the :func:`.app_commands.guild_only` decorator when defining an app command: .. code-block:: python - :emphasize-lines: 2 @client.tree.command() @app_commands.guild_only() @@ -817,24 +815,20 @@ This sets the default permissions a user needs in order to be able to see and in Configured by adding the :func:`.app_commands.default_permissions` decorator when defining an app command: .. code-block:: python - :emphasize-lines: 5 - - import random @client.tree.command() - @app_commands.default_permissions(manage_emojis=True) - async def emoji(interaction: discord.Interaction): - assert interaction.guild is not None - - # sends a random emoji - emojis = interaction.guild.emojis - if not emojis: - await interaction.response.send_message("i don't see any emojis", ephemeral=True) + @app_commands.default_permissions(manage_nicknames=True) + async def nickname(interaction: discord.Interaction, newname: str): + guild = interaction.guild + if not guild: + await interaction.response.send_message("i can't change my name here") else: - emo = random.choice(interaction.guild.emojis) - await interaction.response.send_message(str(emo)) + await guild.me.edit(nick=newname) + await interaction.response.send_message(f"hello i am {newname} now") + +Commands with this check are still visible and invocable in the bot's direct messages, +regardless of the permissions specified. -Commands with this check are still visible in the bot's direct messages. To prevent this, :func:`~.app_commands.guild_only` can also be added. .. warning:: From d8cb7f15cf0245dc95ed8fc78893ad1e4a482b00 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Thu, 21 Sep 2023 15:23:16 +1000 Subject: [PATCH 10/40] feedback --- docs/guide/interactions/slash_commands.rst | 48 ++++++++++++++++------ 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index 8a153f7012a7..135cbd0be73c 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -112,7 +112,7 @@ Some information is logically inferred from the function to populate the slash c - The :attr:`~.app_commands.Command.name` takes after the function name "meow" - The :attr:`~.app_commands.Command.description` takes after the docstring "Meow meow meow" -To change them to something else, ``tree.command()`` accepts ``name`` and ``description`` as keyword arguments. +To change them to something else, ``tree.command()`` takes ``name`` and ``description`` keyword-arguments: .. code-block:: python @@ -120,6 +120,11 @@ To change them to something else, ``tree.command()`` accepts ``name`` and ``desc async def meow(interaction: discord.Interaction): pass + # or... + @client.tree.command(name="list") + async def list_(interaction: discord.Interaction): + # prevent shadowing the "list" builtin + If a description isn't provided through ``description`` or by the docstring, an ellipsis "..." is used instead. Interaction @@ -171,11 +176,11 @@ Syncing In order for this command to show up on Discord, the API needs some information regarding it, namely: - The name and description -- Any parameter names, types, descriptions (covered later) -- Any checks attached (covered later) -- Whether this command is a group (covered later) -- Whether this is a global or local command (covered later) -- Any localisations for the above (covered later) +- Any :ref:`parameter names, types, descriptions ` +- Any :ref:`checks ` attached +- Whether this command is a :ref:`group ` +- Whether this is a :ref:`global or guild command ` +- Any :ref:`localisations ` for the above Syncing is the process of sending this information, which is done by calling the :meth:`.CommandTree.sync` method, typically in :meth:`.Client.setup_hook`: @@ -201,6 +206,8 @@ blocks invocation with this message in red: As another measure, discord.py will log warnings if there's a mismatch with what Discord provides and what the bot defines in code during invocation. +.. _parameters: + Parameters ----------- @@ -416,14 +423,19 @@ For example, to use :func:`~.app_commands.describe` and :func:`~.app_commands.re Choices ++++++++ -To provide the user with a list of options to choose from for an argument, the :func:`.app_commands.choices` decorator can be applied. +:class:`str`, :class:`int` and :class:`float` type parameters can optionally set a list of choices for an argument +using the :func:`.app_commands.choices` decorator. -A user is restricted to selecting a choice and can't type something else. +During invocation, a user is restricted to picking one choice and can't type anything else. Each individual choice contains 2 fields: -- A name, which is what the user sees -- A value, which is hidden to the user and only visible to the API. Typically, this is either the same as the name or something more developer-friendly. Value types are limited to either a :class:`str`, :class:`int` or :class:`float`. +- A name, which is what the user sees in their client +- A value, which is hidden to the user and only visible to the bot and API. + + Typically, this is either the same as the name or something else more developer-friendly. + + Value types are limited to either a :class:`str`, :class:`int` or :class:`float`. To illustrate, the following command has a selection of 3 colours with each value being the colour code: @@ -566,6 +578,8 @@ Since these are properties, they must be decorated with :class:`property`: def type(self) -> discord.AppCommandOptionType: return discord.AppCommandOptionType.user +:meth:`~.Transformer.autocomplete` callbacks can also be defined in-line. + .. _type_conversion: Type conversion @@ -604,7 +618,7 @@ Annotating to either :class:`discord.User` or :class:`discord.Member` both point The actual type given by Discord is dependent on whether the command was invoked in DM-messages or in a guild. -For example, if a parameter annotates to :class:`~discord.Member`, and the command is invoked in a guild, +For example, if a parameter annotates to :class:`~discord.Member`, and the command is invoked in direct-messages, discord.py will raise an error since the actual type given by Discord, :class:`~discord.User`, is incompatible with :class:`~discord.Member`. @@ -633,6 +647,8 @@ To accept member and user, regardless of where the command was invoked, place bo await interaction.response.send_message(info) +.. _command_groups: + Command groups --------------- @@ -720,6 +736,8 @@ can be added on top of a subclass to apply to the group, for example: Due to a Discord limitation, individual subcommands cannot have differing official-checks. +.. _guild_commands: + Guild commands --------------- @@ -773,6 +791,8 @@ to copy all global commands to a certain guild for syncing: You'll typically find this syncing paradigm in some of the examples in the repository. +.. _checks: + Checks ------- @@ -833,7 +853,7 @@ To prevent this, :func:`~.app_commands.guild_only` can also be added. .. warning:: - This can be overriden to a different set of permissions by server administrators through the "Integrations" tab on the official client, + This can be overridden to a different set of permissions by server administrators through the "Integrations" tab on the official client, meaning, an invoking user might not actually have the permissions specified in the decorator. Custom checks @@ -888,7 +908,7 @@ Error handling --------------- So far, any exceptions raised within a command callback, any custom checks or in a transformer should just be -printed out in the program's ``stderr`` or through any custom logging handlers. +logged in the program's ``stderr`` or through any custom logging handlers. In order to catch exceptions, the library uses something called error handlers. @@ -911,6 +931,8 @@ waiting to be written further: - creating custom erors to know which check/transformer raised what - an example logging setup +.. _translating: + Translating ------------ From 2c7bc56c0819e87d3fe51e7e3bfbfc2045f0ca9f Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Thu, 21 Sep 2023 18:58:42 +1000 Subject: [PATCH 11/40] missing image for outdated commands --- .../guide/app_commands/outdated_command.png | Bin 0 -> 25918 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/images/guide/app_commands/outdated_command.png diff --git a/docs/images/guide/app_commands/outdated_command.png b/docs/images/guide/app_commands/outdated_command.png new file mode 100644 index 0000000000000000000000000000000000000000..8780dc5d44e86d4435fd06cb697b14df327c0d1d GIT binary patch literal 25918 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 VqS0|7z?~;pO7iM*6*A_*{{<>n6G;F7 literal 0 HcmV?d00001 From 7113da587afd347cea115b57f08d77f08c3b5acf Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Mon, 2 Oct 2023 10:18:49 +1100 Subject: [PATCH 12/40] error handling, expand a bit on guild commands and some cleanup --- docs/guide/interactions/slash_commands.rst | 324 +++++++++++++++------ 1 file changed, 237 insertions(+), 87 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index 135cbd0be73c..690c06f47154 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -6,13 +6,14 @@ Slash commands =============== Slash commands are one of Discord's primary methods of implementing a user-interface for bots. -Analogous to their name, they're previewed and invoked in the Discord client by beginning your message with a forward-slash. +They're a branch of application commands, the other type being context menu commands, and analogous to their name, +they're previewed and invoked in the Discord client by beginning your message with a forward-slash: .. image:: /images/guide/app_commands/meow_command_preview.png :width: 400 -Application commands are implemented within the :ref:`discord.app_commands ` package. -Code examples in this page will always assume the following two imports: +Both types of app commands are implemented within the :ref:`discord.app_commands ` package. +Any code example in this page will always assume the following two imports: .. code-block:: python @@ -24,7 +25,7 @@ Setting up To work with app commands, bots need the ``applications.commands`` scope. -You can enable this scope when generating an OAuth2 URL for your bot, shown :ref:`here `. +You can enable this scope when generating an OAuth2 URL for your bot, the steps to do so outlined :ref:`here `. Defining a Tree ++++++++++++++++ @@ -333,7 +334,6 @@ Descriptions are added to parameters using the :func:`.app_commands.describe` de where each keyword is treated as a parameter name. .. code-block:: python - :emphasize-lines: 2-5 @client.tree.command() @app_commands.describe( @@ -386,6 +386,9 @@ Examples using a command to add 2 numbers together: :param b: right operand """ +Other meta info can be specified in the docstring, such as the function return type, +but in-practice only the parameter descriptions are used. + If both are used, :func:`.app_commands.describe` always takes precedence. Naming @@ -397,7 +400,6 @@ the library offers a method to rename them with the :func:`.app_commands.rename` In use: .. code-block:: python - :emphasize-lines: 2 @client.tree.command() @app_commands.rename(n_times="number-of-times") @@ -547,8 +549,10 @@ passing the transformed type and transformer respectively. .. code-block:: python + from discord.app_commands import Transform + @client.tree.command() - async def date(interaction: discord.Interaction, when: app_commands.Transform[datetime.datetime, DateTransformer]): + async def date(interaction: discord.Interaction, when: Transform[datetime.datetime, DateTransformer]): # do something with 'when'... It's also possible to instead pass an instance of the transformer instead of the class directly, @@ -675,7 +679,7 @@ This class is customisable by subclassing and passing in any relevant fields at .. code-block:: python - class Todo(app_commands.Group, name="todo", description="manages a todolist"): + class Todo(app_commands.Group, description="manages a todolist"): ... client.tree.add_command(Todo()) # required! @@ -692,7 +696,7 @@ Subcommands can be made in-line by decorating bound methods in the class: .. code-block:: python - class Todo(app_commands.Group, name="todo", description="manages a todolist"): + class Todo(app_commands.Group, description="manages a todolist"): @app_commands.command(name="add", description="add a todo") async def todo_add(self, interaction: discord.Interaction): await interaction.response.send_message("added something to your todolist...!") @@ -708,7 +712,7 @@ To add 1-level of nesting, create another :class:`~.app_commands.Group` in the c .. code-block:: python - class Todo(app_commands.Group, name="todo", description="manages a todolist"): + class Todo(app_commands.Group, description="manages a todolist"): @app_commands.command(name="add", description="add a todo") async def todo_add(self, interaction: discord.Interaction): await interaction.response.send_message("added something to your todolist...!") @@ -725,6 +729,24 @@ To add 1-level of nesting, create another :class:`~.app_commands.Group` in the c .. image:: /images/guide/app_commands/todo_group_nested_preview.png :width: 400 +Nested group commands can be moved into another class if it ends up being a bit too much to read in one class: + +.. code-block:: python + + class TodoLists(app_commands.Group, name="lists"): + """commands for managing different todolists for different purposes""" + + @app_commands.command(name="switch", description="switch to a different todolist"): + async def todo_lists_switch(self, interaction: discord.Interaction): + ... + + class Todo(app_commands.Group, description="manages a todolist"): + @app_commands.command(name="add", description="add a todo") + async def todo_add(self, interaction: discord.Interaction): + await interaction.response.send_message("added something to your todolist...!") + + todo_lists = TodoLists() + Decorators like :func:`.app_commands.default_permissions` and :func:`.app_commands.guild_only` can be added on top of a subclass to apply to the group, for example: @@ -773,6 +795,12 @@ To demonstrate: For these to show up, :meth:`.CommandTree.sync` needs to be called for **each** guild using the ``guild`` keyword-argument. +Whilst multiple guilds can be specified on a single command, it's important to be aware that after +syncing individually to each guild, each guild is then maintaing its own copy of the command. + +New changes will require syncing to every guild again, which can cause a temporary mismatch with what a guild has +and what's defined in code. + Since guild commands can be useful in a development scenario, as often we don't want unfinished commands to propagate to all guilds, the library offers a helper method :meth:`.CommandTree.copy_global_to` to copy all global commands to a certain guild for syncing: @@ -785,7 +813,7 @@ to copy all global commands to a certain guild for syncing: self.tree = app_commands.CommandTree(self) async def setup_hook(self): - guild = discord.Object(695868929154744360) # a testing server + guild = discord.Object(695868929154744360) # a bot testing server self.tree.copy_global_to(guild) await self.tree.sync(guild=guild) @@ -881,9 +909,14 @@ For example: .. code-block:: python - whitelist = {236802254298939392, 402159684724719617} # cool people only + # cool people only + whitelist = { + 236802254298939392, + 402159684724719617, + 155863164544614402 + } - class MyCommandTree(app_commands.CommandTree): + class CoolPeopleTree(app_commands.CommandTree): async def interaction_check(self, interaction: discord.Interaction) -> bool: return interaction.user.id in whitelist @@ -894,51 +927,147 @@ For example: the ``tree_cls`` keyword argument in the bot constructor: .. code-block:: python - :emphasize-lines: 6 from discord.ext import commands bot = commands.Bot( command_prefix="?", intents=discord.Intents.default(), - tree_cls=MyCommandTree + tree_cls=CoolPeopleTree ) Error handling --------------- -So far, any exceptions raised within a command callback, any custom checks or in a transformer should just be -logged in the program's ``stderr`` or through any custom logging handlers. +So far, any exceptions raised within a command callback, any custom checks, in a transformer +or during localisation, et cetera should just be logged in the program's ``stderr`` or through any custom logging handlers. + +In order to catch exceptions and do something else, such as sending a message to let +a user know their invocation failed for some reason, the library uses something called error handlers. -In order to catch exceptions, the library uses something called error handlers. +There are 3 types of handlers: -There are 3 handlers available: +1. A local handler, which only catches exceptions for a specific command +2. A group handler, which catches exceptions only for a certain group's subcommands +3. A global handler, which catches all exceptions in all commands -1. A local handler, which only catches errors for a specific command -2. A group handler, which catches errors only for a certain group's subcommands -3. A global handler, which catches all errors in all commands +If an exception is raised, the library calls **all 3** of these handlers in that order. -If an exception is raised, the library calls all 3 of these handlers in that order. +If a subcommand has multiple parents, the subcommand's parent handler is called first, +followed by its parent handler. -If a subcommand has multiple parents, -the subcommand's parent handler is called first, followed by it's parent handler. +To attach a local handler to a command, use the :meth:`~.app_commands.Command.error` decorator: +.. code-block:: python -waiting to be written further: + @app_commands.command() + @app_commands.checks.has_any_role("v1.0 Alpha Tester", "v2.0 Tester") + async def tester(interaction: discord.Interaction): + await interaction.response.send_message("thanks for testing") -- code examples for each of the error handler types -- CommandInvokeError, TransformerError, __cause__ -- creating custom erors to know which check/transformer raised what -- an example logging setup + @tester.error + async def tester_error(interaction: discord.Interaction, error: app_commands.AppCommandError): + if isinstance(error, app_commands.MissingAnyRole): + roles = ", ".join(str(r) for r in error.missing_roles) + await interaction.response.send_message(f"i only thank people who have one of these roles!: {roles}") + +Catching exceptions from all subcommands in a group: + +.. code-block:: python + + class MyGroup(app_commands.Group): + async def on_error(self, interaction: discord.Interaction, error: app_commands.AppCommandError): + ... + +When an exception that doesn't derive :class:`~.app_commands.AppCommandError` is raised, it's wrapped +into :class:`~.app_commands.CommandInvokeError`, with the original exception being accessible with ``__cause__``. + +Likewise: + +- For transformers, exceptions that don't derive :class:`~.app_commands.AppCommandError` are wrapped in :class:`~.app_commands.TransformerError`. +- For translators, exceptions that don't derive :class:`~.app_commands.TranslationError` are wrapped into it. + +This is helpful to differentiate between exceptions that the bot expects, such as :class:`~.app_commands.MissingAnyRole`, +over exceptions like :class:`TypeError` or :class:`ValueError`, which typically trace back to a programming mistake. + +To catch these exceptions in a global error handler for example: + +.. tab:: Python versions below 3.10 + + .. code-block:: python + + import sys + import traceback + + @client.tree.error + async def on_app_command_error(interaction: discord.Interaction, error: app_commands.AppCommandError): + assert interaction.command is not None + + if isinstance(error, app_commands.CommandInvokeError): + print(f"Ignoring unknown exception in command {interaction.command.name}", file=sys.stderr) + traceback.print_exception(error.__class__, error, error.__traceback__) + +.. tab:: Python versions 3.10+ + + .. code-block:: python + + import sys + import traceback + + @client.tree.error + async def on_app_command_error(interaction: discord.Interaction, error: app_commands.AppCommandError): + assert interaction.command is not None + + if isinstance(error, app_commands.CommandInvokeError): + print(f"Ignoring unknown exception in command {interaction.command.name}", file=sys.stderr) + traceback.print_exception(error) + +.. hint:: + + Global error handlers can also be defined by overriding :meth:`.app_commands.CommandTree.on_error` in a subclass. + +Raising a custom exception from a transformer and catching it: + +.. code-block:: python + + from discord.app_commands import Transform + + class BadDateArgument(app_commands.Translator): + def __init__(self, argument: str): + super().__init__(f'expected a date in DD/MM/YYYY format, not "{argument}".') + + class DateTransformer(app_commands.Transformer): + async def transform(self, interaction: discord.Interaction, value: str) -> datetime.datetime: + try: + when = datetime.datetime.strptime(date, "%d/%m/%Y") + except ValueError: + raise BadDateArgument(value) + + when = when.replace(tzinfo=datetime.timezone.utc) + return when + + @some_command.error + async def some_command_error(interaction: discord.Interaction, error: app_commands.AppCommandError): + if isinstance(error, BadDateArgument): + await interaction.response.send_message(str(error)) + +Logging +++++++++ + +Instead of printing plainly to :obj:`sys.stderr`, the standard ``logging`` module can be configured instead - +which is what discord.py uses to write its own exceptions. + +Whilst logging is a little bit more involved to set up, it has some added benefits such as using coloured text +in a terminal and being able to write to a file. + +Refer to the :ref:`Setting Up logging ` page for more info. .. _translating: Translating ------------ -heavy work-in-progress...! - -Discord supports localisation for the following fields: +Discord supports localisation (l10n) for the following fields: - Command names and descriptions - Parameter names and descriptions @@ -949,13 +1078,13 @@ This allows the above fields to appear differently according to a user client's Localisations can be done :ddocs:`partially ` - when a locale doesn't have a translation for a given field, Discord will use the default/original string instead. -Support for localisation is implemented in discord.py with the :class:`.app_commands.Translator` interface, +Support for l10n is implemented in discord.py with the :class:`.app_commands.Translator` interface, which are effectively classes containing a core ``transform`` method that takes the following parameters: -1. a ``string`` - the string to be translated according to ``locale`` -2. a ``locale`` - the locale to translate to -3. a ``context`` - the context of this translation (what type of string is being translated) +1. ``string`` - the string to be translated according to ``locale`` +2. ``locale`` - the locale to translate to +3. ``context`` - the context of this translation (what type of string is being translated) When :meth:`.CommandTree.sync` is called, this method is called in a heavy loop for each string for each locale. @@ -1013,7 +1142,7 @@ A string should be returned according to the given ``locale``. If no translation :class:`~.app_commands.TranslationContext` provides contextual info for what is being translated. -This contains 2 properties: +This contains 2 attributes: - :attr:`~.app_commands.TranslationContext.location` - an enum representing what is being translated, eg. a command description. @@ -1059,12 +1188,22 @@ In summary: Following is a quick demo using the `Project Fluent `_ translation system and the `Python fluent library `_. -Relative to the bot's working directory is a translation resource -described in fluent's `FTL `_ format - containing -the Japanese (locale: ``ja``) translations for the bot: +Like a lot of other l10n systems, fluent uses directories and files to separate localisations. + +A structure like this is used for this example: + +.. code-block:: + + discord_bot/ + └── l10n/ + ├── ja/ + │ └── commands.ftl + └── bot.py + +``commands.ftl`` is a translation resource described in fluent's `FTL `_ format - +containing the Japanese (locale: ``ja``) localisations for a certain command in the bot: .. code-block:: - :caption: l10n/ja/commands.ftl # command metadata apple-command-name = リンゴ @@ -1076,32 +1215,7 @@ the Japanese (locale: ``ja``) translations for the bot: # responses from the command body apple-command-response = リンゴを{ $apple_count }個食べました。 -In code, strings are only considered translatable if they have an -attached ``fluent_id`` extra: - -.. code-block:: python - - @client.tree.command( - name=_("apple", fluent_id="apple-command-name"), - description=_("tell the bot to eat some apples", fluent_id="apple-command-description") - ) - @app_commands.describe(amount=_("how many apples?", fluent_id="apple-command-amount")) - async def apple(interaction: discord.Interaction, amount: int): - translator = client.tree.translator - - # plurals for the bots native language (english) are handled here in the code. - # fluent can handle plurals for secondary languages if needed. - # see: https://projectfluent.org/fluent/guide/selectors.html - - plural = "apple" if amount == 1 else "apples" - - translated = await translator.translate_string( - _(f"i ate {amount} {plural}", fluent_id="apple-command-response"), - interaction.locale, - apple_count=amount - ) - - await interaction.response.send_message(translated) +Onto the code: .. code-block:: python @@ -1109,35 +1223,23 @@ attached ``fluent_id`` extra: class JapaneseTranslator(app_commands.Translator): def __init__(self): + # read and save any resources when the translator initialises. + # if asynchronous setup is needed, override `Translator.load()`! + self.resources = FluentResourceLoader("l10n/{locale}") self.mapping = { discord.Locale.japanese: FluentLocalization(["ja"], ["commands.ftl"], self.resources), # + additional locales as needed } - # translates a given string for a locale, - # subsituting any required parameters - async def translate_string( - self, - string: locale_str, - locale: discord.Locale, - **params: Any - ) -> str: - l10n = self.mapping.get(locale) - if not l10n: - # return the string untouched - return string.message - - fluent_id = string.extras["fluent_id"] - return l10n.format_value(fluent_id, params) - - # core translate method called by the library async def translate( self, string: locale_str, locale: discord.Locale, context: app_commands.TranslationContext ): + """core translate method called by the library""" + fluent_id = string.extras.get("fluent_id") if not fluent_id: # ignore strings without an attached fluent_id @@ -1151,12 +1253,60 @@ attached ``fluent_id`` extra: # otherwise, a translation is assumed to exist and is returned return l10n.format_value(fluent_id) + async def localise( + self, + string: locale_str, + locale: discord.Locale, + **params: Any + ) -> str: + """translates a given string for a locale, subsituting any required parameters. + meant to be called for things outside what discord handles, eg. a message sent from the bot + """ + + l10n = self.mapping.get(locale) + if not l10n: + # return the string untouched + return string.message + + # strings passed to this method need to include a fluent_id extra + # since we are trying to explicitly localise a string + fluent_id = string.extras["fluent_id"] + + return l10n.format_value(fluent_id, params) + +With the command, strings are only considered translatable if they have an +attached ``fluent_id`` extra: + +.. code-block:: python + + @client.tree.command( + name=_("apple", fluent_id="apple-command-name"), + description=_("tell the bot to eat some apples", fluent_id="apple-command-description") + ) + @app_commands.describe(amount=_("how many apples?", fluent_id="apple-command-amount")) + async def apple(interaction: discord.Interaction, amount: int): + translator = client.tree.translator + + # plurals for the bots native/default language (english) are handled here in the code. + # fluent can handle plurals for secondary languages if needed. + # see: https://projectfluent.org/fluent/guide/selectors.html + + plural = "apple" if amount == 1 else "apples" + + translated = await translator.localise( + _(f"i ate {amount} {plural}", fluent_id="apple-command-response"), + interaction.locale, + apple_count=amount + ) + + await interaction.response.send_message(translated) + Viewing the command with an English (or any other) language setting: .. image:: /images/guide/app_commands/apple_command_english.png :width: 300 -With a Japanese language setting: +A Japanese language setting shows the added localisations: .. image:: /images/guide/app_commands/apple_command_japanese.png :width: 300 @@ -1251,4 +1401,4 @@ bots can still read message content for direct-messages and for messages that me bot = commands.Bot( command_prefix=commands.when_mentioned, intents=discord.Intents.default() - ) \ No newline at end of file + ) From dd58d8224bbd7faa593a57805ee84e85c8d72706 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Mon, 2 Oct 2023 10:34:32 +1100 Subject: [PATCH 13/40] ascii tree was wrong and use a relative path instead --- docs/guide/interactions/slash_commands.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index 690c06f47154..a53d2040e2a6 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -389,7 +389,7 @@ Examples using a command to add 2 numbers together: Other meta info can be specified in the docstring, such as the function return type, but in-practice only the parameter descriptions are used. -If both are used, :func:`.app_commands.describe` always takes precedence. +Parameter descriptions added using :func:`.app_commands.describe` always takes precedence over ones in the docstring. Naming ^^^^^^^ @@ -1195,10 +1195,10 @@ A structure like this is used for this example: .. code-block:: discord_bot/ - └── l10n/ - ├── ja/ - │ └── commands.ftl - └── bot.py + ├── l10n/ + │ └── ja/ + │ └── commands.ftl + └── bot.py ``commands.ftl`` is a translation resource described in fluent's `FTL `_ format - containing the Japanese (locale: ``ja``) localisations for a certain command in the bot: @@ -1223,10 +1223,10 @@ Onto the code: class JapaneseTranslator(app_commands.Translator): def __init__(self): - # read and save any resources when the translator initialises. + # load any resources when the translator initialises. # if asynchronous setup is needed, override `Translator.load()`! - self.resources = FluentResourceLoader("l10n/{locale}") + self.resources = FluentResourceLoader("./l10n/{locale}") self.mapping = { discord.Locale.japanese: FluentLocalization(["ja"], ["commands.ftl"], self.resources), # + additional locales as needed From a623e2675e1a12dddb0a3a548b653d811bb03621 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Tue, 3 Oct 2023 22:16:45 +1100 Subject: [PATCH 14/40] write custom checks, collapse some examples, other cleanups --- docs/guide/interactions/slash_commands.rst | 151 +++++++++++++++++---- 1 file changed, 127 insertions(+), 24 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index a53d2040e2a6..2c9bf7488604 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -443,14 +443,16 @@ To illustrate, the following command has a selection of 3 colours with each valu .. code-block:: python + from discord.app_commands import Choice + @client.tree.command() @app_commands.describe(colour="pick your favourite colour") @app_commands.choices(colour=[ - app_commands.Choice(name="Red", value=0xFF0000), - app_commands.Choice(name="Green", value=0x00FF00), - app_commands.Choice(name="Blue", value=0x0000FF) + Choice(name="Red", value=0xFF0000), + Choice(name="Green", value=0x00FF00), + Choice(name="Blue", value=0x0000FF) ]) - async def colour(interaction: discord.Interaction, colour: app_commands.Choice[int]): + async def colour(interaction: discord.Interaction, colour: Choice[int]): """show a colour""" embed = discord.Embed(title=colour.name, colour=colour.value) @@ -885,22 +887,91 @@ To prevent this, :func:`~.app_commands.guild_only` can also be added. meaning, an invoking user might not actually have the permissions specified in the decorator. Custom checks -++++++++++++++ +-------------- + +A custom check is something that can be applied to a command to check if someone should be able to run it. + +They're unique to the :ref:`officially supported checks ` by Discord in that they're handled +entirely client-side. + +In short, a check is an async function that takes in the :class:`~discord.Interaction` as its sole parameter. +It has the following options: + +- Return a :obj:`True`-like to signal this check passes. + + - If a command has multiple checks, **all** of them need to pass in order for the invocation to continue. + +- Raise a :class:`~.app_commands.AppCommandError`-derived exception to signal a person can't run the command. + + - Exceptions are passed to the bot's :ref:`error handlers `. + +- Return a :obj:`False`-like to signal a person can't run the command. + + - :class:`~.app_commands.CheckFailure` will be raised instead. + +To add a check, use the :func:`.app_commands.check` decorator: + +.. code-block:: python + + import random + + async def predicate(interaction: discord.Interaction) -> bool: + return random.randint(0, 1) == 1 + + @client.tree.command() + @app_commands.check(predicate) + async def fiftyfifty(interaction: discord.Interaction): + await interaction.response.send_message("you're lucky!") + +Transforming the check into its own decorator: + +.. code-block:: python + + import random + + def coinflip(): + async def predicate(interaction: discord.Interaction) -> bool: + return random.randint(0, 1) == 1 + return app_commands.check(predicate) + + @client.tree.command() + @coinflip() + async def fiftyfifty(interaction: discord.Interaction): + await interaction.response.send_message("you're lucky!") + +Checks are called sequentially and retain decorator order, bottom-to-top. + +Take advantage of this order if, for example, you only want a cooldown to apply if a previous check passes: + +.. code-block:: python + + @client.tree.command() + @app_commands.checks.cooldown(1, 5.0, key=lambda i: (i.guild_id, i.user.id)) # called second + @coinflip() # called first + async def ratelimited_fiftyfifty(interaction: discord.Interaction): + await interaction.response.send_message("you're very patient and lucky!") + +Custom checks can either be: -waiting to be written +- local, only running for a single command (as seen above). -cover: +- on a group, running for all child commands, and before any local checks. + + - Added using the :meth:`.app_commands.Group.error` decorator or overriding :meth:`.app_commands.Group.on_error`. + +- :ref:`global `, running for all commands, and before any group or local checks. + +.. note:: -- how to make a check, what it should return, default behaviours -- builtin common checks and exceptions + In the ``app_commands.checks`` namespace, there exists a lot of builtin checks + to account for common use-cases, such as checking for roles or applying a cooldown. -Custom checks come in two forms: + Refer to the :ref:`checks guide ` for more info. -- A local check, which runs for a single command -- A global check, which runs before all commands, and before any local checks +.. _global_check: Global check -^^^^^^^^^^^^^ ++++++++++++++ To define a global check, override :meth:`.CommandTree.interaction_check` in a :class:`~.app_commands.CommandTree` subclass. This method is called before every command invoke. @@ -909,8 +980,8 @@ For example: .. code-block:: python - # cool people only whitelist = { + # cool people only 236802254298939392, 402159684724719617, 155863164544614402 @@ -948,15 +1019,25 @@ a user know their invocation failed for some reason, the library uses something There are 3 types of handlers: 1. A local handler, which only catches exceptions for a specific command + + Attached using the :meth:`.app_commands.Command.error` decorator + 2. A group handler, which catches exceptions only for a certain group's subcommands + + Added by using the :meth:`.app_commands.Group.error` decorator or overriding :meth:`.app_commands.Group.on_error` + 3. A global handler, which catches all exceptions in all commands + Added by using the :meth:`.CommandTree.error` decorator or overriding :meth:`.CommandTree.on_error` + If an exception is raised, the library calls **all 3** of these handlers in that order. If a subcommand has multiple parents, the subcommand's parent handler is called first, followed by its parent handler. -To attach a local handler to a command, use the :meth:`~.app_commands.Command.error` decorator: +Some examples: + +Attaching a local handler to a command to catch a check exception: .. code-block:: python @@ -988,7 +1069,7 @@ Likewise: - For translators, exceptions that don't derive :class:`~.app_commands.TranslationError` are wrapped into it. This is helpful to differentiate between exceptions that the bot expects, such as :class:`~.app_commands.MissingAnyRole`, -over exceptions like :class:`TypeError` or :class:`ValueError`, which typically trace back to a programming mistake. +over exceptions like :class:`TypeError` or :class:`ValueError`, which typically trace back to a programming mistake or HTTP error. To catch these exceptions in a global error handler for example: @@ -1022,11 +1103,34 @@ To catch these exceptions in a global error handler for example: print(f"Ignoring unknown exception in command {interaction.command.name}", file=sys.stderr) traceback.print_exception(error) -.. hint:: +Raising a new error from a check for a more robust method of catching failed checks: + +.. code-block:: python + + import random + + class Unlucky(app_commands.CheckFailure): + def __init__(self): + super().__init__("you're unlucky!") - Global error handlers can also be defined by overriding :meth:`.app_commands.CommandTree.on_error` in a subclass. + def coinflip(): + async def predicate(interaction: discord.Interaction) -> bool: + if random.randint(0, 1) == 0: + raise Unlucky() + return True + return app_commands.check(predicate) -Raising a custom exception from a transformer and catching it: + @client.tree.command() + @coinflip() + async def fiftyfifty(interaction: discord.Interaction): + await interaction.response.send_message("you're lucky!") + + @fiftyfifty.error + async def fiftyfifty_error(interaction: discord.Interaction, error: app_commands.AppCommandError): + if isinstance(error, Unlucky): + await interaction.response.send_message(str(error)) + +Raising an exception from a transformer and catching it: .. code-block:: python @@ -1034,7 +1138,7 @@ Raising a custom exception from a transformer and catching it: class BadDateArgument(app_commands.Translator): def __init__(self, argument: str): - super().__init__(f'expected a date in DD/MM/YYYY format, not "{argument}".') + super().__init__(f'expected a date in dd/mm/yyyy format, not "{argument}".') class DateTransformer(app_commands.Transformer): async def transform(self, interaction: discord.Interaction, value: str) -> datetime.datetime: @@ -1046,14 +1150,13 @@ Raising a custom exception from a transformer and catching it: when = when.replace(tzinfo=datetime.timezone.utc) return when + # pretend `some_command` is a command that uses this transformer + @some_command.error async def some_command_error(interaction: discord.Interaction, error: app_commands.AppCommandError): if isinstance(error, BadDateArgument): await interaction.response.send_message(str(error)) -Logging -++++++++ - Instead of printing plainly to :obj:`sys.stderr`, the standard ``logging`` module can be configured instead - which is what discord.py uses to write its own exceptions. @@ -1215,7 +1318,7 @@ containing the Japanese (locale: ``ja``) localisations for a certain command in # responses from the command body apple-command-response = リンゴを{ $apple_count }個食べました。 -Onto the code: +Onto the translator: .. code-block:: python From 9dea4bb5d7513d08d867fbbfc6e26539469f83b9 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Tue, 3 Oct 2023 22:34:36 +1100 Subject: [PATCH 15/40] that cooldown will fail for dms --- docs/guide/interactions/slash_commands.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index 2c9bf7488604..89b1a962c087 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -923,7 +923,7 @@ To add a check, use the :func:`.app_commands.check` decorator: async def fiftyfifty(interaction: discord.Interaction): await interaction.response.send_message("you're lucky!") -Transforming the check into its own decorator: +Transforming the check into its own decorator for easier usage: .. code-block:: python @@ -946,9 +946,9 @@ Take advantage of this order if, for example, you only want a cooldown to apply .. code-block:: python @client.tree.command() - @app_commands.checks.cooldown(1, 5.0, key=lambda i: (i.guild_id, i.user.id)) # called second + @app_commands.checks.cooldown(1, 5.0) # called second @coinflip() # called first - async def ratelimited_fiftyfifty(interaction: discord.Interaction): + async def fiftyfifty(interaction: discord.Interaction): await interaction.response.send_message("you're very patient and lucky!") Custom checks can either be: @@ -1020,22 +1020,22 @@ There are 3 types of handlers: 1. A local handler, which only catches exceptions for a specific command - Attached using the :meth:`.app_commands.Command.error` decorator + Attached using the :meth:`.app_commands.Command.error` decorator. -2. A group handler, which catches exceptions only for a certain group's subcommands +2. A group handler, which catches exceptions only for a certain group's subcommands. - Added by using the :meth:`.app_commands.Group.error` decorator or overriding :meth:`.app_commands.Group.on_error` + Added by using the :meth:`.app_commands.Group.error` decorator or overriding :meth:`.app_commands.Group.on_error`. -3. A global handler, which catches all exceptions in all commands +3. A global handler, which catches all exceptions in all commands. - Added by using the :meth:`.CommandTree.error` decorator or overriding :meth:`.CommandTree.on_error` + Added by using the :meth:`.CommandTree.error` decorator or overriding :meth:`.CommandTree.on_error`. If an exception is raised, the library calls **all 3** of these handlers in that order. If a subcommand has multiple parents, the subcommand's parent handler is called first, followed by its parent handler. -Some examples: +**Examples** Attaching a local handler to a command to catch a check exception: From 348914048394963a61fd2132ea000c76441f0b2e Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Wed, 4 Oct 2023 05:33:07 +1100 Subject: [PATCH 16/40] normalise string literals to be more inline with project --- docs/guide/interactions/slash_commands.rst | 130 ++++++++++----------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index 89b1a962c087..eabe8de70b30 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -75,7 +75,7 @@ For example, the following code responds with "meow" on invocation: async def meow(interaction: discord.Interaction): """Meow meow meow""" - await interaction.response.send_message("meow") + await interaction.response.send_message('meow') Functions of this pattern are called callbacks, since their execution is left to the library to be called later. @@ -117,12 +117,12 @@ To change them to something else, ``tree.command()`` takes ``name`` and ``descri .. code-block:: python - @client.tree.command(name="woof", description="Woof woof woof") + @client.tree.command(name='woof', description='Woof woof woof') async def meow(interaction: discord.Interaction): pass # or... - @client.tree.command(name="list") + @client.tree.command(name='list') async def list_(interaction: discord.Interaction): # prevent shadowing the "list" builtin @@ -165,11 +165,11 @@ For example, to send a deferred ephemeral message: async def weather(self, interaction: discord.Interaction): await interaction.response.defer(ephemeral=True) # indicates the follow-up message will be ephemeral - weathers = ["clear", "cloudy", "rainy", "stormy"] + weathers = ['sunny', 'clear', 'cloudy', 'rainy', 'stormy', 'snowy'] await asyncio.sleep(5) # an expensive operation... (no more than 15 minutes!) forecast = random.choice(weathers) - await interaction.followup.send(f"the weather today is {forecast}!") + await interaction.followup.send(f'the weather today is {forecast}!') Syncing ++++++++ @@ -226,7 +226,7 @@ certain number of times using a ``content`` and an ``n_times`` parameter: @client.tree.command() async def repeat(interaction: discord.Interaction, content: str, n_times: int): - to_send = textwrap.shorten(f"{content} " * n_times, width=2000) + to_send = textwrap.shorten(f'{content} ' * n_times, width=2000) await interaction.response.send_message(to_send) On the client, these parameters show up as "black boxes" that need to be filled out during invocation: @@ -293,7 +293,7 @@ To specify in code, a parameter should annotate to a :obj:`typing.Union` with al mentionable: Union[discord.User, discord.Member, discord.Role] ): await interaction.response.send_message( - f"i got: {mentionable}, of type: {mentionable.__class__.__name__}" + f'i got: {mentionable}, of type: {mentionable.__class__.__name__}' ) Types that point to other types also don't have to include everything. @@ -337,11 +337,11 @@ where each keyword is treated as a parameter name. @client.tree.command() @app_commands.describe( - content="the text to repeat", - n_times="the number of times to repeat the text" + content='the text to repeat', + n_times='the number of times to repeat the text' ) async def repeat(interaction: discord.Interaction, content: str, n_times: int): - to_send = textwrap.shorten(f"{content} " * n_times, width=2000) + to_send = textwrap.shorten(f'{content} ' * n_times, width=2000) await interaction.response.send_message(to_send) These show up on Discord just beside the parameter's name: @@ -367,7 +367,7 @@ Examples using a command to add 2 numbers together: right operand """ - await interaction.response.send_message(f"{a} + {b} is {a + b}!") + await interaction.response.send_message(f'{a} + {b} is {a + b}!') @client.tree.command() # google async def addition(interaction: discord.Interaction, a: int, b: int): @@ -402,9 +402,9 @@ In use: .. code-block:: python @client.tree.command() - @app_commands.rename(n_times="number-of-times") + @app_commands.rename(n_times='number-of-times') async def repeat(interaction: discord.Interaction, content: str, n_times: int): - to_send = textwrap.shorten(f"{content} " * n_times, width=2000) + to_send = textwrap.shorten(f'{content} ' * n_times, width=2000) await interaction.response.send_message(to_send) When referring to a renamed parameter in other decorators, the original parameter name should be used. @@ -414,12 +414,12 @@ For example, to use :func:`~.app_commands.describe` and :func:`~.app_commands.re @client.tree.command() @app_commands.describe( - content="the text to repeat", - n_times="the number of times to repeat the text" + content='the text to repeat', + n_times='the number of times to repeat the text' ) - @app_commands.rename(n_times="number-of-times") + @app_commands.rename(n_times='number-of-times') async def repeat(interaction: discord.Interaction, content: str, n_times: int): - to_send = textwrap.shorten(f"{content} " * n_times, width=2000) + to_send = textwrap.shorten(f'{content} ' * n_times, width=2000) await interaction.response.send_message(to_send) Choices @@ -446,11 +446,11 @@ To illustrate, the following command has a selection of 3 colours with each valu from discord.app_commands import Choice @client.tree.command() - @app_commands.describe(colour="pick your favourite colour") + @app_commands.describe(colour='pick your favourite colour') @app_commands.choices(colour=[ - Choice(name="Red", value=0xFF0000), - Choice(name="Green", value=0x00FF00), - Choice(name="Blue", value=0x0000FF) + Choice(name='Red', value=0xFF0000), + Choice(name='Green', value=0x00FF00), + Choice(name='Blue', value=0x0000FF) ]) async def colour(interaction: discord.Interaction, colour: Choice[int]): """show a colour""" @@ -523,7 +523,7 @@ For instance, to parse a date string into a :class:`datetime.datetime` we might @client.tree.command() async def date(interaction: discord.Interaction, date: str): - when = datetime.datetime.strptime(date, "%d/%m/%Y") # dd/mm/yyyy format + when = datetime.datetime.strptime(date, '%d/%m/%Y') # dd/mm/yyyy format when = when.replace(tzinfo=datetime.timezone.utc) # attach timezone information # do something with 'when'... @@ -540,7 +540,7 @@ Making one is done by inherting from :class:`.app_commands.Transformer` and over class DateTransformer(app_commands.Transformer): async def transform(self, interaction: discord.Interaction, value: str) -> datetime.datetime: - when = datetime.datetime.strptime(date, "%d/%m/%Y") + when = datetime.datetime.strptime(date, '%d/%m/%Y') when = when.replace(tzinfo=datetime.timezone.utc) return when @@ -648,8 +648,8 @@ To accept member and user, regardless of where the command was invoked, place bo if isinstance(user, discord.Member): joined = user.joined_at if joined: - relative = discord.utils.format_dt(joined, "R") - info = f"{info} (joined this server {relative})" + relative = discord.utils.format_dt(joined, 'R') + info = f'{info} (joined this server {relative})' await interaction.response.send_message(info) @@ -681,7 +681,7 @@ This class is customisable by subclassing and passing in any relevant fields at .. code-block:: python - class Todo(app_commands.Group, description="manages a todolist"): + class Todo(app_commands.Group, description='manages a todolist'): ... client.tree.add_command(Todo()) # required! @@ -698,10 +698,10 @@ Subcommands can be made in-line by decorating bound methods in the class: .. code-block:: python - class Todo(app_commands.Group, description="manages a todolist"): - @app_commands.command(name="add", description="add a todo") + class Todo(app_commands.Group, description='manages a todolist'): + @app_commands.command(name='add', description='add a todo') async def todo_add(self, interaction: discord.Interaction): - await interaction.response.send_message("added something to your todolist...!") + await interaction.response.send_message('added something to your todolist...!') client.tree.add_command(Todo()) @@ -714,17 +714,17 @@ To add 1-level of nesting, create another :class:`~.app_commands.Group` in the c .. code-block:: python - class Todo(app_commands.Group, description="manages a todolist"): - @app_commands.command(name="add", description="add a todo") + class Todo(app_commands.Group, description='manages a todolist'): + @app_commands.command(name='add', description='add a todo') async def todo_add(self, interaction: discord.Interaction): - await interaction.response.send_message("added something to your todolist...!") + await interaction.response.send_message('added something to your todolist...!') todo_lists = app_commands.Group( - name="lists", - description="commands for managing different todolists for different purposes" + name='lists', + description='commands for managing different todolists for different purposes' ) - @todo_lists.command(name="switch", description="switch to a different todolist") + @todo_lists.command(name='switch', description='switch to a different todolist') async def todo_lists_switch(self, interaction: discord.Interaction): ... # /todo lists switch @@ -735,17 +735,17 @@ Nested group commands can be moved into another class if it ends up being a bit .. code-block:: python - class TodoLists(app_commands.Group, name="lists"): + class TodoLists(app_commands.Group, name='lists'): """commands for managing different todolists for different purposes""" - @app_commands.command(name="switch", description="switch to a different todolist"): + @app_commands.command(name='switch', description='switch to a different todolist'): async def todo_lists_switch(self, interaction: discord.Interaction): ... - class Todo(app_commands.Group, description="manages a todolist"): - @app_commands.command(name="add", description="add a todo") + class Todo(app_commands.Group, description='manages a todolist'): + @app_commands.command(name='add', description='add a todo') async def todo_add(self, interaction: discord.Interaction): - await interaction.response.send_message("added something to your todolist...!") + await interaction.response.send_message('added something to your todolist...!') todo_lists = TodoLists() @@ -782,13 +782,13 @@ To demonstrate: @client.tree.command() @app_commands.guilds(discord.Object(336642139381301249)) async def support(interaction: discord.Interaction): - await interaction.response.send_message("hello, welcome to the discord.py server!") + await interaction.response.send_message('hello, welcome to the discord.py server!') # or: @app_commands.command() async def support(interaction: discord.Interaction): - await interaction.response.send_message("hello, welcome to the discord.py server!") + await interaction.response.send_message('hello, welcome to the discord.py server!') client.tree.add_command(support, guild=discord.Object(336642139381301249)) @@ -840,7 +840,7 @@ This can be configured by passing the ``nsfw`` keyword argument within the comma @client.tree.command(nsfw=True) async def evil(interaction: discord.Interaction): - await interaction.response.send_message("******") # very explicit text! + await interaction.response.send_message('******') # very explicit text! Guild-only +++++++++++ @@ -874,7 +874,7 @@ Configured by adding the :func:`.app_commands.default_permissions` decorator whe await interaction.response.send_message("i can't change my name here") else: await guild.me.edit(nick=newname) - await interaction.response.send_message(f"hello i am {newname} now") + await interaction.response.send_message(f'hello i am {newname} now') Commands with this check are still visible and invocable in the bot's direct messages, regardless of the permissions specified. @@ -1002,7 +1002,7 @@ For example: from discord.ext import commands bot = commands.Bot( - command_prefix="?", + command_prefix='?', intents=discord.Intents.default(), tree_cls=CoolPeopleTree ) @@ -1042,15 +1042,15 @@ Attaching a local handler to a command to catch a check exception: .. code-block:: python @app_commands.command() - @app_commands.checks.has_any_role("v1.0 Alpha Tester", "v2.0 Tester") + @app_commands.checks.has_any_role('v1.0 Alpha Tester', 'v2.0 Tester') async def tester(interaction: discord.Interaction): - await interaction.response.send_message("thanks for testing") + await interaction.response.send_message('thanks for testing') @tester.error async def tester_error(interaction: discord.Interaction, error: app_commands.AppCommandError): if isinstance(error, app_commands.MissingAnyRole): - roles = ", ".join(str(r) for r in error.missing_roles) - await interaction.response.send_message(f"i only thank people who have one of these roles!: {roles}") + roles = ', '.join(str(r) for r in error.missing_roles) + await interaction.response.send_message('i only thank people who have one of these roles!: {roles}') Catching exceptions from all subcommands in a group: @@ -1085,7 +1085,7 @@ To catch these exceptions in a global error handler for example: assert interaction.command is not None if isinstance(error, app_commands.CommandInvokeError): - print(f"Ignoring unknown exception in command {interaction.command.name}", file=sys.stderr) + print(f'Ignoring unknown exception in command {interaction.command.name}', file=sys.stderr) traceback.print_exception(error.__class__, error, error.__traceback__) .. tab:: Python versions 3.10+ @@ -1100,7 +1100,7 @@ To catch these exceptions in a global error handler for example: assert interaction.command is not None if isinstance(error, app_commands.CommandInvokeError): - print(f"Ignoring unknown exception in command {interaction.command.name}", file=sys.stderr) + print(f'Ignoring unknown exception in command {interaction.command.name}', file=sys.stderr) traceback.print_exception(error) Raising a new error from a check for a more robust method of catching failed checks: @@ -1143,7 +1143,7 @@ Raising an exception from a transformer and catching it: class DateTransformer(app_commands.Transformer): async def transform(self, interaction: discord.Interaction, value: str) -> datetime.datetime: try: - when = datetime.datetime.strptime(date, "%d/%m/%Y") + when = datetime.datetime.strptime(date, '%d/%m/%Y') except ValueError: raise BadDateArgument(value) @@ -1206,7 +1206,7 @@ simply pass a string wrapped in :class:`~.app_commands.locale_str` in places you from discord.app_commands import locale_str as _ - @client.tree.command(name=_("example"), description=_("an example command")) + @client.tree.command(name=_('example'), description=_('an example command')) async def example(interaction: discord.Interaction): ... @@ -1215,7 +1215,7 @@ to :obj:`False` when creating a command: .. code-block:: python - @client.tree.command(name="example", description="an example command", auto_locale_strings=False) + @client.tree.command(name='example', description='an example command', auto_locale_strings=False) async def example(interaction: discord.Interaction): # i am not translated @@ -1329,9 +1329,9 @@ Onto the translator: # load any resources when the translator initialises. # if asynchronous setup is needed, override `Translator.load()`! - self.resources = FluentResourceLoader("./l10n/{locale}") + self.resources = FluentResourceLoader('./l10n/{locale}') self.mapping = { - discord.Locale.japanese: FluentLocalization(["ja"], ["commands.ftl"], self.resources), + discord.Locale.japanese: FluentLocalization(['ja'], ['commands.ftl'], self.resources), # + additional locales as needed } @@ -1343,7 +1343,7 @@ Onto the translator: ): """core translate method called by the library""" - fluent_id = string.extras.get("fluent_id") + fluent_id = string.extras.get('fluent_id') if not fluent_id: # ignore strings without an attached fluent_id return None @@ -1373,7 +1373,7 @@ Onto the translator: # strings passed to this method need to include a fluent_id extra # since we are trying to explicitly localise a string - fluent_id = string.extras["fluent_id"] + fluent_id = string.extras['fluent_id'] return l10n.format_value(fluent_id, params) @@ -1383,10 +1383,10 @@ attached ``fluent_id`` extra: .. code-block:: python @client.tree.command( - name=_("apple", fluent_id="apple-command-name"), - description=_("tell the bot to eat some apples", fluent_id="apple-command-description") + name=_('apple', fluent_id='apple-command-name'), + description=_('tell the bot to eat some apples', fluent_id='apple-command-description') ) - @app_commands.describe(amount=_("how many apples?", fluent_id="apple-command-amount")) + @app_commands.describe(amount=_('how many apples?', fluent_id='apple-command-amount')) async def apple(interaction: discord.Interaction, amount: int): translator = client.tree.translator @@ -1394,10 +1394,10 @@ attached ``fluent_id`` extra: # fluent can handle plurals for secondary languages if needed. # see: https://projectfluent.org/fluent/guide/selectors.html - plural = "apple" if amount == 1 else "apples" + plural = 'apple' if amount == 1 else 'apples' translated = await translator.localise( - _(f"i ate {amount} {plural}", fluent_id="apple-command-response"), + _(f'i ate {amount} {plural}', fluent_id='apple-command-response'), interaction.locale, apple_count=amount ) @@ -1438,13 +1438,13 @@ The :ref:`commands extension ` makes this easy: intents = discord.Intents.default() intents.message_content = True - bot = commands.Bot(command_prefix="?", intents=intents) + bot = commands.Bot(command_prefix='?', intents=intents) @bot.command() @commands.is_owner() async def sync(ctx: commands.Context): synced = await bot.tree.sync() - await ctx.reply(f"synced {len(synced)} global commands") + await ctx.reply(f'synced {len(synced)} global commands') # invocable only by yourself on discord using ?sync From f09b0e7e990bbf757d04ae62cef473e24085d771 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Fri, 6 Oct 2023 00:30:19 +1100 Subject: [PATCH 17/40] didnt like the repeat command --- docs/guide/interactions/slash_commands.rst | 78 ++++++++++-------- .../app_commands/avatar_command_preview.png | Bin 13979 -> 0 bytes .../bottles_command_described.png | Bin 0 -> 11382 bytes .../app_commands/bottles_command_preview.png | Bin 0 -> 6216 bytes .../app_commands/input_a_valid_integer.png | Bin 0 -> 11205 bytes .../app_commands/repeat_command_described.png | Bin 11974 -> 0 bytes .../app_commands/repeat_command_preview.png | Bin 7797 -> 0 bytes .../repeat_command_wrong_type.png | Bin 12344 -> 0 bytes .../app_commands/this_option_is_required.png | Bin 0 -> 12198 bytes 9 files changed, 45 insertions(+), 33 deletions(-) delete mode 100644 docs/images/guide/app_commands/avatar_command_preview.png create mode 100644 docs/images/guide/app_commands/bottles_command_described.png create mode 100644 docs/images/guide/app_commands/bottles_command_preview.png create mode 100644 docs/images/guide/app_commands/input_a_valid_integer.png delete mode 100644 docs/images/guide/app_commands/repeat_command_described.png delete mode 100644 docs/images/guide/app_commands/repeat_command_preview.png delete mode 100644 docs/images/guide/app_commands/repeat_command_wrong_type.png create mode 100644 docs/images/guide/app_commands/this_option_is_required.png diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index eabe8de70b30..f78c0c85f683 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -217,35 +217,45 @@ Since slash commands are defined by making Python functions, parameters are simi Each parameter must have an assiociated type. This restricts what type of value a user can and cannot input. Types are specified in code through :pep:`526` function annotations. -For example, the following code implements a repeat command that repeats text a -certain number of times using a ``content`` and an ``n_times`` parameter: +For example, the following command has a ``liquid`` string parameter: .. code-block:: python - import textwrap - @client.tree.command() - async def repeat(interaction: discord.Interaction, content: str, n_times: int): - to_send = textwrap.shorten(f'{content} ' * n_times, width=2000) - await interaction.response.send_message(to_send) + async def bottles(interaction: discord.Interaction, liquid: str): + await interaction.response.send_message(f'99 bottles of {liquid} on the wall!') -On the client, these parameters show up as "black boxes" that need to be filled out during invocation: +On the client, parameters show up as these little black boxes that need to be filled out during invocation: -.. image:: /images/guide/app_commands/repeat_command_preview.png +.. image:: /images/guide/app_commands/bottles_command_preview.png :width: 300 -Parameters cannot have a value that doesn't match their type; trying to enter a non-numeric character for ``n_times`` will result in an error: +Since this is a string parameter, virtually anything can be inputted (up to Discord's limits). + +Other parameter types are more restrictive - for example, if an integer parameter is added: + +.. code-block:: python + + @client.tree.command() + async def bottles(interaction: discord.Interaction, liquid: str, amount: int): + await interaction.response.send_message(f'{amount} bottles of {liquid} on the wall!') + +Trying to enter a non-numeric character for ``amount`` will result with this red message: -.. image:: /images/guide/app_commands/repeat_command_wrong_type.png +.. image:: /images/guide/app_commands/input_a_valid_integer.png :width: 300 -Some types of parameters require different modes of input. For example, -annotating to :class:`discord.Member` will show a selection of members to pick from in the current guild. +Additionally, since both of these parameters are required, trying to skip one will result in this message in red: -.. image:: /images/guide/app_commands/avatar_command_preview.png +.. image:: /images/guide/app_commands/this_option_is_required.png :width: 300 -A full list of available parameter types can be seen in the :ref:`type conversion table `. +Some parameter types have different modes of input. + +For example, annotating to :class:`~discord.User` will show a selection of users to +pick from in the current context and :class:`~discord.Attachment` will show a file-dropbox. + +A full overview of supported types can be seen in the :ref:`type conversion table `. typing.Optional ++++++++++++++++ @@ -269,6 +279,7 @@ For example, this command displays a given user's avatar, or the current user's On Discord: .. image:: /images/guide/app_commands/avatar_command_optional_preview.png + :width: 300 `Python version 3.10+ union types `_ are also supported instead of :obj:`typing.Optional`. @@ -337,16 +348,17 @@ where each keyword is treated as a parameter name. @client.tree.command() @app_commands.describe( - content='the text to repeat', - n_times='the number of times to repeat the text' + liquid="what type of liquid is on the wall", + amount="how much of it is on the wall" ) - async def repeat(interaction: discord.Interaction, content: str, n_times: int): - to_send = textwrap.shorten(f'{content} ' * n_times, width=2000) - await interaction.response.send_message(to_send) + async def bottles(interaction: discord.Interaction, liquid: str, amount: int): + await interaction.response.send_message(f'{amount} bottles of {liquid} on the wall!') These show up on Discord just beside the parameter's name: -.. image:: /images/guide/app_commands/repeat_command_described.png +.. image:: /images/guide/app_commands/bottles_command_described.png + +Not specifying a description results with an ellipsis "..." being used instead. In addition to the decorator, parameter descriptions can also be added using Google, Sphinx or Numpy style docstrings. @@ -402,10 +414,9 @@ In use: .. code-block:: python @client.tree.command() - @app_commands.rename(n_times='number-of-times') - async def repeat(interaction: discord.Interaction, content: str, n_times: int): - to_send = textwrap.shorten(f'{content} ' * n_times, width=2000) - await interaction.response.send_message(to_send) + @app_commands.rename(amount="liquid-count") + async def bottles(interaction: discord.Interaction, liquid: str, amount: int): + await interaction.response.send_message(f'{amount} bottles of {liquid} on the wall!') When referring to a renamed parameter in other decorators, the original parameter name should be used. For example, to use :func:`~.app_commands.describe` and :func:`~.app_commands.rename` together: @@ -413,14 +424,13 @@ For example, to use :func:`~.app_commands.describe` and :func:`~.app_commands.re .. code-block:: python @client.tree.command() + @app_commands.rename(amount="liquid-count") @app_commands.describe( - content='the text to repeat', - n_times='the number of times to repeat the text' + liquid="what type of liquid is on the wall", + amount="how much of it is on the wall" ) - @app_commands.rename(n_times='number-of-times') - async def repeat(interaction: discord.Interaction, content: str, n_times: int): - to_send = textwrap.shorten(f'{content} ' * n_times, width=2000) - await interaction.response.send_message(to_send) + async def bottles(interaction: discord.Interaction, liquid: str, amount: int): + await interaction.response.send_message(f'{amount} bottles of {liquid} on the wall!') Choices ++++++++ @@ -532,7 +542,7 @@ However, this can get verbose pretty quickly if the parsing is more complex or w It helps to isolate this code into it's own place, which we can do with transformers. Transformers are effectively classes containing a ``transform`` method that "transforms" a raw argument value into a new value. -Making one is done by inherting from :class:`.app_commands.Transformer` and overriding the :meth:`~.Transformer.transform` method. +Making one is done by inherting from :class:`.app_commands.Transformer` and overriding the :meth:`~.Transformer.transform` method: .. code-block:: python @@ -738,7 +748,7 @@ Nested group commands can be moved into another class if it ends up being a bit class TodoLists(app_commands.Group, name='lists'): """commands for managing different todolists for different purposes""" - @app_commands.command(name='switch', description='switch to a different todolist'): + @app_commands.command(name='switch', description='switch to a different todolist') async def todo_lists_switch(self, interaction: discord.Interaction): ... @@ -1007,6 +1017,8 @@ For example: tree_cls=CoolPeopleTree ) +.. _error_handling: + Error handling --------------- diff --git a/docs/images/guide/app_commands/avatar_command_preview.png b/docs/images/guide/app_commands/avatar_command_preview.png deleted file mode 100644 index 16c98f18e110b720696c0d99ba2536a134ba0639..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13979 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 NSJqT2Q+N^f-vC}~(c%CA diff --git a/docs/images/guide/app_commands/bottles_command_described.png b/docs/images/guide/app_commands/bottles_command_described.png new file mode 100644 index 0000000000000000000000000000000000000000..3fdb96e86882ba8d58d4408b2934c4dec90dd6d1 GIT binary patch literal 11382 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 S{LU4I`YtXnRw-)e|GxkU&Ie5Z literal 0 HcmV?d00001 diff --git a/docs/images/guide/app_commands/bottles_command_preview.png b/docs/images/guide/app_commands/bottles_command_preview.png new file mode 100644 index 0000000000000000000000000000000000000000..acc05acc6e8278d0436a52270e9f8c63a8a08d0f GIT binary patch literal 6216 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 MId$0@8H@1$0^K}1y#N3J literal 0 HcmV?d00001 diff --git a/docs/images/guide/app_commands/input_a_valid_integer.png b/docs/images/guide/app_commands/input_a_valid_integer.png new file mode 100644 index 0000000000000000000000000000000000000000..f1eddad0509d38aef5bda13a5080bc994d6a3f5a GIT binary patch literal 11205 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 literal 0 HcmV?d00001 diff --git a/docs/images/guide/app_commands/repeat_command_described.png b/docs/images/guide/app_commands/repeat_command_described.png deleted file mode 100644 index a7bf789cef029937c81c6813ff699add58f0aab2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11974 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

d=?mzrTSebq^X@*iod_ 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 diff --git a/docs/images/guide/app_commands/this_option_is_required.png b/docs/images/guide/app_commands/this_option_is_required.png new file mode 100644 index 0000000000000000000000000000000000000000..b29d8625530ee65ef4dd9f8b5e8bd0de652c504d GIT binary patch literal 12198 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 Date: Sat, 7 Oct 2023 08:36:35 +1100 Subject: [PATCH 18/40] add a note on when not to sync --- docs/guide/interactions/slash_commands.rst | 41 +++++++++++++++------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index f78c0c85f683..c0f71662347b 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -177,14 +177,17 @@ Syncing In order for this command to show up on Discord, the API needs some information regarding it, namely: - The name and description -- Any :ref:`parameter names, types, descriptions ` + +- Any :ref:`parameter names, types and descriptions ` - Any :ref:`checks ` attached - Whether this command is a :ref:`group ` - Whether this is a :ref:`global or guild command ` - Any :ref:`localisations ` for the above Syncing is the process of sending this information, which is done by -calling the :meth:`.CommandTree.sync` method, typically in :meth:`.Client.setup_hook`: +calling the :meth:`.CommandTree.sync` method. + +Typically, this is called on start-up in :meth:`.Client.setup_hook`: .. code-block:: python @@ -198,13 +201,17 @@ calling the :meth:`.CommandTree.sync` method, typically in :meth:`.Client.setup_ Commands need to be synced again each time a new command is added or removed, or if any of the above properties change. -Reloading your own client is sometimes also needed for new changes to be visible - +Syncing is **not** required when changing client-side behaviour, +such as by adding a :ref:`library-side check `, adding a :ref:`transformer ` +or changing anything within the function body. + +Reloading your own client is sometimes needed for new changes to be visible - old commands tend to linger in the command preview if a client hasn't yet refreshed, but Discord blocks invocation with this message in red: .. image:: /images/guide/app_commands/outdated_command.png -As another measure, discord.py will log warnings if there's a mismatch with what Discord provides and +As another measure, the library will log warnings if there's a mismatch with what Discord provides and what the bot defines in code during invocation. .. _parameters: @@ -348,8 +355,8 @@ where each keyword is treated as a parameter name. @client.tree.command() @app_commands.describe( - liquid="what type of liquid is on the wall", - amount="how much of it is on the wall" + liquid='what type of liquid is on the wall', + amount='how much of it is on the wall' ) async def bottles(interaction: discord.Interaction, liquid: str, amount: int): await interaction.response.send_message(f'{amount} bottles of {liquid} on the wall!') @@ -401,7 +408,8 @@ Examples using a command to add 2 numbers together: Other meta info can be specified in the docstring, such as the function return type, but in-practice only the parameter descriptions are used. -Parameter descriptions added using :func:`.app_commands.describe` always takes precedence over ones in the docstring. +Parameter descriptions added using :func:`.app_commands.describe` always +take precedence over ones specified in the docstring. Naming ^^^^^^^ @@ -414,7 +422,7 @@ In use: .. code-block:: python @client.tree.command() - @app_commands.rename(amount="liquid-count") + @app_commands.rename(amount='liquid-count') async def bottles(interaction: discord.Interaction, liquid: str, amount: int): await interaction.response.send_message(f'{amount} bottles of {liquid} on the wall!') @@ -424,10 +432,10 @@ For example, to use :func:`~.app_commands.describe` and :func:`~.app_commands.re .. code-block:: python @client.tree.command() - @app_commands.rename(amount="liquid-count") + @app_commands.rename(amount='liquid-count') @app_commands.describe( - liquid="what type of liquid is on the wall", - amount="how much of it is on the wall" + liquid='what type of liquid is on the wall', + amount='how much of it is on the wall' ) async def bottles(interaction: discord.Interaction, liquid: str, amount: int): await interaction.response.send_message(f'{amount} bottles of {liquid} on the wall!') @@ -476,6 +484,8 @@ On the client: discord.py also supports 2 other pythonic ways of adding choices to a command, shown :func:`here ` in the reference. +.. _autocompletion: + Autocompletion +++++++++++++++ @@ -521,6 +531,8 @@ For strings, this limits the character count, whereas for numeric types this lim To set a range, a parameter should annotate to :class:`.app_commands.Range`. +.. _transformers: + Transformers +++++++++++++ @@ -542,7 +554,8 @@ However, this can get verbose pretty quickly if the parsing is more complex or w It helps to isolate this code into it's own place, which we can do with transformers. Transformers are effectively classes containing a ``transform`` method that "transforms" a raw argument value into a new value. -Making one is done by inherting from :class:`.app_commands.Transformer` and overriding the :meth:`~.Transformer.transform` method: + +To make one, inherit from :class:`.app_commands.Transformer` and override the :meth:`~.Transformer.transform` method: .. code-block:: python @@ -554,7 +567,7 @@ Making one is done by inherting from :class:`.app_commands.Transformer` and over when = when.replace(tzinfo=datetime.timezone.utc) return when -If you're familar with the commands extension :ref:`ext.commands `, a lot of similarities can be drawn between transformers and converters. +If you're familar with the commands extension (:ref:`ext.commands `), a lot of similarities can be drawn between transformers and converters. To use this transformer in a command, a paramater needs to annotate to :class:`.app_commands.Transform`, passing the transformed type and transformer respectively. @@ -896,6 +909,8 @@ To prevent this, :func:`~.app_commands.guild_only` can also be added. This can be overridden to a different set of permissions by server administrators through the "Integrations" tab on the official client, meaning, an invoking user might not actually have the permissions specified in the decorator. +.. _custom_checks: + Custom checks -------------- From c7f11dd6d632dc55ca29f213ab1c527734c4cdb8 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Sat, 7 Oct 2023 08:40:47 +1100 Subject: [PATCH 19/40] change numpy/google/sphinx examples to use inline tabs ext --- docs/guide/interactions/slash_commands.rst | 66 +++++++++++++--------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index c0f71662347b..5eefb0043811 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -368,42 +368,56 @@ These show up on Discord just beside the parameter's name: Not specifying a description results with an ellipsis "..." being used instead. In addition to the decorator, parameter descriptions can also be added using -Google, Sphinx or Numpy style docstrings. +Google, Sphinx or NumPy style docstrings. Examples using a command to add 2 numbers together: -.. code-block:: python +.. tab:: NumPy + + .. code-block:: python + + @client.tree.command() + async def addition(interaction: discord.Interaction, a: int, b: int): + """adds 2 numbers together. + + Parameters + ----------- + a: int + left operand + b: int + right operand + """ + + await interaction.response.send_message(f'{a} + {b} is {a + b}!') - @client.tree.command() # numpy - async def addition(interaction: discord.Interaction, a: int, b: int): - """adds 2 numbers together. +.. tab:: Google - Parameters - ----------- - a: int - left operand - b: int - right operand - """ + .. code-block:: python + + @client.tree.command() + async def addition(interaction: discord.Interaction, a: int, b: int): + """adds 2 numbers together. - await interaction.response.send_message(f'{a} + {b} is {a + b}!') + Args: + a (int): left operand + b (int): right operand + """ - @client.tree.command() # google - async def addition(interaction: discord.Interaction, a: int, b: int): - """adds 2 numbers together. + await interaction.response.send_message(f'{a} + {b} is {a + b}!') - Args: - a (int): left operand - b (int): right operand - """ +.. tab:: Sphinx - @client.tree.command() # sphinx - async def addition(interaction: discord.Interaction, a: int, b: int): - """adds 2 numbers together. + .. code-block:: python + + @client.tree.command() + async def addition(interaction: discord.Interaction, a: int, b: int): + """adds 2 numbers together. + + :param a: left operand + :param b: right operand + """ - :param a: left operand - :param b: right operand - """ + await interaction.response.send_message(f'{a} + {b} is {a + b}!') Other meta info can be specified in the docstring, such as the function return type, but in-practice only the parameter descriptions are used. From 8855700839b27d348a3f0dea2fe5e050dba0b965 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Sat, 7 Oct 2023 12:18:40 +1100 Subject: [PATCH 20/40] link to proper pep and use the pep directive Co-Authored-By: James Hilton-Balfe --- docs/guide/interactions/slash_commands.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index 5eefb0043811..b4eb468cc492 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -222,7 +222,7 @@ Parameters Since slash commands are defined by making Python functions, parameters are similarly defined with function parameters. Each parameter must have an assiociated type. This restricts what type of value a user can and cannot input. -Types are specified in code through :pep:`526` function annotations. +Types are specified in code through :pep:`3107` function annotations. For example, the following command has a ``liquid`` string parameter: @@ -288,7 +288,7 @@ On Discord: .. image:: /images/guide/app_commands/avatar_command_optional_preview.png :width: 300 -`Python version 3.10+ union types `_ are also supported instead of :obj:`typing.Optional`. +:pep:`Python version 3.10+ union types <604>` are also supported instead of :obj:`typing.Optional`. typing.Union +++++++++++++ From 34d08ef337861043bbaca6772139bf5fd302e77c Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Sat, 7 Oct 2023 12:19:28 +1100 Subject: [PATCH 21/40] add missing ellipsis to example Co-Authored-By: James Hilton-Balfe --- docs/guide/interactions/slash_commands.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index b4eb468cc492..936065bf63ac 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -1258,7 +1258,7 @@ to :obj:`False` when creating a command: @client.tree.command(name='example', description='an example command', auto_locale_strings=False) async def example(interaction: discord.Interaction): - # i am not translated + ... # i am not translated .. hint:: From e2c1567116de21faffba8924ba0766fd78b1d78b Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Sat, 7 Oct 2023 12:21:15 +1100 Subject: [PATCH 22/40] use module directive for stdlibs Co-Authored-By: James Hilton-Balfe --- docs/guide/interactions/slash_commands.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index 936065bf63ac..4959efb85f4c 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -1198,7 +1198,7 @@ Raising an exception from a transformer and catching it: if isinstance(error, BadDateArgument): await interaction.response.send_message(str(error)) -Instead of printing plainly to :obj:`sys.stderr`, the standard ``logging`` module can be configured instead - +Instead of printing plainly to :obj:`sys.stderr`, the standard :mod:`logging` module can be configured instead - which is what discord.py uses to write its own exceptions. Whilst logging is a little bit more involved to set up, it has some added benefits such as using coloured text @@ -1234,8 +1234,7 @@ When :meth:`.CommandTree.sync` is called, this method is called in a heavy loop string for each locale. A wide variety of translation systems can be implemented using this interface, such as -`gettext `_ and -`Project Fluent `_. +:mod:`gettext` and `Project Fluent `_. Only strings marked as ready for translation are passed to the method. By default, every string is considered translatable and passed. From 05ffda3d15381dfaa677b39e6af483ed135f4b78 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Sat, 7 Oct 2023 12:22:22 +1100 Subject: [PATCH 23/40] add missing link to stderr ref Co-Authored-By: James Hilton-Balfe --- docs/guide/interactions/slash_commands.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index 4959efb85f4c..1c3a3a21dca6 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -1052,7 +1052,7 @@ Error handling --------------- So far, any exceptions raised within a command callback, any custom checks, in a transformer -or during localisation, et cetera should just be logged in the program's ``stderr`` or through any custom logging handlers. +or during localisation, et cetera should just be logged in the program's :obj:`~sys.stderr` or through any custom logging handlers. In order to catch exceptions and do something else, such as sending a message to let a user know their invocation failed for some reason, the library uses something called error handlers. From 9d28be925e8a71149b77894fb6f340e5ebf92b28 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Sat, 7 Oct 2023 12:29:05 +1100 Subject: [PATCH 24/40] use = in f-string expression for add command Co-Authored-By: James Hilton-Balfe --- docs/guide/interactions/slash_commands.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index 1c3a3a21dca6..921b6e1e7643 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -388,7 +388,7 @@ Examples using a command to add 2 numbers together: right operand """ - await interaction.response.send_message(f'{a} + {b} is {a + b}!') + await interaction.response.send_message(f'{a + b = }') .. tab:: Google @@ -403,7 +403,7 @@ Examples using a command to add 2 numbers together: b (int): right operand """ - await interaction.response.send_message(f'{a} + {b} is {a + b}!') + await interaction.response.send_message(f'{a + b = }') .. tab:: Sphinx @@ -417,7 +417,7 @@ Examples using a command to add 2 numbers together: :param b: right operand """ - await interaction.response.send_message(f'{a} + {b} is {a + b}!') + await interaction.response.send_message(f'{a + b = }') Other meta info can be specified in the docstring, such as the function return type, but in-practice only the parameter descriptions are used. From cd55a545a2b61559ef0abc9d3e70f629a68310dc Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Sat, 7 Oct 2023 12:31:10 +1100 Subject: [PATCH 25/40] clarify wording on subclassing groups Co-Authored-By: James Hilton-Balfe --- docs/guide/interactions/slash_commands.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index 921b6e1e7643..bda860b80f75 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -714,7 +714,7 @@ Command groups **are not invocable** on their own. Therefore, instead of creating a command the standard way by decorating an async function, groups are created by using :class:`.app_commands.Group`. -This class is customisable by subclassing and passing in any relevant fields at inheritance: +This class is customisable by subclassing and passing in any relevant fields in the class constructor: .. code-block:: python From 684b1e74761d5a57d38ba9e97cebf738631adbe0 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Sat, 7 Oct 2023 12:41:23 +1100 Subject: [PATCH 26/40] redo some wording for typing.Union --- docs/guide/interactions/slash_commands.rst | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index bda860b80f75..c0e94ff27da0 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -293,13 +293,13 @@ On Discord: typing.Union +++++++++++++ -Some types comprise of multiple other types. For example, a ``MENTIONABLE`` type parameter can point to any of these: +Some types comprise of multiple other types. +For example, the ``MENTIONABLE`` type includes both the user and role types: -- :class:`discord.User` -- :class:`discord.Member` +- :class:`discord.User` and :class:`discord.Member` - :class:`discord.Role` -To specify in code, a parameter should annotate to a :obj:`typing.Union` with all the different models: +To use a mentionable type, a parameter should annotate to a :obj:`~typing.Union` with each model: .. code-block:: python @@ -314,9 +314,8 @@ To specify in code, a parameter should annotate to a :obj:`typing.Union` with al f'i got: {mentionable}, of type: {mentionable.__class__.__name__}' ) -Types that point to other types also don't have to include everything. -For example, a ``CHANNEL`` type parameter can point to any channel in a guild, -but can be narrowed down to a specific set of channels: +Not everything has to be included - for example, a ``CHANNEL`` type parameter +can point to any channel in a guild, but can be narrowed down to a specific set of types: .. code-block:: python @@ -337,13 +336,13 @@ but can be narrowed down to a specific set of channels: # Threads and voice channels only pass -.. note:: +.. warning:: Union types can't mix Discord types. Something like ``Union[discord.Member, discord.TextChannel]`` isn't possible. -Refer to the :ref:`type conversion table ` for full information on sub-types. +Refer to the :ref:`type conversion table ` for full information. Describing +++++++++++ From 8c6e8a94a47a29a7ce99043ed161527160d94857 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Sun, 8 Oct 2023 08:53:45 +1100 Subject: [PATCH 27/40] try to tidy error handling section and improve flow --- docs/guide/interactions/slash_commands.rst | 103 +++++++++++++-------- 1 file changed, 65 insertions(+), 38 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index c0e94ff27da0..7f30a5c99317 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -1092,71 +1092,91 @@ Attaching a local handler to a command to catch a check exception: roles = ', '.join(str(r) for r in error.missing_roles) await interaction.response.send_message('i only thank people who have one of these roles!: {roles}') -Catching exceptions from all subcommands in a group: +Attaching an error handler to a group: .. code-block:: python + @my_group.error + async def my_group_error(interaction: discord.Interaction, error: app_commands.AppCommandError): + pass # im called for all subcommands and subgroups + + + # or in a subclass: class MyGroup(app_commands.Group): async def on_error(self, interaction: discord.Interaction, error: app_commands.AppCommandError): - ... + pass + +Adding a global error handler: + +.. code-block:: python + + @client.tree.error + async def on_app_command_error(interaction: discord.Interaction, error: app_commands.AppCommandError): + pass # im called for all commands + + + # alternatively, you can override `CommandTree.on_error` + # when using commands.Bot, ensure you pass this class to the `tree_cls` kwarg in the bot constructor! + + class MyTree(app_commands.CommandTree): + async def on_error(self, interaction: discord.Interaction, error: app_commands.AppCommandError): + pass + +.. warning:: + + When overriding the global error handler, ensure you're at least catching any invocation errors (covered below) + to make sure your bot isn't unexpectedly failing silently. -When an exception that doesn't derive :class:`~.app_commands.AppCommandError` is raised, it's wrapped -into :class:`~.app_commands.CommandInvokeError`, with the original exception being accessible with ``__cause__``. +Invocation errors +++++++++++++++++++ + +When an exception that doesn't derive :class:`~.app_commands.AppCommandError` is raised in a command callback, +it's wrapped into :class:`~.app_commands.CommandInvokeError` before being sent to any error handlers. Likewise: - For transformers, exceptions that don't derive :class:`~.app_commands.AppCommandError` are wrapped in :class:`~.app_commands.TransformerError`. - For translators, exceptions that don't derive :class:`~.app_commands.TranslationError` are wrapped into it. -This is helpful to differentiate between exceptions that the bot expects, such as :class:`~.app_commands.MissingAnyRole`, -over exceptions like :class:`TypeError` or :class:`ValueError`, which typically trace back to a programming mistake or HTTP error. +This exception is helpful to differentiate between exceptions that the bot expects, such as those from a command check, +over exceptions like :class:`TypeError` or :class:`ValueError`, which tend to trace back to a programming mistake or API error. To catch these exceptions in a global error handler for example: -.. tab:: Python versions below 3.10 - - .. code-block:: python - - import sys - import traceback - - @client.tree.error - async def on_app_command_error(interaction: discord.Interaction, error: app_commands.AppCommandError): - assert interaction.command is not None +.. code-block:: python - if isinstance(error, app_commands.CommandInvokeError): - print(f'Ignoring unknown exception in command {interaction.command.name}', file=sys.stderr) - traceback.print_exception(error.__class__, error, error.__traceback__) + import sys + import traceback -.. tab:: Python versions 3.10+ + @client.tree.error + async def on_app_command_error(interaction: discord.Interaction, error: app_commands.AppCommandError): + assert interaction.command is not None - .. code-block:: python + if isinstance(error, app_commands.CommandInvokeError): + print(f'Ignoring unknown exception in command {interaction.command.name}', file=sys.stderr) + traceback.print_exception(error.__class__, error, error.__traceback__) - import sys - import traceback + # the original exception can be accessed using error.__cause__ - @client.tree.error - async def on_app_command_error(interaction: discord.Interaction, error: app_commands.AppCommandError): - assert interaction.command is not None +Custom exceptions +++++++++++++++++++ - if isinstance(error, app_commands.CommandInvokeError): - print(f'Ignoring unknown exception in command {interaction.command.name}', file=sys.stderr) - traceback.print_exception(error) +When a command has multiple checks, it can be hard to know *which* check failed in an error handler, +since the default behaviour is to raise a blanket :class:`~.app_commands.CheckFailure` exception. -Raising a new error from a check for a more robust method of catching failed checks: +To solve this, inherit from the exception and raise it from the check instead of returning :obj:`False`: .. code-block:: python import random class Unlucky(app_commands.CheckFailure): - def __init__(self): - super().__init__("you're unlucky!") + pass def coinflip(): async def predicate(interaction: discord.Interaction) -> bool: if random.randint(0, 1) == 0: - raise Unlucky() + raise Unlucky("you're unlucky!") return True return app_commands.check(predicate) @@ -1170,13 +1190,13 @@ Raising a new error from a check for a more robust method of catching failed che if isinstance(error, Unlucky): await interaction.response.send_message(str(error)) -Raising an exception from a transformer and catching it: +Transformers behave similarly, but should derive :class:`~.app_commands.AppCommandError` instead: .. code-block:: python from discord.app_commands import Transform - class BadDateArgument(app_commands.Translator): + class BadDateArgument(app_commands.AppCommandError): def __init__(self, argument: str): super().__init__(f'expected a date in dd/mm/yyyy format, not "{argument}".') @@ -1197,13 +1217,20 @@ Raising an exception from a transformer and catching it: if isinstance(error, BadDateArgument): await interaction.response.send_message(str(error)) -Instead of printing plainly to :obj:`sys.stderr`, the standard :mod:`logging` module can be configured instead - +Since a unique exception is used, extra state can be attached using :meth:`~object.__init__` for the error handler to work with. + +One example of this is in the library with :attr:`.app_commands.MissingAnyRole.missing_roles`. + +Logging +++++++++ + +Instead of printing plainly to :obj:`~sys.stderr`, the standard :mod:`logging` module can be configured instead - which is what discord.py uses to write its own exceptions. -Whilst logging is a little bit more involved to set up, it has some added benefits such as using coloured text +Whilst its a little bit more involved to set up, it has some added benefits such as using coloured text in a terminal and being able to write to a file. -Refer to the :ref:`Setting Up logging ` page for more info. +Refer to the :ref:`Setting Up logging ` page for more info and examples. .. _translating: From a6a9994f34bb67086ec10287da4e507e73572adf Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Sun, 8 Oct 2023 08:57:27 +1100 Subject: [PATCH 28/40] english --- docs/guide/interactions/slash_commands.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index 7f30a5c99317..e96c6215e06e 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -1227,7 +1227,7 @@ Logging Instead of printing plainly to :obj:`~sys.stderr`, the standard :mod:`logging` module can be configured instead - which is what discord.py uses to write its own exceptions. -Whilst its a little bit more involved to set up, it has some added benefits such as using coloured text +Whilst it's a little bit more involved to set up, it has some added benefits such as using coloured text in a terminal and being able to write to a file. Refer to the :ref:`Setting Up logging ` page for more info and examples. From a9c1077352e4b8fed53091475f6aac2fe31e4573 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Mon, 9 Oct 2023 12:41:12 +1100 Subject: [PATCH 29/40] cover commands showing up twice and a warning on syncing globally --- docs/guide/interactions/slash_commands.rst | 43 +++++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index e96c6215e06e..42f7ea881659 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -128,6 +128,16 @@ To change them to something else, ``tree.command()`` takes ``name`` and ``descri If a description isn't provided through ``description`` or by the docstring, an ellipsis "..." is used instead. +.. warning:: + + Without specifying otherwise, commands are global. + + After `syncing`_ globally, these commands will show up on every guild your bot is in + provided it has the ``applications.commands`` scope. + + To make space for yourself to experiment with app commands safely, + create a new testing bot instead or alternatively sync your commands :ref:`locally `. + Interaction ++++++++++++ @@ -171,6 +181,8 @@ For example, to send a deferred ephemeral message: await interaction.followup.send(f'the weather today is {forecast}!') +.. _syncing: + Syncing ++++++++ @@ -601,11 +613,11 @@ the underlying type and other information must now be provided through the :clas These can be provided by overriding the following properties: -- :attr:`~.Transformer.type` -- :attr:`~.Transformer.min_value` -- :attr:`~.Transformer.max_value` -- :attr:`~.Transformer.choices` -- :attr:`~.Transformer.channel_types` +- :attr:`.Transformer.type` +- :attr:`.Transformer.min_value` +- :attr:`.Transformer.max_value` +- :attr:`.Transformer.choices` +- :attr:`.Transformer.channel_types` Since these are properties, they must be decorated with :class:`property`: @@ -620,6 +632,8 @@ Since these are properties, they must be decorated with :class:`property`: def type(self) -> discord.AppCommandOptionType: return discord.AppCommandOptionType.user +.. (todo) talk about this properly and write an example + :meth:`~.Transformer.autocomplete` callbacks can also be defined in-line. .. _type_conversion: @@ -802,7 +816,7 @@ Guild commands --------------- So far, all the command examples in this page have been global commands, -which every guild your bot is in can see and use. +which every guild your bot is in can see and use, provided it has the ``applications.commands`` scope, and in direct-messages. In contrast, guild commands are only seeable and usable by members of a certain guild. @@ -857,6 +871,23 @@ to copy all global commands to a certain guild for syncing: You'll typically find this syncing paradigm in some of the examples in the repository. +.. warning:: + + If your commands are showing up twice, it's often as a result of a command being synced + both globally and as a guild command. + + Removing a command from Discord needs another call to :meth:`.CommandTree.sync` - + for example, to remove local commands from a guild: + + .. code-block:: python + + guild = discord.Object(695868929154744360) # a bot testing server + + #self.tree.copy_global_to(guild) # dont copy the commands over + await self.tree.sync(guild=guild) + + # afterwards, the local commands should be removed + .. _checks: Checks From ed51795834c8b283b118231d40d328d78dd56334 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Tue, 17 Oct 2023 07:29:29 +1100 Subject: [PATCH 30/40] unhappy with the fluent example --- docs/guide/interactions/slash_commands.rst | 209 ++++-------------- .../app_commands/apple_command_english.png | Bin 12471 -> 0 bytes .../app_commands/apple_command_japanese.png | Bin 11508 -> 0 bytes 3 files changed, 42 insertions(+), 167 deletions(-) delete mode 100644 docs/images/guide/app_commands/apple_command_english.png delete mode 100644 docs/images/guide/app_commands/apple_command_japanese.png diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index 42f7ea881659..c871e4d5cb8f 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -264,7 +264,7 @@ Trying to enter a non-numeric character for ``amount`` will result with this red .. image:: /images/guide/app_commands/input_a_valid_integer.png :width: 300 -Additionally, since both of these parameters are required, trying to skip one will result in this message in red: +Additionally, since both of these parameters are required, trying to skip one will result with: .. image:: /images/guide/app_commands/this_option_is_required.png :width: 300 @@ -554,7 +554,7 @@ Range :class:`str`, :class:`int` and :class:`float` type parameters can optionally set a minimum and maximum value. For strings, this limits the character count, whereas for numeric types this limits the magnitude. -To set a range, a parameter should annotate to :class:`.app_commands.Range`. +Refer to the :class:`.app_commands.Range` page for more info and code examples. .. _transformers: @@ -594,8 +594,8 @@ To make one, inherit from :class:`.app_commands.Transformer` and override the :m If you're familar with the commands extension (:ref:`ext.commands `), a lot of similarities can be drawn between transformers and converters. -To use this transformer in a command, a paramater needs to annotate to :class:`.app_commands.Transform`, -passing the transformed type and transformer respectively. +To use this transformer in a command, a paramater needs to annotate to :class:`~.app_commands.Transform`, +passing the new type and class respectively. .. code-block:: python @@ -667,41 +667,40 @@ The table below outlines the relationship between Discord and Python types. :ddocs:`Application command option types ` as documented by Discord. -User parameter -^^^^^^^^^^^^^^^ +.. note:: -Annotating to either :class:`discord.User` or :class:`discord.Member` both point to a ``USER`` Discord-type. + Annotating to either :class:`discord.User` or :class:`discord.Member` both point to a ``USER`` Discord-type. -The actual type given by Discord is dependent on whether the command was invoked in DM-messages or in a guild. + The actual type given by Discord is dependent on whether the command was invoked in direct-messages or in a guild. -For example, if a parameter annotates to :class:`~discord.Member`, and the command is invoked in direct-messages, -discord.py will raise an error since the actual type given by Discord, -:class:`~discord.User`, is incompatible with :class:`~discord.Member`. + For example, if a parameter annotates to :class:`~discord.Member`, and the command is invoked in direct-messages, + discord.py will raise an error since the actual type given by Discord, + :class:`~discord.User`, is incompatible with :class:`~discord.Member`. -discord.py doesn't raise an error for the other way around, ie. a parameter annotated to :class:`~discord.User` invoked in a guild - -this is because :class:`~discord.Member` is compatible with :class:`~discord.User`. + discord.py doesn't raise an error for the other way around, ie. a parameter annotated to :class:`~discord.User` invoked in a guild - + this is because :class:`~discord.Member` is compatible with :class:`~discord.User`. -To accept member and user, regardless of where the command was invoked, place both types in a :obj:`~typing.Union`: + To accept member and user, regardless of where the command was invoked, place both types in a :obj:`~typing.Union`: -.. code-block:: python + .. code-block:: python - from typing import Union + from typing import Union - @client.tree.command() - async def userinfo( - interaction: discord.Interaction, - user: Union[discord.User, discord.Member] - ): - info = user.name + @client.tree.command() + async def userinfo( + interaction: discord.Interaction, + user: Union[discord.User, discord.Member] + ): + info = user.name - # add some extra info if this command was invoked in a guild - if isinstance(user, discord.Member): - joined = user.joined_at - if joined: - relative = discord.utils.format_dt(joined, 'R') - info = f'{info} (joined this server {relative})' + # add some extra info if this command was invoked in a guild + if isinstance(user, discord.Member): + joined = user.joined_at + if joined: + relative = discord.utils.format_dt(joined, 'R') + info = f'{info} (joined this server {relative})' - await interaction.response.send_message(info) + await interaction.response.send_message(info) .. _command_groups: @@ -984,8 +983,9 @@ To add a check, use the :func:`.app_commands.check` decorator: import random + # takes the interaction and returns a boolean async def predicate(interaction: discord.Interaction) -> bool: - return random.randint(0, 1) == 1 + return random.randint(0, 1) == 1 # 50% chance @client.tree.command() @app_commands.check(predicate) @@ -1250,7 +1250,9 @@ Transformers behave similarly, but should derive :class:`~.app_commands.AppComma Since a unique exception is used, extra state can be attached using :meth:`~object.__init__` for the error handler to work with. -One example of this is in the library with :attr:`.app_commands.MissingAnyRole.missing_roles`. +One such example of this is in the library with the +:attr:`~.app_commands.MissingAnyRole.missing_roles` attribute for the +:class:`~.app_commands.MissingAnyRole` exception. Logging ++++++++ @@ -1373,7 +1375,7 @@ In summary: - Use :class:`~.app_commands.locale_str` in-place of :class:`str` in parts of a command you want translated. - - Done by default, so this step is skipped in-practice. + - Done by default, so this step can be skipped. - Subclass :class:`.app_commands.Translator` and override the :meth:`.Translator.translate` method. @@ -1385,132 +1387,6 @@ In summary: - :meth:`.Translator.translate` will be called on all translatable strings. -Following is a quick demo using the `Project Fluent `_ translation system -and the `Python fluent library `_. - -Like a lot of other l10n systems, fluent uses directories and files to separate localisations. - -A structure like this is used for this example: - -.. code-block:: - - discord_bot/ - ├── l10n/ - │ └── ja/ - │ └── commands.ftl - └── bot.py - -``commands.ftl`` is a translation resource described in fluent's `FTL `_ format - -containing the Japanese (locale: ``ja``) localisations for a certain command in the bot: - -.. code-block:: - - # command metadata - apple-command-name = リンゴ - apple-command-description = ボットにリンゴを食べさせます。 - - # parameters - apple-command-amount = 食べさせるリンゴの数 - - # responses from the command body - apple-command-response = リンゴを{ $apple_count }個食べました。 - -Onto the translator: - -.. code-block:: python - - from fluent.runtime import FluentLocalization, FluentResourceLoader - - class JapaneseTranslator(app_commands.Translator): - def __init__(self): - # load any resources when the translator initialises. - # if asynchronous setup is needed, override `Translator.load()`! - - self.resources = FluentResourceLoader('./l10n/{locale}') - self.mapping = { - discord.Locale.japanese: FluentLocalization(['ja'], ['commands.ftl'], self.resources), - # + additional locales as needed - } - - async def translate( - self, - string: locale_str, - locale: discord.Locale, - context: app_commands.TranslationContext - ): - """core translate method called by the library""" - - fluent_id = string.extras.get('fluent_id') - if not fluent_id: - # ignore strings without an attached fluent_id - return None - - l10n = self.mapping.get(locale) - if not l10n: - # no translation available for this locale - return None - - # otherwise, a translation is assumed to exist and is returned - return l10n.format_value(fluent_id) - - async def localise( - self, - string: locale_str, - locale: discord.Locale, - **params: Any - ) -> str: - """translates a given string for a locale, subsituting any required parameters. - meant to be called for things outside what discord handles, eg. a message sent from the bot - """ - - l10n = self.mapping.get(locale) - if not l10n: - # return the string untouched - return string.message - - # strings passed to this method need to include a fluent_id extra - # since we are trying to explicitly localise a string - fluent_id = string.extras['fluent_id'] - - return l10n.format_value(fluent_id, params) - -With the command, strings are only considered translatable if they have an -attached ``fluent_id`` extra: - -.. code-block:: python - - @client.tree.command( - name=_('apple', fluent_id='apple-command-name'), - description=_('tell the bot to eat some apples', fluent_id='apple-command-description') - ) - @app_commands.describe(amount=_('how many apples?', fluent_id='apple-command-amount')) - async def apple(interaction: discord.Interaction, amount: int): - translator = client.tree.translator - - # plurals for the bots native/default language (english) are handled here in the code. - # fluent can handle plurals for secondary languages if needed. - # see: https://projectfluent.org/fluent/guide/selectors.html - - plural = 'apple' if amount == 1 else 'apples' - - translated = await translator.localise( - _(f'i ate {amount} {plural}', fluent_id='apple-command-response'), - interaction.locale, - apple_count=amount - ) - - await interaction.response.send_message(translated) - -Viewing the command with an English (or any other) language setting: - -.. image:: /images/guide/app_commands/apple_command_english.png - :width: 300 - -A Japanese language setting shows the added localisations: - -.. image:: /images/guide/app_commands/apple_command_japanese.png - :width: 300 - Recipes -------- @@ -1523,7 +1399,7 @@ Syncing app commands on startup, such as inside :meth:`.Client.setup_hook` can o and incur the heavy ratelimits set by Discord. Therefore, it's helpful to control the syncing process manually. -A common and recommended approach is to create an owner-only traditional message command to do this. +A common and recommended approach is to create an owner-only traditional command to do this. The :ref:`commands extension ` makes this easy: @@ -1531,10 +1407,8 @@ The :ref:`commands extension ` makes this easy: from discord.ext import commands - # requires the `message_content` intent to work! - intents = discord.Intents.default() - intents.message_content = True + intents.message_content = True # required! bot = commands.Bot(command_prefix='?', intents=intents) @bot.command() @@ -1545,7 +1419,8 @@ The :ref:`commands extension ` makes this easy: # invocable only by yourself on discord using ?sync -A more complex command that offers higher granularity using arguments: +A `more complex command `_ +that offers higher granularity using arguments: .. code-block:: python @@ -1556,8 +1431,6 @@ A more complex command that offers higher granularity using arguments: # requires the `message_content` intent to work! - # https://about.abstractumbra.dev/discord.py/2023/01/29/sync-command-example.html - @bot.command() @commands.guild_only() @commands.is_owner() @@ -1600,5 +1473,7 @@ bots can still read message content for direct-messages and for messages that me bot = commands.Bot( command_prefix=commands.when_mentioned, - intents=discord.Intents.default() - ) + # or... + command_prefix=commands.when_mentioned_or(">", "!"), + ... + ) \ No newline at end of file diff --git a/docs/images/guide/app_commands/apple_command_english.png b/docs/images/guide/app_commands/apple_command_english.png deleted file mode 100644 index 10da381481326de1cc618f749eaac31d2c806c72..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12471 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 c>C2b=k_opi(HS||j}73IWh}t|7mmH?#Q*>R diff --git a/docs/images/guide/app_commands/apple_command_japanese.png b/docs/images/guide/app_commands/apple_command_japanese.png deleted file mode 100644 index 4cf73104ee9c37175626489a930895996b7e04e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11508 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 bo@hP!tB33>;@J?H%1FxcYH}4aVCeq>g3%zs From ead8f01218701b4c6c1dd1eaf8709f4c98b8a07e Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Wed, 18 Oct 2023 21:46:58 +1100 Subject: [PATCH 31/40] try and improve explanation for USER discord type --- docs/guide/interactions/slash_commands.rst | 208 ++++++++++++--------- 1 file changed, 117 insertions(+), 91 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index c871e4d5cb8f..8a6d389bab02 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -5,43 +5,48 @@ Slash commands =============== -Slash commands are one of Discord's primary methods of implementing a user-interface for bots. -They're a branch of application commands, the other type being context menu commands, and analogous to their name, -they're previewed and invoked in the Discord client by beginning your message with a forward-slash: +Slash commands are one of Discord's native ways to let users interact with apps in the Discord client. +They're a branch of application commands, +with the other type being context menu commands (right-click / tap-hold menu). + +Analogous to their name, slash commands are previewed and invoked in the Discord client +by beginning your message with a forward-slash: .. image:: /images/guide/app_commands/meow_command_preview.png :width: 400 +Setting up +----------- + +To work with app commands, bots need the ``applications.commands`` scope. +You can enable this scope when generating an OAuth2 URL for your bot, the steps to do so outlined :ref:`here `. + Both types of app commands are implemented within the :ref:`discord.app_commands ` package. -Any code example in this page will always assume the following two imports: +Any code example in this page will always assume the following two imports, so make sure to import them! .. code-block:: python import discord from discord import app_commands -Setting up ------------ - -To work with app commands, bots need the ``applications.commands`` scope. - -You can enable this scope when generating an OAuth2 URL for your bot, the steps to do so outlined :ref:`here `. - -Defining a Tree +Defining a tree ++++++++++++++++ -First, an :class:`.app_commands.CommandTree` needs to be created -- which acts as a container holding all of the bot's application commands. +A :class:`~.app_commands.CommandTree` acts as a container holding all of the bot's app commands. -This class contains a few dedicated methods for managing and viewing app commands: +It contains a few dedicated methods for adding, viewing and removing app commands internally: - :meth:`.CommandTree.add_command` to add a command - :meth:`.CommandTree.remove_command` to remove a command -- :meth:`.CommandTree.get_command` to find a specific command -- :meth:`.CommandTree.get_commands` to return all commands +- :meth:`.CommandTree.get_command` to find a specific command (returns the command object) +- :meth:`.CommandTree.get_commands` to return all commands (returns a list) + +An instance of this (only one!) needs to be created so that we can begin adding commands to it. -Preferably, this tree should be attached to the client instance to -play well with type checkers and to allow for easy access from anywhere in the code: +It helps to directly attach the tree to the client instance, since this plays well with +any type checker if you're using one, and to allow for easy access from anywhere in the code. + +To do this, simply attach the tree to the ``self`` instance in a client subclass: .. code-block:: python @@ -59,15 +64,16 @@ play well with type checkers and to allow for easy access from anywhere in the c If your project instead uses :class:`.ext.commands.Bot` as the client instance, a :class:`~discord.app_commands.CommandTree` has already been defined at :attr:`.Bot.tree`, - so this step is largely skipped. + so this step is technically skipped. -Creating a command -------------------- +Creating a slash command +------------------------- -Slash commands are created by decorating an async function. -This function is then called whenever the slash command is invoked. +Slash commands are created by decorating an async function, +and that function is then called whenever the slash command is invoked by someone, +in a "callback" fashion. -For example, the following code responds with "meow" on invocation: +For example, the following code registers a command that responds with "meow" on invocation: .. code-block:: python @@ -77,15 +83,12 @@ For example, the following code responds with "meow" on invocation: await interaction.response.send_message('meow') -Functions of this pattern are called callbacks, since their execution is -left to the library to be called later. - -There are two main decorators to use when creating a command: +Two main decorators can be used: 1. :meth:`tree.command() <.CommandTree.command>` (as seen above) 2. :func:`.app_commands.command` -Both decorators wrap an async function into a :class:`~.app_commands.Command`, however, +Both decorators wrap an async function into a :class:`~.app_commands.Command` instance, however the former also adds the command to the tree, which skips the step of having to add it manually using :meth:`.CommandTree.add_command()`. @@ -108,7 +111,7 @@ For example, these two are functionally equivalent: Since ``tree.command()`` is more concise and easier to understand, it'll be the main method used to create slash commands in this guide. -Some information is logically inferred from the function to populate the slash command's fields: +Some information is logically inferred from the async function to populate the slash command's fields: - The :attr:`~.app_commands.Command.name` takes after the function name "meow" - The :attr:`~.app_commands.Command.description` takes after the docstring "Meow meow meow" @@ -124,20 +127,10 @@ To change them to something else, ``tree.command()`` takes ``name`` and ``descri # or... @client.tree.command(name='list') async def list_(interaction: discord.Interaction): - # prevent shadowing the "list" builtin + # prevent shadowing the 'list' builtin If a description isn't provided through ``description`` or by the docstring, an ellipsis "..." is used instead. -.. warning:: - - Without specifying otherwise, commands are global. - - After `syncing`_ globally, these commands will show up on every guild your bot is in - provided it has the ``applications.commands`` scope. - - To make space for yourself to experiment with app commands safely, - create a new testing bot instead or alternatively sync your commands :ref:`locally `. - Interaction ++++++++++++ @@ -186,12 +179,11 @@ For example, to send a deferred ephemeral message: Syncing ++++++++ -In order for this command to show up on Discord, the API needs some information regarding it, namely: +In order for this command to show up on Discord, the API needs some information to render it, namely: - The name and description - - Any :ref:`parameter names, types and descriptions ` -- Any :ref:`checks ` attached +- Any :ref:`checks ` attached - Whether this command is a :ref:`group ` - Whether this is a :ref:`global or guild command ` - Any :ref:`localisations ` for the above @@ -199,7 +191,8 @@ In order for this command to show up on Discord, the API needs some information Syncing is the process of sending this information, which is done by calling the :meth:`.CommandTree.sync` method. -Typically, this is called on start-up in :meth:`.Client.setup_hook`: +Typically, this is called on start-up in :meth:`.Client.setup_hook`, after all the commands have +been added to the tree: .. code-block:: python @@ -215,16 +208,26 @@ Commands need to be synced again each time a new command is added or removed, or Syncing is **not** required when changing client-side behaviour, such as by adding a :ref:`library-side check `, adding a :ref:`transformer ` -or changing anything within the function body. +or changing anything within the function body (how you respond is up to you!). + +If there's a mismatch with how the command looks in Discord compared to your code, +the library will log warning's and block any incoming invocations. -Reloading your own client is sometimes needed for new changes to be visible - +After syncing, reloading your own client is sometimes also needed for new changes to be visible - old commands tend to linger in the command preview if a client hasn't yet refreshed, but Discord blocks invocation with this message in red: .. image:: /images/guide/app_commands/outdated_command.png -As another measure, the library will log warnings if there's a mismatch with what Discord provides and -what the bot defines in code during invocation. +.. warning:: + + Without specifying otherwise, slash commands are global. + + After syncing globally, these commands will show up on every guild your bot is in + provided it has the ``applications.commands`` scope. + + To make space for yourself to experiment with app commands safely, + create a new testing bot instead or alternatively sync your commands :ref:`locally `. .. _parameters: @@ -233,7 +236,7 @@ Parameters Since slash commands are defined by making Python functions, parameters are similarly defined with function parameters. -Each parameter must have an assiociated type. This restricts what type of value a user can and cannot input. +Each parameter must have an assiociated type, which restricts what type of value a user can and cannot input. Types are specified in code through :pep:`3107` function annotations. For example, the following command has a ``liquid`` string parameter: @@ -295,11 +298,20 @@ For example, this command displays a given user's avatar, or the current user's avatar = (user or interaction.user).display_avatar await interaction.response.send_message(avatar.url) -On Discord: +After syncing: .. image:: /images/guide/app_commands/avatar_command_optional_preview.png :width: 300 +When assigning a default value that isn't ``None``, the default's type needs to match the parameter type: + +.. code-block:: python + + @client.tree.command() + async def is_even(interaction: discord.Interaction, number: int = '2'): # not allowed! + even = (number % 2) == 0 + await interaction.response.send_message('yes' if even else 'no!') + :pep:`Python version 3.10+ union types <604>` are also supported instead of :obj:`typing.Optional`. typing.Union @@ -514,8 +526,8 @@ shown :func:`here ` in the reference. Autocompletion +++++++++++++++ -Autocomplete callbacks allow the bot to dynamically return up to 25 choices -to a user as they type a parameter. +Autocompletes allow the bot to dynamically suggest up to 25 choices +to a user as they type an argument. In short: @@ -544,9 +556,9 @@ Code examples for either method can be found in the corresponding reference page .. warning:: Since exceptions raised from within an autocomplete callback are not considered handleable, - they're silently ignored and discarded. + they're not sent sent to any :ref:`error handlers `. - Instead, an empty list is returned to the user. + An empty list is returned by the library instead of the autocomplete failing after the 3 second timeout. Range ++++++ @@ -677,30 +689,46 @@ The table below outlines the relationship between Discord and Python types. discord.py will raise an error since the actual type given by Discord, :class:`~discord.User`, is incompatible with :class:`~discord.Member`. - discord.py doesn't raise an error for the other way around, ie. a parameter annotated to :class:`~discord.User` invoked in a guild - - this is because :class:`~discord.Member` is compatible with :class:`~discord.User`. + discord.py doesn't raise an error for the other way around (a parameter annotated to :class:`~discord.User` invoked in a guild) + since :class:`~discord.Member` implements the same interface as :class:`~discord.User`. - To accept member and user, regardless of where the command was invoked, place both types in a :obj:`~typing.Union`: + Some examples to help visualise: .. code-block:: python - from typing import Union + @client.tree.command() + async def memberinfo(interaction: discord.Interaction, member: discord.Member): + ... # unsafe, `member` could be a `discord.User`! + + @client.tree.command() + @app_commands.guild_only() + async def memberinfo(interaction: discord.Interaction, member: discord.Member): + ... # safe, since this command can't be invoked in direct-messages + + + # you can take advantage of this behaviour: @client.tree.command() async def userinfo( interaction: discord.Interaction, - user: Union[discord.User, discord.Member] + user: discord.User ): - info = user.name + embed = discord.Embed() + + embed.set_author(name=user.name, icon_url=user.display_avatar.url) + embed.add_field(name='ID', value=str(user.id)) - # add some extra info if this command was invoked in a guild if isinstance(user, discord.Member): + # add some extra info if this command was invoked in a guild joined = user.joined_at if joined: relative = discord.utils.format_dt(joined, 'R') - info = f'{info} (joined this server {relative})' + embed.add_field(name='Joined', value=relative) - await interaction.response.send_message(info) + # change the embed's colour to match their role + embed.colour = user.colour + + await interaction.response.send_message(embed=embed) .. _command_groups: @@ -721,7 +749,7 @@ Meaning, a structure like this is possible: ├── /todo add └── /todo delete -Command groups **are not invocable** on their own. +Command groups **are not invocable** on their own due to a Discord limitation. Therefore, instead of creating a command the standard way by decorating an async function, groups are created by using :class:`.app_commands.Group`. @@ -807,7 +835,7 @@ can be added on top of a subclass to apply to the group, for example: class Emojis(app_commands.Group): ... -Due to a Discord limitation, individual subcommands cannot have differing official-checks. +Due to a Discord limitation, individual subcommands cannot have differing :ref:`integration checks `. .. _guild_commands: @@ -887,20 +915,21 @@ You'll typically find this syncing paradigm in some of the examples in the repos # afterwards, the local commands should be removed -.. _checks: +.. _integration_checks: -Checks -------- +Integration checks +------------------- -Checks refer to the restrictions an app command can have for invocation. +Integration checks refer officially supported restrictions an app command can have for invocation. A user needs to pass all checks on a command in order to be able to invoke and see the command on their client. +Since this behaviour is handled by Discord alone, bots can't add any extra or custom behaviour. + Age-restriction ++++++++++++++++ Indicates whether this command can only be used in NSFW channels or not. - -This can be configured by passing the ``nsfw`` keyword argument within the command decorator: +Configured by passing the ``nsfw`` keyword argument within the command decorator: .. code-block:: python @@ -912,16 +941,19 @@ Guild-only +++++++++++ Indicates whether this command can only be used in guilds or not. - Enabled by adding the :func:`.app_commands.guild_only` decorator when defining an app command: .. code-block:: python + import random + @client.tree.command() @app_commands.guild_only() - async def serverinfo(interaction: discord.Interaction): + async def roulette(interaction: discord.Interaction): assert interaction.guild is not None - await interaction.response.send_message(interaction.guild.name) + + victim = random.choice(interaction.guild.members) + await victim.ban(reason='unlucky') Default permissions ++++++++++++++++++++ @@ -949,7 +981,8 @@ To prevent this, :func:`~.app_commands.guild_only` can also be added. .. warning:: - This can be overridden to a different set of permissions by server administrators through the "Integrations" tab on the official client, + This can be overridden to a different set of permissions by server administrators + through the "Integrations" tab on the Discord client, meaning, an invoking user might not actually have the permissions specified in the decorator. .. _custom_checks: @@ -959,13 +992,11 @@ Custom checks A custom check is something that can be applied to a command to check if someone should be able to run it. -They're unique to the :ref:`officially supported checks ` by Discord in that they're handled -entirely client-side. +At their core, a check is a basic predicate that takes in the :class:`~discord.Interaction` as its sole parameter. -In short, a check is an async function that takes in the :class:`~discord.Interaction` as its sole parameter. It has the following options: -- Return a :obj:`True`-like to signal this check passes. +- Return a ``True``-like to signal this check passes. - If a command has multiple checks, **all** of them need to pass in order for the invocation to continue. @@ -973,7 +1004,7 @@ It has the following options: - Exceptions are passed to the bot's :ref:`error handlers `. -- Return a :obj:`False`-like to signal a person can't run the command. +- Return a ``False``-like to signal a person can't run the command. - :class:`~.app_commands.CheckFailure` will be raised instead. @@ -1010,7 +1041,7 @@ Transforming the check into its own decorator for easier usage: Checks are called sequentially and retain decorator order, bottom-to-top. -Take advantage of this order if, for example, you only want a cooldown to apply if a previous check passes: +Take advantage of this order if, for example, you only want a certain check to apply if a previous check passes: .. code-block:: python @@ -1221,7 +1252,7 @@ To solve this, inherit from the exception and raise it from the check instead of if isinstance(error, Unlucky): await interaction.response.send_message(str(error)) -Transformers behave similarly, but should derive :class:`~.app_commands.AppCommandError` instead: +Transformers behave similarly, but exceptions should derive :class:`~.app_commands.AppCommandError` instead: .. code-block:: python @@ -1236,7 +1267,7 @@ Transformers behave similarly, but should derive :class:`~.app_commands.AppComma try: when = datetime.datetime.strptime(date, '%d/%m/%Y') except ValueError: - raise BadDateArgument(value) + raise BadDateArgument(value) from None when = when.replace(tzinfo=datetime.timezone.utc) return when @@ -1387,13 +1418,8 @@ In summary: - :meth:`.Translator.translate` will be called on all translatable strings. -Recipes --------- - -This section covers some common use-cases for slash commands. - Manually syncing -+++++++++++++++++ +----------------- Syncing app commands on startup, such as inside :meth:`.Client.setup_hook` can often be spammy and incur the heavy ratelimits set by Discord. From 3b91e08eacbc42bac492abb06b82cbb47658dad1 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Sat, 21 Oct 2023 13:33:16 +1100 Subject: [PATCH 32/40] expand a bit on extras for locale string --- docs/guide/interactions/slash_commands.rst | 102 ++++++++++++--------- 1 file changed, 58 insertions(+), 44 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index 8a6d389bab02..e380f5c4a0d5 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -477,6 +477,8 @@ For example, to use :func:`~.app_commands.describe` and :func:`~.app_commands.re async def bottles(interaction: discord.Interaction, liquid: str, amount: int): await interaction.response.send_message(f'{amount} bottles of {liquid} on the wall!') +.. _choices: + Choices ++++++++ @@ -920,7 +922,7 @@ You'll typically find this syncing paradigm in some of the examples in the repos Integration checks ------------------- -Integration checks refer officially supported restrictions an app command can have for invocation. +Integration checks refer to the officially supported restrictions an app command can have for invocation. A user needs to pass all checks on a command in order to be able to invoke and see the command on their client. Since this behaviour is handled by Discord alone, bots can't add any extra or custom behaviour. @@ -1301,63 +1303,77 @@ Refer to the :ref:`Setting Up logging ` page for more info and ex Translating ------------ -Discord supports localisation (l10n) for the following fields: +Localisations can be added to the following fields, such that they'll appear differently +depending on a user's language setting: - Command names and descriptions - Parameter names and descriptions -- Choice names (choices and autocomplete) - -This allows the above fields to appear differently according to a user client's language setting. +- Choice names (used for both :ref:`choices ` and :ref:`autocomplete `) Localisations can be done :ddocs:`partially ` - -when a locale doesn't have a translation for a given field, Discord will use the default/original string instead. +when a field doesn't have a translation for a given locale, Discord instead uses the original string. + +.. warning:: + + A translation should only be added for a locale if it's something distinct from the original string - + duplicates are ignored by the API. -Support for l10n is implemented in discord.py with the :class:`.app_commands.Translator` interface, -which are effectively classes containing a core ``transform`` method that -takes the following parameters: +In discord.py, localisations are set using the :class:`.app_commands.Translator` interface, +which is a class containing a ``transform`` method that needs to be overriden with the following parameters: 1. ``string`` - the string to be translated according to ``locale`` 2. ``locale`` - the locale to translate to 3. ``context`` - the context of this translation (what type of string is being translated) -When :meth:`.CommandTree.sync` is called, this method is called in a heavy loop for each -string for each locale. - -A wide variety of translation systems can be implemented using this interface, such as -:mod:`gettext` and `Project Fluent `_. +When :meth:`.CommandTree.sync` is called, this method is called in a heavy loop for each string for each locale. -Only strings marked as ready for translation are passed to the method. -By default, every string is considered translatable and passed. - -Nonetheless, to specify a translatable string explicitly, -simply pass a string wrapped in :class:`~.app_commands.locale_str` in places you'd usually use :class:`str`: +Strings need to be marked as ready for translation in order for this method to be called, +which you can do by using a special :class:`~.app_commands.locale_str` type in places you'd usually :class:`str`: .. code-block:: python from discord.app_commands import locale_str as _ - @client.tree.command(name=_('example'), description=_('an example command')) - async def example(interaction: discord.Interaction): - ... + @client.tree.command(name=_('avatar'), description=_('display your avatar')) + async def avatar(interaction: discord.Interaction): + url = interaction.user.avatar.url + await interaction.response.send_message(url) -To toggle this behaviour, set the ``auto_locale_strings`` keyword-argument -to :obj:`False` when creating a command: +.. hint:: -.. code-block:: python + Every string is actually already considered translatable by default and + wrapped into :class:`~.app_commands.locale_str` before being passed to ``transform``, + so this step can be skipped in some cases. - @client.tree.command(name='example', description='an example command', auto_locale_strings=False) - async def example(interaction: discord.Interaction): - ... # i am not translated + To toggle this behaviour, set the ``auto_locale_strings`` keyword-argument to ``False`` when creating a command: -.. hint:: + .. code-block:: python + + @client.tree.command(name='avatar', description='display your avatar', auto_locale_strings=False) + async def avatar(interaction: discord.Interaction): + ... # this command is ignored by the translator + +From this example, ``'avatar'`` and ``'display your avatar'`` are used as the default strings for the command name and description respectively. + +Translation systems like `Project Fluent `_ require a separate translation ID than the default string as-is. +For this reason, additional keyword-arguments passed to the :class:`~.app_commands.locale_str` constructor +are inferred as "extra" information by the library, which is kept untouched at :attr:`.locale_str.extras`. + +For example, to pass a ``fluent_id`` extra whilst keeping the original string: - Additional keyword-arguments passed to the :class:`~.app_commands.locale_str` constructor are - inferred as "extra" information, which is kept untouched by the library in :attr:`~.locale_str.extras`. +.. code-block:: python - Utilise this field if additional info surrounding the string is required for translation. + @client.tree.command( + name=_('avatar', fluent_id='avatar-cmd.name'), + description=_('display your avatar', fluent_id='avatar-cmd.description') + ) + async def avatar(interaction: discord.Interaction): + ... -Next, to create a translator, inherit from :class:`.app_commands.Translator` and -override the :meth:`~.Translator.translate` method: +A translator can then read off of :attr:`~.locale_str.extras` for the translation identifier. +Systems like :mod:`gettext` don't need this type of behaviour, so it works out of the box without specifying the extra. + +Next, to create a translator, inherit from :class:`.app_commands.Translator` and override the :meth:`~.Translator.translate` method: .. code-block:: python @@ -1370,27 +1386,25 @@ override the :meth:`~.Translator.translate` method: ) -> str: ... -A string should be returned according to the given ``locale``. If no translation is available, -:obj:`None` should be returned instead. +A string should be returned according to the given ``locale``. If no translation is available, ``None`` should be returned instead. :class:`~.app_commands.TranslationContext` provides contextual info for what is being translated. - -This contains 2 attributes: +It contains 2 attributes: - :attr:`~.app_commands.TranslationContext.location` - an enum representing what is being translated, eg. a command description. - :attr:`~.app_commands.TranslationContext.data` - can point to different things depending on the ``location``. - - When translating a field for a command or group, such as the name, this points to the command in question. + - When translating a field for a command or group, such as the name, this points to the command in question. - - When translating a parameter name, this points to the :class:`~.app_commands.Parameter`. + - When translating a parameter name, this points to the :class:`~.app_commands.Parameter`. - - For choice names, this points to the :class:`~.app_commands.Choice`. + - For choice names, this points to the :class:`~.app_commands.Choice`. Lastly, in order for a translator to be used, it needs to be attached to the tree by calling :meth:`.CommandTree.set_translator`. -Since this is an async method, it's ideal to call it in an async entry-point, such as :meth:`.Client.setup_hook`: +Since this is a coroutine, it's ideal to call it in an async entry-point, such as :meth:`.Client.setup_hook`: .. code-block:: python @@ -1418,11 +1432,11 @@ In summary: - :meth:`.Translator.translate` will be called on all translatable strings. -Manually syncing +Syncing manually ----------------- Syncing app commands on startup, such as inside :meth:`.Client.setup_hook` can often be spammy -and incur the heavy ratelimits set by Discord. +and incur the heavy ratelimits set by the API. Therefore, it's helpful to control the syncing process manually. A common and recommended approach is to create an owner-only traditional command to do this. From 7b29de5a630bd50d90afb404f2e29b67eebcaa06 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Sun, 22 Oct 2023 06:59:45 +1100 Subject: [PATCH 33/40] prefix each anchor with _guide_slash_commands like how other guides do it --- docs/guide/interactions/slash_commands.rst | 84 +++++++++++----------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index e380f5c4a0d5..ff8664ee1b3b 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -1,6 +1,6 @@ :orphan: -.. _discord_slash_commands: +.. _guide_slash_commands: Slash commands =============== @@ -66,8 +66,8 @@ To do this, simply attach the tree to the ``self`` instance in a client subclass a :class:`~discord.app_commands.CommandTree` has already been defined at :attr:`.Bot.tree`, so this step is technically skipped. -Creating a slash command -------------------------- +Creating a command +------------------- Slash commands are created by decorating an async function, and that function is then called whenever the slash command is invoked by someone, @@ -139,9 +139,9 @@ a Discord model used for both app commands and UI message components. When an interaction is created on command invoke, some information about the surrounding context is given, such as: -- :class:`discord.Interaction.channel` - the channel it was invoked in -- :class:`discord.Interaction.guild` - the guild it was invoked in, if any -- :class:`discord.Interaction.user` - the user or member who invoked the command +- :attr:`.Interaction.channel` - the channel it was invoked in +- :attr:`.Interaction.guild` - the guild it was invoked in, if any +- :attr:`.Interaction.user` - the user or member who invoked the command Attributes like these and others are a given, however when it comes to responding to an interaction, by sending a message or otherwise, the methods from :attr:`.Interaction.response` need to be used. @@ -174,7 +174,7 @@ For example, to send a deferred ephemeral message: await interaction.followup.send(f'the weather today is {forecast}!') -.. _syncing: +.. _guide_slash_commands_syncing: Syncing ++++++++ @@ -182,11 +182,11 @@ Syncing In order for this command to show up on Discord, the API needs some information to render it, namely: - The name and description -- Any :ref:`parameter names, types and descriptions ` -- Any :ref:`checks ` attached -- Whether this command is a :ref:`group ` -- Whether this is a :ref:`global or guild command ` -- Any :ref:`localisations ` for the above +- Any :ref:`parameter names, types and descriptions ` +- Any :ref:`integration checks ` attached +- Whether this command is a :ref:`group ` +- Whether this is a :ref:`global or guild command ` +- Any :ref:`localisations ` for the above Syncing is the process of sending this information, which is done by calling the :meth:`.CommandTree.sync` method. @@ -207,7 +207,7 @@ been added to the tree: Commands need to be synced again each time a new command is added or removed, or if any of the above properties change. Syncing is **not** required when changing client-side behaviour, -such as by adding a :ref:`library-side check `, adding a :ref:`transformer ` +such as by adding a :ref:`library-side check `, adding a :ref:`transformer ` or changing anything within the function body (how you respond is up to you!). If there's a mismatch with how the command looks in Discord compared to your code, @@ -227,9 +227,9 @@ blocks invocation with this message in red: provided it has the ``applications.commands`` scope. To make space for yourself to experiment with app commands safely, - create a new testing bot instead or alternatively sync your commands :ref:`locally `. + create a new testing bot instead or alternatively sync your commands :ref:`locally `. -.. _parameters: +.. _guide_slash_commands_parameters: Parameters ----------- @@ -277,7 +277,7 @@ Some parameter types have different modes of input. For example, annotating to :class:`~discord.User` will show a selection of users to pick from in the current context and :class:`~discord.Attachment` will show a file-dropbox. -A full overview of supported types can be seen in the :ref:`type conversion table `. +A full overview of supported types can be seen in the :ref:`type conversion table `. typing.Optional ++++++++++++++++ @@ -366,7 +366,7 @@ can point to any channel in a guild, but can be narrowed down to a specific set Something like ``Union[discord.Member, discord.TextChannel]`` isn't possible. -Refer to the :ref:`type conversion table ` for full information. +Refer to the :ref:`type conversion table ` for full information. Describing +++++++++++ @@ -400,7 +400,7 @@ Examples using a command to add 2 numbers together: .. code-block:: python @client.tree.command() - async def addition(interaction: discord.Interaction, a: int, b: int): + async def add(interaction: discord.Interaction, a: int, b: int): """adds 2 numbers together. Parameters @@ -418,7 +418,7 @@ Examples using a command to add 2 numbers together: .. code-block:: python @client.tree.command() - async def addition(interaction: discord.Interaction, a: int, b: int): + async def add(interaction: discord.Interaction, a: int, b: int): """adds 2 numbers together. Args: @@ -433,7 +433,7 @@ Examples using a command to add 2 numbers together: .. code-block:: python @client.tree.command() - async def addition(interaction: discord.Interaction, a: int, b: int): + async def add(interaction: discord.Interaction, a: int, b: int): """adds 2 numbers together. :param a: left operand @@ -443,10 +443,10 @@ Examples using a command to add 2 numbers together: await interaction.response.send_message(f'{a + b = }') Other meta info can be specified in the docstring, such as the function return type, -but in-practice only the parameter descriptions are used. +but only the parameter descriptions are read by the library. -Parameter descriptions added using :func:`.app_commands.describe` always -take precedence over ones specified in the docstring. +Descriptions added using :func:`.app_commands.describe` always take precedence over +ones specified in the docstring. Naming ^^^^^^^ @@ -469,15 +469,15 @@ For example, to use :func:`~.app_commands.describe` and :func:`~.app_commands.re .. code-block:: python @client.tree.command() - @app_commands.rename(amount='liquid-count') @app_commands.describe( liquid='what type of liquid is on the wall', amount='how much of it is on the wall' ) + @app_commands.rename(amount='liquid-count') async def bottles(interaction: discord.Interaction, liquid: str, amount: int): await interaction.response.send_message(f'{amount} bottles of {liquid} on the wall!') -.. _choices: +.. _guide_slash_commands_choices: Choices ++++++++ @@ -503,12 +503,12 @@ To illustrate, the following command has a selection of 3 colours with each valu from discord.app_commands import Choice @client.tree.command() - @app_commands.describe(colour='pick your favourite colour') @app_commands.choices(colour=[ Choice(name='Red', value=0xFF0000), Choice(name='Green', value=0x00FF00), Choice(name='Blue', value=0x0000FF) ]) + @app_commands.describe(colour='pick your favourite colour') async def colour(interaction: discord.Interaction, colour: Choice[int]): """show a colour""" @@ -523,7 +523,7 @@ On the client: discord.py also supports 2 other pythonic ways of adding choices to a command, shown :func:`here ` in the reference. -.. _autocompletion: +.. _guide_slash_commands_autocompletion: Autocompletion +++++++++++++++ @@ -558,7 +558,7 @@ Code examples for either method can be found in the corresponding reference page .. warning:: Since exceptions raised from within an autocomplete callback are not considered handleable, - they're not sent sent to any :ref:`error handlers `. + they're not sent sent to any :ref:`error handlers `. An empty list is returned by the library instead of the autocomplete failing after the 3 second timeout. @@ -570,7 +570,7 @@ For strings, this limits the character count, whereas for numeric types this lim Refer to the :class:`.app_commands.Range` page for more info and code examples. -.. _transformers: +.. _guide_slash_commands_transformers: Transformers +++++++++++++ @@ -650,7 +650,7 @@ Since these are properties, they must be decorated with :class:`property`: :meth:`~.Transformer.autocomplete` callbacks can also be defined in-line. -.. _type_conversion: +.. _guide_slash_commands_type_conversion: Type conversion ++++++++++++++++ @@ -689,7 +689,7 @@ The table below outlines the relationship between Discord and Python types. For example, if a parameter annotates to :class:`~discord.Member`, and the command is invoked in direct-messages, discord.py will raise an error since the actual type given by Discord, - :class:`~discord.User`, is incompatible with :class:`~discord.Member`. + :class:`~discord.User`, is incompatible with :class:`~discord.Member`, due to the presence of guild-specific attributes. discord.py doesn't raise an error for the other way around (a parameter annotated to :class:`~discord.User` invoked in a guild) since :class:`~discord.Member` implements the same interface as :class:`~discord.User`. @@ -732,7 +732,7 @@ The table below outlines the relationship between Discord and Python types. await interaction.response.send_message(embed=embed) -.. _command_groups: +.. _guide_slash_commands_command_groups: Command groups --------------- @@ -837,9 +837,9 @@ can be added on top of a subclass to apply to the group, for example: class Emojis(app_commands.Group): ... -Due to a Discord limitation, individual subcommands cannot have differing :ref:`integration checks `. +Due to a Discord limitation, individual subcommands cannot have differing :ref:`integration checks `. -.. _guild_commands: +.. _guide_slash_commands_guild_commands: Guild commands --------------- @@ -917,7 +917,7 @@ You'll typically find this syncing paradigm in some of the examples in the repos # afterwards, the local commands should be removed -.. _integration_checks: +.. _guide_slash_commands_integration_checks: Integration checks ------------------- @@ -987,7 +987,7 @@ To prevent this, :func:`~.app_commands.guild_only` can also be added. through the "Integrations" tab on the Discord client, meaning, an invoking user might not actually have the permissions specified in the decorator. -.. _custom_checks: +.. _guide_slash_commands_custom_checks: Custom checks -------------- @@ -1004,7 +1004,7 @@ It has the following options: - Raise a :class:`~.app_commands.AppCommandError`-derived exception to signal a person can't run the command. - - Exceptions are passed to the bot's :ref:`error handlers `. + - Exceptions are passed to the bot's :ref:`error handlers `. - Return a ``False``-like to signal a person can't run the command. @@ -1061,7 +1061,7 @@ Custom checks can either be: - Added using the :meth:`.app_commands.Group.error` decorator or overriding :meth:`.app_commands.Group.on_error`. -- :ref:`global `, running for all commands, and before any group or local checks. +- :ref:`global `, running for all commands, and before any group or local checks. .. note:: @@ -1070,7 +1070,7 @@ Custom checks can either be: Refer to the :ref:`checks guide ` for more info. -.. _global_check: +.. _guide_slash_commands_global_check: Global check +++++++++++++ @@ -1109,7 +1109,7 @@ For example: tree_cls=CoolPeopleTree ) -.. _error_handling: +.. _guide_slash_commands_error_handling: Error handling --------------- @@ -1298,7 +1298,7 @@ in a terminal and being able to write to a file. Refer to the :ref:`Setting Up logging ` page for more info and examples. -.. _translating: +.. _guide_slash_commands_translating: Translating ------------ @@ -1308,7 +1308,7 @@ depending on a user's language setting: - Command names and descriptions - Parameter names and descriptions -- Choice names (used for both :ref:`choices ` and :ref:`autocomplete `) +- Choice names (used for both :ref:`choices ` and :ref:`autocomplete `) Localisations can be done :ddocs:`partially ` - when a field doesn't have a translation for a given locale, Discord instead uses the original string. From 0e236dd27e6dbfb4b70d8e9caea435e4138f8eb7 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Sun, 22 Oct 2023 15:56:01 +1100 Subject: [PATCH 34/40] respond in the cmd example --- docs/guide/interactions/slash_commands.rst | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index ff8664ee1b3b..e6d4095885bc 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -954,9 +954,15 @@ Enabled by adding the :func:`.app_commands.guild_only` decorator when defining a async def roulette(interaction: discord.Interaction): assert interaction.guild is not None - victim = random.choice(interaction.guild.members) + members = interaction.guild.members + victim = random.choice(members) await victim.ban(reason='unlucky') + chance = 1 / len(members) + await interaction.response.send_message( + f'{victim.name} was chosen... ({chance:.2f}% chance)' + ) + Default permissions ++++++++++++++++++++ @@ -983,7 +989,7 @@ To prevent this, :func:`~.app_commands.guild_only` can also be added. .. warning:: - This can be overridden to a different set of permissions by server administrators + Default permissions can be overridden to a different set of permissions by server administrators through the "Integrations" tab on the Discord client, meaning, an invoking user might not actually have the permissions specified in the decorator. From 0d961074afd802a603a35780d3c8cf22c8ab6aae Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Fri, 17 Nov 2023 14:03:23 +1100 Subject: [PATCH 35/40] dont remember what i changed --- docs/guide/interactions/slash_commands.rst | 224 ++++++++++++++------- 1 file changed, 155 insertions(+), 69 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index e6d4095885bc..17a663c0fd19 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -134,8 +134,8 @@ If a description isn't provided through ``description`` or by the docstring, an Interaction ++++++++++++ -As shown above, app commands always keep the first parameter for an :class:`~discord.Interaction`, -a Discord model used for both app commands and UI message components. +Shown above, the first parameter is ``interaction`` - app commands always need to have this as the first parameter. +It represents a :class:`~discord.Interaction`, a Discord model used for both app commands and UI message components. When an interaction is created on command invoke, some information about the surrounding context is given, such as: @@ -155,7 +155,7 @@ In practice, it's common to use either of the following two methods: - :meth:`.InteractionResponse.send_message` to send a message - :meth:`.InteractionResponse.defer` to defer a response -In the case of deferring, a follow-up message needs to be sent within 15 minutes for app commands. +When deferring, a follow-up message needs to be sent within 15 minutes for app commands. For example, to send a deferred ephemeral message: @@ -168,9 +168,9 @@ For example, to send a deferred ephemeral message: async def weather(self, interaction: discord.Interaction): await interaction.response.defer(ephemeral=True) # indicates the follow-up message will be ephemeral - weathers = ['sunny', 'clear', 'cloudy', 'rainy', 'stormy', 'snowy'] - await asyncio.sleep(5) # an expensive operation... (no more than 15 minutes!) - forecast = random.choice(weathers) + climates = ['sunny', 'clear', 'cloudy', 'rainy', 'stormy', 'snowy'] + await asyncio.sleep(5.0) # an expensive operation... (no more than 15 minutes!) + forecast = random.choice(climates) await interaction.followup.send(f'the weather today is {forecast}!') @@ -191,8 +191,8 @@ In order for this command to show up on Discord, the API needs some information Syncing is the process of sending this information, which is done by calling the :meth:`.CommandTree.sync` method. -Typically, this is called on start-up in :meth:`.Client.setup_hook`, after all the commands have -been added to the tree: +Typically, this is called on start-up in :meth:`.Client.setup_hook`, since usually this is a spot +where all the app commands have been added to the tree: .. code-block:: python @@ -224,7 +224,7 @@ blocks invocation with this message in red: Without specifying otherwise, slash commands are global. After syncing globally, these commands will show up on every guild your bot is in - provided it has the ``applications.commands`` scope. + provided it has the ``applications.commands`` scope for that guild. To make space for yourself to experiment with app commands safely, create a new testing bot instead or alternatively sync your commands :ref:`locally `. @@ -272,7 +272,7 @@ Additionally, since both of these parameters are required, trying to skip one wi .. image:: /images/guide/app_commands/this_option_is_required.png :width: 300 -Some parameter types have different modes of input. +Other parameter types have different modes of input. For example, annotating to :class:`~discord.User` will show a selection of users to pick from in the current context and :class:`~discord.Attachment` will show a file-dropbox. @@ -282,10 +282,9 @@ A full overview of supported types can be seen in the :ref:`type conversion tabl typing.Optional ++++++++++++++++ -Discord supports optional parameters, wherein a user doesn't need to provide a value during invocation. +When a parameter is optional, a user can skip inputting a value for it during invocation. -A parameter is considered optional if its assigned a default value and/or annotated -to :obj:`~typing.Optional`. +To do this, wrap the existing type with :obj:`typing.Optional`, and/or assign a default value. For example, this command displays a given user's avatar, or the current user's avatar: @@ -309,7 +308,7 @@ When assigning a default value that isn't ``None``, the default's type needs to @client.tree.command() async def is_even(interaction: discord.Interaction, number: int = '2'): # not allowed! - even = (number % 2) == 0 + even = (int(number) % 2) == 0 await interaction.response.send_message('yes' if even else 'no!') :pep:`Python version 3.10+ union types <604>` are also supported instead of :obj:`typing.Optional`. @@ -323,7 +322,7 @@ For example, the ``MENTIONABLE`` type includes both the user and role types: - :class:`discord.User` and :class:`discord.Member` - :class:`discord.Role` -To use a mentionable type, a parameter should annotate to a :obj:`~typing.Union` with each model: +Since you need to specify multiple distinct types here, a :obj:`~typing.Union` annotation needs to be used: .. code-block:: python @@ -390,10 +389,15 @@ These show up on Discord just beside the parameter's name: Not specifying a description results with an ellipsis "..." being used instead. -In addition to the decorator, parameter descriptions can also be added using -Google, Sphinx or NumPy style docstrings. +For programmers who like to document their commands, to avoid stacking a lot of info within decorators +or to maintain a style more consistent with other functions, the library actions this by +parsing 3 popular and widely-used docstring formats in the Python ecosystem: -Examples using a command to add 2 numbers together: +- `Google's original styleguide for docstrings `_ +- `NumPy's header-based docstring standard `_ (this is what discord.py uses!) +- `Sphinx's reST style docstrings `_ + +Examples: .. tab:: NumPy @@ -401,7 +405,7 @@ Examples using a command to add 2 numbers together: @client.tree.command() async def add(interaction: discord.Interaction, a: int, b: int): - """adds 2 numbers together. + """adds two numbers together. Parameters ----------- @@ -417,16 +421,18 @@ Examples using a command to add 2 numbers together: .. code-block:: python + from urllib.parse import quote_plus + @client.tree.command() - async def add(interaction: discord.Interaction, a: int, b: int): - """adds 2 numbers together. + async def google(interaction: discord.Interaction, query: str): + """search google with a query. Args: - a (int): left operand - b (int): right operand + query (str): what you want to search for """ - await interaction.response.send_message(f'{a + b = }') + url = 'https://google.com/search?q=' + await interaction.response.send_message(url + quote_plus(query)) .. tab:: Sphinx @@ -434,25 +440,30 @@ Examples using a command to add 2 numbers together: @client.tree.command() async def add(interaction: discord.Interaction, a: int, b: int): - """adds 2 numbers together. + """adds two numbers together. :param a: left operand + :type a: int + :param b: right operand + :type a: int """ await interaction.response.send_message(f'{a + b = }') -Other meta info can be specified in the docstring, such as the function return type, -but only the parameter descriptions are read by the library. +Lots of info is skipped over and ignored (only the parameter descriptions matter) so these +formats are not strict and thus weakly parsed. -Descriptions added using :func:`.app_commands.describe` always take precedence over -ones specified in the docstring. +.. note:: + + When the :func:`~.app_commands.describe` decorator is used in conjunction with a docstring, + it always take precedence. Naming ^^^^^^^ Since parameter names are confined to the rules of Python's syntax, -the library offers a method to rename them with the :func:`.app_commands.rename` decorator. +the library offers a method to later rename them with the :func:`.app_commands.rename` decorator. In use: @@ -482,12 +493,17 @@ For example, to use :func:`~.app_commands.describe` and :func:`~.app_commands.re Choices ++++++++ -:class:`str`, :class:`int` and :class:`float` type parameters can optionally set a list of choices for an argument -using the :func:`.app_commands.choices` decorator. +A list of values can be optionally set as choices using the :func:`.app_commands.choices` decorator +for the following 3 primitive types: + +- :class:`str` +- :class:`int` +- :class:`float` -During invocation, a user is restricted to picking one choice and can't type anything else. +Normally, a user can type out any value for the parameter, but with choices, +they're restricted to selecting one choice and can't type anything else. -Each individual choice contains 2 fields: +Each individual choice is an object containing two fields: - A name, which is what the user sees in their client - A value, which is hidden to the user and only visible to the bot and API. @@ -515,13 +531,14 @@ To illustrate, the following command has a selection of 3 colours with each valu embed = discord.Embed(title=colour.name, colour=colour.value) await interaction.response.send_message(embed=embed) -On the client: +On the client, after syncing: .. image:: /images/guide/app_commands/colour_command_preview.png :width: 400 -discord.py also supports 2 other pythonic ways of adding choices to a command, -shown :func:`here ` in the reference. +Choices can also be set in two other pythonic ways (:class:`~typing.Literal` typehints and from the values in an :class:`~enum.Enum`) + +:func:`Jump ` to the reference for more info and code examples! .. _guide_slash_commands_autocompletion: @@ -565,10 +582,17 @@ Code examples for either method can be found in the corresponding reference page Range ++++++ -:class:`str`, :class:`int` and :class:`float` type parameters can optionally set a minimum and maximum value. -For strings, this limits the character count, whereas for numeric types this limits the magnitude. +Setting a range allows for keeping user-input within certain boundaries or limits. + +Only the following 3 parameter types support ranges: + +- :class:`str` +- :class:`int` +- :class:`float` -Refer to the :class:`.app_commands.Range` page for more info and code examples. +For a string, a range limits the character count, whereas for the numeric types, the magnitude is limited. + +Jump to the :class:`.app_commands.Range` page for further info and code examples! .. _guide_slash_commands_transformers: @@ -581,13 +605,19 @@ For instance, to parse a date string into a :class:`datetime.datetime` we might .. code-block:: python import datetime + import random @client.tree.command() - async def date(interaction: discord.Interaction, date: str): + async def forecast(interaction: discord.Interaction, date: str): when = datetime.datetime.strptime(date, '%d/%m/%Y') # dd/mm/yyyy format when = when.replace(tzinfo=datetime.timezone.utc) # attach timezone information - # do something with 'when'... + # randomly find the forecast using the day of the month as a seed... + seed = random.Random(when.day) + weather = seed.choice(['clear', 'cloudy', 'stormy']) + + with_weekday = when.strftime('%A %d/%m/%Y') + await interaction.response.send_message(f'{with_weekday} - {weather}') However, this can get verbose pretty quickly if the parsing is more complex or we need to do this parsing in multiple commands. It helps to isolate this code into it's own place, which we can do with transformers. @@ -606,22 +636,54 @@ To make one, inherit from :class:`.app_commands.Transformer` and override the :m when = when.replace(tzinfo=datetime.timezone.utc) return when -If you're familar with the commands extension (:ref:`ext.commands `), a lot of similarities can be drawn between transformers and converters. +If you're familar with the commands extension (:ref:`ext.commands `), +you can draw a lot of similarities in the design with converters. -To use this transformer in a command, a paramater needs to annotate to :class:`~.app_commands.Transform`, -passing the new type and class respectively. +To then attach this transformer to a parameter, annotate to :class:`~.app_commands.Transform`: .. code-block:: python from discord.app_commands import Transform @client.tree.command() - async def date(interaction: discord.Interaction, when: Transform[datetime.datetime, DateTransformer]): - # do something with 'when'... + async def forecast( + interaction: discord.Interaction, + day: Transform[datetime.datetime, DateTransformer] + ): + # accurately find the forecast... + + @client.tree.command() + async def birthday( + interaction: discord.Interaction, + date: Transform[datetime.datetime, DateTransformer] + ): + # prepare birthday celebrations... It's also possible to instead pass an instance of the transformer instead of the class directly, which opens up the possibility of setting up some state in :meth:`~object.__init__`. +For example, we could modify the constructor to take a ``past`` parameter to exclude past dates: + +.. code-block:: python + + class DateTransformer(app_commands.Transformer): + def __init__(self, *, past: bool = True): + self._allow_past_dates = past + + async def transform(self, interaction: discord.Interaction, value: str) -> datetime.datetime: + when = datetime.datetime.strptime(date, '%d/%m/%Y') + when = when.replace(tzinfo=datetime.timezone.utc) + + now = discord.utils.utcnow().date() # only get the `date` component from the datetime + if not self._allow_past_dates and when < now: + raise app_commands.AppCommandError('this date is in the past!') + # note: more on exceptions and error handling later in this page... + + return when + + # to disallow past dates when annotating: + Transform[datetime.datetime, DateTransformer(past=False)] + Since the parameter's type annotation is replaced with :class:`~.app_commands.Transform`, the underlying type and other information must now be provided through the :class:`~.app_commands.Transformer` itself. @@ -810,7 +872,7 @@ To add 1-level of nesting, create another :class:`~.app_commands.Group` in the c .. image:: /images/guide/app_commands/todo_group_nested_preview.png :width: 400 -Nested group commands can be moved into another class if it ends up being a bit too much to read in one class: +Nested group commands can be moved out into a separate class if it ends up being a bit too much to read in one level: .. code-block:: python @@ -828,8 +890,8 @@ Nested group commands can be moved into another class if it ends up being a bit todo_lists = TodoLists() -Decorators like :func:`.app_commands.default_permissions` and :func:`.app_commands.guild_only` -can be added on top of a subclass to apply to the group, for example: +Since decorators can be used on both functions and classes, you can also add them here +to make them apply to the group, for example: .. code-block:: python @@ -837,7 +899,24 @@ can be added on top of a subclass to apply to the group, for example: class Emojis(app_commands.Group): ... -Due to a Discord limitation, individual subcommands cannot have differing :ref:`integration checks `. +.. warning:: + + Integration checks can only be added to the root command; individual subcommands and subgroups + can't have their own checks due to a Discord limitation. + + .. code-block:: python + + @app_commands.default_permissions(manage_emojis=True) + class Emojis(app_commands.Group): + subgroup = app_commands.Group( + name="subgroup", + description="...", + + default_permissions=discord.Permissions(manage_messages=True) + # these default permissions are ignored. + # users only need `manage_emojis` above to be + # able to invoke all subcommands and subgroups + ) .. _guide_slash_commands_guild_commands: @@ -879,8 +958,9 @@ To demonstrate: Whilst multiple guilds can be specified on a single command, it's important to be aware that after syncing individually to each guild, each guild is then maintaing its own copy of the command. -New changes will require syncing to every guild again, which can cause a temporary mismatch with what a guild has -and what's defined in code. +New changes will require syncing to every guild again - +for very large guild sets, this can cause a temporary mismatch with what a guild currently has, +compared to whats newly defined in code. Since guild commands can be useful in a development scenario, as often we don't want unfinished commands to propagate to all guilds, the library offers a helper method :meth:`.CommandTree.copy_global_to` @@ -906,16 +986,16 @@ You'll typically find this syncing paradigm in some of the examples in the repos both globally and as a guild command. Removing a command from Discord needs another call to :meth:`.CommandTree.sync` - - for example, to remove local commands from a guild: + so, to remove local commands from a guild we can do: .. code-block:: python guild = discord.Object(695868929154744360) # a bot testing server - #self.tree.copy_global_to(guild) # dont copy the commands over + #self.tree.copy_global_to(guild) # dont copy the commands over this time await self.tree.sync(guild=guild) - # afterwards, the local commands should be removed + # after the sync, the local commands should be removed .. _guide_slash_commands_integration_checks: @@ -952,15 +1032,15 @@ Enabled by adding the :func:`.app_commands.guild_only` decorator when defining a @client.tree.command() @app_commands.guild_only() async def roulette(interaction: discord.Interaction): - assert interaction.guild is not None + assert interaction.guild is not None # (optional) for type-checkers members = interaction.guild.members victim = random.choice(members) await victim.ban(reason='unlucky') - chance = 1 / len(members) + chance = 1 / len(members) * 100.0 await interaction.response.send_message( - f'{victim.name} was chosen... ({chance:.2f}% chance)' + f'{victim.name} was chosen... ({chance:.2}% chance)' ) Default permissions @@ -1160,7 +1240,7 @@ Attaching a local handler to a command to catch a check exception: async def tester_error(interaction: discord.Interaction, error: app_commands.AppCommandError): if isinstance(error, app_commands.MissingAnyRole): roles = ', '.join(str(r) for r in error.missing_roles) - await interaction.response.send_message('i only thank people who have one of these roles!: {roles}') + await interaction.response.send_message(f'you need at least one of these roles to use this command: {roles}') Attaching an error handler to a group: @@ -1220,7 +1300,7 @@ To catch these exceptions in a global error handler for example: @client.tree.error async def on_app_command_error(interaction: discord.Interaction, error: app_commands.AppCommandError): - assert interaction.command is not None + assert interaction.command is not None # for type-checking purposes if isinstance(error, app_commands.CommandInvokeError): print(f'Ignoring unknown exception in command {interaction.command.name}', file=sys.stderr) @@ -1316,7 +1396,7 @@ depending on a user's language setting: - Parameter names and descriptions - Choice names (used for both :ref:`choices ` and :ref:`autocomplete `) -Localisations can be done :ddocs:`partially ` - +Localisations are done :ddocs:`partially ` - when a field doesn't have a translation for a given locale, Discord instead uses the original string. .. warning:: @@ -1340,15 +1420,16 @@ which you can do by using a special :class:`~.app_commands.locale_str` type in p from discord.app_commands import locale_str as _ - @client.tree.command(name=_('avatar'), description=_('display your avatar')) - async def avatar(interaction: discord.Interaction): - url = interaction.user.avatar.url + @client.tree.command(name=_('avatar'), description=_('display someones avatar')) + @app_commands.rename(member=_('member')) + async def avatar(interaction: discord.Interaction, member: discord.Member): + url = member.display_avatar.url await interaction.response.send_message(url) .. hint:: - Every string is actually already considered translatable by default and - wrapped into :class:`~.app_commands.locale_str` before being passed to ``transform``, + To make things easier, every string is actually already wrapped into + :class:`~.app_commands.locale_str` before being passed to ``transform``, so this step can be skipped in some cases. To toggle this behaviour, set the ``auto_locale_strings`` keyword-argument to ``False`` when creating a command: @@ -1377,7 +1458,7 @@ For example, to pass a ``fluent_id`` extra whilst keeping the original string: ... A translator can then read off of :attr:`~.locale_str.extras` for the translation identifier. -Systems like :mod:`gettext` don't need this type of behaviour, so it works out of the box without specifying the extra. +Systems like :mod:`GNU gettext ` don't need this type of behaviour, so it works out of the box without specifying the extra. Next, to create a translator, inherit from :class:`.app_commands.Translator` and override the :meth:`~.Translator.translate` method: @@ -1513,7 +1594,12 @@ that offers higher granularity using arguments: If your bot isn't able to use the message content intent, due to verification requirements or otherwise, bots can still read message content for direct-messages and for messages that mention the bot. -:func:`.commands.when_mentioned` can be used to apply a mention prefix to your bot: +Two builtin prefix callables exist that can be used to quickly apply a mention prefix to your bot: + +- :func:`.commands.when_mentioned` to match mentions exclusively +- :func:`.commands.when_mentioned_or` to add mentions on top of other prefixes + +For example: .. code-block:: python From 0f2c78b58ecc1b9c9dabe50edbb441fd059359be Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Sun, 3 Dec 2023 22:02:53 +1100 Subject: [PATCH 36/40] random? changes --- docs/guide/interactions/slash_commands.rst | 35 +++++++++++++--------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index 17a663c0fd19..668ee7e01173 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -90,7 +90,7 @@ Two main decorators can be used: Both decorators wrap an async function into a :class:`~.app_commands.Command` instance, however the former also adds the command to the tree, -which skips the step of having to add it manually using :meth:`.CommandTree.add_command()`. +which skips the step of having to later add it yourself by calling :meth:`.CommandTree.add_command()`. For example, these two are functionally equivalent: @@ -536,7 +536,10 @@ On the client, after syncing: .. image:: /images/guide/app_commands/colour_command_preview.png :width: 400 -Choices can also be set in two other pythonic ways (:class:`~typing.Literal` typehints and from the values in an :class:`~enum.Enum`) +You can also set choices in two other pythonic ways: + +- Via a :class:`~typing.Literal` typehint +- From the values in an :class:`~enum.Enum` :func:`Jump ` to the reference for more info and code examples! @@ -548,19 +551,19 @@ Autocompletion Autocompletes allow the bot to dynamically suggest up to 25 choices to a user as they type an argument. -In short: +Quick rundown: - User starts typing. - After a brief debounced pause from typing, Discord requests a list of choices from the bot. -- An autocomplete callback is called with the current user input. +- An **autocomplete callback** is called with the current user input. - Returned choices are sent back to Discord and shown in the user's client. - An empty list can be returned to denote no choices. -Attaching an autocomplete function to a parameter can be done in 2 main ways: +Attaching an autocomplete callback to a parameter can be done in two main ways: 1. From the command, with the :meth:`~.app_commands.Command.autocomplete` decorator 2. With a separate decorator, :func:`.app_commands.autocomplete` @@ -600,7 +603,8 @@ Transformers +++++++++++++ Sometimes additional logic for parsing arguments is wanted. -For instance, to parse a date string into a :class:`datetime.datetime` we might do: +For instance, to parse a date string into a :class:`datetime.datetime` we might +use :meth:`datetime.strptime` in the command callback: .. code-block:: python @@ -636,8 +640,10 @@ To make one, inherit from :class:`.app_commands.Transformer` and override the :m when = when.replace(tzinfo=datetime.timezone.utc) return when -If you're familar with the commands extension (:ref:`ext.commands `), -you can draw a lot of similarities in the design with converters. +.. hint:: + + If you're familar with the commands extension (:ref:`ext.commands `), + you can draw a lot of similarities in the design with converters. To then attach this transformer to a parameter, annotate to :class:`~.app_commands.Transform`: @@ -659,10 +665,13 @@ To then attach this transformer to a parameter, annotate to :class:`~.app_comman ): # prepare birthday celebrations... +Since the parsing responsibility is abstracted away from the command, it makes it easier +to do it in multiple commands. + It's also possible to instead pass an instance of the transformer instead of the class directly, which opens up the possibility of setting up some state in :meth:`~object.__init__`. -For example, we could modify the constructor to take a ``past`` parameter to exclude past dates: +For example, we could modify the constructor to take a ``past`` parameter to exclude past dates: .. code-block:: python @@ -685,7 +694,8 @@ For example, we could modify the constructor to take a ``past`` parameter to ex Transform[datetime.datetime, DateTransformer(past=False)] Since the parameter's type annotation is replaced with :class:`~.app_commands.Transform`, -the underlying type and other information must now be provided through the :class:`~.app_commands.Transformer` itself. +the underlying Discord type and other information must now be provided +through the :class:`~.app_commands.Transformer` itself. These can be provided by overriding the following properties: @@ -773,10 +783,7 @@ The table below outlines the relationship between Discord and Python types. # you can take advantage of this behaviour: @client.tree.command() - async def userinfo( - interaction: discord.Interaction, - user: discord.User - ): + async def userinfo(interaction: discord.Interaction, user: discord.User): embed = discord.Embed() embed.set_author(name=user.name, icon_url=user.display_avatar.url) From c765195750c85c606273f87b329cbdd31d383175 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Sun, 3 Dec 2023 22:04:17 +1100 Subject: [PATCH 37/40] i like this word more Co-Authored-By: Noelle Wang <73260931+no767@users.noreply.github.com> --- docs/guide/interactions/slash_commands.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index 668ee7e01173..3fe7fffb4b45 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -43,7 +43,7 @@ It contains a few dedicated methods for adding, viewing and removing app command An instance of this (only one!) needs to be created so that we can begin adding commands to it. -It helps to directly attach the tree to the client instance, since this plays well with +Its customary to directly attach the tree to the client instance, since this plays well with any type checker if you're using one, and to allow for easy access from anywhere in the code. To do this, simply attach the tree to the ``self`` instance in a client subclass: From 1e6a5ac6503dcc37abd340f82fded57435c9f1a2 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Fri, 12 Jan 2024 03:31:47 +1100 Subject: [PATCH 38/40] interaction stuff --- docs/guide/interactions/slash_commands.rst | 47 +++++++++++++++++----- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index 3fe7fffb4b45..452c1f538b0f 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -137,25 +137,54 @@ Interaction Shown above, the first parameter is ``interaction`` - app commands always need to have this as the first parameter. It represents a :class:`~discord.Interaction`, a Discord model used for both app commands and UI message components. -When an interaction is created on command invoke, some information about the surrounding context is given, such as: +When a user invokes an app command, an interaction is created representing that action, hence the name. +Some information from the surrounding context is given from this model, including but not limited to: - :attr:`.Interaction.channel` - the channel it was invoked in -- :attr:`.Interaction.guild` - the guild it was invoked in, if any -- :attr:`.Interaction.user` - the user or member who invoked the command +- :attr:`.Interaction.guild` - the guild it was invoked in - this can be ``None`` in a direct-message scenario +- :attr:`.Interaction.user` - the :class:`~discord.User` or :class:`~discord.Member` object representing who invoked the command Attributes like these and others are a given, however when it comes to responding to an interaction, by sending a message or otherwise, the methods from :attr:`.Interaction.response` need to be used. -A response needs to occur within 3 seconds, otherwise this message pops up on Discord in red: - -.. image:: /images/guide/app_commands/interaction_failed.png - In practice, it's common to use either of the following two methods: - :meth:`.InteractionResponse.send_message` to send a message - :meth:`.InteractionResponse.defer` to defer a response -When deferring, a follow-up message needs to be sent within 15 minutes for app commands. +.. warning:: + + A response needs to occur within 3 seconds, otherwise this message pops up on Discord in red: + + .. image:: /images/guide/app_commands/interaction_failed.png + +Sending a single message is straightforward: + +.. code-block:: python + + @client.tree.command() + async def hi(interaction: discord.Interaction): + await interaction.response.send_message('hello!') + +After this, the interaction is completed "done" and subsequent calls to +any method from :class:`~discord.InteractionResponse` will result +with a :class:`404 Not Found` exception. + +To send additional messages, the "follow-up" webhook needs to be used, which opens up after +the initial response: + +.. code-block:: python + + @client.tree.command() + async def hi(interaction: discord.Interaction): + await interaction.response.send_message('hello!') + await interaction.followup.send('goodbye!') + +.. note:: + + Follow-up webhooks only stay valid for 15 minutes. + +Deferring is another way to respond to an interaction - for app commands, it indicates that a message will be sent later. For example, to send a deferred ephemeral message: @@ -166,7 +195,7 @@ For example, to send a deferred ephemeral message: @client.tree.command() async def weather(self, interaction: discord.Interaction): - await interaction.response.defer(ephemeral=True) # indicates the follow-up message will be ephemeral + await interaction.response.defer(ephemeral=True) # makes the bot's "thinking" indicator ephemeral climates = ['sunny', 'clear', 'cloudy', 'rainy', 'stormy', 'snowy'] await asyncio.sleep(5.0) # an expensive operation... (no more than 15 minutes!) From e7f85d540615bfc2765aeb8316b3a9a7e9733b32 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Fri, 12 Jan 2024 03:34:11 +1100 Subject: [PATCH 39/40] rephrase autocomplete rundown --- docs/guide/interactions/slash_commands.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index 452c1f538b0f..285d2f4e3ae6 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -580,7 +580,7 @@ Autocompletion Autocompletes allow the bot to dynamically suggest up to 25 choices to a user as they type an argument. -Quick rundown: +To outline the process: - User starts typing. From f63a31e168d1ace77b36a2aa03a474434787a7f4 Mon Sep 17 00:00:00 2001 From: nanika2 <101696371+nanika2@users.noreply.github.com> Date: Mon, 19 Feb 2024 22:06:33 +1100 Subject: [PATCH 40/40] reword intro a bit --- docs/guide/interactions/slash_commands.rst | 97 ++++++++++++---------- 1 file changed, 55 insertions(+), 42 deletions(-) diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst index 285d2f4e3ae6..47a6dbf3efb4 100644 --- a/docs/guide/interactions/slash_commands.rst +++ b/docs/guide/interactions/slash_commands.rst @@ -5,36 +5,45 @@ Slash commands =============== -Slash commands are one of Discord's native ways to let users interact with apps in the Discord client. -They're a branch of application commands, -with the other type being context menu commands (right-click / tap-hold menu). - -Analogous to their name, slash commands are previewed and invoked in the Discord client -by beginning your message with a forward-slash: +Slash commands are those commands you can invoke through Discord's official UI by prefixing your message +with a forward-slash (``/``): .. image:: /images/guide/app_commands/meow_command_preview.png - :width: 400 + :width: 300 + +They're a branch of application commands (app commands for short), with the other type +being context-menu commands - which you can invoke through the right-click menu, or tap-hold on mobile devices. + +By the end of this guide, we hope you'll have a solid foundation for implementing slash commands +into your discord.py project. Setting up ----------- -To work with app commands, bots need the ``applications.commands`` scope. -You can enable this scope when generating an OAuth2 URL for your bot, the steps to do so outlined :ref:`here `. +To start off, ensure your bot has the ``applications.commands`` scope for each guild +you want your app commands to work in - you can enable this scope when generating +an OAuth2 URL for your bot, the steps to do so outlined :ref:`here `. -Both types of app commands are implemented within the :ref:`discord.app_commands ` package. -Any code example in this page will always assume the following two imports, so make sure to import them! +Whilst you can still create commands normally as described in this page, +they'll be non-functional (and hidden!) without the scope. + +As a side note, both types of app commands are implemented within the +:ref:`discord.app_commands ` subpackage - any code example +in this page will always assume the following two modules, so make sure to import them! .. code-block:: python import discord from discord import app_commands -Defining a tree -++++++++++++++++ +Defining a ``CommandTree`` ++++++++++++++++++++++++++++ -A :class:`~.app_commands.CommandTree` acts as a container holding all of the bot's app commands. +A command tree is something that acts as a container holding all of the bot's app commands. +Whenever you define a command in your code, it always needs to be added to this tree +to actually be handled by the client. -It contains a few dedicated methods for adding, viewing and removing app commands internally: +In code, this is a :class:`~.app_commands.CommandTree` - it contains a few dedicated methods for registering commands: - :meth:`.CommandTree.add_command` to add a command - :meth:`.CommandTree.remove_command` to remove a command @@ -43,10 +52,10 @@ It contains a few dedicated methods for adding, viewing and removing app command An instance of this (only one!) needs to be created so that we can begin adding commands to it. -Its customary to directly attach the tree to the client instance, since this plays well with -any type checker if you're using one, and to allow for easy access from anywhere in the code. +Its customary to directly attach the tree to your client instance, as this allows for easy access from anywhere in your code. +It'll also play well with a static type-checker if you're using one (discord.py is fully-typed for `pyright`_!). -To do this, simply attach the tree to the ``self`` instance in a client subclass: +To attach it, simply bind the tree to ``self`` in a client subclass: .. code-block:: python @@ -62,37 +71,39 @@ To do this, simply attach the tree to the ``self`` instance in a client subclass .. note:: - If your project instead uses :class:`.ext.commands.Bot` as the client instance, + If your project instead uses :class:`commands.Bot` as the client instance, a :class:`~discord.app_commands.CommandTree` has already been defined at :attr:`.Bot.tree`, - so this step is technically skipped. + so this step can be skipped! Creating a command ------------------- -Slash commands are created by decorating an async function, -and that function is then called whenever the slash command is invoked by someone, -in a "callback" fashion. +Like with most other things in the library, discord.py uses a callback-based approach for slash commands. + +An async function should be decorated, and that function is then called whenever the slash command is invoked +by someone on Discord. -For example, the following code registers a command that responds with "meow" on invocation: +For example, the following code defines a command that responds with "meow" on invocation: .. code-block:: python @client.tree.command() async def meow(interaction: discord.Interaction): """Meow meow meow""" - await interaction.response.send_message('meow') +``meow`` now points to an :class:`.app_commands.Command` object instead of a plain async function. + Two main decorators can be used: 1. :meth:`tree.command() <.CommandTree.command>` (as seen above) 2. :func:`.app_commands.command` Both decorators wrap an async function into a :class:`~.app_commands.Command` instance, however -the former also adds the command to the tree, +the former also adds the command to the tree (remember the :meth:`.CommandTree.add_command` method?), which skips the step of having to later add it yourself by calling :meth:`.CommandTree.add_command()`. -For example, these two are functionally equivalent: +For example, these two ultimately do the same thing: .. code-block:: python @@ -111,12 +122,12 @@ For example, these two are functionally equivalent: Since ``tree.command()`` is more concise and easier to understand, it'll be the main method used to create slash commands in this guide. -Some information is logically inferred from the async function to populate the slash command's fields: +Some information is logically inferred from the decorated function to populate the slash command's fields: - The :attr:`~.app_commands.Command.name` takes after the function name "meow" - The :attr:`~.app_commands.Command.description` takes after the docstring "Meow meow meow" -To change them to something else, ``tree.command()`` takes ``name`` and ``description`` keyword-arguments: +To change them to something else, the decorators take ``name`` and ``description`` keyword-arguments: .. code-block:: python @@ -124,18 +135,17 @@ To change them to something else, ``tree.command()`` takes ``name`` and ``descri async def meow(interaction: discord.Interaction): pass - # or... - @client.tree.command(name='list') - async def list_(interaction: discord.Interaction): - # prevent shadowing the 'list' builtin + @client.tree.command(name='class') + async def class_(interaction: discord.Interaction): + # class is otherwise a syntax error -If a description isn't provided through ``description`` or by the docstring, an ellipsis "..." is used instead. +If a description isn't provided either through ``description`` or by the docstring, an ellipsis "..." is used instead. -Interaction -++++++++++++ +A primer on Interactions ++++++++++++++++++++++++++ Shown above, the first parameter is ``interaction`` - app commands always need to have this as the first parameter. -It represents a :class:`~discord.Interaction`, a Discord model used for both app commands and UI message components. +It represents an :class:`~discord.Interaction`, a Discord model used for both app commands and UI message components. When a user invokes an app command, an interaction is created representing that action, hence the name. Some information from the surrounding context is given from this model, including but not limited to: @@ -208,7 +218,8 @@ For example, to send a deferred ephemeral message: Syncing ++++++++ -In order for this command to show up on Discord, the API needs some information to render it, namely: +In order for any command you define to actually show up on Discord's UI, +the API needs some information to render it, namely: - The name and description - Any :ref:`parameter names, types and descriptions ` @@ -217,13 +228,14 @@ In order for this command to show up on Discord, the API needs some information - Whether this is a :ref:`global or guild command ` - Any :ref:`localisations ` for the above -Syncing is the process of sending this information, which is done by -calling the :meth:`.CommandTree.sync` method. +**Syncing** is the process of sending this info, which is done by +calling the :meth:`~.CommandTree.sync` method on your command tree. -Typically, this is called on start-up in :meth:`.Client.setup_hook`, since usually this is a spot +To start off, you can call this method start-up in :meth:`.Client.setup_hook`, since typically this is a spot where all the app commands have been added to the tree: .. code-block:: python + :emphasize-lines: 7 class MyClient(discord.Client): def __init__(self): @@ -256,7 +268,8 @@ blocks invocation with this message in red: provided it has the ``applications.commands`` scope for that guild. To make space for yourself to experiment with app commands safely, - create a new testing bot instead or alternatively sync your commands :ref:`locally `. + create a new testing bot instead or alternatively + sync your commands :ref:`locally `. .. _guide_slash_commands_parameters: