slash_commands_guilds param re-named to guilds. #81
							
								
								
									
										2
									
								
								.github/ISSUE_TEMPLATE/bug_report.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/ISSUE_TEMPLATE/bug_report.yml
									
									
									
									
										vendored
									
									
								
							@@ -6,7 +6,7 @@ body:
 | 
			
		||||
    attributes:
 | 
			
		||||
      value: >
 | 
			
		||||
        Thanks for taking the time to fill out a bug.
 | 
			
		||||
        If you want real-time support, consider joining our Discord at https://discord.gg/r3sSKJJ instead.
 | 
			
		||||
        If you want real-time support, consider joining our Discord at https://discord.gg/TvqYBrGXEm instead.
 | 
			
		||||
 | 
			
		||||
        Please note that this form is for bugs only!
 | 
			
		||||
  - type: input
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							@@ -5,4 +5,4 @@ contact_links:
 | 
			
		||||
    url: https://github.com/Rapptz/discord.py/discussions
 | 
			
		||||
  - name: Discord Server
 | 
			
		||||
    about: Use our official Discord server to ask for help and questions as well.
 | 
			
		||||
    url: https://discord.gg/r3sSKJJ
 | 
			
		||||
    url: https://discord.gg/TvqYBrGXEm
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										25
									
								
								.github/workflows/black.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										25
									
								
								.github/workflows/black.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,25 +0,0 @@
 | 
			
		||||
name: Lint
 | 
			
		||||
 | 
			
		||||
on: [push]
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  lint:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: checkout
 | 
			
		||||
        uses: actions/checkout@v2
 | 
			
		||||
 | 
			
		||||
      - name: Setup Python
 | 
			
		||||
        uses: actions/setup-python@v1
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: 3.8
 | 
			
		||||
 | 
			
		||||
      - name: install black
 | 
			
		||||
        run: pip install black
 | 
			
		||||
 | 
			
		||||
      - name: run linter
 | 
			
		||||
        uses: wearerequired/lint-action@v1
 | 
			
		||||
        with:
 | 
			
		||||
          black: true
 | 
			
		||||
          black_args: ". --line-length 120"
 | 
			
		||||
          auto_fix: true
 | 
			
		||||
							
								
								
									
										38
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
name: CI
 | 
			
		||||
 | 
			
		||||
on: [push, pull_request]
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  pyright:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: checkout
 | 
			
		||||
        uses: actions/checkout@v2
 | 
			
		||||
 | 
			
		||||
      - name: Setup Python
 | 
			
		||||
        uses: actions/setup-python@v1
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: 3.8
 | 
			
		||||
      
 | 
			
		||||
      - name: Setup node.js (for pyright)
 | 
			
		||||
        uses: actions/setup-node@v1
 | 
			
		||||
        with:
 | 
			
		||||
          node-version: "14"
 | 
			
		||||
          
 | 
			
		||||
      - name: Run type checking
 | 
			
		||||
        run: |
 | 
			
		||||
          npm install -g pyright
 | 
			
		||||
          pip install .
 | 
			
		||||
          pyright --lib --verifytypes discord --ignoreexternal
 | 
			
		||||
 | 
			
		||||
  black:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout
 | 
			
		||||
        uses: actions/checkout@v2
 | 
			
		||||
 | 
			
		||||
      - name: Run linter
 | 
			
		||||
        uses: psf/black@stable
 | 
			
		||||
        with:
 | 
			
		||||
          options: "--line-length 120 --check"
 | 
			
		||||
          src: "./discord"
 | 
			
		||||
							
								
								
									
										1
									
								
								.python-black
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.python-black
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
 | 
			
		||||
@@ -2,7 +2,7 @@ enhanced-discord.py
 | 
			
		||||
===================
 | 
			
		||||
 | 
			
		||||
.. image:: https://discord.com/api/guilds/514232441498763279/embed.png
 | 
			
		||||
   :target: https://discord.gg/PYAfZzpsjG
 | 
			
		||||
   :target: https://discord.gg/TvqYBrGXEm
 | 
			
		||||
   :alt: Discord server invite
 | 
			
		||||
.. image:: https://img.shields.io/pypi/v/enhanced-dpy.svg
 | 
			
		||||
   :target: https://pypi.python.org/pypi/enhanced-dpy
 | 
			
		||||
@@ -117,5 +117,5 @@ Links
 | 
			
		||||
------
 | 
			
		||||
 | 
			
		||||
- `Documentation <https://enhanced-dpy.readthedocs.io/en/latest/index.html>`_
 | 
			
		||||
- `Official Discord Server <https://discord.gg/PYAfZzpsjG>`_
 | 
			
		||||
- `Official Discord Server <https://discord.gg/TvqYBrGXEm>`_
 | 
			
		||||
- `Discord API <https://discord.gg/discord-api>`_
 | 
			
		||||
 
 | 
			
		||||
@@ -624,7 +624,7 @@ class Client:
 | 
			
		||||
    async def start(self, token: str, *, reconnect: bool = True) -> None:
 | 
			
		||||
        """|coro|
 | 
			
		||||
 | 
			
		||||
        A shorthand coroutine for :meth:`login` + :meth:`connect`.
 | 
			
		||||
        A shorthand coroutine for :meth:`login` + :meth:`setup` + :meth:`connect`.
 | 
			
		||||
 | 
			
		||||
        Raises
 | 
			
		||||
        -------
 | 
			
		||||
@@ -632,8 +632,21 @@ class Client:
 | 
			
		||||
            An unexpected keyword argument was received.
 | 
			
		||||
        """
 | 
			
		||||
        await self.login(token)
 | 
			
		||||
        await self.setup()
 | 
			
		||||
        await self.connect(reconnect=reconnect)
 | 
			
		||||
 | 
			
		||||
    async def setup(self) -> Any:
 | 
			
		||||
        """|coro|
 | 
			
		||||
 | 
			
		||||
        A coroutine to be called to setup the bot, by default this is blank.
 | 
			
		||||
 | 
			
		||||
        To perform asynchronous setup after the bot is logged in but before
 | 
			
		||||
        it has connected to the Websocket, overwrite this coroutine.
 | 
			
		||||
 | 
			
		||||
        .. versionadded:: 2.0
 | 
			
		||||
        """
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def run(self, *args: Any, **kwargs: Any) -> None:
 | 
			
		||||
        """A blocking call that abstracts away the event loop
 | 
			
		||||
        initialisation from you.
 | 
			
		||||
@@ -722,7 +735,7 @@ class Client:
 | 
			
		||||
        """:class:`.Status`:
 | 
			
		||||
        The status being used upon logging on to Discord.
 | 
			
		||||
 | 
			
		||||
        .. versionadded: 2.0
 | 
			
		||||
        .. versionadded:: 2.0
 | 
			
		||||
        """
 | 
			
		||||
        if self._connection._status in set(state.value for state in Status):
 | 
			
		||||
            return Status(self._connection._status)
 | 
			
		||||
 
 | 
			
		||||
@@ -398,16 +398,14 @@ class Embed:
 | 
			
		||||
        return EmbedProxy(getattr(self, "_image", {}))  # type: ignore
 | 
			
		||||
 | 
			
		||||
    @image.setter
 | 
			
		||||
    def image(self: E, url: Any):
 | 
			
		||||
    def image(self, url: Any):
 | 
			
		||||
        if url is EmptyEmbed:
 | 
			
		||||
            del self._image
 | 
			
		||||
            del self.image
 | 
			
		||||
        else:
 | 
			
		||||
            self._image = {
 | 
			
		||||
                "url": str(url),
 | 
			
		||||
            }
 | 
			
		||||
            self._image = {"url": str(url)}
 | 
			
		||||
 | 
			
		||||
    @image.deleter
 | 
			
		||||
    def image(self: E):
 | 
			
		||||
    def image(self):
 | 
			
		||||
        try:
 | 
			
		||||
            del self._image
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
@@ -429,7 +427,6 @@ class Embed:
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        self.image = url
 | 
			
		||||
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
@@ -448,13 +445,11 @@ class Embed:
 | 
			
		||||
        return EmbedProxy(getattr(self, "_thumbnail", {}))  # type: ignore
 | 
			
		||||
 | 
			
		||||
    @thumbnail.setter
 | 
			
		||||
    def thumbnail(self: E, url: Any):
 | 
			
		||||
    def thumbnail(self, url: Any):
 | 
			
		||||
        if url is EmptyEmbed:
 | 
			
		||||
            del self._thumbnail
 | 
			
		||||
            del self.thumbnail
 | 
			
		||||
        else:
 | 
			
		||||
            self._thumbnail = {
 | 
			
		||||
                "url": str(url),
 | 
			
		||||
            }
 | 
			
		||||
            self._thumbnail = {"url": str(url)}
 | 
			
		||||
 | 
			
		||||
    @thumbnail.deleter
 | 
			
		||||
    def thumbnail(self):
 | 
			
		||||
@@ -463,7 +458,7 @@ class Embed:
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    def set_thumbnail(self: E, *, url: MaybeEmpty[Any]):
 | 
			
		||||
    def set_thumbnail(self, *, url: MaybeEmpty[Any]):
 | 
			
		||||
        """Sets the thumbnail for the embed content.
 | 
			
		||||
 | 
			
		||||
        This function returns the class instance to allow for fluent-style
 | 
			
		||||
@@ -479,7 +474,6 @@ class Embed:
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        self.thumbnail = url
 | 
			
		||||
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
 
 | 
			
		||||
@@ -28,18 +28,43 @@ from __future__ import annotations
 | 
			
		||||
import asyncio
 | 
			
		||||
import collections
 | 
			
		||||
import collections.abc
 | 
			
		||||
 | 
			
		||||
import inspect
 | 
			
		||||
import importlib.util
 | 
			
		||||
import sys
 | 
			
		||||
import traceback
 | 
			
		||||
import types
 | 
			
		||||
from typing import Any, Callable, Mapping, List, Dict, TYPE_CHECKING, Optional, TypeVar, Type, Union
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from discord.http import HTTPClient
 | 
			
		||||
