diff --git a/docs/guide/interactions/slash_commands.rst b/docs/guide/interactions/slash_commands.rst new file mode 100644 index 000000000000..47a6dbf3efb4 --- /dev/null +++ b/docs/guide/interactions/slash_commands.rst @@ -0,0 +1,1660 @@ +:orphan: + +.. _guide_slash_commands: + +Slash commands +=============== + +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: 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 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 `. + +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 ``CommandTree`` ++++++++++++++++++++++++++++ + +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. + +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 +- :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. + +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 attach it, simply bind the tree to ``self`` in a client subclass: + +.. code-block:: python + + import discord + from discord import app_commands + + 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:`commands.Bot` as the client instance, + a :class:`~discord.app_commands.CommandTree` has already been defined at :attr:`.Bot.tree`, + so this step can be skipped! + +Creating a command +------------------- + +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 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 (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 ultimately do the same thing: + +.. code-block:: python + + @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 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, the decorators take ``name`` and ``description`` keyword-arguments: + +.. code-block:: python + + @client.tree.command(name='woof', description='Woof woof woof') + async def meow(interaction: discord.Interaction): + pass + + @client.tree.command(name='class') + async def class_(interaction: discord.Interaction): + # class is otherwise a syntax error + +If a description isn't provided either through ``description`` or by the docstring, an ellipsis "..." is used instead. + +A primer on Interactions ++++++++++++++++++++++++++ + +Shown above, the first parameter is ``interaction`` - app commands always need to have this as the first parameter. +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: + +- :attr:`.Interaction.channel` - the channel it was invoked in +- :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. + +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 + +.. 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: + +.. code-block:: python + + import asyncio + import random + + @client.tree.command() + async def weather(self, interaction: discord.Interaction): + 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!) + forecast = random.choice(climates) + + await interaction.followup.send(f'the weather today is {forecast}!') + +.. _guide_slash_commands_syncing: + +Syncing +++++++++ + +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 ` +- 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 info, which is done by +calling the :meth:`~.CommandTree.sync` method on your command tree. + +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): + 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. + +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 (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. + +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 + +.. 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 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 `. + +.. _guide_slash_commands_parameters: + +Parameters +----------- + +Since slash commands are defined by making Python functions, parameters are similarly defined with function parameters. + +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: + +.. code-block:: python + + @client.tree.command() + async def bottles(interaction: discord.Interaction, liquid: str): + await interaction.response.send_message(f'99 bottles of {liquid} on the wall!') + +On the client, parameters show up as these little black boxes that need to be filled out during invocation: + +.. image:: /images/guide/app_commands/bottles_command_preview.png + :width: 300 + +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/input_a_valid_integer.png + :width: 300 + +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 + +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. + +A full overview of supported types can be seen in the :ref:`type conversion table `. + +typing.Optional +++++++++++++++++ + +When a parameter is optional, a user can skip inputting a value for it during invocation. + +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: + +.. code-block:: python + + 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) + +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 = (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`. + +typing.Union ++++++++++++++ + +Some types comprise of multiple other types. +For example, the ``MENTIONABLE`` type includes both the user and role types: + +- :class:`discord.User` and :class:`discord.Member` +- :class:`discord.Role` + +Since you need to specify multiple distinct types here, a :obj:`~typing.Union` annotation needs to be used: + +.. code-block:: python + + 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__}' + ) + +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 + + 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 + +.. 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. + +Describing ++++++++++++ + +Descriptions are added to parameters using the :func:`.app_commands.describe` decorator, +where each keyword is treated as a parameter name. + +.. code-block:: python + + @client.tree.command() + @app_commands.describe( + 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!') + +These show up on Discord just beside the parameter's name: + +.. image:: /images/guide/app_commands/bottles_command_described.png + +Not specifying a description results with an ellipsis "..." being used instead. + +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: + +- `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 + + .. code-block:: python + + @client.tree.command() + async def add(interaction: discord.Interaction, a: int, b: int): + """adds two numbers together. + + Parameters + ----------- + a: int + left operand + b: int + right operand + """ + + await interaction.response.send_message(f'{a + b = }') + +.. tab:: Google + + .. code-block:: python + + from urllib.parse import quote_plus + + @client.tree.command() + async def google(interaction: discord.Interaction, query: str): + """search google with a query. + + Args: + query (str): what you want to search for + """ + + url = 'https://google.com/search?q=' + await interaction.response.send_message(url + quote_plus(query)) + +.. tab:: Sphinx + + .. code-block:: python + + @client.tree.command() + async def add(interaction: discord.Interaction, a: int, b: int): + """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 = }') + +Lots of info is skipped over and ignored (only the parameter descriptions matter) so these +formats are not strict and thus weakly parsed. + +.. 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 later rename them with the :func:`.app_commands.rename` decorator. + +In use: + +.. code-block:: python + + @client.tree.command() + @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: + +.. code-block:: python + + @client.tree.command() + @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!') + +.. _guide_slash_commands_choices: + +Choices +++++++++ + +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` + +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 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. + + 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: + +.. code-block:: python + + from discord.app_commands import Choice + + @client.tree.command() + @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""" + + embed = discord.Embed(title=colour.name, colour=colour.value) + await interaction.response.send_message(embed=embed) + +On the client, after syncing: + +.. image:: /images/guide/app_commands/colour_command_preview.png + :width: 400 + +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! + +.. _guide_slash_commands_autocompletion: + +Autocompletion ++++++++++++++++ + +Autocompletes allow the bot to dynamically suggest up to 25 choices +to a user as they type an argument. + +To outline the process: + +- 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 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` + +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 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. + +Range +++++++ + +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` + +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: + +Transformers ++++++++++++++ + +Sometimes additional logic for parsing arguments is wanted. +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 + + import datetime + import random + + @client.tree.command() + 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 + + # 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. + +Transformers are effectively classes containing a ``transform`` method that "transforms" a raw argument value into a new value. + +To make one, inherit from :class:`.app_commands.Transformer` and override the :meth:`~.Transformer.transform` method: + +.. code-block:: python + + # 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 + +.. 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`: + +.. code-block:: python + + from discord.app_commands import Transform + + @client.tree.command() + 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... + +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: + +.. 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 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: + +- :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`: + +.. code-block:: python + + class UserAvatar(app_commands.Transformer): + 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 + +.. (todo) talk about this properly and write an example + +:meth:`~.Transformer.autocomplete` callbacks can also be defined in-line. + +.. _guide_slash_commands_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. + +.. note:: + + 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 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`, 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`. + + Some examples to help visualise: + + .. code-block:: python + + @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: discord.User): + embed = discord.Embed() + + embed.set_author(name=user.name, icon_url=user.display_avatar.url) + embed.add_field(name='ID', value=str(user.id)) + + 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') + embed.add_field(name='Joined', value=relative) + + # change the embed's colour to match their role + embed.colour = user.colour + + await interaction.response.send_message(embed=embed) + +.. _guide_slash_commands_command_groups: + +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 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`. + +This class is customisable by subclassing and passing in any relevant fields in the class constructor: + +.. code-block:: python + + class Todo(app_commands.Group, 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, 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, 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 + +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 + + 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() + +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 + + @app_commands.default_permissions(manage_emojis=True) + class Emojis(app_commands.Group): + ... + +.. 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: + +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, 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. + +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. + +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 - +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` +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 bot 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. + +.. 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` - + 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 this time + await self.tree.sync(guild=guild) + + # after the sync, the local commands should be removed + +.. _guide_slash_commands_integration_checks: + +Integration checks +------------------- + +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. + +Age-restriction +++++++++++++++++ + +Indicates whether this command can only be used in NSFW channels or not. +Configured by passing the ``nsfw`` keyword argument within the command decorator: + +.. code-block:: python + + @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 an app command: + +.. code-block:: python + + import random + + @client.tree.command() + @app_commands.guild_only() + async def roulette(interaction: discord.Interaction): + 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) * 100.0 + await interaction.response.send_message( + f'{victim.name} was chosen... ({chance:.2}% chance)' + ) + +Default permissions +++++++++++++++++++++ + +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 an app command: + +.. code-block:: python + + @client.tree.command() + @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: + 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. + +To prevent this, :func:`~.app_commands.guild_only` can also be added. + +.. warning:: + + 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. + +.. _guide_slash_commands_custom_checks: + +Custom checks +-------------- + +A custom check is something that can be applied to a command to check if someone should be able to run it. + +At their core, a check is a basic predicate that takes in the :class:`~discord.Interaction` as its sole parameter. + +It has the following options: + +- 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. + +- 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 ``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 + + # takes the interaction and returns a boolean + async def predicate(interaction: discord.Interaction) -> bool: + return random.randint(0, 1) == 1 # 50% chance + + @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 for easier usage: + +.. 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 certain check to apply if a previous check passes: + +.. code-block:: python + + @client.tree.command() + @app_commands.checks.cooldown(1, 5.0) # called second + @coinflip() # called first + async def fiftyfifty(interaction: discord.Interaction): + await interaction.response.send_message("you're very patient and lucky!") + +Custom checks can either be: + +- local, only running for a single command (as seen above). + +- 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:: + + 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. + + Refer to the :ref:`checks guide ` for more info. + +.. _guide_slash_commands_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. + +For example: + +.. code-block:: python + + whitelist = { + # cool people only + 236802254298939392, + 402159684724719617, + 155863164544614402 + } + + class CoolPeopleTree(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:: python + + from discord.ext import commands + + bot = commands.Bot( + command_prefix='?', + intents=discord.Intents.default(), + tree_cls=CoolPeopleTree + ) + +.. _guide_slash_commands_error_handling: + +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 :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. + +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. + +**Examples** + +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') + async def tester(interaction: discord.Interaction): + 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'you need at least one of these roles to use this command: {roles}') + +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. + +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 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: + +.. 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 # for type-checking purposes + + 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__) + + # the original exception can be accessed using error.__cause__ + +Custom exceptions +++++++++++++++++++ + +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. + +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): + pass + + def coinflip(): + async def predicate(interaction: discord.Interaction) -> bool: + if random.randint(0, 1) == 0: + raise Unlucky("you're unlucky!") + return True + return app_commands.check(predicate) + + @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)) + +Transformers behave similarly, but exceptions should derive :class:`~.app_commands.AppCommandError` instead: + +.. code-block:: python + + from discord.app_commands import Transform + + class BadDateArgument(app_commands.AppCommandError): + 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) from None + + 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)) + +Since a unique exception is used, extra state can be attached using :meth:`~object.__init__` for the error handler to work with. + +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 +++++++++ + +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 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. + +.. _guide_slash_commands_translating: + +Translating +------------ + +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 (used for both :ref:`choices ` and :ref:`autocomplete `) + +Localisations are done :ddocs:`partially ` - +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. + +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. + +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=_('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:: + + 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: + + .. 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: + +.. code-block:: python + + @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): + ... + +A translator can then read off of :attr:`~.locale_str.extras` for the translation identifier. +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: + +.. 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, ``None`` should be returned instead. + +:class:`~.app_commands.TranslationContext` provides contextual info for what is being translated. +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 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 a coroutine, 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 can be skipped. + +- 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. + +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 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. + +The :ref:`commands extension ` makes this easy: + +.. code-block:: python + + from discord.ext import commands + + intents = discord.Intents.default() + intents.message_content = True # required! + 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') + + # invocable only by yourself on discord using ?sync + +A `more complex command `_ +that offers higher granularity using arguments: + +.. code-block:: python + + from typing import Literal, Optional + + import discord + from discord.ext import commands + + # requires the `message_content` intent to work! + + @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)}.") + +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. + +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 + + bot = commands.Bot( + command_prefix=commands.when_mentioned, + # or... + command_prefix=commands.when_mentioned_or(">", "!"), + ... + ) \ 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 000000000000..43480c5d846c Binary files /dev/null and b/docs/images/guide/app_commands/avatar_command_optional_preview.png differ 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 000000000000..3fdb96e86882 Binary files /dev/null and b/docs/images/guide/app_commands/bottles_command_described.png differ 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 000000000000..acc05acc6e82 Binary files /dev/null and b/docs/images/guide/app_commands/bottles_command_preview.png differ 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 000000000000..7c0ff91dcfb5 Binary files /dev/null and b/docs/images/guide/app_commands/colour_command_preview.png differ 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 000000000000..f1eddad0509d Binary files /dev/null and b/docs/images/guide/app_commands/input_a_valid_integer.png differ 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 000000000000..030bdd074dcf Binary files /dev/null and b/docs/images/guide/app_commands/interaction_failed.png differ diff --git a/docs/images/guide/app_commands/meow_command_preview.png b/docs/images/guide/app_commands/meow_command_preview.png new file mode 100644 index 000000000000..ac4f61c68396 Binary files /dev/null and b/docs/images/guide/app_commands/meow_command_preview.png differ 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 000000000000..8780dc5d44e8 Binary files /dev/null and b/docs/images/guide/app_commands/outdated_command.png differ 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 000000000000..b29d8625530e Binary files /dev/null and b/docs/images/guide/app_commands/this_option_is_required.png differ 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 000000000000..1f134a5f5c75 Binary files /dev/null and b/docs/images/guide/app_commands/todo_group_nested_preview.png differ diff --git a/docs/images/guide/app_commands/todo_group_preview.png b/docs/images/guide/app_commands/todo_group_preview.png new file mode 100644 index 000000000000..7acd174643b2 Binary files /dev/null and b/docs/images/guide/app_commands/todo_group_preview.png differ