* Most slash command support completed, needs some debugging (and reindent) * Implement a ctx.send helper for slash commands * Add group command support * Add Option converter, fix default optional, fix help command * Add client.setup and move readying commands to that * Implement _FakeSlashMessage.from_interaction * Rename normmal_command to message_command * Add docs for added params * Add slash_command_guilds to bot and decos * Fix merge conflict * Remove name from commands.Option, wasn't used * Move slash command processing to BotBase.process_slash_commands * Create slash_only.py Basic example for slash commands * Create slash_and_message.py Basic example for mixed commands * Fix slash_command and normal_command bools * Add some basic error handling for registration * Fixed converter upload errors * Fix some logic and make an actual example * Thanks Safety Jim * docstrings, *args, and error changes * Add proper literal support * Add basic documentation on slash commands * Fix non-slash command interactions * Fix ctx.reply in slash command context * Fix typing on Context.reply * Fix multiple optional argument sorting * Update ctx.message docs to mention error instead of warning * Move slash command creation to BotBase * Fix code style issues with Black * Rearrange some stuff and add flag support * Change some errors and fix interaction.channel fixing * Fix slash command quoting for *args Co-authored-by: iDutchy <42503862+iDutchy@users.noreply.github.com> Co-authored-by: Lint Action <lint-action@samuelmeuli.com>
		
			
				
	
	
		
			946 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
			
		
		
	
	
			946 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
.. currentmodule:: discord
 | 
						|
 | 
						|
.. _ext_commands_commands:
 | 
						|
 | 
						|
Commands
 | 
						|
==========
 | 
						|
 | 
						|
One of the most appealing aspects of the command extension is how easy it is to define commands and
 | 
						|
how you can arbitrarily nest groups and commands to have a rich sub-command system.
 | 
						|
 | 
						|
Commands are defined by attaching it to a regular Python function. The command is then invoked by the user using a similar
 | 
						|
signature to the Python function.
 | 
						|
 | 
						|
For example, in the given command definition:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    async def foo(ctx, arg):
 | 
						|
        await ctx.send(arg)
 | 
						|
 | 
						|
With the following prefix (``$``), it would be invoked by the user via:
 | 
						|
 | 
						|
.. code-block:: none
 | 
						|
 | 
						|
    $foo abc
 | 
						|
 | 
						|
A command must always have at least one parameter, ``ctx``, which is the :class:`.Context` as the first one.
 | 
						|
 | 
						|
There are two ways of registering a command. The first one is by using :meth:`.Bot.command` decorator,
 | 
						|
as seen in the example above. The second is using the :func:`~ext.commands.command` decorator followed by
 | 
						|
:meth:`.Bot.add_command` on the instance.
 | 
						|
 | 
						|
Essentially, these two are equivalent: ::
 | 
						|
 | 
						|
    from discord.ext import commands
 | 
						|
 | 
						|
    bot = commands.Bot(command_prefix='$')
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    async def test(ctx):
 | 
						|
        pass
 | 
						|
 | 
						|
    # or:
 | 
						|
 | 
						|
    @commands.command()
 | 
						|
    async def test(ctx):
 | 
						|
        pass
 | 
						|
 | 
						|
    bot.add_command(test)
 | 
						|
 | 
						|
Since the :meth:`.Bot.command` decorator is shorter and easier to comprehend, it will be the one used throughout the
 | 
						|
documentation here.
 | 
						|
 | 
						|
Any parameter that is accepted by the :class:`.Command` constructor can be passed into the decorator. For example, to change
 | 
						|
the name to something other than the function would be as simple as doing this:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    @bot.command(name='list')
 | 
						|
    async def _list(ctx, arg):
 | 
						|
        pass
 | 
						|
 | 
						|
Slash Commands
 | 
						|
--------------
 | 
						|
Slash Commands can be enabled in the :class:`.Bot` constructor or :class:`.Command` constructor, using
 | 
						|
``slash_commands=True`` or ``slash_command=True`` respectfully. All features of the commands extension
 | 
						|
should work with these options enabled, however many will not have direct discord counterparts and therefore
 | 
						|
will be subsituted for supported versions when uploaded to discord.
 | 
						|
 | 
						|
Parameters
 | 
						|
------------
 | 
						|
 | 
						|
Since we define commands by making Python functions, we also define the argument passing behaviour by the function
 | 
						|
parameters.
 | 
						|
 | 
						|
Certain parameter types do different things in the user side and most forms of parameter types are supported.
 | 
						|
 | 
						|
Positional
 | 
						|
++++++++++++
 | 
						|
 | 
						|
The most basic form of parameter passing is the positional parameter. This is where we pass a parameter as-is:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    async def test(ctx, arg):
 | 
						|
        await ctx.send(arg)
 | 
						|
 | 
						|
 | 
						|
On the bot using side, you can provide positional arguments by just passing a regular string:
 | 
						|
 | 
						|
.. image:: /images/commands/positional1.png
 | 
						|
 | 
						|
To make use of a word with spaces in between, you should quote it:
 | 
						|
 | 
						|
.. image:: /images/commands/positional2.png
 | 
						|
 | 
						|
As a note of warning, if you omit the quotes, you will only get the first word:
 | 
						|
 | 
						|
.. image:: /images/commands/positional3.png
 | 
						|
 | 
						|
Since positional arguments are just regular Python arguments, you can have as many as you want:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    async def test(ctx, arg1, arg2):
 | 
						|
        await ctx.send(f'You passed {arg1} and {arg2}')
 | 
						|
 | 
						|
Variable
 | 
						|
