diff --git a/discord/ext/commands/converter.py b/discord/ext/commands/converter.py index 8504f4a0..feff650d 100644 --- a/discord/ext/commands/converter.py +++ b/discord/ext/commands/converter.py @@ -1008,27 +1008,50 @@ class Greedy(List[T]): return cls(converter=converter) -if TYPE_CHECKING: +class Option(Generic[T, DT]): # type: ignore + """A special 'converter' to apply a description to slash command options. + For example in the following code: + + .. code-block:: python3 + + @bot.command() + async def ban(ctx, + member: discord.Member, *, + reason: str = commands.Option('no reason', description='the reason to ban this member') + ): + await member.ban(reason=reason) + + The description would be ``the reason to ban this member`` and the default would be ``no reason`` + + .. versionadded:: 2.0 + + Attributes + ------------ + default: Optional[Any] + The default for this option, overwrites Option during parsing. + description: :class:`str` + The description for this option, is unpacked to :attr:`.Command.option_descriptions` + """ + + description: DT + default: Union[T, inspect._empty] + __slots__ = ( + "default", + "description", + ) + + def __init__(self, default: T = inspect.Parameter.empty, *, description: DT) -> None: + self.description = description + self.default = default + + +if TYPE_CHECKING: + # Terrible workaround for type checking reasons def Option(default: T = inspect.Parameter.empty, *, description: str) -> T: ... -else: - - class Option(Generic[T, DT]): - description: DT - default: Union[T, inspect.Parameter.empty] - __slots__ = ( - "default", - "description", - ) - - def __init__(self, default: T = inspect.Parameter.empty, *, description: DT) -> None: - self.description = description - self.default = default - - def _convert_to_bool(argument: str) -> bool: lowered = argument.lower() if lowered in ("yes", "y", "true", "t", "1", "enable", "on"): diff --git a/discord/ext/commands/core.py b/discord/ext/commands/core.py index 5947dc19..3f1f25d9 100644 --- a/discord/ext/commands/core.py +++ b/discord/ext/commands/core.py @@ -123,7 +123,7 @@ application_option_type_lookup = { discord.Member, discord.User, ): 6, # Preferably discord.abc.User, but 'Protocols with non-method members don't support issubclass()' - (discord.abc.GuildChannel, discord.DMChannel): 7, + (discord.abc.GuildChannel, discord.Thread): 7, discord.Role: 8, discord.Object: 9, float: 10, @@ -322,11 +322,16 @@ class Command(_BaseCommand, Generic[CogT, P, T]): This overwrites the global ``slash_commands`` parameter of :class:`.Bot`. .. versionadded:: 2.0 - slash_command_guilds: Optional[:class:`List[int]`] + slash_command_guilds: Optional[List[:class:`int`]] If this is set, only upload this slash command to these guild IDs. This overwrites the global ``slash_command_guilds`` parameter of :class:`.Bot`. + .. versionadded:: 2.0 + + option_descriptions: Dict[:class:`str`, :class:`str`] + The unpacked option descriptions from :class:`.Option`. + .. versionadded:: 2.0 """ __original_kwargs__: Dict[str, Any] @@ -399,9 +404,9 @@ class Command(_BaseCommand, Generic[CogT, P, T]): command_attrs = {} try: - checks = command_attrs.pop("checks") + checks = func.__commands_checks__ checks.reverse() - except KeyError: + except AttributeError: checks = kwargs.get("checks", []) try: diff --git a/docs/ext/commands/api.rst b/docs/ext/commands/api.rst index e9631514..f70d835c 100644 --- a/docs/ext/commands/api.rst +++ b/docs/ext/commands/api.rst @@ -429,6 +429,12 @@ Converters .. autofunction:: discord.ext.commands.run_converters +Option +~~~~~~ + +.. autoclass:: discord.ext.commands.Option + :members: + Flag Converter ~~~~~~~~~~~~~~~ diff --git a/docs/ext/commands/commands.rst b/docs/ext/commands/commands.rst index 0f73f050..f29dc70b 100644 --- a/docs/ext/commands/commands.rst +++ b/docs/ext/commands/commands.rst @@ -61,12 +61,6 @@ the name to something other than the function would be as simple as doing this: 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 ------------ @@ -140,6 +134,11 @@ at all: Since the ``args`` variable is a :class:`py:tuple`, you can do anything you would usually do with one. +.. admonition:: Slash Command Only + + This functionally is currently not supported by the slash command API, so is turned into + a single ``STRING`` parameter on discord's end which we do our own parsing on. + Keyword-Only Arguments ++++++++++++++++++++++++ @@ -186,7 +185,8 @@ know how the command was executed. It contains a lot of useful information: 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:: +.. admonition:: Slash Command Only + :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. @@ -412,47 +412,55 @@ 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` | -+--------------------------+-------------------------------------------------+ ++--------------------------+-------------------------------------------------+-----------------------------+ +| Discord Class | Converter | Supported By Slash Commands | ++--------------------------+-------------------------------------------------+-----------------------------+ +| :class:`Object` | :class:`~ext.commands.ObjectConverter` | Not currently | ++--------------------------+-------------------------------------------------+-----------------------------+ +| :class:`Member` | :class:`~ext.commands.MemberConverter` | Yes, as type 6 (USER) | ++--------------------------+-------------------------------------------------+-----------------------------+ +| :class:`User` | :class:`~ext.commands.UserConverter` | Yes, as type 6 (USER) | ++--------------------------+-------------------------------------------------+-----------------------------+ +| :class:`Message` | :class:`~ext.commands.MessageConverter` | Not currently | ++--------------------------+-------------------------------------------------+-----------------------------+ +| :class:`PartialMessage` | :class:`~ext.commands.PartialMessageConverter` | Not currently | ++--------------------------+-------------------------------------------------+-----------------------------+ +| :class:`.GuildChannel` | :class:`~ext.commands.GuildChannelConverter` | Yes, as type 7 (CHANNEL) | ++--------------------------+-------------------------------------------------+-----------------------------+ +| :class:`TextChannel` | :class:`~ext.commands.TextChannelConverter` | Yes, as type 7 (CHANNEL) | ++--------------------------+-------------------------------------------------+-----------------------------+ +| :class:`VoiceChannel` | :class:`~ext.commands.VoiceChannelConverter` | Yes, as type 7 (CHANNEL) | ++--------------------------+-------------------------------------------------+-----------------------------+ +| :class:`StageChannel` | :class:`~ext.commands.StageChannelConverter` | Yes, as type 7 (CHANNEL) | ++--------------------------+-------------------------------------------------+-----------------------------+ +| :class:`StoreChannel` | :class:`~ext.commands.StoreChannelConverter` | Yes, as type 7 (CHANNEL) | ++--------------------------+-------------------------------------------------+-----------------------------+ +| :class:`CategoryChannel` | :class:`~ext.commands.CategoryChannelConverter` | Yes, as type 7 (CHANNEL) | ++--------------------------+-------------------------------------------------+-----------------------------+ +| :class:`Thread` | :class:`~ext.commands.ThreadConverter` | Yes, as type 7 (CHANNEL) | ++--------------------------+-------------------------------------------------+-----------------------------+ +| :class:`Invite` | :class:`~ext.commands.InviteConverter` | Not currently | ++--------------------------+-------------------------------------------------+-----------------------------+ +| :class:`Guild` | :class:`~ext.commands.GuildConverter` | Not currently | ++--------------------------+-------------------------------------------------+-----------------------------+ +| :class:`Role` | :class:`~ext.commands.RoleConverter` | Yes, as type 8 (ROLE) | ++--------------------------+-------------------------------------------------+-----------------------------+ +| :class:`Game` | :class:`~ext.commands.GameConverter` | Not currently | ++--------------------------+-------------------------------------------------+-----------------------------+ +| :class:`Colour` | :class:`~ext.commands.ColourConverter` | Not currently | ++--------------------------+-------------------------------------------------+-----------------------------+ +| :class:`Emoji` | :class:`~ext.commands.EmojiConverter` | Not currently | ++--------------------------+-------------------------------------------------+-----------------------------+ +| :class:`PartialEmoji` | :class:`~ext.commands.PartialEmojiConverter` | Not currently | ++--------------------------+-------------------------------------------------+-----------------------------+ + +.. admonition:: Slash Command Only + + If a slash command is not marked on the table above as supported, it will be sent as type 3 (STRING) + and parsed by normal content parsing, see + `the discord documentation `_ + for all supported types by the API. + By providing the converter it allows us to use them as building blocks for another converter: @@ -499,6 +507,10 @@ 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`. +.. admonition:: Slash Command Only + + These are not currently supported by the Discord API and will be sent as type 3 (STRING) + typing.Optional ^^^^^^^^^^^^^^^^^ @@ -692,6 +704,11 @@ In order to customise the flag syntax we also have a few options that can be pas 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. +.. admonition:: Slash Command Only + + As these are built very similar to slash command options, they are converted into options and parsed + back into flags when the slash command is executed. + 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. diff --git a/docs/ext/commands/index.rst b/docs/ext/commands/index.rst index fba57359..9fd5e63c 100644 --- a/docs/ext/commands/index.rst +++ b/docs/ext/commands/index.rst @@ -15,4 +15,5 @@ extension library that handles this for you. commands cogs extensions + slash-commands api diff --git a/docs/ext/commands/slash-commands.rst b/docs/ext/commands/slash-commands.rst new file mode 100644 index 00000000..db0b89b6 --- /dev/null +++ b/docs/ext/commands/slash-commands.rst @@ -0,0 +1,23 @@ +.. currentmodule:: discord + +.. _ext_commands_slash_commands: + +Slash Commands +============== + +Slash Commands are currently supported in enhanced-discord.py using a system on top of ext.commands. + +This system is very simple to use, and can be enabled via :attr:`.Bot.slash_commands` globally, +or only for specific commands via :attr:`.Command.slash_command`. + +There is also the parameter ``slash_command_guilds`` which can be passed to either :class:`.Bot` or the command +decorator in order to only upload the commands as guild commands to these specific guild IDs, however this +should only be used for testing or small (<10 guilds) bots. + +If you want to add option descriptions to your commands, you should use :class:`.Option` + +For troubleshooting, see the :ref:`FAQ ` + +.. admonition:: Slash Command Only + + For parts of the docs specific to slash commands, look for this box! diff --git a/docs/faq.rst b/docs/faq.rst index 3f46d2a9..556bb34f 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -410,3 +410,34 @@ Example: :: await ctx.send(f'Pushing to {remote} {branch}') This could then be used as ``?git push origin master``. + +How do I make slash commands? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +See :doc:`/ext/commands/slash-commands` + +My slash commands aren't showing up! +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. _ext_commands_slash_command_troubleshooting: + +You need to invite your bot with the ``application.commands`` scope on each guild and +you need the :attr:`Permissions.use_slash_commands` permission in order to see slash commands. + +.. image:: /images/discord_oauth2_slash_scope.png + :alt: The scopes checkbox with "bot" and "applications.commands" ticked. + +Global slash commands (created by not specifying :attr:`~ext.commands.Bot.slash_command_guilds`) will also take up an +hour to refresh on discord's end, so it is recommended to set :attr:`~ext.commands.Bot.slash_command_guilds` for development. + +If none of this works, make sure you are actually running enhanced-discord.py by doing ``print(bot.slash_commands)`` + +My bot won't start after enabling slash commands! +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This means some of your command metadata is invalid for slash commands. +Make sure your command names and option names are lowercase, and they have to match the regex ``^[\w-]{1,32}$`` + +If you cannot figure out the problem, you should disable slash commands globally (:attr:`~ext.commands.Bot.slash_commands`\=False) +then go through commands, enabling them specifically with :attr:`~.commands.Command.slash_command`\=True until it +errors, then you can debug the problem with that command specifically.