from typing import (
 | 
			
		||||
    Any,
 | 
			
		||||
    Callable,
 | 
			
		||||
    Iterable,
 | 
			
		||||
    Tuple,
 | 
			
		||||
    cast,
 | 
			
		||||
    Mapping,
 | 
			
		||||
    List,
 | 
			
		||||
    Dict,
 | 
			
		||||
    TYPE_CHECKING,
 | 
			
		||||
    Optional,
 | 
			
		||||
    TypeVar,
 | 
			
		||||
    Type,
 | 
			
		||||
    Union,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
import discord
 | 
			
		||||
from discord.types.interactions import (
 | 
			
		||||
    ApplicationCommandInteractionData,
 | 
			
		||||
    ApplicationCommandInteractionDataOption,
 | 
			
		||||
    EditApplicationCommand,
 | 
			
		||||
    _ApplicationCommandInteractionDataOptionString,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from .core import GroupMixin
 | 
			
		||||
from .view import StringView
 | 
			
		||||
from .converter import Greedy
 | 
			
		||||
from .view import StringView, supported_quotes
 | 
			
		||||
from .context import Context
 | 
			
		||||
from .flags import FlagConverter
 | 
			
		||||
from . import errors
 | 
			
		||||
from .help import HelpCommand, DefaultHelpCommand
 | 
			
		||||
from .cog import Cog
 | 
			
		||||
@@ -67,6 +92,23 @@ CFT = TypeVar("CFT", bound="CoroFunc")
 | 
			
		||||
CXT = TypeVar("CXT", bound="Context")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _FakeSlashMessage(discord.PartialMessage):
 | 
			
		||||
    activity = application = edited_at = reference = webhook_id = None
 | 
			
		||||
    attachments = components = reactions = stickers = mentions = []
 | 
			
		||||
    author: Union[discord.User, discord.Member]
 | 
			
		||||
    tts = False
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def from_interaction(
 | 
			
		||||
        cls, interaction: discord.Interaction, channel: Union[discord.TextChannel, discord.DMChannel, discord.Thread]
 | 
			
		||||
    ):
 | 
			
		||||
        self = cls(channel=channel, id=interaction.id)
 | 
			
		||||
        assert interaction.user is not None
 | 
			
		||||
        self.author = interaction.user
 | 
			
		||||
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def when_mentioned(bot: Union[Bot, AutoShardedBot], msg: Message) -> List[str]:
 | 
			
		||||
    """A callable that implements a command prefix equivalent to being mentioned.
 | 
			
		||||
 | 
			
		||||
@@ -118,6 +160,35 @@ def _is_submodule(parent: str, child: str) -> bool:
 | 
			
		||||
    return parent == child or child.startswith(parent + ".")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _unwrap_slash_groups(
 | 
			
		||||
    data: ApplicationCommandInteractionData,
 | 
			
		||||
) -> Tuple[str, List[ApplicationCommandInteractionDataOption]]:
 | 
			
		||||
    command_name = data["name"]
 | 
			
		||||
    command_options = data.get("options") or []
 | 
			
		||||
    while any(o["type"] in {1, 2} for o in command_options):  # type: ignore
 | 
			
		||||
        for option in command_options:  # type: ignore
 | 
			
		||||
            if option["type"] in {1, 2}:  # type: ignore
 | 
			
		||||
                command_name += f' {option["name"]}'  # type: ignore
 | 
			
		||||
                command_options = option.get("options") or []
 | 
			
		||||
 | 
			
		||||
    return command_name, command_options
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _quote_string_safe(string: str) -> str:
 | 
			
		||||
    # we need to quote this string otherwise we may spill into
 | 
			
		||||
    # other parameters and cause all kinds of trouble, as many
 | 
			
		||||
    # quotes are supported and some may be in the option, we
 | 
			
		||||
    # loop through all supported quotes and if neither open or
 | 
			
		||||
    # close are in the string, we add them
 | 
			
		||||
    for open, close in supported_quotes.items():
 | 
			
		||||
        if open not in string and close not in string:
 | 
			
		||||
            return f"{open}{string}{close}"
 | 
			
		||||
 | 
			
		||||
    # all supported quotes are in the message and we cannot add any
 | 
			
		||||
    # safely, very unlikely but still got to be covered
 | 
			
		||||
    raise errors.UnexpectedQuoteError(string)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _DefaultRepr:
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return "<default-help-command>"
 | 
			
		||||
@@ -127,9 +198,22 @@ _default = _DefaultRepr()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BotBase(GroupMixin):
 | 
			
		||||
    def __init__(self, command_prefix, help_command=_default, description=None, *, intents: discord.Intents, **options):
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        command_prefix,
 | 
			
		||||
        help_command=_default,
 | 
			
		||||
        description=None,
 | 
			
		||||
        *,
 | 
			
		||||
        intents: discord.Intents,
 | 
			
		||||
        message_commands: bool = True,
 | 
			
		||||
        slash_commands: bool = False,
 | 
			
		||||
        **options,
 | 
			
		||||
    ):
 | 
			
		||||
        super().__init__(**options, intents=intents)
 | 
			
		||||
 | 
			
		||||
        self.command_prefix = command_prefix
 | 
			
		||||
        self.slash_commands = slash_commands
 | 
			
		||||
        self.message_commands = message_commands
 | 
			
		||||
        self.extra_events: Dict[str, List[CoroFunc]] = {}
 | 
			
		||||
        self.__cogs: Dict[str, Cog] = {}
 | 
			
		||||
        self.__extensions: Dict[str, types.ModuleType] = {}
 | 
			
		||||
@@ -142,6 +226,7 @@ class BotBase(GroupMixin):
 | 
			
		||||
        self.owner_id = options.get("owner_id")
 | 
			
		||||
        self.owner_ids = options.get("owner_ids", set())
 | 
			
		||||
        self.strip_after_prefix = options.get("strip_after_prefix", False)
 | 
			
		||||
        self.slash_command_guilds: Optional[Iterable[int]] = options.get("slash_command_guilds", None)
 | 
			
		||||
 | 
			
		||||
        if self.owner_id and self.owner_ids:
 | 
			
		||||
            raise TypeError("Both owner_id and owner_ids are set.")
 | 
			
		||||
@@ -149,6 +234,9 @@ class BotBase(GroupMixin):
 | 
			
		||||
        if self.owner_ids and not isinstance(self.owner_ids, collections.abc.Collection):
 | 
			
		||||
            raise TypeError(f"owner_ids must be a collection not {self.owner_ids.__class__!r}")
 | 
			
		||||
 | 
			
		||||
        if not (message_commands or slash_commands):
 | 
			
		||||
            raise ValueError("Both message_commands and slash_commands are disabled.")
 | 
			
		||||
 | 
			
		||||
        if help_command is _default:
 | 
			
		||||
            self.help_command = DefaultHelpCommand()
 | 
			
		||||
        else:
 | 
			
		||||
@@ -163,6 +251,55 @@ class BotBase(GroupMixin):
 | 
			
		||||
        for event in self.extra_events.get(ev, []):
 | 
			
		||||
            self._schedule_event(event, ev, *args, **kwargs)  # type: ignore
 | 
			
		||||
 | 
			
		||||
    async def setup(self):
 | 
			
		||||
        await self.create_slash_commands()
 | 
			
		||||
 | 
			
		||||
    async def create_slash_commands(self):
 | 
			
		||||
        commands: defaultdict[Optional[int], List[EditApplicationCommand]] = defaultdict(list)
 | 
			
		||||
        for command in self.commands:
 | 
			
		||||
            if command.hidden or (command.slash_command is None and not self.slash_commands):
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                payload = command.to_application_command()
 | 
			
		||||
            except Exception:
 | 
			
		||||
                raise errors.ApplicationCommandRegistrationError(command)
 | 
			
		||||
 | 
			
		||||
            if payload is None:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            guilds = command.slash_command_guilds or self.slash_command_guilds
 | 
			
		||||
            if guilds is None:
 | 
			
		||||
                commands[None].append(payload)
 | 
			
		||||
            else:
 | 
			
		||||
                for guild in guilds:
 | 
			
		||||
                    commands[guild].append(payload)
 | 
			
		||||
 | 
			
		||||
        http: HTTPClient = self.http  # type: ignore
 | 
			
		||||
        global_commands = commands.pop(None, None)
 | 
			
		||||
        application_id = self.application_id or (await self.application_info()).id  # type: ignore
 | 
			
		||||
        if global_commands is not None:
 | 
			
		||||
            if self.slash_command_guilds is None:
 | 
			
		||||
                await http.bulk_upsert_global_commands(
 | 
			
		||||
                    payload=global_commands,
 | 
			
		||||
                    application_id=application_id,
 | 
			
		||||
                )
 | 
			
		||||
            else:
 | 
			
		||||
                for guild in self.slash_command_guilds:
 | 
			
		||||
                    await http.bulk_upsert_guild_commands(
 | 
			
		||||
                        guild_id=guild,
 | 
			
		||||
                        payload=global_commands,
 | 
			
		||||
                        application_id=application_id,
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
        for guild, guild_commands in commands.items():
 | 
			
		||||
            assert guild is not None
 | 
			
		||||
            await http.bulk_upsert_guild_commands(
 | 
			
		||||
                guild_id=guild,
 | 
			
		||||
                payload=guild_commands,
 | 
			
		||||
                application_id=application_id,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    @discord.utils.copy_doc(discord.Client.close)
 | 
			
		||||
    async def close(self) -> None:
 | 
			
		||||
        for extension in tuple(self.__extensions):
 | 
			
		||||
@@ -928,6 +1065,9 @@ class BotBase(GroupMixin):
 | 
			
		||||
            A list of prefixes or a single prefix that the bot is
 | 
			
		||||
            listening for.
 | 
			
		||||
        """
 | 
			
		||||
        if isinstance(message, _FakeSlashMessage):
 | 
			
		||||
            return "/"
 | 
			
		||||
 | 
			
		||||
        prefix = ret = self.command_prefix
 | 
			
		||||
        if callable(prefix):
 | 
			
		||||
            ret = await discord.utils.maybe_coroutine(prefix, self, message)
 | 
			
		||||
@@ -1084,9 +1224,94 @@ class BotBase(GroupMixin):
 | 
			
		||||
        ctx = await self.get_context(message)
 | 
			
		||||
        await self.invoke(ctx)
 | 
			
		||||
 | 
			
		||||
    async def process_slash_commands(self, interaction: discord.Interaction):
 | 
			
		||||
        """|coro|
 | 
			
		||||
 | 
			
		||||
        This function processes a slash command interaction into a usable
 | 
			
		||||
        message and calls :meth:`.process_commands` based on it. Without this
 | 
			
		||||
        coroutine slash commands will not be triggered.
 | 
			
		||||
 | 
			
		||||
        By default, this coroutine is called inside the :func:`.on_interaction`
 | 
			
		||||
        event. If you choose to override the :func:`.on_interaction` event,
 | 
			
		||||
        then you should invoke this coroutine as well.
 | 
			
		||||
 | 
			
		||||
        .. versionadded:: 2.0
 | 
			
		||||
 | 
			
		||||
        Parameters
 | 
			
		||||
        -----------
 | 
			
		||||
        interaction: :class:`discord.Interaction`
 | 
			
		||||
            The interaction to process slash commands for.
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        if interaction.type != discord.InteractionType.application_command:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        interaction.data = cast(ApplicationCommandInteractionData, interaction.data)
 | 
			
		||||
        command_name, command_options = _unwrap_slash_groups(interaction.data)
 | 
			
		||||
 | 
			
		||||
        command = self.get_command(command_name)
 | 
			
		||||
        if command is None:
 | 
			
		||||
            raise errors.CommandNotFound(f'Command "{command_name}" is not found')
 | 
			
		||||
 | 
			
		||||
        # Ensure the interaction channel is usable
 | 
			
		||||
        channel = interaction.channel
 | 
			
		||||
        if channel is None or isinstance(channel, discord.PartialMessageable):
 | 
			
		||||
            if interaction.guild is None:
 | 
			
		||||
                assert interaction.user is not None
 | 
			
		||||
                channel = await interaction.user.create_dm()
 | 
			
		||||
            elif interaction.channel_id is not None:
 | 
			
		||||
                channel = await interaction.guild.fetch_channel(interaction.channel_id)
 | 
			
		||||
            else:
 | 
			
		||||
                return  # cannot do anything without stable channel
 | 
			
		||||
 | 
			
		||||
        # Make our fake message so we can pass it to ext.commands
 | 
			
		||||
        message: discord.Message = _FakeSlashMessage.from_interaction(interaction, channel)  # type: ignore
 | 
			
		||||
        message.content = f"/{command_name} "
 | 
			
		||||
 | 
			
		||||
        # Add arguments to fake message content, in the right order
 | 
			
		||||
        ignore_params: List[inspect.Parameter] = []
 | 
			
		||||
        for name, param in command.clean_params.items():
 | 
			
		||||
            if inspect.isclass(param.annotation) and issubclass(param.annotation, FlagConverter):
 | 
			
		||||
                for name, flag in param.annotation.get_flags().items():
 | 
			
		||||
                    option = next((o for o in command_options if o["name"] == name), None)
 | 
			
		||||
 | 
			
		||||
                    if option is None:
 | 
			
		||||
                        if flag.required:
 | 
			
		||||
                            raise errors.MissingRequiredFlag(flag)
 | 
			
		||||
                    else:
 | 
			
		||||
                        prefix = param.annotation.__commands_flag_prefix__
 | 
			
		||||
                        delimiter = param.annotation.__commands_flag_delimiter__
 | 
			
		||||
                        message.content += f"{prefix}{name} {option['value']}{delimiter}"  # type: ignore
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            option = next((o for o in command_options if o["name"] == name), None)
 | 
			
		||||
            if option is None:
 | 
			
		||||
                if param.default is param.empty and not command._is_typing_optional(param.annotation):
 | 
			
		||||
                    raise errors.MissingRequiredArgument(param)
 | 
			
		||||
                else:
 | 
			
		||||
                    ignore_params.append(param)
 | 
			
		||||
            elif (
 | 
			
		||||
                option["type"] == 3
 | 
			
		||||
                and not isinstance(param.annotation, Greedy)
 | 
			
		||||
                and param.kind in {param.POSITIONAL_OR_KEYWORD, param.POSITIONAL_ONLY}
 | 
			
		||||
            ):
 | 
			
		||||
                # String with space in without "consume rest"
 | 
			
		||||
                option = cast(_ApplicationCommandInteractionDataOptionString, option)
 | 
			
		||||
                message.content += f"{_quote_string_safe(option['value'])} "
 | 
			
		||||
            else:
 | 
			
		||||
                message.content += f'{option.get("value", "")} '
 | 
			
		||||
 | 
			
		||||
        ctx = await self.get_context(message)
 | 
			
		||||
        ctx._ignored_params = ignore_params
 | 
			
		||||
        ctx.interaction = interaction
 | 
			
		||||
        await self.invoke(ctx)
 | 
			
		||||
 | 
			
		||||
    async def on_message(self, message):
 | 
			
		||||
        await self.process_commands(message)
 | 
			
		||||
 | 
			
		||||
    async def on_interaction(self, interaction: discord.Interaction):
 | 
			
		||||
        await self.process_slash_commands(interaction)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Bot(BotBase, discord.Client):
 | 
			
		||||
    """Represents a discord bot.
 | 
			
		||||
@@ -1157,6 +1382,28 @@ class Bot(BotBase, discord.Client):
 | 
			
		||||
        the ``command_prefix`` is set to ``!``. Defaults to ``False``.
 | 
			
		||||
 | 
			
		||||
        .. versionadded:: 1.7
 | 
			
		||||
    message_commands: Optional[:class:`bool`]
 | 
			
		||||
        Whether to process commands based on messages.
 | 
			
		||||
 | 
			
		||||
        Can be overwritten per command in the command decorators or when making
 | 
			
		||||
        a :class:`Command` object via the ``message_command`` parameter
 | 
			
		||||
 | 
			
		||||
        .. versionadded:: 2.0
 | 
			
		||||
    slash_commands: Optional[:class:`bool`]
 | 
			
		||||
        Whether to upload and process slash commands.
 | 
			
		||||
 | 
			
		||||
        Can be overwritten per command in the command decorators or when making
 | 
			
		||||
        a :class:`Command` object via the ``slash_command`` parameter
 | 
			
		||||
 | 
			
		||||
        .. versionadded:: 2.0
 | 
			
		||||
    slash_command_guilds: Optional[:class:`List[int]`]
 | 
			
		||||
        If this is set, only upload slash commands to these guild IDs.
 | 
			
		||||
 | 
			
		||||
        Can be overwritten per command in the command decorators or when making
 | 
			
		||||
        a :class:`Command` object via the ``slash_command_guilds`` parameter
 | 
			
		||||
 | 
			
		||||
        .. versionadded:: 2.0
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    pass
 | 
			
		||||
 
 | 
			
		||||
@@ -25,8 +25,8 @@ from __future__ import annotations
 | 
			
		||||
 | 
			
		||||
import inspect
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
from typing import Any, Dict, Generic, List, Optional, TYPE_CHECKING, TypeVar, Union
 | 
			
		||||
from datetime import timedelta
 | 
			
		||||
from typing import Any, Dict, Generic, List, Literal, Optional, TYPE_CHECKING, TypeVar, Union, overload
 | 
			
		||||
 | 
			
		||||
import discord.abc
 | 
			
		||||
import discord.utils
 | 
			
		||||
@@ -42,6 +42,8 @@ if TYPE_CHECKING:
 | 
			
		||||
    from discord.member import Member
 | 
			
		||||
    from discord.state import ConnectionState
 | 
			
		||||
    from discord.user import ClientUser, User
 | 
			
		||||
    from discord.webhook import WebhookMessage
 | 
			
		||||
    from discord.interactions import Interaction
 | 
			
		||||
    from discord.voice_client import VoiceProtocol
 | 
			
		||||
 | 
			
		||||
    from .bot import Bot, AutoShardedBot
 | 
			
		||||
@@ -120,6 +122,7 @@ class Context(discord.abc.Messageable, Generic[BotT]):
 | 
			
		||||
        A boolean that indicates if the command failed to be parsed, checked,
 | 
			
		||||
        or invoked.
 | 
			
		||||
    """
 | 
			
		||||
    interaction: Optional[Interaction] = None
 | 
			
		||||
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
@@ -151,6 +154,7 @@ class Context(discord.abc.Messageable, Generic[BotT]):
 | 
			
		||||
        self.subcommand_passed: Optional[str] = subcommand_passed
 | 
			
		||||
        self.command_failed: bool = command_failed
 | 
			
		||||
        self.current_parameter: Optional[inspect.Parameter] = current_parameter
 | 
			
		||||
        self._ignored_params: List[inspect.Parameter] = []
 | 
			
		||||
        self._state: ConnectionState = self.message._state
 | 
			
		||||
 | 
			
		||||
    async def invoke(self, command: Command[CogT, P, T], /, *args: P.args, **kwargs: P.kwargs) -> T:
 | 
			
		||||
@@ -402,6 +406,97 @@ class Context(discord.abc.Messageable, Generic[BotT]):
 | 
			
		||||
        except CommandError as e:
 | 
			
		||||
            await cmd.on_help_command_error(self, e)
 | 
			
		||||
 | 
			
		||||
    @overload
 | 
			
		||||
    async def send(
 | 
			
		||||
        self,
 | 
			
		||||
        content: Optional[str] = None,
 | 
			
		||||
        return_message: Literal[False] = False,
 | 
			
		||||
        ephemeral: bool = False,
 | 
			
		||||
        **kwargs: Any,
 | 
			
		||||
    ) -> Optional[Union[Message, WebhookMessage]]:
 | 
			
		||||
        ...
 | 
			
		||||
 | 
			
		||||
    @overload
 | 
			
		||||
    async def send(
 | 
			
		||||
        self,
 | 
			
		||||
        content: Optional[str] = None,
 | 
			
		||||
        return_message: Literal[True] = True,
 | 
			
		||||
        ephemeral: bool = False,
 | 
			
		||||
        **kwargs: Any,
 | 
			
		||||
    ) -> Union[Message, WebhookMessage]:
 | 
			
		||||
        ...
 | 
			
		||||
 | 
			
		||||
    async def send(
 | 
			
		||||
        self, content: Optional[str] = None, return_message: bool = True, ephemeral: bool = False, **kwargs: Any
 | 
			
		||||
    ) -> Optional[Union[Message, WebhookMessage]]:
 | 
			
		||||
        """
 | 
			
		||||
        |coro|
 | 
			
		||||
 | 
			
		||||
        A shortcut method to :meth:`.abc.Messageable.send` with interaction helpers.
 | 
			
		||||
 | 
			
		||||
        This function takes all the parameters of :meth:`.abc.Messageable.send` plus the following:
 | 
			
		||||
 | 
			
		||||
        Parameters
 | 
			
		||||
        ------------
 | 
			
		||||
        return_message: :class:`bool`
 | 
			
		||||
            Ignored if not in a slash command context.
 | 
			
		||||
            If this is set to False more native interaction methods will be used.
 | 
			
		||||
        ephemeral: :class:`bool`
 | 
			
		||||
            Ignored if not in a slash command context.
 | 
			
		||||
            Indicates if the message should only be visible to the user who started the interaction.
 | 
			
		||||
            If a view is sent with an ephemeral message and it has no timeout set then the timeout
 | 
			
		||||
            is set to 15 minutes.
 | 
			
		||||
 | 
			
		||||
        Returns
 | 
			
		||||
        --------
 | 
			
		||||
        Optional[Union[:class:`.Message`, :class:`.WebhookMessage`]]
 | 
			
		||||
            In a slash command context, the message that was sent if return_message is True.
 | 
			
		||||
 | 
			
		||||
            In a normal context, it always returns a :class:`.Message`
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        if self.interaction is None or (
 | 
			
		||||
            self.interaction.response.responded_at is not None
 | 
			
		||||
            and discord.utils.utcnow() - self.interaction.response.responded_at >= timedelta(minutes=15)
 | 
			
		||||
        ):
 | 
			
		||||
            return await super().send(content, **kwargs)
 | 
			
		||||
 | 
			
		||||
        # Remove unsupported arguments from kwargs
 | 
			
		||||
        kwargs.pop("nonce", None)
 | 
			
		||||
        kwargs.pop("stickers", None)
 | 
			
		||||
        kwargs.pop("reference", None)
 | 
			
		||||
        kwargs.pop("delete_after", None)
 | 
			
		||||
        kwargs.pop("mention_author", None)
 | 
			
		||||
 | 
			
		||||
        if not (
 | 
			
		||||
            return_message
 | 
			
		||||
            or self.interaction.response.is_done()
 | 
			
		||||
            or any(arg in kwargs for arg in ("file", "files", "allowed_mentions"))
 | 
			
		||||
        ):
 | 
			
		||||
            send = self.interaction.response.send_message
 | 
			
		||||
        else:
 | 
			
		||||
            # We have to defer in order to use the followup webhook
 | 
			
		||||
            if not self.interaction.response.is_done():
 | 
			
		||||
                await self.interaction.response.defer(ephemeral=ephemeral)
 | 
			
		||||
 | 
			
		||||
            send = self.interaction.followup.send
 | 
			
		||||
 | 
			
		||||
        return await send(content, ephemeral=ephemeral, **kwargs)  # type: ignore
 | 
			
		||||
 | 
			
		||||
    @overload
 | 
			
		||||
    async def reply(
 | 
			
		||||
        self, content: Optional[str] = None, return_message: Literal[False] = False, **kwargs: Any
 | 
			
		||||
    ) -> Optional[Union[Message, WebhookMessage]]:
 | 
			
		||||
        ...
 | 
			
		||||
 | 
			
		||||
    @overload
 | 
			
		||||
    async def reply(
 | 
			
		||||
        self, content: Optional[str] = None, return_message: Literal[True] = True, **kwargs: Any
 | 
			
		||||
    ) -> Union[Message, WebhookMessage]:
 | 
			
		||||
        ...
 | 
			
		||||
 | 
			
		||||
    @discord.utils.copy_doc(Message.reply)
 | 
			
		||||
    async def reply(self, content: Optional[str] = None, **kwargs: Any) -> Message:
 | 
			
		||||
        return await self.message.reply(content, **kwargs)
 | 
			
		||||
    async def reply(
 | 
			
		||||
        self, content: Optional[str] = None, return_message: bool = True, **kwargs: Any
 | 
			
		||||
    ) -> Optional[Union[Message, WebhookMessage]]:
 | 
			
		||||
        return await self.send(content, return_message=return_message, reference=self.message, **kwargs)  # type: ignore
 | 
			
		||||
 
 | 
			
		||||
@@ -77,6 +77,7 @@ __all__ = (
 | 
			
		||||
    "GuildStickerConverter",
 | 
			
		||||
    "clean_content",
 | 
			
		||||
    "Greedy",
 | 
			
		||||
    "Option",
 | 
			
		||||
    "run_converters",
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -96,6 +97,8 @@ T_co = TypeVar("T_co", covariant=True)
 | 
			
		||||
CT = TypeVar("CT", bound=discord.abc.GuildChannel)
 | 
			
		||||
TT = TypeVar("TT", bound=discord.Thread)
 | 
			
		||||
 | 
			
		||||
DT = TypeVar("DT", bound=str)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@runtime_checkable
 | 
			
		||||
class Converter(Protocol[T_co]):
 | 
			
		||||
@@ -583,7 +586,7 @@ class ThreadConverter(IDConverter[discord.Thread]):
 | 
			
		||||
    2. Lookup by mention.
 | 
			
		||||
    3. Lookup by name.
 | 
			
		||||
 | 
			
		||||
    .. versionadded: 2.0
 | 
			
		||||
    .. versionadded:: 2.0
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    async def convert(self, ctx: Context, argument: str) -> discord.Thread:
 | 
			
		||||
@@ -1005,6 +1008,50 @@ class Greedy(List[T]):
 | 
			
		||||
        return cls(converter=converter)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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:
 | 
			
		||||
        ...
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _convert_to_bool(argument: str) -> bool:
 | 
			
		||||
    lowered = argument.lower()
 | 
			
		||||
    if lowered in ("yes", "y", "true", "t", "1", "enable", "on"):
 | 
			
		||||
 
 | 
			
		||||
@@ -23,12 +23,14 @@ DEALINGS IN THE SOFTWARE.
 | 
			
		||||
"""
 | 
			
		||||
from __future__ import annotations
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from typing import (
 | 
			
		||||
    Any,
 | 
			
		||||
    Callable,
 | 
			
		||||
    Dict,
 | 
			
		||||
    Generator,
 | 
			
		||||
    Generic,
 | 
			
		||||
    Iterable,
 | 
			
		||||
    Literal,
 | 
			
		||||
    List,
 | 
			
		||||
    Optional,
 | 
			
		||||
@@ -38,27 +40,41 @@ from typing import (
 | 
			
		||||
    TypeVar,
 | 
			
		||||
    Type,
 | 
			
		||||
    TYPE_CHECKING,
 | 
			
		||||
    cast,
 | 
			
		||||
    overload,
 | 
			
		||||
)
 | 
			
		||||
import asyncio
 | 
			
		||||
import functools
 | 
			
		||||
import inspect
 | 
			
		||||
import datetime
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from operator import itemgetter
 | 
			
		||||
 | 
			
		||||
import discord
 | 
			
		||||
 | 
			
		||||
from .errors import *
 | 
			
		||||
from .cooldowns import Cooldown, BucketType, CooldownMapping, MaxConcurrency, DynamicCooldownMapping
 | 
			
		||||
from .converter import run_converters, get_converter, Greedy
 | 
			
		||||
from .converter import (
 | 
			
		||||
    CONVERTER_MAPPING,
 | 
			
		||||
    Converter,
 | 
			
		||||
    MemberConverter,
 | 
			
		||||
    RoleConverter,
 | 
			
		||||
    run_converters,
 | 
			
		||||
    get_converter,
 | 
			
		||||
    Greedy,
 | 
			
		||||
    Option,
 | 
			
		||||
)
 | 
			
		||||
from ._types import _BaseCommand
 | 
			
		||||
from .cog import Cog
 | 
			
		||||
from .context import Context
 | 
			
		||||
from .flags import FlagConverter
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if TYPE_CHECKING:
 | 
			
		||||
    from typing_extensions import Concatenate, ParamSpec, TypeGuard
 | 
			
		||||
 | 
			
		||||
    from discord.message import Message
 | 
			
		||||
    from discord.types.interactions import EditApplicationCommand, ApplicationCommandInteractionDataOption
 | 
			
		||||
 | 
			
		||||
    from ._types import (
 | 
			
		||||
        Coro,
 | 
			
		||||
@@ -107,6 +123,20 @@ GroupT = TypeVar("GroupT", bound="Group")
 | 
			
		||||
HookT = TypeVar("HookT", bound="Hook")
 | 
			
		||||
ErrorT = TypeVar("ErrorT", bound="Error")
 | 
			
		||||
 | 
			
		||||
REVERSED_CONVERTER_MAPPING = {v: k for k, v in CONVERTER_MAPPING.items()}
 | 
			
		||||
application_option_type_lookup = {
 | 
			
		||||
    str: 3,
 | 
			
		||||
    bool: 5,
 | 
			
		||||
    int: 4,
 | 
			
		||||
    (
 | 
			
		||||
        discord.Member,
 | 
			
		||||
        discord.User,
 | 
			
		||||
    ): 6,  # Preferably discord.abc.User, but 'Protocols with non-method members don't support issubclass()'
 | 
			
		||||
    (discord.abc.GuildChannel, discord.Thread): 7,
 | 
			
		||||
    discord.Role: 8,
 | 
			
		||||
    float: 10,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if TYPE_CHECKING:
 | 
			
		||||
    P = ParamSpec("P")
 | 
			
		||||
else:
 | 
			
		||||
@@ -124,13 +154,21 @@ def unwrap_function(function: Callable[..., Any]) -> Callable[..., Any]:
 | 
			
		||||
            return function
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_signature_parameters(function: Callable[..., Any], globalns: Dict[str, Any]) -> Dict[str, inspect.Parameter]:
 | 
			
		||||
def get_signature_parameters(
 | 
			
		||||
    function: Callable[..., Any], globalns: Dict[str, Any]
 | 
			
		||||
) -> Tuple[Dict[str, inspect.Parameter], Dict[str, str]]:
 | 
			
		||||
    signature = inspect.signature(function)
 | 
			
		||||
    params = {}
 | 
			
		||||
    cache: Dict[str, Any] = {}
 | 
			
		||||
    descriptions = defaultdict(lambda: "no description")
 | 
			
		||||
    eval_annotation = discord.utils.evaluate_annotation
 | 
			
		||||
    for name, parameter in signature.parameters.items():
 | 
			
		||||
        annotation = parameter.annotation
 | 
			
		||||
        if isinstance(parameter.default, Option):  # type: ignore
 | 
			
		||||
            option = parameter.default
 | 
			
		||||
            descriptions[name] = option.description
 | 
			
		||||
            parameter = parameter.replace(default=option.default)
 | 
			
		||||
 | 
			
		||||
        if annotation is parameter.empty:
 | 
			
		||||
            params[name] = parameter
 | 
			
		||||
            continue
 | 
			
		||||
@@ -144,7 +182,7 @@ def get_signature_parameters(function: Callable[..., Any], globalns: Dict[str, A
 | 
			
		||||
 | 
			
		||||
        params[name] = parameter.replace(annotation=annotation)
 | 
			
		||||
 | 
			
		||||
    return params
 | 
			
		||||
    return params, descriptions
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def wrap_callback(coro):
 | 
			
		||||
@@ -276,13 +314,36 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
 | 
			
		||||
    extras: :class:`dict`
 | 
			
		||||
        A dict of user provided extras to attach to the Command.
 | 
			
		||||
 | 
			
		||||
        .. versionadded:: 2.0
 | 
			
		||||
 | 
			
		||||
        .. note::
 | 
			
		||||
            This object may be copied by the library.
 | 
			
		||||
    message_command: Optional[:class:`bool`]
 | 
			
		||||
        Whether to process this command based on messages.
 | 
			
		||||
 | 
			
		||||
        This overwrites the global ``message_commands`` parameter of :class:`.Bot`.
 | 
			
		||||
 | 
			
		||||
        .. versionadded:: 2.0
 | 
			
		||||
    slash_command: Optional[:class:`bool`]
 | 
			
		||||
        Whether to upload and process this command as a slash command.
 | 
			
		||||
 | 
			
		||||
        This overwrites the global ``slash_commands`` parameter of :class:`.Bot`.
 | 
			
		||||
 | 
			
		||||
        .. versionadded:: 2.0
 | 
			
		||||
    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]
 | 
			
		||||
    _max_concurrency: Optional[MaxConcurrency]
 | 
			
		||||
 | 
			
		||||
    def __new__(cls: Type[CommandT], *args: Any, **kwargs: Any) -> CommandT:
 | 
			
		||||
        # if you're wondering why this is done, it's because we need to ensure
 | 
			
		||||
@@ -319,6 +380,10 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
 | 
			
		||||
        self.callback = func
 | 
			
		||||
        self.enabled: bool = kwargs.get("enabled", True)
 | 
			
		||||
 | 
			
		||||
        self.slash_command: Optional[bool] = kwargs.get("slash_command", None)
 | 
			
		||||
        self.message_command: Optional[bool] = kwargs.get("message_command", None)
 | 
			
		||||
        self.slash_command_guilds: Optional[Iterable[int]] = kwargs.get("slash_command_guilds", None)
 | 
			
		||||
 | 
			
		||||
        help_doc = kwargs.get("help")
 | 
			
		||||
        if help_doc is not None:
 | 
			
		||||
            help_doc = inspect.cleandoc(help_doc)
 | 
			
		||||
@@ -341,17 +406,20 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
 | 
			
		||||
        self.description: str = inspect.cleandoc(kwargs.get("description", ""))
 | 
			
		||||
        self.hidden: bool = kwargs.get("hidden", False)
 | 
			
		||||
 | 
			
		||||
        if hasattr(func, "__command_attrs__"):
 | 
			
		||||
            command_attrs: Dict[str, Any] = func.__command_attrs__
 | 
			
		||||
        else:
 | 
			
		||||
            command_attrs = {}
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            checks = func.__commands_checks__
 | 
			
		||||
            checks.reverse()
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            checks = kwargs.get("checks", [])
 | 
			
		||||
 | 
			
		||||
        self.checks: List[Check] = checks
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            cooldown = func.__commands_cooldown__
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            cooldown = command_attrs.pop("cooldown")
 | 
			
		||||
        except KeyError:
 | 
			
		||||
            cooldown = kwargs.get("cooldown")
 | 
			
		||||
 | 
			
		||||
        if cooldown is None:
 | 
			
		||||
@@ -360,14 +428,10 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
 | 
			
		||||
            buckets = cooldown
 | 
			
		||||
        else:
 | 
			
		||||
            raise TypeError("Cooldown must be a an instance of CooldownMapping or None.")
 | 
			
		||||
 | 
			
		||||
        self.checks: List[Check] = checks
 | 
			
		||||
        self._buckets: CooldownMapping = buckets
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            max_concurrency = func.__commands_max_concurrency__
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            max_concurrency = kwargs.get("max_concurrency")
 | 
			
		||||
 | 
			
		||||
        self._max_concurrency: Optional[MaxConcurrency] = max_concurrency
 | 
			
		||||
        self._max_concurrency = kwargs.get("max_concurrency")
 | 
			
		||||
 | 
			
		||||
        self.require_var_positional: bool = kwargs.get("require_var_positional", False)
 | 
			
		||||
        self.ignore_extra: bool = kwargs.get("ignore_extra", True)
 | 
			
		||||
@@ -377,23 +441,30 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
 | 
			
		||||
        # bandaid for the fact that sometimes parent can be the bot instance
 | 
			
		||||
        parent = kwargs.get("parent")
 | 
			
		||||
        self.parent: Optional[GroupMixin] = parent if isinstance(parent, _BaseCommand) else None  # type: ignore
 | 
			
		||||
        if self.slash_command_guilds is not None and self.parent is not None:
 | 
			
		||||
            raise ValueError(
 | 
			
		||||
                "Cannot set specific guilds for a subcommand. They are inherited from the top level group."
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        self._before_invoke: Optional[Hook] = None
 | 
			
		||||
        try:
 | 
			
		||||
            before_invoke = func.__before_invoke__
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            before_invoke = command_attrs.pop("before_invoke")
 | 
			
		||||
        except KeyError:
 | 
			
		||||
            pass
 | 
			
		||||
        else:
 | 
			
		||||
            self.before_invoke(before_invoke)
 | 
			
		||||
 | 
			
		||||
        self._after_invoke: Optional[Hook] = None
 | 
			
		||||
        try:
 | 
			
		||||
            after_invoke = func.__after_invoke__
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            after_invoke = command_attrs.pop("after_invoke")
 | 
			
		||||
        except KeyError:
 | 
			
		||||
            pass
 | 
			
		||||
        else:
 | 
			
		||||
            self.after_invoke(after_invoke)
 | 
			
		||||
 | 
			
		||||
        # Handle user provided command attrs
 | 
			
		||||
        self._update_attrs(**command_attrs)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def callback(
 | 
			
		||||
        self,
 | 
			
		||||
@@ -417,7 +488,11 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            globalns = {}
 | 
			
		||||
 | 
			
		||||
        self.params = get_signature_parameters(function, globalns)
 | 
			
		||||
        self.params, self.option_descriptions = get_signature_parameters(function, globalns)
 | 
			
		||||
 | 
			
		||||
    def _update_attrs(self, **command_attrs: Any):
 | 
			
		||||
        for key, value in command_attrs.items():
 | 
			
		||||
            setattr(self, key, value)
 | 
			
		||||
 | 
			
		||||
    def add_check(self, func: Check) -> None:
 | 
			
		||||
        """Adds a check to the command.
 | 
			
		||||
@@ -541,6 +616,10 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
 | 
			
		||||
            ctx.bot.dispatch("command_error", ctx, error)
 | 
			
		||||
 | 
			
		||||
    async def transform(self, ctx: Context, param: inspect.Parameter) -> Any:
 | 
			
		||||
        if param in ctx._ignored_params:
 | 
			
		||||
            # in a slash command, we need a way to mark a param as default so ctx._ignored_params is used
 | 
			
		||||
            return param.default if param.default is not param.empty else None
 | 
			
		||||
 | 
			
		||||
        required = param.default is param.empty
 | 
			
		||||
        converter = get_converter(param)
 | 
			
		||||
        consume_rest_is_special = param.kind == param.KEYWORD_ONLY and not self.rest_is_raw
 | 
			
		||||
@@ -1109,10 +1188,19 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
 | 
			
		||||
        :class:`bool`
 | 
			
		||||
            A boolean indicating if the command can be invoked.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        if not self.enabled:
 | 
			
		||||
            raise DisabledCommand(f"{self.name} command is disabled")
 | 
			
		||||
 | 
			
		||||
        if ctx.interaction is None and (
 | 
			
		||||
            self.message_command is False or (self.message_command is None and not ctx.bot.message_commands)
 | 
			
		||||
        ):
 | 
			
		||||
            raise DisabledCommand(f"{self.name} command cannot be run as a message command")
 | 
			
		||||
 | 
			
		||||
        if ctx.interaction is not None and (
 | 
			
		||||
            self.slash_command is False or (self.slash_command is None and not ctx.bot.slash_commands)
 | 
			
		||||
        ):
 | 
			
		||||
            raise DisabledCommand(f"{self.name} command cannot be run as a slash command")
 | 
			
		||||
 | 
			
		||||
        original = ctx.command
 | 
			
		||||
        ctx.command = self
 | 
			
		||||
 | 
			
		||||
@@ -1137,6 +1225,92 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
 | 
			
		||||
        finally:
 | 
			
		||||
            ctx.command = original
 | 
			
		||||
 | 
			
		||||
    def _param_to_options(
 | 
			
		||||
        self, name: str, annotation: Any, required: bool, varadic: bool
 | 
			
		||||
    ) -> List[Optional[ApplicationCommandInteractionDataOption]]:
 | 
			
		||||
        origin = getattr(annotation, "__origin__", None)
 | 
			
		||||
        if inspect.isclass(annotation) and issubclass(annotation, FlagConverter):
 | 
			
		||||
            return [
 | 
			
		||||
                param
 | 
			
		||||
                for name, flag in annotation.get_flags().items()
 | 
			
		||||
                for param in self._param_to_options(
 | 
			
		||||
                    name, flag.annotation, required=flag.required, varadic=flag.annotation is tuple
 | 
			
		||||
                )
 | 
			
		||||
            ]
 | 
			
		||||
 | 
			
		||||
        if varadic:
 | 
			
		||||
            annotation = str
 | 
			
		||||
            origin = None
 | 
			
		||||
 | 
			
		||||
        if not required and origin is not None and len(annotation.__args__) == 2:
 | 
			
		||||
            # Unpack Optional[T] (Union[T, None]) into just T
 | 
			
		||||
            annotation, origin = annotation.__args__[0], None
 | 
			
		||||
 | 
			
		||||
        option: Dict[str, Any] = {
 | 
			
		||||
            "type": 3,
 | 
			
		||||
            "name": name,
 | 
			
		||||
            "required": required,
 | 
			
		||||
            "description": self.option_descriptions[name],
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if origin is None:
 | 
			
		||||
            if not inspect.isclass(annotation):
 | 
			
		||||
                annotation = type(annotation)
 | 
			
		||||
 | 
			
		||||
            if issubclass(annotation, Converter):
 | 
			
		||||
                # If this is a converter, we want to check if it is a native
 | 
			
		||||
                # one, in which we can get the original type, eg, (MemberConverter -> Member)
 | 
			
		||||
                annotation = REVERSED_CONVERTER_MAPPING.get(annotation, annotation)
 | 
			
		||||
 | 
			
		||||
            for python_type, discord_type in application_option_type_lookup.items():
 | 
			
		||||
                if issubclass(annotation, python_type):
 | 
			
		||||
                    option["type"] = discord_type
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
        elif origin is Union:
 | 
			
		||||
            if annotation in {Union[discord.Member, discord.Role], Union[MemberConverter, RoleConverter]}:
 | 
			
		||||
                option["type"] = 9
 | 
			
		||||
 | 
			
		||||
        elif origin is Literal:
 | 
			
		||||
            literal_values = annotation.__args__
 | 
			
		||||
            python_type = type(literal_values[0])
 | 
			
		||||
            if (
 | 
			
		||||
                all(type(value) == python_type for value in literal_values)
 | 
			
		||||
                and python_type in application_option_type_lookup.keys()
 | 
			
		||||
            ):
 | 
			
		||||
 | 
			
		||||
                option["type"] = application_option_type_lookup[python_type]
 | 
			
		||||
                option["choices"] = [
 | 
			
		||||
                    {"name": literal_value, "value": literal_value} for literal_value in annotation.__args__
 | 
			
		||||
                ]
 | 
			
		||||
 | 
			
		||||
        return [option]  # type: ignore
 | 
			
		||||
 | 
			
		||||
    def to_application_command(self, nested: int = 0) -> Optional[EditApplicationCommand]:
 | 
			
		||||
        if self.slash_command is False:
 | 
			
		||||
            return
 | 
			
		||||
        elif nested == 3:
 | 
			
		||||
            raise ApplicationCommandRegistrationError(self, f"{self.qualified_name} is too deeply nested!")
 | 
			
		||||
 | 
			
		||||
        payload = {"name": self.name, "description": self.short_doc or "no description", "options": []}
 | 
			
		||||
        if nested != 0:
 | 
			
		||||
            payload["type"] = 1
 | 
			
		||||
 | 
			
		||||
        for name, param in self.clean_params.items():
 | 
			
		||||
            options = self._param_to_options(
 | 
			
		||||
                name,
 | 
			
		||||
                param.annotation if param.annotation is not param.empty else str,
 | 
			
		||||
                varadic=param.kind == param.KEYWORD_ONLY or isinstance(param.annotation, Greedy),
 | 
			
		||||
                required=(param.default is param.empty and not self._is_typing_optional(param.annotation))
 | 
			
		||||
                or param.kind == param.VAR_POSITIONAL,
 | 
			
		||||
            )
 | 
			
		||||
            if options is not None:
 | 
			
		||||
                payload["options"].extend(option for option in options if option is not None)
 | 
			
		||||
 | 
			
		||||
        # Now we have all options, make sure required is before optional.
 | 
			
		||||
        payload["options"] = sorted(payload["options"], key=itemgetter("required"), reverse=True)
 | 
			
		||||
        return payload  # type: ignore
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GroupMixin(Generic[CogT]):
 | 
			
		||||
    """A mixin that implements common functionality for classes that behave
 | 
			
		||||
@@ -1510,6 +1684,19 @@ class Group(GroupMixin[CogT], Command[CogT, P, T]):
 | 
			
		||||
            view.previous = previous
 | 
			
		||||
            await super().reinvoke(ctx, call_hooks=call_hooks)
 | 
			
		||||
 | 
			
		||||
    def to_application_command(self, nested: int = 0) -> Optional[EditApplicationCommand]:
 | 
			
		||||
        if self.slash_command is False:
 | 
			
		||||
            return
 | 
			
		||||
        elif nested == 2:
 | 
			
		||||
            raise ApplicationCommandRegistrationError(self, f"{self.qualified_name} is too deeply nested!")
 | 
			
		||||
 | 
			
		||||
        return {  # type: ignore
 | 
			
		||||
            "name": self.name,
 | 
			
		||||
            "type": int(not (nested - 1)) + 1,
 | 
			
		||||
            "description": self.short_doc or "no description",
 | 
			
		||||
            "options": [cmd.to_application_command(nested=nested + 1) for cmd in sorted(self.commands, key=lambda x: x.name)],
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Decorators
 | 
			
		||||
 | 
			
		||||
@@ -1664,7 +1851,7 @@ def group(
 | 
			
		||||
    return command(name=name, cls=cls, **attrs)  # type: ignore
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def check(predicate: Check) -> Callable[[T], T]:
 | 
			
		||||
def check(predicate: Check, **command_attrs: Any) -> Callable[[T], T]:
 | 
			
		||||
    r"""A decorator that adds a check to the :class:`.Command` or its
 | 
			
		||||
    subclasses. These checks could be accessed via :attr:`.Command.checks`.
 | 
			
		||||
 | 
			
		||||
@@ -1733,16 +1920,22 @@ def check(predicate: Check) -> Callable[[T], T]:
 | 
			
		||||
    -----------
 | 
			
		||||
    predicate: Callable[[:class:`Context`], :class:`bool`]
 | 
			
		||||
        The predicate to check if the command should be invoked.
 | 
			
		||||
    **command_attrs: Dict[:class:`str`, Any]
 | 
			
		||||
        key: value pairs to be added to the command's attributes.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def decorator(func: Union[Command, CoroFunc]) -> Union[Command, CoroFunc]:
 | 
			
		||||
        if isinstance(func, Command):
 | 
			
		||||
            func.checks.append(predicate)
 | 
			
		||||
            func._update_attrs(**command_attrs)
 | 
			
		||||
        else:
 | 
			
		||||
            if not hasattr(func, "__commands_checks__"):
 | 
			
		||||
                func.__commands_checks__ = []
 | 
			
		||||
            if not hasattr(func, "__command_attrs__"):
 | 
			
		||||
                func.__command_attrs__ = {}
 | 
			
		||||
 | 
			
		||||
            func.__commands_checks__.append(predicate)
 | 
			
		||||
            func.__command_attrs__.update(command_attrs)
 | 
			
		||||
 | 
			
		||||
        return func
 | 
			
		||||
 | 
			
		||||
@@ -1915,7 +2108,7 @@ def has_any_role(*items: Union[int, str]) -> Callable[[T], T]:
 | 
			
		||||
            return True
 | 
			
		||||
        raise MissingAnyRole(list(items))
 | 
			
		||||
 | 
			
		||||
    return check(predicate)
 | 
			
		||||
    return check(predicate, required_roles=items)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def bot_has_role(item: int) -> Callable[[T], T]:
 | 
			
		||||
@@ -1945,7 +2138,7 @@ def bot_has_role(item: int) -> Callable[[T], T]:
 | 
			
		||||
            raise BotMissingRole(item)
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    return check(predicate)
 | 
			
		||||
    return check(predicate, bot_required_role=item)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def bot_has_any_role(*items: int) -> Callable[[T], T]:
 | 
			
		||||
@@ -1974,7 +2167,7 @@ def bot_has_any_role(*items: int) -> Callable[[T], T]:
 | 
			
		||||
            return True
 | 
			
		||||
        raise BotMissingAnyRole(list(items))
 | 
			
		||||
 | 
			
		||||
    return check(predicate)
 | 
			
		||||
    return check(predicate, bot_required_roles=items)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def has_permissions(**perms: bool) -> Callable[[T], T]:
 | 
			
		||||
@@ -2022,7 +2215,7 @@ def has_permissions(**perms: bool) -> Callable[[T], T]:
 | 
			
		||||
 | 
			
		||||
        raise MissingPermissions(missing)
 | 
			
		||||
 | 
			
		||||
    return check(predicate)
 | 
			
		||||
    return check(predicate, required_permissions=perms)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def bot_has_permissions(**perms: bool) -> Callable[[T], T]:
 | 
			
		||||
@@ -2049,7 +2242,7 @@ def bot_has_permissions(**perms: bool) -> Callable[[T], T]:
 | 
			
		||||
 | 
			
		||||
        raise BotMissingPermissions(missing)
 | 
			
		||||
 | 
			
		||||
    return check(predicate)
 | 
			
		||||
    return check(predicate, bot_required_permissions=perms)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def has_guild_permissions(**perms: bool) -> Callable[[T], T]:
 | 
			
		||||
@@ -2078,7 +2271,7 @@ def has_guild_permissions(**perms: bool) -> Callable[[T], T]:
 | 
			
		||||
 | 
			
		||||
        raise MissingPermissions(missing)
 | 
			
		||||
 | 
			
		||||
    return check(predicate)
 | 
			
		||||
    return check(predicate, required_guild_permissions=perms)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def bot_has_guild_permissions(**perms: bool) -> Callable[[T], T]:
 | 
			
		||||
@@ -2104,7 +2297,7 @@ def bot_has_guild_permissions(**perms: bool) -> Callable[[T], T]:
 | 
			
		||||
 | 
			
		||||
        raise BotMissingPermissions(missing)
 | 
			
		||||
 | 
			
		||||
    return check(predicate)
 | 
			
		||||
    return check(predicate, bot_required_guild_permissions=perms)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def dm_only() -> Callable[[T], T]:
 | 
			
		||||
@@ -2215,7 +2408,10 @@ def cooldown(
 | 
			
		||||
        if isinstance(func, Command):
 | 
			
		||||
            func._buckets = CooldownMapping(Cooldown(rate, per), type)
 | 
			
		||||
        else:
 | 
			
		||||
            func.__commands_cooldown__ = CooldownMapping(Cooldown(rate, per), type)
 | 
			
		||||
            if not hasattr(func, "__command_attrs__"):
 | 
			
		||||
                func.__command_attrs__ = {}
 | 
			
		||||
 | 
			
		||||
            func.__command_attrs__["cooldown"] = CooldownMapping(Cooldown(rate, per), type)
 | 
			
		||||
        return func
 | 
			
		||||
 | 
			
		||||
    return decorator  # type: ignore
 | 
			
		||||
@@ -2259,7 +2455,10 @@ def dynamic_cooldown(
 | 
			
		||||
        if isinstance(func, Command):
 | 
			
		||||
            func._buckets = DynamicCooldownMapping(cooldown, type)
 | 
			
		||||
        else:
 | 
			
		||||
            func.__commands_cooldown__ = DynamicCooldownMapping(cooldown, type)
 | 
			
		||||
            if not hasattr(func, "__command_attrs__"):
 | 
			
		||||
                func.__command_attrs__ = {}
 | 
			
		||||
 | 
			
		||||
            func.__command_attrs__["cooldown"] = DynamicCooldownMapping(cooldown, type)
 | 
			
		||||
        return func
 | 
			
		||||
 | 
			
		||||
    return decorator  # type: ignore
 | 
			
		||||
@@ -2294,7 +2493,10 @@ def max_concurrency(number: int, per: BucketType = BucketType.default, *, wait:
 | 
			
		||||
        if isinstance(func, Command):
 | 
			
		||||
            func._max_concurrency = value
 | 
			
		||||
        else:
 | 
			
		||||
            func.__commands_max_concurrency__ = value
 | 
			
		||||
            if not hasattr(func, "__command_attrs__"):
 | 
			
		||||
                func.__command_attrs__ = {}
 | 
			
		||||
 | 
			
		||||
            func.__command_attrs__["_max_concurrency"] = value
 | 
			
		||||
        return func
 | 
			
		||||
 | 
			
		||||
    return decorator  # type: ignore
 | 
			
		||||
@@ -2343,7 +2545,10 @@ def before_invoke(coro) -> Callable[[T], T]:
 | 
			
		||||
        if isinstance(func, Command):
 | 
			
		||||
            func.before_invoke(coro)
 | 
			
		||||
        else:
 | 
			
		||||
            func.__before_invoke__ = coro
 | 
			
		||||
            if not hasattr(func, "__command_attrs__"):
 | 
			
		||||
                func.__command_attrs__ = {}
 | 
			
		||||
 | 
			
		||||
            func.__command_attrs__["before_invoke"] = coro
 | 
			
		||||
        return func
 | 
			
		||||
 | 
			
		||||
    return decorator  # type: ignore
 | 
			
		||||
@@ -2362,7 +2567,7 @@ def after_invoke(coro) -> Callable[[T], T]:
 | 
			
		||||
        if isinstance(func, Command):
 | 
			
		||||
            func.after_invoke(coro)
 | 
			
		||||
        else:
 | 
			
		||||
            func.__after_invoke__ = coro
 | 
			
		||||
            func.__command_attrs__["after_invoke"] = coro
 | 
			
		||||
        return func
 | 
			
		||||
 | 
			
		||||
    return decorator  # type: ignore
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,7 @@ if TYPE_CHECKING:
 | 
			
		||||
 | 
			
		||||
    from .converter import Converter
 | 
			
		||||
    from .context import Context
 | 
			
		||||
    from .core import Command
 | 
			
		||||
    from .cooldowns import Cooldown, BucketType
 | 
			
		||||
    from .flags import Flag
 | 
			
		||||
    from discord.abc import GuildChannel
 | 
			
		||||
@@ -93,6 +94,7 @@ __all__ = (
 | 
			
		||||
    "ExtensionFailed",
 | 
			
		||||
    "ExtensionNotFound",
 | 
			
		||||
    "CommandRegistrationError",
 | 
			
		||||
    "ApplicationCommandRegistrationError",
 | 
			
		||||
    "FlagError",
 | 
			
		||||
    "BadFlagArgument",
 | 
			
		||||
    "MissingFlagArgument",
 | 
			
		||||
@@ -453,6 +455,11 @@ class BadInviteArgument(BadArgument):
 | 
			
		||||
    This inherits from :exc:`BadArgument`
 | 
			
		||||
 | 
			
		||||
    .. versionadded:: 1.5
 | 
			
		||||
    
 | 
			
		||||
    Attributes
 | 
			
		||||
    -----------
 | 
			
		||||
    argument: :class:`str`
 | 
			
		||||
        The invite supplied by the caller that was not found
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, argument: str) -> None:
 | 
			
		||||
@@ -1014,6 +1021,25 @@ class CommandRegistrationError(ClientException):
 | 
			
		||||
        super().__init__(f"The {type_} {name} is already an existing command or alias.")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ApplicationCommandRegistrationError(ClientException):
 | 
			
		||||
    """An exception raised when a command cannot be converted to an
 | 
			
		||||
    application command.
 | 
			
		||||
 | 
			
		||||
    This inherits from :exc:`discord.ClientException`
 | 
			
		||||
 | 
			
		||||
    .. versionadded:: 2.0
 | 
			
		||||
 | 
			
		||||
    Attributes
 | 
			
		||||
    ----------
 | 
			
		||||
    command: :class:`Command`
 | 
			
		||||
        The command that failed to be converted.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, command: Command, msg: str = None) -> None:
 | 
			
		||||
        self.command = command
 | 
			
		||||
        super().__init__(msg or f"{command.qualified_name} failed to converted to an application command.")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FlagError(BadArgument):
 | 
			
		||||
    """The base exception type for all flag parsing related errors.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -615,7 +615,7 @@ class HelpCommand:
 | 
			
		||||
        :class:`.abc.Messageable`
 | 
			
		||||
            The destination where the help command will be output.
 | 
			
		||||
        """
 | 
			
		||||
        return self.context.channel
 | 
			
		||||
        return self.context
 | 
			
		||||
 | 
			
		||||
    async def send_error_message(self, error):
 | 
			
		||||
        """|coro|
 | 
			
		||||
@@ -977,6 +977,10 @@ class DefaultHelpCommand(HelpCommand):
 | 
			
		||||
        for page in self.paginator.pages:
 | 
			
		||||
            await destination.send(page)
 | 
			
		||||
 | 
			
		||||
        interaction = self.context.interaction
 | 
			
		||||
        if interaction is not None and destination == self.context.author and not interaction.response.is_done():
 | 
			
		||||
            await interaction.response.send_message("Sent help to your DMs!", ephemeral=True)
 | 
			
		||||
 | 
			
		||||
    def add_command_formatting(self, command):
 | 
			
		||||
        """A utility function to format the non-indented block of commands and groups.
 | 
			
		||||
 | 
			
		||||
@@ -1007,7 +1011,7 @@ class DefaultHelpCommand(HelpCommand):
 | 
			
		||||
        elif self.dm_help is None and len(self.paginator) > self.dm_help_threshold:
 | 
			
		||||
            return ctx.author
 | 
			
		||||
        else:
 | 
			
		||||
            return ctx.channel
 | 
			
		||||
            return ctx
 | 
			
		||||
 | 
			
		||||
    async def prepare_help_command(self, ctx, command):
 | 
			
		||||
        self.paginator.clear()
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ DEALINGS IN THE SOFTWARE.
 | 
			
		||||
from .errors import UnexpectedQuoteError, InvalidEndOfQuotedStringError, ExpectedClosingQuoteError
 | 
			
		||||
 | 
			
		||||
# map from opening quotes to closing quotes
 | 
			
		||||
_quotes = {
 | 
			
		||||
supported_quotes = {
 | 
			
		||||
    '"': '"',
 | 
			
		||||
    "‘": "’",
 | 
			
		||||
    "‚": "‛",
 | 
			
		||||
@@ -44,7 +44,7 @@ _quotes = {
 | 
			
		||||
    "《": "》",
 | 
			
		||||
    "〈": "〉",
 | 
			
		||||
}
 | 
			
		||||
_all_quotes = set(_quotes.keys()) | set(_quotes.values())
 | 
			
		||||
_all_quotes = set(supported_quotes.keys()) | set(supported_quotes.values())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StringView:
 | 
			
		||||
@@ -130,7 +130,7 @@ class StringView:
 | 
			
		||||
        if current is None:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        close_quote = _quotes.get(current)
 | 
			
		||||
        close_quote = supported_quotes.get(current)
 | 
			
		||||
        is_quoted = bool(close_quote)
 | 
			
		||||
        if is_quoted:
 | 
			
		||||
            result = []
 | 
			
		||||
 
 | 
			
		||||
@@ -47,6 +47,8 @@ __all__ = (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if TYPE_CHECKING:
 | 
			
		||||
    from datetime import datetime
 | 
			
		||||
 | 
			
		||||
    from .types.interactions import (
 | 
			
		||||
        Interaction as InteractionPayload,
 | 
			
		||||
        InteractionData,
 | 
			
		||||
@@ -58,12 +60,10 @@ if TYPE_CHECKING:
 | 
			
		||||
    from aiohttp import ClientSession
 | 
			
		||||
    from .embeds import Embed
 | 
			
		||||
    from .ui.view import View
 | 
			
		||||
    from .channel import VoiceChannel, StageChannel, TextChannel, CategoryChannel, StoreChannel, PartialMessageable
 | 
			
		||||
    from .channel import TextChannel, CategoryChannel, StoreChannel, PartialMessageable
 | 
			
		||||
    from .threads import Thread
 | 
			
		||||
 | 
			
		||||
    InteractionChannel = Union[
 | 
			
		||||
        VoiceChannel, StageChannel, TextChannel, CategoryChannel, StoreChannel, Thread, PartialMessageable
 | 
			
		||||
    ]
 | 
			
		||||
    InteractionChannel = Union[TextChannel, CategoryChannel, StoreChannel, Thread, PartialMessageable]
 | 
			
		||||
 | 
			
		||||
MISSING: Any = utils.MISSING
 | 
			
		||||
 | 
			
		||||
@@ -179,7 +179,7 @@ class Interaction:
 | 
			
		||||
                type = ChannelType.text if self.guild_id is not None else ChannelType.private
 | 
			
		||||
                return PartialMessageable(state=self._state, id=self.channel_id, type=type)
 | 
			
		||||
            return None
 | 
			
		||||
        return channel
 | 
			
		||||
        return channel  # type: ignore
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def permissions(self) -> Permissions:
 | 
			
		||||
@@ -369,20 +369,20 @@ class InteractionResponse:
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    __slots__: Tuple[str, ...] = (
 | 
			
		||||
        "_responded",
 | 
			
		||||
        "responded_at",
 | 
			
		||||
        "_parent",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def __init__(self, parent: Interaction):
 | 
			
		||||
        self.responded_at: Optional[datetime] = None
 | 
			
		||||
        self._parent: Interaction = parent
 | 
			
		||||
        self._responded: bool = False
 | 
			
		||||
 | 
			
		||||
    def is_done(self) -> bool:
 | 
			
		||||
        """:class:`bool`: Indicates whether an interaction response has been done before.
 | 
			
		||||
 | 
			
		||||
        An interaction can only be responded to once.
 | 
			
		||||
        """
 | 
			
		||||
        return self._responded
 | 
			
		||||
        return self.responded_at is not None
 | 
			
		||||
 | 
			
		||||
    async def defer(self, *, ephemeral: bool = False) -> None:
 | 
			
		||||
        """|coro|
 | 
			
		||||
@@ -405,7 +405,7 @@ class InteractionResponse:
 | 
			
		||||
        InteractionResponded
 | 
			
		||||
            This interaction has already been responded to before.
 | 
			
		||||
        """
 | 
			
		||||
        if self._responded:
 | 
			
		||||
        if self.is_done():
 | 
			
		||||
            raise InteractionResponded(self._parent)
 | 
			
		||||
 | 
			
		||||
        defer_type: int = 0
 | 
			
		||||
@@ -423,7 +423,8 @@ class InteractionResponse:
 | 
			
		||||
            await adapter.create_interaction_response(
 | 
			
		||||
                parent.id, parent.token, session=parent._session, type=defer_type, data=data
 | 
			
		||||
            )
 | 
			
		||||
            self._responded = True
 | 
			
		||||
 | 
			
		||||
            self.responded_at = utils.utcnow()
 | 
			
		||||
 | 
			
		||||
    async def pong(self) -> None:
 | 
			
		||||
        """|coro|
 | 
			
		||||
@@ -439,7 +440,7 @@ class InteractionResponse:
 | 
			
		||||
        InteractionResponded
 | 
			
		||||
            This interaction has already been responded to before.
 | 
			
		||||
        """
 | 
			
		||||
        if self._responded:
 | 
			
		||||
        if self.is_done():
 | 
			
		||||
            raise InteractionResponded(self._parent)
 | 
			
		||||
 | 
			
		||||
        parent = self._parent
 | 
			
		||||
@@ -448,7 +449,7 @@ class InteractionResponse:
 | 
			
		||||
            await adapter.create_interaction_response(
 | 
			
		||||
                parent.id, parent.token, session=parent._session, type=InteractionResponseType.pong.value
 | 
			
		||||
            )
 | 
			
		||||
            self._responded = True
 | 
			
		||||
            self.responded_at = utils.utcnow()
 | 
			
		||||
 | 
			
		||||
    async def send_message(
 | 
			
		||||
        self,
 | 
			
		||||
@@ -494,7 +495,7 @@ class InteractionResponse:
 | 
			
		||||
        InteractionResponded
 | 
			
		||||
            This interaction has already been responded to before.
 | 
			
		||||
        """
 | 
			
		||||
        if self._responded:
 | 
			
		||||
        if self.is_done():
 | 
			
		||||
            raise InteractionResponded(self._parent)
 | 
			
		||||
 | 
			
		||||
        payload: Dict[str, Any] = {
 | 
			
		||||
@@ -537,7 +538,7 @@ class InteractionResponse:
 | 
			
		||||
 | 
			
		||||
            self._parent._state.store_view(view)
 | 
			
		||||
 | 
			
		||||
        self._responded = True
 | 
			
		||||
        self.responded_at = utils.utcnow()
 | 
			
		||||
 | 
			
		||||
    async def edit_message(
 | 
			
		||||
        self,
 | 
			
		||||
@@ -578,7 +579,7 @@ class InteractionResponse:
 | 
			
		||||
        InteractionResponded
 | 
			
		||||
            This interaction has already been responded to before.
 | 
			
		||||
        """
 | 
			
		||||
        if self._responded:
 | 
			
		||||
        if self.is_done():
 | 
			
		||||
            raise InteractionResponded(self._parent)
 | 
			
		||||
 | 
			
		||||
        parent = self._parent
 | 
			
		||||
@@ -629,7 +630,7 @@ class InteractionResponse:
 | 
			
		||||
        if view and not view.is_finished():
 | 
			
		||||
            state.store_view(view, message_id)
 | 
			
		||||
 | 
			
		||||
        self._responded = True
 | 
			
		||||
        self.responded_at = utils.utcnow()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _InteractionMessageState:
 | 
			
		||||
 
 | 
			
		||||
@@ -169,9 +169,14 @@ class Attachment(Hashable):
 | 
			
		||||
        The attachment's `media type <https://en.wikipedia.org/wiki/Media_type>`_
 | 
			
		||||
 | 
			
		||||
        .. versionadded:: 1.7
 | 
			
		||||
    ephemeral: Optional[:class:`bool`]
 | 
			
		||||
        If the attachment is ephemeral. Ephemeral attachments are temporary and
 | 
			
		||||
        will automatically be removed after a set period of time.
 | 
			
		||||
 | 
			
		||||
        .. versionadded:: 2.0
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    __slots__ = ("id", "size", "height", "width", "filename", "url", "proxy_url", "_http", "content_type")
 | 
			
		||||
    __slots__ = ("id", "size", "height", "width", "filename", "url", "proxy_url", "ephemeral", "_http", "content_type")
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *, data: AttachmentPayload, state: ConnectionState):
 | 
			
		||||
        self.id: int = int(data["id"])
 | 
			
		||||
@@ -183,6 +188,7 @@ class Attachment(Hashable):
 | 
			
		||||
        self.proxy_url: str = data.get("proxy_url")
 | 
			
		||||
        self._http = state.http
 | 
			
		||||
        self.content_type: Optional[str] = data.get("content_type")
 | 
			
		||||
        self.ephemeral: Optional[bool] = data.get("ephemeral")
 | 
			
		||||
 | 
			
		||||
    def is_spoiler(self) -> bool:
 | 
			
		||||
        """:class:`bool`: Whether this attachment contains a spoiler."""
 | 
			
		||||
 
 | 
			
		||||
@@ -227,8 +227,8 @@ class _EditApplicationCommandOptional(TypedDict, total=False):
 | 
			
		||||
    description: str
 | 
			
		||||
    options: Optional[List[ApplicationCommandOption]]
 | 
			
		||||
    type: ApplicationCommandType
 | 
			
		||||
    default_permission: bool
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EditApplicationCommand(_EditApplicationCommandOptional):
 | 
			
		||||
    name: str
 | 
			
		||||
    default_permission: bool
 | 
			
		||||
 
 | 
			
		||||
@@ -53,6 +53,7 @@ class _AttachmentOptional(TypedDict, total=False):
 | 
			
		||||
    height: Optional[int]
 | 
			
		||||
    width: Optional[int]
 | 
			
		||||
    content_type: str
 | 
			
		||||
    ephemeral: bool
 | 
			
		||||
    spoiler: bool
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -353,7 +353,7 @@ class View:
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            await item.callback(interaction)
 | 
			
		||||
            if not interaction.response._responded:
 | 
			
		||||
            if not interaction.response.is_done():
 | 
			
		||||
                await interaction.response.defer()
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            return await self.on_error(e, item, interaction)
 | 
			
		||||
 
 | 
			
		||||
@@ -80,7 +80,7 @@ class BaseUser(_UserTag):
 | 
			
		||||
        _state: ConnectionState
 | 
			
		||||
        _avatar: Optional[str]
 | 
			
		||||
        _banner: Optional[str]
 | 
			
		||||
        _accent_colour: Optional[str]
 | 
			
		||||
        _accent_colour: Optional[int]
 | 
			
		||||
        _public_flags: int
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *, state: ConnectionState, data: UserPayload) -> None:
 | 
			
		||||
 
 | 
			
		||||
@@ -159,7 +159,7 @@ html_experimental_html5_writer = True
 | 
			
		||||
html_theme = "basic"
 | 
			
		||||
 | 
			
		||||
html_context = {
 | 
			
		||||
    "discord_invite": "https://discord.gg/r3sSKJJ",
 | 
			
		||||
    "discord_invite": "https://discord.gg/TvqYBrGXEm",
 | 
			
		||||
    "discord_extensions": [
 | 
			
		||||
        ("discord.ext.commands", "ext/commands"),
 | 
			
		||||
        ("discord.ext.tasks", "ext/tasks"),
 | 
			
		||||
@@ -167,7 +167,7 @@ html_context = {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
resource_links = {
 | 
			
		||||
    "discord": "https://discord.gg/r3sSKJJ",
 | 
			
		||||
    "discord": "https://discord.gg/TvqYBrGXEm",
 | 
			
		||||
    "issues": "https://github.com/Rapptz/discord.py/issues",
 | 
			
		||||
    "discussions": "https://github.com/Rapptz/discord.py/discussions",
 | 
			
		||||
    "examples": f"https://github.com/Rapptz/discord.py/tree/{branch}/examples",
 | 
			
		||||
@@ -352,5 +352,5 @@ texinfo_documents = [
 | 
			
		||||
def setup(app):
 | 
			
		||||
    if app.config.language == "ja":
 | 
			
		||||
        app.config.intersphinx_mapping["py"] = ("https://docs.python.org/ja/3", None)
 | 
			
		||||
        app.config.html_context["discord_invite"] = "https://discord.gg/nXzj3dg"
 | 
			
		||||
        app.config.resource_links["discord"] = "https://discord.gg/nXzj3dg"
 | 
			
		||||
        app.config.html_context["discord_invite"] = "https://discord.gg/TvqYBrGXEm"
 | 
			
		||||
        app.config.resource_links["discord"] = "https://discord.gg/TvqYBrGXEm"
 | 
			
		||||
 
 | 
			
		||||
@@ -429,6 +429,12 @@ Converters
 | 
			
		||||
 | 
			
		||||
.. autofunction:: discord.ext.commands.run_converters
 | 
			
		||||
 | 
			
		||||
Option
 | 
			
		||||
~~~~~~
 | 
			
		||||
 | 
			
		||||
.. autoclass:: discord.ext.commands.Option
 | 
			
		||||
    :members:
 | 
			
		||||
 | 
			
		||||
Flag Converter
 | 
			
		||||
~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -61,6 +61,7 @@ the name to something other than the function would be as simple as doing this:
 | 
			
		||||
    async def _list(ctx, arg):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Parameters
 | 
			
		||||
------------
 | 
			
		||||
 | 
			
		||||
@@ -133,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
 | 
			
		||||
++++++++++++++++++++++++
 | 
			
		||||
 | 
			
		||||
@@ -179,6 +185,12 @@ 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`.
 | 
			
		||||
 | 
			
		||||
.. 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.
 | 
			
		||||
 | 
			
		||||
Converters
 | 
			
		||||
------------
 | 
			
		||||
 | 
			
		||||
@@ -400,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 <https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-type>`_
 | 
			
		||||
    for all supported types by the API.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
By providing the converter it allows us to use them as building blocks for another converter:
 | 
			
		||||
 | 
			
		||||
@@ -487,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
 | 
			
		||||
^^^^^^^^^^^^^^^^^
 | 
			
		||||
 | 
			
		||||
@@ -680,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.
 | 
			
		||||
 
 | 
			
		||||
@@ -15,4 +15,5 @@ extension library that handles this for you.
 | 
			
		||||
    commands
 | 
			
		||||
    cogs
 | 
			
		||||
    extensions
 | 
			
		||||
    slash-commands
 | 
			
		||||
    api
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								docs/ext/commands/slash-commands.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								docs/ext/commands/slash-commands.rst
									
									
									
									
									
										Normal file
									
								
							@@ -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 <ext_commands_slash_command_troubleshooting>`
 | 
			
		||||
 | 
			
		||||
.. admonition:: Slash Command Only
 | 
			
		||||
 | 
			
		||||
    For parts of the docs specific to slash commands, look for this box!
 | 
			
		||||
							
								
								
									
										31
									
								
								docs/faq.rst
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								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.
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@ Breaking Changes
 | 
			
		||||
- :attr:`GroupChannel.owner` is now Optional
 | 
			
		||||
- ``edit`` methods now only accept None if it actually means something (e.g. clearing it)
 | 
			
		||||
- ``timeout`` parameter for ``ui.View.__init__`` is now keyword only
 | 
			
		||||
- When an interaction has already been responded and another one is sent, :exc:`InteractionResponded`is now raised.
 | 
			
		||||
- When an interaction has already been responded and another one is sent, :exc:`InteractionResponded` is now raised.
 | 
			
		||||
    - Discord's API only allows a single :attr:`interaction.response`.
 | 
			
		||||
- Separate :func:`on_member_update` and :func:`on_presence_update`
 | 
			
		||||
    - The new event :func:`on_presence_update` is now called when status/activity is changed.
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ async def userinfo(ctx: commands.Context, user: discord.User):
 | 
			
		||||
    # and can do the following:
 | 
			
		||||
    user_id = user.id
 | 
			
		||||
    username = user.name
 | 
			
		||||
    avatar = user.avatar.url
 | 
			
		||||
    avatar = user.display_avatar.url
 | 
			
		||||
    await ctx.send(f"User found: {user_id} -- {username}\n{avatar}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										41
									
								
								examples/slash_commands.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								examples/slash_commands.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
import discord
 | 
			
		||||
from discord.ext import commands
 | 
			
		||||
 | 
			
		||||
# Set slash commands=True when constructing your bot to enable all slash commands
 | 
			
		||||
# if your bot is only for a couple of servers, you can use the parameter
 | 
			
		||||
# `slash_command_guilds=[list, of, guild, ids]` to specify this,
 | 
			
		||||
# then the commands will be much faster to upload.
 | 
			
		||||
bot = commands.Bot("!", intents=discord.Intents(guilds=True, messages=True), slash_commands=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bot.event
 | 
			
		||||
async def on_ready():
 | 
			
		||||
    print(f"Logged in as {bot.user} (ID: {bot.user.id})")
 | 
			
		||||
    print("------")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bot.command()
 | 
			
		||||
# You can use commands.Option to define descriptions for your options, and converters will still work fine.
 | 
			
		||||
async def ping(
 | 
			
		||||
    ctx: commands.Context, emoji: bool = commands.Option(description="whether to use an emoji when responding")
 | 
			
		||||
):
 | 
			
		||||
    # This command can be used with slash commands or message commands
 | 
			
		||||
    if emoji:
 | 
			
		||||
        await ctx.send("\U0001f3d3")
 | 
			
		||||
    else:
 | 
			
		||||
        await ctx.send("Pong!")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bot.command(message_command=False)
 | 
			
		||||
async def only_slash(ctx: commands.Context):
 | 
			
		||||
    # This command can only be used with slash commands
 | 
			
		||||
    await ctx.send("Hello from slash commands!")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bot.command(slash_command=False)
 | 
			
		||||
async def only_message(ctx: commands.Context):
 | 
			
		||||
    # This command can only be used with a message
 | 
			
		||||
    await ctx.send("Hello from message commands!")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
bot.run("token")
 | 
			
		||||
		Reference in New Issue
	
	Block a user