++++++++++
 | 
						|
 | 
						|
Sometimes you want users to pass in an undetermined number of parameters. The library supports this
 | 
						|
similar to how variable list parameters are done in Python:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    async def test(ctx, *args):
 | 
						|
        arguments = ', '.join(args)
 | 
						|
        await ctx.send(f'{len(args)} arguments: {arguments}')
 | 
						|
 | 
						|
This allows our user to accept either one or many arguments as they please. This works similar to positional arguments,
 | 
						|
so multi-word parameters should be quoted.
 | 
						|
 | 
						|
For example, on the bot side:
 | 
						|
 | 
						|
.. image:: /images/commands/variable1.png
 | 
						|
 | 
						|
If the user wants to input a multi-word argument, they have to quote it like earlier:
 | 
						|
 | 
						|
.. image:: /images/commands/variable2.png
 | 
						|
 | 
						|
Do note that similar to the Python function behaviour, a user can technically pass no arguments
 | 
						|
at all:
 | 
						|
 | 
						|
.. image:: /images/commands/variable3.png
 | 
						|
 | 
						|
Since the ``args`` variable is a :class:`py:tuple`,
 | 
						|
you can do anything you would usually do with one.
 | 
						|
 | 
						|
Keyword-Only Arguments
 | 
						|
++++++++++++++++++++++++
 | 
						|
 | 
						|
When you want to handle parsing of the argument yourself or do not feel like you want to wrap multi-word user input into
 | 
						|
quotes, you can ask the library to give you the rest as a single argument. We do this by using a **keyword-only argument**,
 | 
						|
seen below:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    async def test(ctx, *, arg):
 | 
						|
        await ctx.send(arg)
 | 
						|
 | 
						|
.. warning::
 | 
						|
 | 
						|
    You can only have one keyword-only argument due to parsing ambiguities.
 | 
						|
 | 
						|
On the bot side, we do not need to quote input with spaces:
 | 
						|
 | 
						|
.. image:: /images/commands/keyword1.png
 | 
						|
 | 
						|
Do keep in mind that wrapping it in quotes leaves it as-is:
 | 
						|
 | 
						|
.. image:: /images/commands/keyword2.png
 | 
						|
 | 
						|
By default, the keyword-only arguments are stripped of white space to make it easier to work with. This behaviour can be
 | 
						|
toggled by the :attr:`.Command.rest_is_raw` argument in the decorator.
 | 
						|
 | 
						|
.. _ext_commands_context:
 | 
						|
 | 
						|
Invocation Context
 | 
						|
-------------------
 | 
						|
 | 
						|
As seen earlier, every command must take at least a single parameter, called the :class:`~ext.commands.Context`.
 | 
						|
 | 
						|
This parameter gives you access to something called the "invocation context". Essentially all the information you need to
 | 
						|
know how the command was executed. It contains a lot of useful information:
 | 
						|
 | 
						|
- :attr:`.Context.guild` to fetch the :class:`Guild` of the command, if any.
 | 
						|
- :attr:`.Context.message` to fetch the :class:`Message` of the command.
 | 
						|
- :attr:`.Context.author` to fetch the :class:`Member` or :class:`User` that called the command.
 | 
						|
- :meth:`.Context.send` to send a message to the channel the command was used in.
 | 
						|
 | 
						|
The context implements the :class:`abc.Messageable` interface, so anything you can do on a :class:`abc.Messageable` you
 | 
						|
can do on the :class:`~ext.commands.Context`.
 | 
						|
 | 
						|
.. warning::
 | 
						|
    :attr:`.Context.message` will be fake if in a slash command, it is not
 | 
						|
    recommended to access if :attr:`.Context.interaction` is not None as most
 | 
						|
    methods will error due to the message not actually existing.
 | 
						|
 | 
						|
Converters
 | 
						|
------------
 | 
						|
 | 
						|
Adding bot arguments with function parameters is only the first step in defining your bot's command interface. To actually
 | 
						|
make use of the arguments, we usually want to convert the data into a target type. We call these
 | 
						|
:ref:`ext_commands_api_converters`.
 | 
						|
 | 
						|
Converters come in a few flavours:
 | 
						|
 | 
						|
- A regular callable object that takes an argument as a sole parameter and returns a different type.
 | 
						|
 | 
						|
    - These range from your own function, to something like :class:`bool` or :class:`int`.
 | 
						|
 | 
						|
- A custom class that inherits from :class:`~ext.commands.Converter`.
 | 
						|
 | 
						|
.. _ext_commands_basic_converters:
 | 
						|
 | 
						|
Basic Converters
 | 
						|
++++++++++++++++++
 | 
						|
 | 
						|
At its core, a basic converter is a callable that takes in an argument and turns it into something else.
 | 
						|
 | 
						|
For example, if we wanted to add two numbers together, we could request that they are turned into integers
 | 
						|
for us by specifying the converter:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    async def add(ctx, a: int, b: int):
 | 
						|
        await ctx.send(a + b)
 | 
						|
 | 
						|
We specify converters by using something called a **function annotation**. This is a Python 3 exclusive feature that was
 | 
						|
introduced in :pep:`3107`.
 | 
						|
 | 
						|
This works with any callable, such as a function that would convert a string to all upper-case:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    def to_upper(argument):
 | 
						|
        return argument.upper()
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    async def up(ctx, *, content: to_upper):
 | 
						|
        await ctx.send(content)
 | 
						|
 | 
						|
bool
 | 
						|
^^^^^^
 | 
						|
 | 
						|
Unlike the other basic converters, the :class:`bool` converter is treated slightly different. Instead of casting directly to the :class:`bool` type, which would result in any non-empty argument returning ``True``, it instead evaluates the argument as ``True`` or ``False`` based on its given content:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    if lowered in ('yes', 'y', 'true', 't', '1', 'enable', 'on'):
 | 
						|
        return True
 | 
						|
    elif lowered in ('no', 'n', 'false', 'f', '0', 'disable', 'off'):
 | 
						|
        return False
 | 
						|
 | 
						|
.. _ext_commands_adv_converters:
 | 
						|
 | 
						|
Advanced Converters
 | 
						|
+++++++++++++++++++++
 | 
						|
 | 
						|
Sometimes a basic converter doesn't have enough information that we need. For example, sometimes we want to get some
 | 
						|
information from the :class:`Message` that called the command or we want to do some asynchronous processing.
 | 
						|
 | 
						|
For this, the library provides the :class:`~ext.commands.Converter` interface. This allows you to have access to the
 | 
						|
:class:`.Context` and have the callable be asynchronous. Defining a custom converter using this interface requires
 | 
						|
overriding a single method, :meth:`.Converter.convert`.
 | 
						|
 | 
						|
An example converter:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    import random
 | 
						|
 | 
						|
    class Slapper(commands.Converter):
 | 
						|
        async def convert(self, ctx, argument):
 | 
						|
            to_slap = random.choice(ctx.guild.members)
 | 
						|
            return f'{ctx.author} slapped {to_slap} because *{argument}*'
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    async def slap(ctx, *, reason: Slapper):
 | 
						|
        await ctx.send(reason)
 | 
						|
 | 
						|
The converter provided can either be constructed or not. Essentially these two are equivalent:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    async def slap(ctx, *, reason: Slapper):
 | 
						|
        await ctx.send(reason)
 | 
						|
 | 
						|
    # is the same as...
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    async def slap(ctx, *, reason: Slapper()):
 | 
						|
        await ctx.send(reason)
 | 
						|
 | 
						|
Having the possibility of the converter be constructed allows you to set up some state in the converter's ``__init__`` for
 | 
						|
fine tuning the converter. An example of this is actually in the library, :class:`~ext.commands.clean_content`.
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    async def clean(ctx, *, content: commands.clean_content):
 | 
						|
        await ctx.send(content)
 | 
						|
 | 
						|
    # or for fine-tuning
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    async def clean(ctx, *, content: commands.clean_content(use_nicknames=False)):
 | 
						|
        await ctx.send(content)
 | 
						|
 | 
						|
 | 
						|
If a converter fails to convert an argument to its designated target type, the :exc:`.BadArgument` exception must be
 | 
						|
raised.
 | 
						|
 | 
						|
Inline Advanced Converters
 | 
						|
+++++++++++++++++++++++++++++
 | 
						|
 | 
						|
If we don't want to inherit from :class:`~ext.commands.Converter`, we can still provide a converter that has the
 | 
						|
advanced functionalities of an advanced converter and save us from specifying two types.
 | 
						|
 | 
						|
For example, a common idiom would be to have a class and a converter for that class:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    class JoinDistance:
 | 
						|
        def __init__(self, joined, created):
 | 
						|
            self.joined = joined
 | 
						|
            self.created = created
 | 
						|
 | 
						|
        @property
 | 
						|
        def delta(self):
 | 
						|
            return self.joined - self.created
 | 
						|
 | 
						|
    class JoinDistanceConverter(commands.MemberConverter):
 | 
						|
        async def convert(self, ctx, argument):
 | 
						|
            member = await super().convert(ctx, argument)
 | 
						|
            return JoinDistance(member.joined_at, member.created_at)
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    async def delta(ctx, *, member: JoinDistanceConverter):
 | 
						|
        is_new = member.delta.days < 100
 | 
						|
        if is_new:
 | 
						|
            await ctx.send("Hey you're pretty new!")
 | 
						|
        else:
 | 
						|
            await ctx.send("Hm you're not so new.")
 | 
						|
 | 
						|
This can get tedious, so an inline advanced converter is possible through a :func:`classmethod` inside the type:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    class JoinDistance:
 | 
						|
        def __init__(self, joined, created):
 | 
						|
            self.joined = joined
 | 
						|
            self.created = created
 | 
						|
 | 
						|
        @classmethod
 | 
						|
        async def convert(cls, ctx, argument):
 | 
						|
            member = await commands.MemberConverter().convert(ctx, argument)
 | 
						|
            return cls(member.joined_at, member.created_at)
 | 
						|
 | 
						|
        @property
 | 
						|
        def delta(self):
 | 
						|
            return self.joined - self.created
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    async def delta(ctx, *, member: JoinDistance):
 | 
						|
        is_new = member.delta.days < 100
 | 
						|
        if is_new:
 | 
						|
            await ctx.send("Hey you're pretty new!")
 | 
						|
        else:
 | 
						|
            await ctx.send("Hm you're not so new.")
 | 
						|
 | 
						|
Discord Converters
 | 
						|
++++++++++++++++++++
 | 
						|
 | 
						|
Working with :ref:`discord_api_models` is a fairly common thing when defining commands, as a result the library makes
 | 
						|
working with them easy.
 | 
						|
 | 
						|
For example, to receive a :class:`Member` you can just pass it as a converter:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    async def joined(ctx, *, member: discord.Member):
 | 
						|
        await ctx.send(f'{member} joined on {member.joined_at}')
 | 
						|
 | 
						|
When this command is executed, it attempts to convert the string given into a :class:`Member` and then passes it as a
 | 
						|
parameter for the function. This works by checking if the string is a mention, an ID, a nickname, a username + discriminator,
 | 
						|
or just a regular username. The default set of converters have been written to be as easy to use as possible.
 | 
						|
 | 
						|
A lot of discord models work out of the gate as a parameter:
 | 
						|
 | 
						|
- :class:`Object` (since v2.0)
 | 
						|
- :class:`Member`
 | 
						|
- :class:`User`
 | 
						|
- :class:`Message` (since v1.1)
 | 
						|
- :class:`PartialMessage` (since v1.7)
 | 
						|
- :class:`abc.GuildChannel` (since 2.0)
 | 
						|
- :class:`TextChannel`
 | 
						|
- :class:`VoiceChannel`
 | 
						|
- :class:`StageChannel` (since v1.7)
 | 
						|
- :class:`StoreChannel` (since v1.7)
 | 
						|
- :class:`CategoryChannel`
 | 
						|
- :class:`Invite`
 | 
						|
- :class:`Guild` (since v1.7)
 | 
						|
- :class:`Role`
 | 
						|
- :class:`Game`
 | 
						|
- :class:`Colour`
 | 
						|
- :class:`Emoji`
 | 
						|
- :class:`PartialEmoji`
 | 
						|
- :class:`Thread` (since v2.0)
 | 
						|
 | 
						|
Having any of these set as the converter will intelligently convert the argument to the appropriate target type you
 | 
						|
specify.
 | 
						|
 | 
						|
Under the hood, these are implemented by the :ref:`ext_commands_adv_converters` interface. A table of the equivalent
 | 
						|
converter is given below:
 | 
						|
 | 
						|
+--------------------------+-------------------------------------------------+
 | 
						|
|     Discord Class        |                    Converter                    |
 | 
						|
+--------------------------+-------------------------------------------------+
 | 
						|
| :class:`Object`          | :class:`~ext.commands.ObjectConverter`          |
 | 
						|
+--------------------------+-------------------------------------------------+
 | 
						|
| :class:`Member`          | :class:`~ext.commands.MemberConverter`          |
 | 
						|
+--------------------------+-------------------------------------------------+
 | 
						|
| :class:`User`            | :class:`~ext.commands.UserConverter`            |
 | 
						|
+--------------------------+-------------------------------------------------+
 | 
						|
| :class:`Message`         | :class:`~ext.commands.MessageConverter`         |
 | 
						|
+--------------------------+-------------------------------------------------+
 | 
						|
| :class:`PartialMessage`  | :class:`~ext.commands.PartialMessageConverter`  |
 | 
						|
+--------------------------+-------------------------------------------------+
 | 
						|
| :class:`.GuildChannel`   | :class:`~ext.commands.GuildChannelConverter`    |
 | 
						|
+--------------------------+-------------------------------------------------+
 | 
						|
| :class:`TextChannel`     | :class:`~ext.commands.TextChannelConverter`     |
 | 
						|
+--------------------------+-------------------------------------------------+
 | 
						|
| :class:`VoiceChannel`    | :class:`~ext.commands.VoiceChannelConverter`    |
 | 
						|
+--------------------------+-------------------------------------------------+
 | 
						|
| :class:`StageChannel`    | :class:`~ext.commands.StageChannelConverter`    |
 | 
						|
+--------------------------+-------------------------------------------------+
 | 
						|
| :class:`StoreChannel`    | :class:`~ext.commands.StoreChannelConverter`    |
 | 
						|
+--------------------------+-------------------------------------------------+
 | 
						|
| :class:`CategoryChannel` | :class:`~ext.commands.CategoryChannelConverter` |
 | 
						|
+--------------------------+-------------------------------------------------+
 | 
						|
| :class:`Invite`          | :class:`~ext.commands.InviteConverter`          |
 | 
						|
+--------------------------+-------------------------------------------------+
 | 
						|
| :class:`Guild`           | :class:`~ext.commands.GuildConverter`           |
 | 
						|
+--------------------------+-------------------------------------------------+
 | 
						|
| :class:`Role`            | :class:`~ext.commands.RoleConverter`            |
 | 
						|
+--------------------------+-------------------------------------------------+
 | 
						|
| :class:`Game`            | :class:`~ext.commands.GameConverter`            |
 | 
						|
+--------------------------+-------------------------------------------------+
 | 
						|
| :class:`Colour`          | :class:`~ext.commands.ColourConverter`          |
 | 
						|
+--------------------------+-------------------------------------------------+
 | 
						|
| :class:`Emoji`           | :class:`~ext.commands.EmojiConverter`           |
 | 
						|
+--------------------------+-------------------------------------------------+
 | 
						|
| :class:`PartialEmoji`    | :class:`~ext.commands.PartialEmojiConverter`    |
 | 
						|
+--------------------------+-------------------------------------------------+
 | 
						|
| :class:`Thread`          | :class:`~ext.commands.ThreadConverter`          |
 | 
						|
+--------------------------+-------------------------------------------------+
 | 
						|
 | 
						|
By providing the converter it allows us to use them as building blocks for another converter:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    class MemberRoles(commands.MemberConverter):
 | 
						|
        async def convert(self, ctx, argument):
 | 
						|
            member = await super().convert(ctx, argument)
 | 
						|
            return [role.name for role in member.roles[1:]] # Remove everyone role!
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    async def roles(ctx, *, member: MemberRoles):
 | 
						|
        """Tells you a member's roles."""
 | 
						|
        await ctx.send('I see the following roles: ' + ', '.join(member))
 | 
						|
 | 
						|
.. _ext_commands_special_converters:
 | 
						|
 | 
						|
Special Converters
 | 
						|
++++++++++++++++++++
 | 
						|
 | 
						|
The command extension also has support for certain converters to allow for more advanced and intricate use cases that go
 | 
						|
beyond the generic linear parsing. These converters allow you to introduce some more relaxed and dynamic grammar to your
 | 
						|
commands in an easy to use manner.
 | 
						|
 | 
						|
typing.Union
 | 
						|
^^^^^^^^^^^^^^
 | 
						|
 | 
						|
A :data:`typing.Union` is a special type hint that allows for the command to take in any of the specific types instead of
 | 
						|
a singular type. For example, given the following:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    import typing
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    async def union(ctx, what: typing.Union[discord.TextChannel, discord.Member]):
 | 
						|
        await ctx.send(what)
 | 
						|
 | 
						|
 | 
						|
The ``what`` parameter would either take a :class:`discord.TextChannel` converter or a :class:`discord.Member` converter.
 | 
						|
The way this works is through a left-to-right order. It first attempts to convert the input to a
 | 
						|
:class:`discord.TextChannel`, and if it fails it tries to convert it to a :class:`discord.Member`. If all converters fail,
 | 
						|
then a special error is raised, :exc:`~ext.commands.BadUnionArgument`.
 | 
						|
 | 
						|
Note that any valid converter discussed above can be passed in to the argument list of a :data:`typing.Union`.
 | 
						|
 | 
						|
typing.Optional
 | 
						|
^^^^^^^^^^^^^^^^^
 | 
						|
 | 
						|
A :data:`typing.Optional` is a special type hint that allows for "back-referencing" behaviour. If the converter fails to
 | 
						|
parse into the specified type, the parser will skip the parameter and then either ``None`` or the specified default will be
 | 
						|
passed into the parameter instead. The parser will then continue on to the next parameters and converters, if any.
 | 
						|
 | 
						|
Consider the following example:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    import typing
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    async def bottles(ctx, amount: typing.Optional[int] = 99, *, liquid="beer"):
 | 
						|
        await ctx.send(f'{amount} bottles of {liquid} on the wall!')
 | 
						|
 | 
						|
 | 
						|
.. image:: /images/commands/optional1.png
 | 
						|
 | 
						|
In this example, since the argument could not be converted into an ``int``, the default of ``99`` is passed and the parser
 | 
						|
resumes handling, which in this case would be to pass it into the ``liquid`` parameter.
 | 
						|
 | 
						|
.. note::
 | 
						|
 | 
						|
    This converter only works in regular positional parameters, not variable parameters or keyword-only parameters.
 | 
						|
 | 
						|
typing.Literal
 | 
						|
^^^^^^^^^^^^^^^^
 | 
						|
 | 
						|
A :data:`typing.Literal` is a special type hint that requires the passed parameter to be equal to one of the listed values
 | 
						|
after being converted to the same type. For example, given the following:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    from typing import Literal
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    async def shop(ctx, buy_sell: Literal['buy', 'sell'], amount: Literal[1, 2], *, item: str):
 | 
						|
        await ctx.send(f'{buy_sell.capitalize()}ing {amount} {item}(s)!')
 | 
						|
 | 
						|
 | 
						|
The ``buy_sell`` parameter must be either the literal string ``"buy"`` or ``"sell"`` and ``amount`` must convert to the
 | 
						|
``int`` ``1`` or ``2``. If ``buy_sell`` or ``amount`` don't match any value, then a special error is raised,
 | 
						|
:exc:`~.ext.commands.BadLiteralArgument`. Any literal values can be mixed and matched within the same :data:`typing.Literal` converter.
 | 
						|
 | 
						|
Note that ``typing.Literal[True]`` and ``typing.Literal[False]`` still follow the :class:`bool` converter rules.
 | 
						|
 | 
						|
Greedy
 | 
						|
^^^^^^^^
 | 
						|
 | 
						|
The :class:`~ext.commands.Greedy` converter is a generalisation of the :data:`typing.Optional` converter, except applied
 | 
						|
to a list of arguments. In simple terms, this means that it tries to convert as much as it can until it can't convert
 | 
						|
any further.
 | 
						|
 | 
						|
Consider the following example:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    async def slap(ctx, members: commands.Greedy[discord.Member], *, reason='no reason'):
 | 
						|
        slapped = ", ".join(x.name for x in members)
 | 
						|
        await ctx.send(f'{slapped} just got slapped for {reason}')
 | 
						|
 | 
						|
When invoked, it allows for any number of members to be passed in:
 | 
						|
 | 
						|
.. image:: /images/commands/greedy1.png
 | 
						|
 | 
						|
The type passed when using this converter depends on the parameter type that it is being attached to:
 | 
						|
 | 
						|
- Positional parameter types will receive either the default parameter or a :class:`list` of the converted values.
 | 
						|
- Variable parameter types will be a :class:`tuple` as usual.
 | 
						|
- Keyword-only parameter types will be the same as if :class:`~ext.commands.Greedy` was not passed at all.
 | 
						|
 | 
						|
:class:`~ext.commands.Greedy` parameters can also be made optional by specifying an optional value.
 | 
						|
 | 
						|
When mixed with the :data:`typing.Optional` converter you can provide simple and expressive command invocation syntaxes:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    import typing
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    async def ban(ctx, members: commands.Greedy[discord.Member],
 | 
						|
                       delete_days: typing.Optional[int] = 0, *,
 | 
						|
                       reason: str):
 | 
						|
        """Mass bans members with an optional delete_days parameter"""
 | 
						|
        for member in members:
 | 
						|
            await member.ban(delete_message_days=delete_days, reason=reason)
 | 
						|
 | 
						|
 | 
						|
This command can be invoked any of the following ways:
 | 
						|
 | 
						|
.. code-block:: none
 | 
						|
 | 
						|
    $ban @Member @Member2 spam bot
 | 
						|
    $ban @Member @Member2 7 spam bot
 | 
						|
    $ban @Member spam
 | 
						|
 | 
						|
.. warning::
 | 
						|
 | 
						|
    The usage of :class:`~ext.commands.Greedy` and :data:`typing.Optional` are powerful and useful, however as a
 | 
						|
    price, they open you up to some parsing ambiguities that might surprise some people.
 | 
						|
 | 
						|
    For example, a signature expecting a :data:`typing.Optional` of a :class:`discord.Member` followed by a
 | 
						|
    :class:`int` could catch a member named after a number due to the different ways a
 | 
						|
    :class:`~ext.commands.MemberConverter` decides to fetch members. You should take care to not introduce
 | 
						|
    unintended parsing ambiguities in your code. One technique would be to clamp down the expected syntaxes
 | 
						|
    allowed through custom converters or reordering the parameters to minimise clashes.
 | 
						|
 | 
						|
    To help aid with some parsing ambiguities, :class:`str`, ``None``, :data:`typing.Optional` and
 | 
						|
    :class:`~ext.commands.Greedy` are forbidden as parameters for the :class:`~ext.commands.Greedy` converter.
 | 
						|
 | 
						|
.. _ext_commands_flag_converter:
 | 
						|
 | 
						|
FlagConverter
 | 
						|
++++++++++++++
 | 
						|
 | 
						|
.. versionadded:: 2.0
 | 
						|
 | 
						|
A :class:`~ext.commands.FlagConverter` allows the user to specify user-friendly "flags" using :pep:`526` type annotations
 | 
						|
or a syntax more reminiscent of the :mod:`py:dataclasses` module.
 | 
						|
 | 
						|
For example, the following code:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    from discord.ext import commands
 | 
						|
    import discord
 | 
						|
 | 
						|
    class BanFlags(commands.FlagConverter):
 | 
						|
        member: discord.Member
 | 
						|
        reason: str
 | 
						|
        days: int = 1
 | 
						|
 | 
						|
    @commands.command()
 | 
						|
    async def ban(ctx, *, flags: BanFlags):
 | 
						|
        plural = f'{flags.days} days' if flags.days != 1 else f'{flags.days} day'
 | 
						|
        await ctx.send(f'Banned {flags.member} for {flags.reason!r} (deleted {plural} worth of messages)')
 | 
						|
 | 
						|
Allows the user to invoke the command using a simple flag-like syntax:
 | 
						|
 | 
						|
.. image:: /images/commands/flags1.png
 | 
						|
 | 
						|
Flags use a syntax that allows the user to not require quotes when passing in values to the flag. The goal of the
 | 
						|
flag syntax is to be as user-friendly as possible. This makes flags a good choice for complicated commands that can have
 | 
						|
multiple knobs to turn or simulating keyword-only parameters in your external command interface. **It is recommended to use
 | 
						|
keyword-only parameters with the flag converter**. This ensures proper parsing and behaviour with quoting.
 | 
						|
 | 
						|
Internally, the :class:`~ext.commands.FlagConverter` class examines the class to find flags. A flag can either be a
 | 
						|
class variable with a type annotation or a class variable that's been assigned the result of the :func:`~ext.commands.flag`
 | 
						|
function. These flags are then used to define the interface that your users will use. The annotations correspond to
 | 
						|
the converters that the flag arguments must adhere to.
 | 
						|
 | 
						|
For most use cases, no extra work is required to define flags. However, if customisation is needed to control the flag name
 | 
						|
or the default value then the :func:`~ext.commands.flag` function can come in handy:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    from typing import List
 | 
						|
 | 
						|
    class BanFlags(commands.FlagConverter):
 | 
						|
        members: List[discord.Member] = commands.flag(name='member', default=lambda ctx: [])
 | 
						|
 | 
						|
This tells the parser that the ``members`` attribute is mapped to a flag named ``member`` and that
 | 
						|
the default value is an empty list. For greater customisability, the default can either be a value or a callable
 | 
						|
that takes the :class:`~ext.commands.Context` as a sole parameter. This callable can either be a function or a coroutine.
 | 
						|
 | 
						|
In order to customise the flag syntax we also have a few options that can be passed to the class parameter list:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    # --hello world syntax
 | 
						|
    class PosixLikeFlags(commands.FlagConverter, delimiter=' ', prefix='--'):
 | 
						|
        hello: str
 | 
						|
 | 
						|
 | 
						|
    # /make food
 | 
						|
    class WindowsLikeFlags(commands.FlagConverter, prefix='/', delimiter=''):
 | 
						|
        make: str
 | 
						|
 | 
						|
    # TOPIC: not allowed nsfw: yes Slowmode: 100
 | 
						|
    class Settings(commands.FlagConverter, case_insensitive=True):
 | 
						|
        topic: Optional[str]
 | 
						|
        nsfw: Optional[bool]
 | 
						|
        slowmode: Optional[int]
 | 
						|
 | 
						|
.. note::
 | 
						|
 | 
						|
    Despite the similarities in these examples to command like arguments, the syntax and parser is not
 | 
						|
    a command line parser. The syntax is mainly inspired by Discord's search bar input and as a result
 | 
						|
    all flags need a corresponding value.
 | 
						|
 | 
						|
The flag converter is similar to regular commands and allows you to use most types of converters
 | 
						|
(with the exception of :class:`~ext.commands.Greedy`) as the type annotation. Some extra support is added for specific
 | 
						|
annotations as described below.
 | 
						|
 | 
						|
typing.List
 | 
						|
^^^^^^^^^^^^^
 | 
						|
 | 
						|
If a list is given as a flag annotation it tells the parser that the argument can be passed multiple times.
 | 
						|
 | 
						|
For example, augmenting the example above:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    from discord.ext import commands
 | 
						|
    from typing import List
 | 
						|
    import discord
 | 
						|
 | 
						|
    class BanFlags(commands.FlagConverter):
 | 
						|
        members: List[discord.Member] = commands.flag(name='member')
 | 
						|
        reason: str
 | 
						|
        days: int = 1
 | 
						|
 | 
						|
    @commands.command()
 | 
						|
    async def ban(ctx, *, flags: BanFlags):
 | 
						|
        for member in flags.members:
 | 
						|
            await member.ban(reason=flags.reason, delete_message_days=flags.days)
 | 
						|
 | 
						|
        members = ', '.join(str(member) for member in flags.members)
 | 
						|
        plural = f'{flags.days} days' if flags.days != 1 else f'{flags.days} day'
 | 
						|
        await ctx.send(f'Banned {members} for {flags.reason!r} (deleted {plural} worth of messages)')
 | 
						|
 | 
						|
This is called by repeatedly specifying the flag:
 | 
						|
 | 
						|
.. image:: /images/commands/flags2.png
 | 
						|
 | 
						|
typing.Tuple
 | 
						|
^^^^^^^^^^^^^
 | 
						|
 | 
						|
Since the above syntax can be a bit repetitive when specifying a flag many times, the :class:`py:tuple` type annotation
 | 
						|
allows for "greedy-like" semantics using a variadic tuple:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    from discord.ext import commands
 | 
						|
    from typing import Tuple
 | 
						|
    import discord
 | 
						|
 | 
						|
    class BanFlags(commands.FlagConverter):
 | 
						|
        members: Tuple[discord.Member, ...]
 | 
						|
        reason: str
 | 
						|
        days: int = 1
 | 
						|
 | 
						|
This allows the previous ``ban`` command to be called like this:
 | 
						|
 | 
						|
.. image:: /images/commands/flags3.png
 | 
						|
 | 
						|
The :class:`py:tuple` annotation also allows for parsing of pairs. For example, given the following code:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    # point: 10 11 point: 12 13
 | 
						|
    class Coordinates(commands.FlagConverter):
 | 
						|
        point: Tuple[int, int]
 | 
						|
 | 
						|
 | 
						|
.. warning::
 | 
						|
 | 
						|
    Due to potential parsing ambiguities, the parser expects tuple arguments to be quoted
 | 
						|
    if they require spaces. So if one of the inner types is :class:`str` and the argument requires spaces
 | 
						|
    then quotes should be used to disambiguate it from the other element of the tuple.
 | 
						|
 | 
						|
typing.Dict
 | 
						|
^^^^^^^^^^^^^
 | 
						|
 | 
						|
A :class:`dict` annotation is functionally equivalent to ``List[Tuple[K, V]]`` except with the return type
 | 
						|
given as a :class:`dict` rather than a :class:`list`.
 | 
						|
 | 
						|
 | 
						|
.. _ext_commands_error_handler:
 | 
						|
 | 
						|
Error Handling
 | 
						|
----------------
 | 
						|
 | 
						|
When our commands fail to parse we will, by default, receive a noisy error in ``stderr`` of our console that tells us
 | 
						|
that an error has happened and has been silently ignored.
 | 
						|
 | 
						|
In order to handle our errors, we must use something called an error handler. There is a global error handler, called
 | 
						|
:func:`.on_command_error` which works like any other event in the :ref:`discord-api-events`. This global error handler is
 | 
						|
called for every error reached.
 | 
						|
 | 
						|
Most of the time however, we want to handle an error local to the command itself. Luckily, commands come with local error
 | 
						|
handlers that allow us to do just that. First we decorate an error handler function with :meth:`.Command.error`:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    async def info(ctx, *, member: discord.Member):
 | 
						|
        """Tells you some info about the member."""
 | 
						|
        msg = f'{member} joined on {member.joined_at} and has {len(member.roles)} roles.'
 | 
						|
        await ctx.send(msg)
 | 
						|
 | 
						|
    @info.error
 | 
						|
    async def info_error(ctx, error):
 | 
						|
        if isinstance(error, commands.BadArgument):
 | 
						|
            await ctx.send('I could not find that member...')
 | 
						|
 | 
						|
The first parameter of the error handler is the :class:`.Context` while the second one is an exception that is derived from
 | 
						|
:exc:`~ext.commands.CommandError`. A list of errors is found in the :ref:`ext_commands_api_errors` page of the documentation.
 | 
						|
 | 
						|
Checks
 | 
						|
-------
 | 
						|
 | 
						|
There are cases when we don't want a user to use our commands. They don't have permissions to do so or maybe we blocked
 | 
						|
them from using our bot earlier. The commands extension comes with full support for these things in a concept called a
 | 
						|
:ref:`ext_commands_api_checks`.
 | 
						|
 | 
						|
A check is a basic predicate that can take in a :class:`.Context` as its sole parameter. Within it, you have the following
 | 
						|
options:
 | 
						|
 | 
						|
- Return ``True`` to signal that the person can run the command.
 | 
						|
- Return ``False`` to signal that the person cannot run the command.
 | 
						|
- Raise a :exc:`~ext.commands.CommandError` derived exception to signal the person cannot run the command.
 | 
						|
 | 
						|
    - This allows you to have custom error messages for you to handle in the
 | 
						|
      :ref:`error handlers <ext_commands_error_handler>`.
 | 
						|
 | 
						|
To register a check for a command, we would have two ways of doing so. The first is using the :meth:`~ext.commands.check`
 | 
						|
decorator. For example:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    async def is_owner(ctx):
 | 
						|
        return ctx.author.id == 316026178463072268
 | 
						|
 | 
						|
    @bot.command(name='eval')
 | 
						|
    @commands.check(is_owner)
 | 
						|
    async def _eval(ctx, *, code):
 | 
						|
        """A bad example of an eval command"""
 | 
						|
        await ctx.send(eval(code))
 | 
						|
 | 
						|
This would only evaluate the command if the function ``is_owner`` returns ``True``. Sometimes we re-use a check often and
 | 
						|
want to split it into its own decorator. To do that we can just add another level of depth:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    def is_owner():
 | 
						|
        async def predicate(ctx):
 | 
						|
            return ctx.author.id == 316026178463072268
 | 
						|
        return commands.check(predicate)
 | 
						|
 | 
						|
    @bot.command(name='eval')
 | 
						|
    @is_owner()
 | 
						|
    async def _eval(ctx, *, code):
 | 
						|
        """A bad example of an eval command"""
 | 
						|
        await ctx.send(eval(code))
 | 
						|
 | 
						|
 | 
						|
Since an owner check is so common, the library provides it for you (:func:`~ext.commands.is_owner`):
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    @bot.command(name='eval')
 | 
						|
    @commands.is_owner()
 | 
						|
    async def _eval(ctx, *, code):
 | 
						|
        """A bad example of an eval command"""
 | 
						|
        await ctx.send(eval(code))
 | 
						|
 | 
						|
When multiple checks are specified, **all** of them must be ``True``:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    def is_in_guild(guild_id):
 | 
						|
        async def predicate(ctx):
 | 
						|
            return ctx.guild and ctx.guild.id == guild_id
 | 
						|
        return commands.check(predicate)
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    @commands.is_owner()
 | 
						|
    @is_in_guild(41771983423143937)
 | 
						|
    async def secretguilddata(ctx):
 | 
						|
        """super secret stuff"""
 | 
						|
        await ctx.send('secret stuff')
 | 
						|
 | 
						|
If any of those checks fail in the example above, then the command will not be run.
 | 
						|
 | 
						|
When an error happens, the error is propagated to the :ref:`error handlers <ext_commands_error_handler>`. If you do not
 | 
						|
raise a custom :exc:`~ext.commands.CommandError` derived exception, then it will get wrapped up into a
 | 
						|
:exc:`~ext.commands.CheckFailure` exception as so:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    @bot.command()
 | 
						|
    @commands.is_owner()
 | 
						|
    @is_in_guild(41771983423143937)
 | 
						|
    async def secretguilddata(ctx):
 | 
						|
        """super secret stuff"""
 | 
						|
        await ctx.send('secret stuff')
 | 
						|
 | 
						|
    @secretguilddata.error
 | 
						|
    async def secretguilddata_error(ctx, error):
 | 
						|
        if isinstance(error, commands.CheckFailure):
 | 
						|
            await ctx.send('nothing to see here comrade.')
 | 
						|
 | 
						|
If you want a more robust error system, you can derive from the exception and raise it instead of returning ``False``:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    class NoPrivateMessages(commands.CheckFailure):
 | 
						|
        pass
 | 
						|
 | 
						|
    def guild_only():
 | 
						|
        async def predicate(ctx):
 | 
						|
            if ctx.guild is None:
 | 
						|
                raise NoPrivateMessages('Hey no DMs!')
 | 
						|
            return True
 | 
						|
        return commands.check(predicate)
 | 
						|
 | 
						|
    @guild_only()
 | 
						|
    async def test(ctx):
 | 
						|
        await ctx.send('Hey this is not a DM! Nice.')
 | 
						|
 | 
						|
    @test.error
 | 
						|
    async def test_error(ctx, error):
 | 
						|
        if isinstance(error, NoPrivateMessages):
 | 
						|
            await ctx.send(error)
 | 
						|
 | 
						|
.. note::
 | 
						|
 | 
						|
    Since having a ``guild_only`` decorator is pretty common, it comes built-in via :func:`~ext.commands.guild_only`.
 | 
						|
 | 
						|
Global Checks
 | 
						|
++++++++++++++
 | 
						|
 | 
						|
Sometimes we want to apply a check to **every** command, not just certain commands. The library supports this as well
 | 
						|
using the global check concept.
 | 
						|
 | 
						|
Global checks work similarly to regular checks except they are registered with the :meth:`.Bot.check` decorator.
 | 
						|
 | 
						|
For example, to block all DMs we could do the following:
 | 
						|
 | 
						|
.. code-block:: python3
 | 
						|
 | 
						|
    @bot.check
 | 
						|
    async def globally_block_dms(ctx):
 | 
						|
        return ctx.guild is not None
 | 
						|
 | 
						|
.. warning::
 | 
						|
 | 
						|
    Be careful on how you write your global checks, as it could also lock you out of your own bot.
 | 
						|
 | 
						|
.. need a note on global check once here I think
 |