From 3c371795516ebb5c9fac67ed037b8741bdcf0844 Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Sun, 17 Sep 2023 00:23:38 +0300 Subject: [PATCH 01/34] A draft of the app commands storage refactor --- disnake/ext/commands/cog.py | 23 +- disnake/ext/commands/errors.py | 37 ++ disnake/ext/commands/interaction_bot_base.py | 346 +++++++++++++------ disnake/interactions/application_command.py | 10 +- 4 files changed, 294 insertions(+), 122 deletions(-) diff --git a/disnake/ext/commands/cog.py b/disnake/ext/commands/cog.py index a0305ccea7..6c40a4bd6e 100644 --- a/disnake/ext/commands/cog.py +++ b/disnake/ext/commands/cog.py @@ -754,21 +754,11 @@ def _inject(self, bot: AnyBot) -> Self: for index, command in enumerate(self.__cog_app_commands__): command.cog = self try: - if isinstance(command, InvokableSlashCommand): - bot.add_slash_command(command) - elif isinstance(command, InvokableUserCommand): - bot.add_user_command(command) - elif isinstance(command, InvokableMessageCommand): - bot.add_message_command(command) + bot.add_app_command(command) except Exception: # undo our additions for to_undo in self.__cog_app_commands__[:index]: - if isinstance(to_undo, InvokableSlashCommand): - bot.remove_slash_command(to_undo.name) - elif isinstance(to_undo, InvokableUserCommand): - bot.remove_user_command(to_undo.name) - elif isinstance(to_undo, InvokableMessageCommand): - bot.remove_message_command(to_undo.name) + bot.remove_app_command(to_undo.body.type, to_undo.name, to_undo.guild_ids) raise if not hasattr(self.cog_load.__func__, "__cog_special_method__"): @@ -841,12 +831,9 @@ def _eject(self, bot: AnyBot) -> None: bot.remove_command(command.name) # type: ignore for app_command in self.__cog_app_commands__: - if isinstance(app_command, InvokableSlashCommand): - bot.remove_slash_command(app_command.name) - elif isinstance(app_command, InvokableUserCommand): - bot.remove_user_command(app_command.name) - elif isinstance(app_command, InvokableMessageCommand): - bot.remove_message_command(app_command.name) + bot.remove_app_command( + app_command.body.type, app_command.name, app_command.guild_ids + ) for name, method_name in self.__cog_listeners__: bot.remove_listener(getattr(self, method_name), name) diff --git a/disnake/ext/commands/errors.py b/disnake/ext/commands/errors.py index 25329d9305..5b65e40f48 100644 --- a/disnake/ext/commands/errors.py +++ b/disnake/ext/commands/errors.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Any, Callable, List, Optional, Tuple, Type, Union +from disnake.enums import ApplicationCommandType from disnake.errors import ClientException, DiscordException from disnake.utils import humanize_list @@ -74,6 +75,7 @@ "ExtensionFailed", "ExtensionNotFound", "CommandRegistrationError", + "AppCommandRegistrationError", "FlagError", "BadFlagArgument", "MissingFlagArgument", @@ -1021,6 +1023,41 @@ def __init__(self, name: str, *, alias_conflict: bool = False) -> None: type_ = "alias" if alias_conflict else "command" super().__init__(f"The {type_} {name} is already an existing command or alias.") +# we inherit CommandRegistrationError for backwards compatibility, +# because this error replaced CommandRegistrationError in several places +class AppCommandRegistrationError(CommandRegistrationError): + """An exception raised when the app command can't be added + because a command with the same key already exists. + A key is determined by command type, name, and guild_id. + + This inherits from :exc:`disnake.CommandRegistrationError` + + .. versionadded:: 2.10 + + Attributes + ---------- + cmd_type: :class:`ApplicationCommandType` + The command type. + name: :class:`str` + The command name. + guild_id: Optional[:class:`int`] + The ID of the guild where the command was supposed to be registered + or ``None`` if it was a global command. + """ + + def __init__(self, cmd_type: ApplicationCommandType, name: str, guild_id: Optional[int]) -> None: + self.cmd_type: ApplicationCommandType = cmd_type + self.name: str = name + self.guild_id: Optional[int] = guild_id + # fixed API naming here because no one calls slash commands "chat input" + type_ = "slash" if cmd_type is ApplicationCommandType.chat_input else cmd_type.name + if guild_id is None: + msg = f"Global {type_} command {name} was specified earlier in your code." + else: + msg = f"Local {type_} command {name} with guild ID {guild_id} was specified earlier in your code." + # this bypasses CommandRegistrationError.__init__ + super(CommandRegistrationError, self).__init__(msg) + class FlagError(BadArgument): """The base exception type for all flag parsing related errors. diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 25308c3649..5ea5b291a5 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -7,7 +7,6 @@ import sys import traceback import warnings -from itertools import chain from typing import ( TYPE_CHECKING, Any, @@ -15,6 +14,7 @@ Dict, Iterable, List, + NamedTuple, Optional, Sequence, Set, @@ -22,6 +22,7 @@ TypedDict, TypeVar, Union, + cast, ) import disnake @@ -39,7 +40,7 @@ message_command, user_command, ) -from .errors import CommandRegistrationError +from .errors import AppCommandRegistrationError from .flags import CommandSyncFlags from .slash_core import InvokableSlashCommand, SubCommand, SubCommandGroup, slash_command @@ -79,6 +80,12 @@ class _Diff(TypedDict): delete_ignored: NotRequired[List[ApplicationCommand]] +class AppCmdIndex(NamedTuple): + type: ApplicationCommandType + name: str + guild_id: Optional[int] + + def _get_to_send_from_diff(diff: _Diff): return diff["no_changes"] + diff["upsert"] + diff["edit"] + diff.get("delete_ignored", []) @@ -139,6 +146,21 @@ def _format_diff(diff: _Diff) -> str: return "\n".join(f"| {line}" for line in lines) +def _match_subcommand_chain( + command: InvokableSlashCommand, chain: List[str] +) -> Union[InvokableSlashCommand, SubCommand, SubCommandGroup, None]: + if command.name != chain[0]: + return None + if len(chain) == 1: + return command + if len(chain) == 2: + return command.children.get(chain[1]) + if len(chain) == 3: + group = command.children.get(chain[1]) + if isinstance(group, SubCommandGroup): + return group.children.get(chain[2]) + + class InteractionBotBase(CommonBotBase): def __init__( self, @@ -213,9 +235,8 @@ def __init__( self._before_message_command_invoke = None self._after_message_command_invoke = None - self.all_slash_commands: Dict[str, InvokableSlashCommand] = {} - self.all_user_commands: Dict[str, InvokableUserCommand] = {} - self.all_message_commands: Dict[str, InvokableMessageCommand] = {} + self.all_app_commands: Dict[AppCmdIndex, InvokableApplicationCommand] = {} + # TODO: add .all_xxx_commands properties with deprecation warnings @disnake.utils.copy_doc(disnake.Client.login) async def login(self, token: str) -> None: @@ -232,31 +253,82 @@ def command_sync_flags(self) -> CommandSyncFlags: return CommandSyncFlags._from_value(self._command_sync_flags.value) def application_commands_iterator(self) -> Iterable[InvokableApplicationCommand]: - return chain( - self.all_slash_commands.values(), - self.all_user_commands.values(), - self.all_message_commands.values(), + warn_deprecated( + "application_commands_iterator is deprecated and will be removed in a future version. " + "Use all_app_commands.values() instead.", + stacklevel=3, ) + return self.all_app_commands.values() @property def application_commands(self) -> Set[InvokableApplicationCommand]: """Set[:class:`InvokableApplicationCommand`]: A set of all application commands the bot has.""" - return set(self.application_commands_iterator()) + return set(self.all_app_commands.values()) @property def slash_commands(self) -> Set[InvokableSlashCommand]: """Set[:class:`InvokableSlashCommand`]: A set of all slash commands the bot has.""" - return set(self.all_slash_commands.values()) + return set( + cmd for cmd in self.all_app_commands.values() if isinstance(cmd, InvokableSlashCommand) + ) @property def user_commands(self) -> Set[InvokableUserCommand]: """Set[:class:`InvokableUserCommand`]: A set of all user commands the bot has.""" - return set(self.all_user_commands.values()) + return set( + cmd for cmd in self.all_app_commands.values() if isinstance(cmd, InvokableUserCommand) + ) @property def message_commands(self) -> Set[InvokableMessageCommand]: """Set[:class:`InvokableMessageCommand`]: A set of all message commands the bot has.""" - return set(self.all_message_commands.values()) + return set( + cmd for cmd in self.all_app_commands.values() if isinstance(cmd, InvokableMessageCommand) + ) + + def add_app_command(self, app_command: InvokableApplicationCommand) -> None: + """Adds an :class:`InvokableApplicationCommand` into the internal list of app commands. + + This is usually not called, instead shortcut decorators are used. + + .. versionadded:: 2.10 + + Parameters + ---------- + app_command: :class:`InvokableApplicationCommand` + The app command to add. + + Raises + ------ + AppCommandRegistrationError + The app command is already registered. + TypeError + The app command passed is not an instance of :class:`InvokableApplicationCommand`. + """ + + if not isinstance(self, disnake.Client): + raise NotImplementedError("This method is only usable in disnake.Client subclasses") + + if not isinstance(slash_command, InvokableSlashCommand): + raise TypeError("The slash_command passed must be an instance of InvokableSlashCommand") + + if app_command.guild_ids is None: + # if test_guilds are specified then we add the same command for each test guild + guild_ids = (None,) if self._test_guilds is None else self._test_guilds + else: + guild_ids = app_command.guild_ids + + for guild_id in guild_ids: + cmd_index = AppCmdIndex( + type=app_command.body.type, name=app_command.name, guild_id=guild_id + ) + if cmd_index in self.all_app_commands: + raise AppCommandRegistrationError(cmd_index.type, cmd_index.name, cmd_index.guild_id) + + # localization may be called multiple times for the same command but it's harmless + app_command.body.localize(self.i18n) + + self.all_app_commands[cmd_index] = app_command def add_slash_command(self, slash_command: InvokableSlashCommand) -> None: """Adds an :class:`InvokableSlashCommand` into the internal list of slash commands. @@ -276,17 +348,12 @@ def add_slash_command(self, slash_command: InvokableSlashCommand) -> None: TypeError The slash command passed is not an instance of :class:`InvokableSlashCommand`. """ - if not isinstance(self, disnake.Client): - raise NotImplementedError("This method is only usable in disnake.Client subclasses") - - if not isinstance(slash_command, InvokableSlashCommand): - raise TypeError("The slash_command passed must be an instance of InvokableSlashCommand") - - if slash_command.name in self.all_slash_commands: - raise CommandRegistrationError(slash_command.name) - - slash_command.body.localize(self.i18n) - self.all_slash_commands[slash_command.name] = slash_command + warn_deprecated( + "add_slash_command is deprecated and will be removed in a future version. " + "Use add_app_command instead.", + stacklevel=3, + ) + self.add_app_command(slash_command) def add_user_command(self, user_command: InvokableUserCommand) -> None: """Adds an :class:`InvokableUserCommand` into the internal list of user commands. @@ -306,17 +373,12 @@ def add_user_command(self, user_command: InvokableUserCommand) -> None: TypeError The user command passed is not an instance of :class:`InvokableUserCommand`. """ - if not isinstance(self, disnake.Client): - raise NotImplementedError("This method is only usable in disnake.Client subclasses") - - if not isinstance(user_command, InvokableUserCommand): - raise TypeError("The user_command passed must be an instance of InvokableUserCommand") - - if user_command.name in self.all_user_commands: - raise CommandRegistrationError(user_command.name) - - user_command.body.localize(self.i18n) - self.all_user_commands[user_command.name] = user_command + warn_deprecated( + "add_user_command is deprecated and will be removed in a future version. " + "Use add_app_command instead.", + stacklevel=3, + ) + self.add_app_command(user_command) def add_message_command(self, message_command: InvokableMessageCommand) -> None: """Adds an :class:`InvokableMessageCommand` into the internal list of message commands. @@ -336,19 +398,71 @@ def add_message_command(self, message_command: InvokableMessageCommand) -> None: TypeError The message command passed is not an instance of :class:`InvokableMessageCommand`. """ - if not isinstance(self, disnake.Client): - raise NotImplementedError("This method is only usable in disnake.Client subclasses") + warn_deprecated( + "add_message_command is deprecated and will be removed in a future version. " + "Use add_app_command instead.", + stacklevel=3, + ) + self.add_app_command(message_command) - if not isinstance(message_command, InvokableMessageCommand): - raise TypeError( - "The message_command passed must be an instance of InvokableMessageCommand" - ) + def remove_app_command( + self, cmd_type: ApplicationCommandType, name: str, guild_ids: Optional[Tuple[int, ...]] + ) -> Optional[InvokableApplicationCommand]: + """Removes an :class:`InvokableApplicationCommand` from the internal list of app commands. + + .. versionadded:: 2.10 + + Parameters + ---------- + cmd_type: :class:`ApplicationCommandType` + The type of the app command to remove. + name: :class:`str` + The name of the app command to remove. + guild_ids: Optional[Tuple[:class:`str`, ...]] + The IDs of the guilds from which the command should be removed. + + Returns + ------- + Optional[:class:`InvokableApplicationCommand`] + The app command that was removed. If the key data was not valid then ``None`` is returned instead. + """ + + if guild_ids is None: + extended_guild_ids = (None,) + # a global command may end up being a local command if test_guilds were specified + # so we should remove this "global" command from each test guild + if self._test_guilds is not None: + extended_guild_ids += self._test_guilds + else: + extended_guild_ids = guild_ids + + result = None + for guild_id in extended_guild_ids: + cmd_index = AppCmdIndex(type=cmd_type, name=name, guild_id=guild_id) + cmd = self.all_app_commands.pop(cmd_index, None) + if result is None: + result = cmd + return result + + def _emulate_old_app_command_remove(self, cmd_type: ApplicationCommandType, name: str) -> Any: + type_info = "slash" if cmd_type is ApplicationCommandType.chat_input else cmd_type.name + warn_deprecated( + f"remove_{type_info}_command is deprecated and will be removed in a future version. " + "Use remove_app_command instead.", + stacklevel=3, + ) + bad_keys = [] + for key in self.all_app_commands.keys(): + if key.type is cmd_type and key.name == name: + bad_keys.append(key) - if message_command.name in self.all_message_commands: - raise CommandRegistrationError(message_command.name) + result = None + for key in bad_keys: + cmd = self.all_app_commands.pop(key, None) + if result is None: + result = cmd - message_command.body.localize(self.i18n) - self.all_message_commands[message_command.name] = message_command + return result def remove_slash_command(self, name: str) -> Optional[InvokableSlashCommand]: """Removes an :class:`InvokableSlashCommand` from the internal list @@ -364,10 +478,7 @@ def remove_slash_command(self, name: str) -> Optional[InvokableSlashCommand]: Optional[:class:`InvokableSlashCommand`] The slash command that was removed. If the name is not valid then ``None`` is returned instead. """ - command = self.all_slash_commands.pop(name, None) - if command is None: - return None - return command + self._emulate_old_app_command_remove(ApplicationCommandType.chat_input, name) def remove_user_command(self, name: str) -> Optional[InvokableUserCommand]: """Removes an :class:`InvokableUserCommand` from the internal list @@ -383,10 +494,7 @@ def remove_user_command(self, name: str) -> Optional[InvokableUserCommand]: Optional[:class:`InvokableUserCommand`] The user command that was removed. If the name is not valid then ``None`` is returned instead. """ - command = self.all_user_commands.pop(name, None) - if command is None: - return None - return command + self._emulate_old_app_command_remove(ApplicationCommandType.user, name) def remove_message_command(self, name: str) -> Optional[InvokableMessageCommand]: """Removes an :class:`InvokableMessageCommand` from the internal list @@ -402,13 +510,10 @@ def remove_message_command(self, name: str) -> Optional[InvokableMessageCommand] Optional[:class:`InvokableMessageCommand`] The message command that was removed. If the name is not valid then ``None`` is returned instead. """ - command = self.all_message_commands.pop(name, None) - if command is None: - return None - return command + self._emulate_old_app_command_remove(ApplicationCommandType.message, name) def get_slash_command( - self, name: str + self, name: str, guild_id: Optional[int] = MISSING ) -> Optional[Union[InvokableSlashCommand, SubCommandGroup, SubCommand]]: """Works like ``Bot.get_command``, but for slash commands. @@ -421,6 +526,9 @@ def get_slash_command( ---------- name: :class:`str` The name of the slash command to get. + guild_id: Optional[:class:`int`] + The guild ID corresponding to the slash command or ``None`` if it's a global command. + If this is not specified then the first match is returned instead. Raises ------ @@ -436,20 +544,25 @@ def get_slash_command( raise TypeError(f"Expected name to be str, not {name.__class__}") chain = name.split() - slash = self.all_slash_commands.get(chain[0]) - if slash is None: - return None - - if len(chain) == 1: - return slash - elif len(chain) == 2: - return slash.children.get(chain[1]) - elif len(chain) == 3: - group = slash.children.get(chain[1]) - if isinstance(group, SubCommandGroup): - return group.children.get(chain[2]) - - def get_user_command(self, name: str) -> Optional[InvokableUserCommand]: + if guild_id is not MISSING: + cmd_index = AppCmdIndex( + type=ApplicationCommandType.chat_input, name=chain[0], guild_id=guild_id + ) + command = self.all_app_commands.get(cmd_index) + if command is None: + return None + return _match_subcommand_chain(command, chain) # type: ignore + + for command in self.all_app_commands.values(): + if not isinstance(command, InvokableSlashCommand): + continue + result = _match_subcommand_chain(command, chain) + if result is not None: + return result + + def get_user_command( + self, name: str, guild_id: Optional[int] = MISSING + ) -> Optional[InvokableUserCommand]: """Gets an :class:`InvokableUserCommand` from the internal list of user commands. @@ -457,15 +570,31 @@ def get_user_command(self, name: str) -> Optional[InvokableUserCommand]: ---------- name: :class:`str` The name of the user command to get. + guild_id: Optional[:class:`int`] + The guild ID corresponding to the user command or ``None`` if it's a global command. + If this is not specified then the first match is returned instead. Returns ------- Optional[:class:`InvokableUserCommand`] The user command that was requested. If not found, returns ``None``. """ - return self.all_user_commands.get(name) - - def get_message_command(self, name: str) -> Optional[InvokableMessageCommand]: + if guild_id is not MISSING: + cmd_index = AppCmdIndex( + type=ApplicationCommandType.user, name=name, guild_id=guild_id + ) + command = self.all_app_commands.get(cmd_index) + if command is None: + return None + return command # type: ignore + + for command in self.all_app_commands.values(): + if isinstance(command, InvokableUserCommand) and command.name == name: + return command + + def get_message_command( + self, name: str, guild_id: Optional[int] = MISSING + ) -> Optional[InvokableMessageCommand]: """Gets an :class:`InvokableMessageCommand` from the internal list of message commands. @@ -473,13 +602,27 @@ def get_message_command(self, name: str) -> Optional[InvokableMessageCommand]: ---------- name: :class:`str` The name of the message command to get. + guild_id: Optional[:class:`int`] + The guild ID corresponding to the message command or ``None`` if it's a global command. + If this is not specified then the first match is returned instead. Returns ------- Optional[:class:`InvokableMessageCommand`] The message command that was requested. If not found, returns ``None``. """ - return self.all_message_commands.get(name) + if guild_id is not MISSING: + cmd_index = AppCmdIndex( + type=ApplicationCommandType.message, name=name, guild_id=guild_id + ) + command = self.all_app_commands.get(cmd_index) + if command is None: + return None + return command # type: ignore + + for command in self.all_app_commands.values(): + if isinstance(command, InvokableMessageCommand) and command.name == name: + return command def slash_command( self, @@ -570,7 +713,7 @@ def decorator(func: CommandCallback) -> InvokableSlashCommand: extras=extras, **kwargs, )(func) - self.add_slash_command(result) + self.add_app_command(result) return result return decorator @@ -647,7 +790,7 @@ def decorator( extras=extras, **kwargs, )(func) - self.add_user_command(result) + self.add_app_command(result) return result return decorator @@ -724,7 +867,7 @@ def decorator( extras=extras, **kwargs, )(func) - self.add_message_command(result) + self.add_app_command(result) return result return decorator @@ -734,24 +877,22 @@ def decorator( def _ordered_unsynced_commands( self, test_guilds: Optional[Sequence[int]] = None ) -> Tuple[List[ApplicationCommand], Dict[int, List[ApplicationCommand]]]: + # TODO: deprecation warning for the test_guilds arg global_cmds = [] guilds = {} - for cmd in self.application_commands_iterator(): + for key, cmd in self.all_app_commands.items(): if not cmd.auto_sync: cmd.body._always_synced = True - guild_ids = cmd.guild_ids or test_guilds + guild_id = key.guild_id - if guild_ids is None: + if guild_id is None: global_cmds.append(cmd.body) - continue - - for guild_id in guild_ids: - if guild_id not in guilds: - guilds[guild_id] = [cmd.body] - else: - guilds[guild_id].append(cmd.body) + elif guild_id not in guilds: + guilds[guild_id] = [cmd.body] + else: + guilds[guild_id].append(cmd.body) return global_cmds, guilds @@ -759,7 +900,7 @@ async def _cache_application_commands(self) -> None: if not isinstance(self, disnake.Client): raise NotImplementedError("This method is only usable in disnake.Client subclasses") - _, guilds = self._ordered_unsynced_commands(self._test_guilds) + _, guilds = self._ordered_unsynced_commands() # Here we only cache global commands and commands from guilds that are specified in the code. # They're collected from the "test_guilds" kwarg of commands.InteractionBotBase @@ -794,8 +935,8 @@ async def _sync_application_commands(self) -> None: return # We assume that all commands are already cached. - # Sort all invokable commands between guild IDs: - global_cmds, guild_cmds = self._ordered_unsynced_commands(self._test_guilds) + # Group all invokable commands by guild IDs: + global_cmds, guild_cmds = self._ordered_unsynced_commands() if self._command_sync_flags.sync_global_commands: # Update global commands first @@ -1280,11 +1421,17 @@ async def process_app_command_autocompletion( inter: :class:`disnake.ApplicationCommandInteraction` The interaction to process. """ - slash_command = self.all_slash_commands.get(inter.data.name) + cmd_index = AppCmdIndex( + type=inter.data.type, name=inter.data.name, guild_id=inter.data.guild_id + ) + # this happens to always be a slash command + slash_command = self.all_app_commands.get(cmd_index) if slash_command is None: return + slash_command = cast(InvokableSlashCommand, slash_command) + inter.application_command = slash_command if slash_command.guild_ids is None or inter.guild_id in slash_command.guild_ids: await slash_command._call_relevant_autocompleter(inter) @@ -1316,7 +1463,7 @@ async def process_application_commands( # and we're instructed to sync guild commands and self._command_sync_flags.sync_guild_commands # and the current command was registered to a guild - and interaction.data.get("guild_id") + and interaction.data.guild_id # and we don't know the command and not self.get_guild_command(interaction.guild_id, interaction.data.id) # type: ignore ): @@ -1351,20 +1498,17 @@ async def process_application_commands( return command_type = interaction.data.type - command_name = interaction.data.name - app_command = None event_name = None + cmd_index = AppCmdIndex( + type=command_type, name=interaction.data.name, guild_id=interaction.data.guild_id + ) + app_command = self.all_app_commands.get(cmd_index) if command_type is ApplicationCommandType.chat_input: - app_command = self.all_slash_commands.get(command_name) event_name = "slash_command" - elif command_type is ApplicationCommandType.user: - app_command = self.all_user_commands.get(command_name) event_name = "user_command" - elif command_type is ApplicationCommandType.message: - app_command = self.all_message_commands.get(command_name) event_name = "message_command" if event_name is None or app_command is None: diff --git a/disnake/interactions/application_command.py b/disnake/interactions/application_command.py index 13c96c02de..8fb465d7c6 100644 --- a/disnake/interactions/application_command.py +++ b/disnake/interactions/application_command.py @@ -174,16 +174,19 @@ class ApplicationCommandInteractionData(Dict[str, Any]): All resolved objects related to this interaction. options: List[:class:`ApplicationCommandInteractionDataOption`] A list of options from the API. + guild_id: Optional[:class:`int`] + ID of the guild the command is registered to. target_id: :class:`int` - ID of the user or message targetted by a user or message command + ID of the user or message targetted by a user or message command. target: Union[:class:`User`, :class:`Member`, :class:`Message`] - The user or message targetted by a user or message command + The user or message targetted by a user or message command. """ __slots__ = ( "id", "name", "type", + "guild_id", "target_id", "target", "resolved", @@ -195,7 +198,7 @@ def __init__( *, data: ApplicationCommandInteractionDataPayload, state: ConnectionState, - guild_id: Optional[int], + guild_id: Optional[int], # the ID of the guild where this command has been invoked ) -> None: super().__init__(data) self.id: int = int(data["id"]) @@ -205,6 +208,7 @@ def __init__( self.resolved = InteractionDataResolved( data=data.get("resolved", {}), state=state, guild_id=guild_id ) + self.guild_id: Optional[int] = utils._get_as_snowflake(data, "guild_id") self.target_id: Optional[int] = utils._get_as_snowflake(data, "target_id") target = self.resolved.get_by_id(self.target_id) self.target: Optional[Union[User, Member, Message]] = target # type: ignore From b6500097608e15168f74662ef734b4eddbc6a27d Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Mon, 18 Sep 2023 13:02:53 +0300 Subject: [PATCH 02/34] Add missing deprecation warnings and properties --- disnake/ext/commands/interaction_bot_base.py | 69 +++++++++++++++++--- 1 file changed, 60 insertions(+), 9 deletions(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 5ea5b291a5..6ff5f490ad 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -236,7 +236,6 @@ def __init__( self._after_message_command_invoke = None self.all_app_commands: Dict[AppCmdIndex, InvokableApplicationCommand] = {} - # TODO: add .all_xxx_commands properties with deprecation warnings @disnake.utils.copy_doc(disnake.Client.login) async def login(self, token: str) -> None: @@ -286,6 +285,48 @@ def message_commands(self) -> Set[InvokableMessageCommand]: cmd for cmd in self.all_app_commands.values() if isinstance(cmd, InvokableMessageCommand) ) + @property + def all_slash_commands(self) -> Dict[str, InvokableSlashCommand]: + # no docstring because it was an attribute and now it's deprecated + warn_deprecated( + "all_slash_commands is deprecated and will be removed in a future version. " + "Use all_app_commands as a replacement.", + stacklevel=3, + ) + return { + cmd.name: cmd + for cmd in self.all_app_commands.values() + if isinstance(cmd, InvokableSlashCommand) + } + + @property + def all_user_commands(self) -> Dict[str, InvokableUserCommand]: + # no docstring because it was an attribute and now it's deprecated + warn_deprecated( + "all_user_commands is deprecated and will be removed in a future version. " + "Use all_app_commands as a replacement.", + stacklevel=3, + ) + return { + cmd.name: cmd + for cmd in self.all_app_commands.values() + if isinstance(cmd, InvokableUserCommand) + } + + @property + def all_message_commands(self) -> Dict[str, InvokableMessageCommand]: + # no docstring because it was an attribute and now it's deprecated + warn_deprecated( + "all_message_commands is deprecated and will be removed in a future version. " + "Use all_app_commands as a replacement.", + stacklevel=3, + ) + return { + cmd.name: cmd + for cmd in self.all_app_commands.values() + if isinstance(cmd, InvokableMessageCommand) + } + def add_app_command(self, app_command: InvokableApplicationCommand) -> None: """Adds an :class:`InvokableApplicationCommand` into the internal list of app commands. @@ -327,7 +368,9 @@ def add_app_command(self, app_command: InvokableApplicationCommand) -> None: # localization may be called multiple times for the same command but it's harmless app_command.body.localize(self.i18n) - + # note that we're adding the same command object for each guild_id + # this ensures that any changes that happen to app_command after add_app_command + # (such as hook attachments or permission modifications) apply properly self.all_app_commands[cmd_index] = app_command def add_slash_command(self, slash_command: InvokableSlashCommand) -> None: @@ -428,11 +471,12 @@ def remove_app_command( """ if guild_ids is None: - extended_guild_ids = (None,) # a global command may end up being a local command if test_guilds were specified # so we should remove this "global" command from each test guild if self._test_guilds is not None: - extended_guild_ids += self._test_guilds + extended_guild_ids = self._test_guilds + else: + extended_guild_ids = (None,) else: extended_guild_ids = guild_ids @@ -442,6 +486,7 @@ def remove_app_command( cmd = self.all_app_commands.pop(cmd_index, None) if result is None: result = cmd + return result def _emulate_old_app_command_remove(self, cmd_type: ApplicationCommandType, name: str) -> Any: @@ -552,7 +597,7 @@ def get_slash_command( if command is None: return None return _match_subcommand_chain(command, chain) # type: ignore - + # this is mostly for backwards compatibility, as previously guild_id arg didn't exist for command in self.all_app_commands.values(): if not isinstance(command, InvokableSlashCommand): continue @@ -587,7 +632,7 @@ def get_user_command( if command is None: return None return command # type: ignore - + # this is mostly for backwards compatibility, as previously guild_id arg didn't exist for command in self.all_app_commands.values(): if isinstance(command, InvokableUserCommand) and command.name == name: return command @@ -619,7 +664,7 @@ def get_message_command( if command is None: return None return command # type: ignore - + # this is mostly for backwards compatibility, as previously guild_id arg didn't exist for command in self.all_app_commands.values(): if isinstance(command, InvokableMessageCommand) and command.name == name: return command @@ -875,9 +920,15 @@ def decorator( # command synchronisation def _ordered_unsynced_commands( - self, test_guilds: Optional[Sequence[int]] = None + self, test_guilds: Optional[Sequence[int]] = MISSING ) -> Tuple[List[ApplicationCommand], Dict[int, List[ApplicationCommand]]]: - # TODO: deprecation warning for the test_guilds arg + if test_guilds is not MISSING: + warn_deprecated( + "Argument test_guilds of _ordered_unsynced_commands is deprecated " + "and will be removed in a future version.", + stacklevel=3, + ) + global_cmds = [] guilds = {} From ec3896d0b6f42d99ed9f9d329a61351d53b35680 Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Mon, 18 Sep 2023 15:09:37 +0300 Subject: [PATCH 03/34] Fix a bad check --- disnake/ext/commands/interaction_bot_base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 6ff5f490ad..44a70793f7 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -350,8 +350,8 @@ def add_app_command(self, app_command: InvokableApplicationCommand) -> None: if not isinstance(self, disnake.Client): raise NotImplementedError("This method is only usable in disnake.Client subclasses") - if not isinstance(slash_command, InvokableSlashCommand): - raise TypeError("The slash_command passed must be an instance of InvokableSlashCommand") + if not isinstance(app_command, InvokableApplicationCommand): + raise TypeError("The app_command passed must be an instance of InvokableApplicationCommand") if app_command.guild_ids is None: # if test_guilds are specified then we add the same command for each test guild From ea1ad6586061cb773a7f59c80c7bf69ca8e7963a Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Mon, 18 Sep 2023 15:38:16 +0300 Subject: [PATCH 04/34] Fix linting issues --- disnake/ext/commands/errors.py | 4 +++- disnake/ext/commands/interaction_bot_base.py | 16 ++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/disnake/ext/commands/errors.py b/disnake/ext/commands/errors.py index 5b65e40f48..3e9a9360b6 100644 --- a/disnake/ext/commands/errors.py +++ b/disnake/ext/commands/errors.py @@ -1045,7 +1045,9 @@ class AppCommandRegistrationError(CommandRegistrationError): or ``None`` if it was a global command. """ - def __init__(self, cmd_type: ApplicationCommandType, name: str, guild_id: Optional[int]) -> None: + def __init__( + self, cmd_type: ApplicationCommandType, name: str, guild_id: Optional[int] + ) -> None: self.cmd_type: ApplicationCommandType = cmd_type self.name: str = name self.guild_id: Optional[int] = guild_id diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 44a70793f7..7df3cbe0b7 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -282,7 +282,9 @@ def user_commands(self) -> Set[InvokableUserCommand]: def message_commands(self) -> Set[InvokableMessageCommand]: """Set[:class:`InvokableMessageCommand`]: A set of all message commands the bot has.""" return set( - cmd for cmd in self.all_app_commands.values() if isinstance(cmd, InvokableMessageCommand) + cmd + for cmd in self.all_app_commands.values() + if isinstance(cmd, InvokableMessageCommand) ) @property @@ -351,7 +353,9 @@ def add_app_command(self, app_command: InvokableApplicationCommand) -> None: raise NotImplementedError("This method is only usable in disnake.Client subclasses") if not isinstance(app_command, InvokableApplicationCommand): - raise TypeError("The app_command passed must be an instance of InvokableApplicationCommand") + raise TypeError( + "The app_command passed must be an instance of InvokableApplicationCommand" + ) if app_command.guild_ids is None: # if test_guilds are specified then we add the same command for each test guild @@ -364,7 +368,9 @@ def add_app_command(self, app_command: InvokableApplicationCommand) -> None: type=app_command.body.type, name=app_command.name, guild_id=guild_id ) if cmd_index in self.all_app_commands: - raise AppCommandRegistrationError(cmd_index.type, cmd_index.name, cmd_index.guild_id) + raise AppCommandRegistrationError( + cmd_index.type, cmd_index.name, cmd_index.guild_id + ) # localization may be called multiple times for the same command but it's harmless app_command.body.localize(self.i18n) @@ -625,9 +631,7 @@ def get_user_command( The user command that was requested. If not found, returns ``None``. """ if guild_id is not MISSING: - cmd_index = AppCmdIndex( - type=ApplicationCommandType.user, name=name, guild_id=guild_id - ) + cmd_index = AppCmdIndex(type=ApplicationCommandType.user, name=name, guild_id=guild_id) command = self.all_app_commands.get(cmd_index) if command is None: return None From 424ed03e8790b8cde17e00bc0b39c279401520ea Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Mon, 18 Sep 2023 16:10:20 +0300 Subject: [PATCH 05/34] Update docs and add changelog --- changelog/260.bugfix.rst | 1 + changelog/260.deprecate.0.rst | 1 + changelog/260.deprecate.1.rst | 1 + changelog/260.deprecate.2.rst | 1 + changelog/260.deprecate.3.rst | 1 + changelog/260.deprecate.4.rst | 1 + changelog/260.deprecate.5.rst | 1 + changelog/260.deprecate.6.rst | 1 + changelog/260.deprecate.7.rst | 1 + changelog/260.deprecate.8.rst | 1 + changelog/260.feature.0.rst | 1 + changelog/260.feature.1.rst | 1 + changelog/260.feature.2.rst | 1 + changelog/260.feature.3.rst | 1 + changelog/260.feature.4.rst | 1 + changelog/260.feature.5.rst | 1 + disnake/ext/commands/errors.py | 1 + docs/ext/commands/api/exceptions.rst | 3 +++ 18 files changed, 20 insertions(+) create mode 100644 changelog/260.bugfix.rst create mode 100644 changelog/260.deprecate.0.rst create mode 100644 changelog/260.deprecate.1.rst create mode 100644 changelog/260.deprecate.2.rst create mode 100644 changelog/260.deprecate.3.rst create mode 100644 changelog/260.deprecate.4.rst create mode 100644 changelog/260.deprecate.5.rst create mode 100644 changelog/260.deprecate.6.rst create mode 100644 changelog/260.deprecate.7.rst create mode 100644 changelog/260.deprecate.8.rst create mode 100644 changelog/260.feature.0.rst create mode 100644 changelog/260.feature.1.rst create mode 100644 changelog/260.feature.2.rst create mode 100644 changelog/260.feature.3.rst create mode 100644 changelog/260.feature.4.rst create mode 100644 changelog/260.feature.5.rst diff --git a/changelog/260.bugfix.rst b/changelog/260.bugfix.rst new file mode 100644 index 0000000000..3a77eb513c --- /dev/null +++ b/changelog/260.bugfix.rst @@ -0,0 +1 @@ +|commands| Allow registering 2 commands with the same type and name in different guilds. diff --git a/changelog/260.deprecate.0.rst b/changelog/260.deprecate.0.rst new file mode 100644 index 0000000000..5f001a5a2f --- /dev/null +++ b/changelog/260.deprecate.0.rst @@ -0,0 +1 @@ +|commands| :attr:`InteractionBotBase.all_slash_commands` is deprecated. diff --git a/changelog/260.deprecate.1.rst b/changelog/260.deprecate.1.rst new file mode 100644 index 0000000000..b8f41dbdff --- /dev/null +++ b/changelog/260.deprecate.1.rst @@ -0,0 +1 @@ +|commands| :attr:`InteractionBotBase.all_user_commands` is deprecated. diff --git a/changelog/260.deprecate.2.rst b/changelog/260.deprecate.2.rst new file mode 100644 index 0000000000..7eb5aad372 --- /dev/null +++ b/changelog/260.deprecate.2.rst @@ -0,0 +1 @@ +|commands| :attr:`InteractionBotBase.all_message_commands` is deprecated. diff --git a/changelog/260.deprecate.3.rst b/changelog/260.deprecate.3.rst new file mode 100644 index 0000000000..bd6cfc053d --- /dev/null +++ b/changelog/260.deprecate.3.rst @@ -0,0 +1 @@ +|commands| :meth:`InteractionBotBase.add_slash_command` is deprecated. diff --git a/changelog/260.deprecate.4.rst b/changelog/260.deprecate.4.rst new file mode 100644 index 0000000000..371f949640 --- /dev/null +++ b/changelog/260.deprecate.4.rst @@ -0,0 +1 @@ +|commands| :meth:`InteractionBotBase.add_user_command` is deprecated. diff --git a/changelog/260.deprecate.5.rst b/changelog/260.deprecate.5.rst new file mode 100644 index 0000000000..ccd4e28be5 --- /dev/null +++ b/changelog/260.deprecate.5.rst @@ -0,0 +1 @@ +|commands| :meth:`InteractionBotBase.add_message_command` is deprecated. diff --git a/changelog/260.deprecate.6.rst b/changelog/260.deprecate.6.rst new file mode 100644 index 0000000000..117b504a0a --- /dev/null +++ b/changelog/260.deprecate.6.rst @@ -0,0 +1 @@ +|commands| :meth:`InteractionBotBase.remove_slash_command` is deprecated. diff --git a/changelog/260.deprecate.7.rst b/changelog/260.deprecate.7.rst new file mode 100644 index 0000000000..c1930a4bc2 --- /dev/null +++ b/changelog/260.deprecate.7.rst @@ -0,0 +1 @@ +|commands| :meth:`InteractionBotBase.remove_user_command` is deprecated. diff --git a/changelog/260.deprecate.8.rst b/changelog/260.deprecate.8.rst new file mode 100644 index 0000000000..c45e1eee27 --- /dev/null +++ b/changelog/260.deprecate.8.rst @@ -0,0 +1 @@ +|commands| :meth:`InteractionBotBase.remove_message_command` is deprecated. diff --git a/changelog/260.feature.0.rst b/changelog/260.feature.0.rst new file mode 100644 index 0000000000..b10c4b3332 --- /dev/null +++ b/changelog/260.feature.0.rst @@ -0,0 +1 @@ +|commands| Add :meth:`InteractionBotBase.add_app_command`. diff --git a/changelog/260.feature.1.rst b/changelog/260.feature.1.rst new file mode 100644 index 0000000000..e6a715b02b --- /dev/null +++ b/changelog/260.feature.1.rst @@ -0,0 +1 @@ +|commands| Add :meth:`InteractionBotBase.remove_app_command`. diff --git a/changelog/260.feature.2.rst b/changelog/260.feature.2.rst new file mode 100644 index 0000000000..aa06ab03e5 --- /dev/null +++ b/changelog/260.feature.2.rst @@ -0,0 +1 @@ +|commands| Add ``guild_id`` parameter to :meth:`InteractionBotBase.get_slash_command`. diff --git a/changelog/260.feature.3.rst b/changelog/260.feature.3.rst new file mode 100644 index 0000000000..da14bd48b8 --- /dev/null +++ b/changelog/260.feature.3.rst @@ -0,0 +1 @@ +|commands| Add ``guild_id`` parameter to :meth:`InteractionBotBase.get_user_command`. diff --git a/changelog/260.feature.4.rst b/changelog/260.feature.4.rst new file mode 100644 index 0000000000..6523484883 --- /dev/null +++ b/changelog/260.feature.4.rst @@ -0,0 +1 @@ +|commands| Add ``guild_id`` parameter to :meth:`InteractionBotBase.get_message_command`. diff --git a/changelog/260.feature.5.rst b/changelog/260.feature.5.rst new file mode 100644 index 0000000000..fb1c42fdee --- /dev/null +++ b/changelog/260.feature.5.rst @@ -0,0 +1 @@ +|commands| Add :class:`AppCommandRegistrationError`. diff --git a/disnake/ext/commands/errors.py b/disnake/ext/commands/errors.py index 3e9a9360b6..f2fdb2893f 100644 --- a/disnake/ext/commands/errors.py +++ b/disnake/ext/commands/errors.py @@ -1023,6 +1023,7 @@ def __init__(self, name: str, *, alias_conflict: bool = False) -> None: type_ = "alias" if alias_conflict else "command" super().__init__(f"The {type_} {name} is already an existing command or alias.") + # we inherit CommandRegistrationError for backwards compatibility, # because this error replaced CommandRegistrationError in several places class AppCommandRegistrationError(CommandRegistrationError): diff --git a/docs/ext/commands/api/exceptions.rst b/docs/ext/commands/api/exceptions.rst index bde1d7d446..4dd874f51e 100644 --- a/docs/ext/commands/api/exceptions.rst +++ b/docs/ext/commands/api/exceptions.rst @@ -186,6 +186,9 @@ Exceptions .. autoexception:: CommandRegistrationError :members: +.. autoexception:: AppCommandRegistrationError + :members: + Exception Hierarchy ~~~~~~~~~~~~~~~~~~~ From a2b067727ea91a4992175e1c37110db36d757259 Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Mon, 18 Sep 2023 16:31:50 +0300 Subject: [PATCH 06/34] Avoid unnecessary generators --- disnake/ext/commands/interaction_bot_base.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 7df3cbe0b7..83f7b8ddb7 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -267,25 +267,25 @@ def application_commands(self) -> Set[InvokableApplicationCommand]: @property def slash_commands(self) -> Set[InvokableSlashCommand]: """Set[:class:`InvokableSlashCommand`]: A set of all slash commands the bot has.""" - return set( + return { cmd for cmd in self.all_app_commands.values() if isinstance(cmd, InvokableSlashCommand) - ) + } @property def user_commands(self) -> Set[InvokableUserCommand]: """Set[:class:`InvokableUserCommand`]: A set of all user commands the bot has.""" - return set( + return { cmd for cmd in self.all_app_commands.values() if isinstance(cmd, InvokableUserCommand) - ) + } @property def message_commands(self) -> Set[InvokableMessageCommand]: """Set[:class:`InvokableMessageCommand`]: A set of all message commands the bot has.""" - return set( + return { cmd for cmd in self.all_app_commands.values() if isinstance(cmd, InvokableMessageCommand) - ) + } @property def all_slash_commands(self) -> Dict[str, InvokableSlashCommand]: @@ -348,7 +348,6 @@ def add_app_command(self, app_command: InvokableApplicationCommand) -> None: TypeError The app command passed is not an instance of :class:`InvokableApplicationCommand`. """ - if not isinstance(self, disnake.Client): raise NotImplementedError("This method is only usable in disnake.Client subclasses") @@ -475,7 +474,6 @@ def remove_app_command( Optional[:class:`InvokableApplicationCommand`] The app command that was removed. If the key data was not valid then ``None`` is returned instead. """ - if guild_ids is None: # a global command may end up being a local command if test_guilds were specified # so we should remove this "global" command from each test guild From dbd7920210bead4d60d855cd45382798c967c583 Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Mon, 18 Sep 2023 17:07:38 +0300 Subject: [PATCH 07/34] Fix docs references --- disnake/ext/commands/errors.py | 4 ++-- disnake/ext/commands/interaction_bot_base.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/disnake/ext/commands/errors.py b/disnake/ext/commands/errors.py index f2fdb2893f..e7ecd8cf28 100644 --- a/disnake/ext/commands/errors.py +++ b/disnake/ext/commands/errors.py @@ -1031,13 +1031,13 @@ class AppCommandRegistrationError(CommandRegistrationError): because a command with the same key already exists. A key is determined by command type, name, and guild_id. - This inherits from :exc:`disnake.CommandRegistrationError` + This inherits from :exc:`CommandRegistrationError` .. versionadded:: 2.10 Attributes ---------- - cmd_type: :class:`ApplicationCommandType` + cmd_type: :class:`disnake.ApplicationCommandType` The command type. name: :class:`str` The command name. diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 83f7b8ddb7..df7b00ecfa 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -462,7 +462,7 @@ def remove_app_command( Parameters ---------- - cmd_type: :class:`ApplicationCommandType` + cmd_type: :class:`disnake.ApplicationCommandType` The type of the app command to remove. name: :class:`str` The name of the app command to remove. From e86e5cb646a2c3ba99c2bb0318acd3f2fec51572 Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Tue, 19 Sep 2023 23:05:20 +0300 Subject: [PATCH 08/34] Add errors for ambiguous app command searches --- disnake/ext/commands/interaction_bot_base.py | 72 +++++++++++++++++--- 1 file changed, 64 insertions(+), 8 deletions(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index df7b00ecfa..eac94aec4e 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -149,6 +149,9 @@ def _format_diff(diff: _Diff) -> str: def _match_subcommand_chain( command: InvokableSlashCommand, chain: List[str] ) -> Union[InvokableSlashCommand, SubCommand, SubCommandGroup, None]: + """This is an internal function that returns a subcommand with a route matching the chain. + If there's no match then ``None`` is returned. + """ if command.name != chain[0]: return None if len(chain) == 1: @@ -332,7 +335,8 @@ def all_message_commands(self) -> Dict[str, InvokableMessageCommand]: def add_app_command(self, app_command: InvokableApplicationCommand) -> None: """Adds an :class:`InvokableApplicationCommand` into the internal list of app commands. - This is usually not called, instead shortcut decorators are used. + This is usually not called, instead shortcut decorators are used, such as + :meth:`.slash_command`, :meth:`.user_command` or :meth:`.message_command`. .. versionadded:: 2.10 @@ -583,6 +587,9 @@ def get_slash_command( ------ TypeError The name is not a string. + ValueError + Parameter ``guild_id`` was not provided in a case where different slash commands + have the same name but different guild_ids. Returns ------- @@ -601,13 +608,28 @@ def get_slash_command( if command is None: return None return _match_subcommand_chain(command, chain) # type: ignore + # this is mostly for backwards compatibility, as previously guild_id arg didn't exist + result = None for command in self.all_app_commands.values(): if not isinstance(command, InvokableSlashCommand): continue - result = _match_subcommand_chain(command, chain) - if result is not None: - return result + chain_match = _match_subcommand_chain(command, chain) + if chain_match is None: + continue + if result is None: + result = chain_match + continue + # we should check whether there's an ambiguity in command search + result_guild_ids = (result.root_parent or result).guild_ids + match_guild_ids = (chain_match.root_parent or chain_match).guild_ids + if result_guild_ids != match_guild_ids: + raise ValueError( + "Argument guild_id must be provided if there're different slash commands " + "with the same name but different guilds or one of them is global." + ) + + return result def get_user_command( self, name: str, guild_id: Optional[int] = MISSING @@ -623,6 +645,12 @@ def get_user_command( The guild ID corresponding to the user command or ``None`` if it's a global command. If this is not specified then the first match is returned instead. + Raises + ------ + ValueError + Parameter ``guild_id`` was not provided in a case where different user commands + have the same name but different guild_ids. + Returns ------- Optional[:class:`InvokableUserCommand`] @@ -635,9 +663,20 @@ def get_user_command( return None return command # type: ignore # this is mostly for backwards compatibility, as previously guild_id arg didn't exist + result = None for command in self.all_app_commands.values(): - if isinstance(command, InvokableUserCommand) and command.name == name: - return command + if not isinstance(command, InvokableUserCommand) or command.name != name: + continue + if result is None: + result = command + # we should check whether there's an ambiguity in command search + elif result.guild_ids != command.guild_ids: + raise ValueError( + "Argument guild_id must be provided if there're different user commands " + "with the same name but different guilds or one of them is global." + ) + + return result def get_message_command( self, name: str, guild_id: Optional[int] = MISSING @@ -653,6 +692,12 @@ def get_message_command( The guild ID corresponding to the message command or ``None`` if it's a global command. If this is not specified then the first match is returned instead. + Raises + ------ + ValueError + Parameter ``guild_id`` was not provided in a case where different message commands + have the same name but different guild_ids. + Returns ------- Optional[:class:`InvokableMessageCommand`] @@ -667,9 +712,20 @@ def get_message_command( return None return command # type: ignore # this is mostly for backwards compatibility, as previously guild_id arg didn't exist + result = None for command in self.all_app_commands.values(): - if isinstance(command, InvokableMessageCommand) and command.name == name: - return command + if not isinstance(command, InvokableMessageCommand) or command.name != name: + continue + if result is None: + result = command + # we should check whether there's an ambiguity in command search + elif result.guild_ids != command.guild_ids: + raise ValueError( + "Argument guild_id must be provided if there're different message commands " + "with the same name but different guilds or one of them is global." + ) + + return result def slash_command( self, From f80dbf69c52a9f07c5c398381e10a83128241058 Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Tue, 19 Sep 2023 23:07:21 +0300 Subject: [PATCH 09/34] First word of the docstring should not be "This" --- disnake/ext/commands/interaction_bot_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index eac94aec4e..576f047674 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -149,7 +149,7 @@ def _format_diff(diff: _Diff) -> str: def _match_subcommand_chain( command: InvokableSlashCommand, chain: List[str] ) -> Union[InvokableSlashCommand, SubCommand, SubCommandGroup, None]: - """This is an internal function that returns a subcommand with a route matching the chain. + """An internal function that returns a subcommand with a route matching the chain. If there's no match then ``None`` is returned. """ if command.name != chain[0]: From 4ed930d4b2ee55ec19506601c26279a5182e5a3a Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Fri, 22 Sep 2023 23:08:32 +0300 Subject: [PATCH 10/34] docs: group some changelog entries --- changelog/260.deprecate.0.rst | 4 +++- changelog/260.deprecate.1.rst | 4 +++- changelog/260.deprecate.2.rst | 4 +++- changelog/260.deprecate.3.rst | 1 - changelog/260.deprecate.4.rst | 1 - changelog/260.deprecate.5.rst | 1 - changelog/260.deprecate.6.rst | 1 - changelog/260.deprecate.7.rst | 1 - changelog/260.deprecate.8.rst | 1 - changelog/260.feature.0.rst | 2 +- changelog/260.feature.1.rst | 3 ++- changelog/260.feature.2.rst | 2 +- changelog/260.feature.3.rst | 1 - changelog/260.feature.4.rst | 1 - changelog/260.feature.5.rst | 1 - 15 files changed, 13 insertions(+), 15 deletions(-) delete mode 100644 changelog/260.deprecate.3.rst delete mode 100644 changelog/260.deprecate.4.rst delete mode 100644 changelog/260.deprecate.5.rst delete mode 100644 changelog/260.deprecate.6.rst delete mode 100644 changelog/260.deprecate.7.rst delete mode 100644 changelog/260.deprecate.8.rst delete mode 100644 changelog/260.feature.3.rst delete mode 100644 changelog/260.feature.4.rst delete mode 100644 changelog/260.feature.5.rst diff --git a/changelog/260.deprecate.0.rst b/changelog/260.deprecate.0.rst index 5f001a5a2f..6253334c55 100644 --- a/changelog/260.deprecate.0.rst +++ b/changelog/260.deprecate.0.rst @@ -1 +1,3 @@ -|commands| :attr:`InteractionBotBase.all_slash_commands` is deprecated. +|commands| :attr:`InteractionBot.all_slash_commands`, +:attr:`InteractionBot.all_user_commands`, +:attr:`InteractionBot.all_message_commands` are deprecated. diff --git a/changelog/260.deprecate.1.rst b/changelog/260.deprecate.1.rst index b8f41dbdff..4267953adb 100644 --- a/changelog/260.deprecate.1.rst +++ b/changelog/260.deprecate.1.rst @@ -1 +1,3 @@ -|commands| :attr:`InteractionBotBase.all_user_commands` is deprecated. +|commands| :meth:`InteractionBot.add_slash_command`, +:meth:`InteractionBot.add_user_command`, +:meth:`InteractionBot.add_message_command` are deprecated. diff --git a/changelog/260.deprecate.2.rst b/changelog/260.deprecate.2.rst index 7eb5aad372..35848c65b7 100644 --- a/changelog/260.deprecate.2.rst +++ b/changelog/260.deprecate.2.rst @@ -1 +1,3 @@ -|commands| :attr:`InteractionBotBase.all_message_commands` is deprecated. +|commands| :meth:`InteractionBot.remove_slash_command`, +:meth:`InteractionBot.remove_user_command`, +:meth:`InteractionBot.remove_message_command` are deprecated. diff --git a/changelog/260.deprecate.3.rst b/changelog/260.deprecate.3.rst deleted file mode 100644 index bd6cfc053d..0000000000 --- a/changelog/260.deprecate.3.rst +++ /dev/null @@ -1 +0,0 @@ -|commands| :meth:`InteractionBotBase.add_slash_command` is deprecated. diff --git a/changelog/260.deprecate.4.rst b/changelog/260.deprecate.4.rst deleted file mode 100644 index 371f949640..0000000000 --- a/changelog/260.deprecate.4.rst +++ /dev/null @@ -1 +0,0 @@ -|commands| :meth:`InteractionBotBase.add_user_command` is deprecated. diff --git a/changelog/260.deprecate.5.rst b/changelog/260.deprecate.5.rst deleted file mode 100644 index ccd4e28be5..0000000000 --- a/changelog/260.deprecate.5.rst +++ /dev/null @@ -1 +0,0 @@ -|commands| :meth:`InteractionBotBase.add_message_command` is deprecated. diff --git a/changelog/260.deprecate.6.rst b/changelog/260.deprecate.6.rst deleted file mode 100644 index 117b504a0a..0000000000 --- a/changelog/260.deprecate.6.rst +++ /dev/null @@ -1 +0,0 @@ -|commands| :meth:`InteractionBotBase.remove_slash_command` is deprecated. diff --git a/changelog/260.deprecate.7.rst b/changelog/260.deprecate.7.rst deleted file mode 100644 index c1930a4bc2..0000000000 --- a/changelog/260.deprecate.7.rst +++ /dev/null @@ -1 +0,0 @@ -|commands| :meth:`InteractionBotBase.remove_user_command` is deprecated. diff --git a/changelog/260.deprecate.8.rst b/changelog/260.deprecate.8.rst deleted file mode 100644 index c45e1eee27..0000000000 --- a/changelog/260.deprecate.8.rst +++ /dev/null @@ -1 +0,0 @@ -|commands| :meth:`InteractionBotBase.remove_message_command` is deprecated. diff --git a/changelog/260.feature.0.rst b/changelog/260.feature.0.rst index b10c4b3332..a9d250f21d 100644 --- a/changelog/260.feature.0.rst +++ b/changelog/260.feature.0.rst @@ -1 +1 @@ -|commands| Add :meth:`InteractionBotBase.add_app_command`. +|commands| Add :meth:`InteractionBot.add_app_command` and :meth:`InteractionBot.remove_app_command`. diff --git a/changelog/260.feature.1.rst b/changelog/260.feature.1.rst index e6a715b02b..494986d99b 100644 --- a/changelog/260.feature.1.rst +++ b/changelog/260.feature.1.rst @@ -1 +1,2 @@ -|commands| Add :meth:`InteractionBotBase.remove_app_command`. +|commands| Add ``guild_id`` parameter to :meth:`InteractionBot.get_slash_command`, +:meth:`InteractionBot.get_user_command`, and :meth:`InteractionBot.get_message_command`. diff --git a/changelog/260.feature.2.rst b/changelog/260.feature.2.rst index aa06ab03e5..fb1c42fdee 100644 --- a/changelog/260.feature.2.rst +++ b/changelog/260.feature.2.rst @@ -1 +1 @@ -|commands| Add ``guild_id`` parameter to :meth:`InteractionBotBase.get_slash_command`. +|commands| Add :class:`AppCommandRegistrationError`. diff --git a/changelog/260.feature.3.rst b/changelog/260.feature.3.rst deleted file mode 100644 index da14bd48b8..0000000000 --- a/changelog/260.feature.3.rst +++ /dev/null @@ -1 +0,0 @@ -|commands| Add ``guild_id`` parameter to :meth:`InteractionBotBase.get_user_command`. diff --git a/changelog/260.feature.4.rst b/changelog/260.feature.4.rst deleted file mode 100644 index 6523484883..0000000000 --- a/changelog/260.feature.4.rst +++ /dev/null @@ -1 +0,0 @@ -|commands| Add ``guild_id`` parameter to :meth:`InteractionBotBase.get_message_command`. diff --git a/changelog/260.feature.5.rst b/changelog/260.feature.5.rst deleted file mode 100644 index fb1c42fdee..0000000000 --- a/changelog/260.feature.5.rst +++ /dev/null @@ -1 +0,0 @@ -|commands| Add :class:`AppCommandRegistrationError`. From 192d2b93f1c6ed5969635c9db7f13d29f880badc Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Fri, 22 Sep 2023 23:14:32 +0300 Subject: [PATCH 11/34] chore: rename `AppCommandRegistrationError` Also adds `alias_conflict` attribute for backwards compatibility --- changelog/260.feature.2.rst | 2 +- disnake/ext/commands/errors.py | 6 ++++-- disnake/ext/commands/interaction_bot_base.py | 6 +++--- docs/ext/commands/api/exceptions.rst | 3 ++- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/changelog/260.feature.2.rst b/changelog/260.feature.2.rst index fb1c42fdee..7abeb8d277 100644 --- a/changelog/260.feature.2.rst +++ b/changelog/260.feature.2.rst @@ -1 +1 @@ -|commands| Add :class:`AppCommandRegistrationError`. +|commands| Add :class:`ApplicationCommandRegistrationError`. diff --git a/disnake/ext/commands/errors.py b/disnake/ext/commands/errors.py index e7ecd8cf28..515951ffda 100644 --- a/disnake/ext/commands/errors.py +++ b/disnake/ext/commands/errors.py @@ -75,7 +75,7 @@ "ExtensionFailed", "ExtensionNotFound", "CommandRegistrationError", - "AppCommandRegistrationError", + "ApplicationCommandRegistrationError", "FlagError", "BadFlagArgument", "MissingFlagArgument", @@ -1026,7 +1026,7 @@ def __init__(self, name: str, *, alias_conflict: bool = False) -> None: # we inherit CommandRegistrationError for backwards compatibility, # because this error replaced CommandRegistrationError in several places -class AppCommandRegistrationError(CommandRegistrationError): +class ApplicationCommandRegistrationError(CommandRegistrationError): """An exception raised when the app command can't be added because a command with the same key already exists. A key is determined by command type, name, and guild_id. @@ -1052,6 +1052,8 @@ def __init__( self.cmd_type: ApplicationCommandType = cmd_type self.name: str = name self.guild_id: Optional[int] = guild_id + # backwards compatibility + self.alias_conflict: bool = False # fixed API naming here because no one calls slash commands "chat input" type_ = "slash" if cmd_type is ApplicationCommandType.chat_input else cmd_type.name if guild_id is None: diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 576f047674..4c69f06335 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -40,7 +40,7 @@ message_command, user_command, ) -from .errors import AppCommandRegistrationError +from .errors import ApplicationCommandRegistrationError from .flags import CommandSyncFlags from .slash_core import InvokableSlashCommand, SubCommand, SubCommandGroup, slash_command @@ -347,7 +347,7 @@ def add_app_command(self, app_command: InvokableApplicationCommand) -> None: Raises ------ - AppCommandRegistrationError + ApplicationCommandRegistrationError The app command is already registered. TypeError The app command passed is not an instance of :class:`InvokableApplicationCommand`. @@ -371,7 +371,7 @@ def add_app_command(self, app_command: InvokableApplicationCommand) -> None: type=app_command.body.type, name=app_command.name, guild_id=guild_id ) if cmd_index in self.all_app_commands: - raise AppCommandRegistrationError( + raise ApplicationCommandRegistrationError( cmd_index.type, cmd_index.name, cmd_index.guild_id ) diff --git a/docs/ext/commands/api/exceptions.rst b/docs/ext/commands/api/exceptions.rst index 4dd874f51e..094ac5dbbc 100644 --- a/docs/ext/commands/api/exceptions.rst +++ b/docs/ext/commands/api/exceptions.rst @@ -186,7 +186,7 @@ Exceptions .. autoexception:: CommandRegistrationError :members: -.. autoexception:: AppCommandRegistrationError +.. autoexception:: ApplicationCommandRegistrationError :members: @@ -255,6 +255,7 @@ Exception Hierarchy - :exc:`ExtensionNotFound` - :exc:`~.ClientException` - :exc:`CommandRegistrationError` + - :exc:`ApplicationCommandRegistrationError` Warnings -------- From 686bd488c6cd3c5ce853b77ac4bc73d23fab7966 Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Sat, 23 Sep 2023 02:32:02 +0300 Subject: [PATCH 12/34] docs: fix references in changelog entries --- changelog/260.deprecate.0.rst | 4 +--- changelog/260.deprecate.1.rst | 4 +--- changelog/260.deprecate.2.rst | 4 +--- changelog/260.feature.0.rst | 2 +- changelog/260.feature.1.rst | 3 +-- changelog/260.feature.2.rst | 2 +- 6 files changed, 6 insertions(+), 13 deletions(-) diff --git a/changelog/260.deprecate.0.rst b/changelog/260.deprecate.0.rst index 6253334c55..51658a46cd 100644 --- a/changelog/260.deprecate.0.rst +++ b/changelog/260.deprecate.0.rst @@ -1,3 +1 @@ -|commands| :attr:`InteractionBot.all_slash_commands`, -:attr:`InteractionBot.all_user_commands`, -:attr:`InteractionBot.all_message_commands` are deprecated. +|commands| :attr:`.ext.commands.InteractionBot.all_slash_commands`, :attr:`.ext.commands.InteractionBot.all_user_commands`, :attr:`.ext.commands.InteractionBot.all_message_commands` are deprecated. diff --git a/changelog/260.deprecate.1.rst b/changelog/260.deprecate.1.rst index 4267953adb..d383c8394b 100644 --- a/changelog/260.deprecate.1.rst +++ b/changelog/260.deprecate.1.rst @@ -1,3 +1 @@ -|commands| :meth:`InteractionBot.add_slash_command`, -:meth:`InteractionBot.add_user_command`, -:meth:`InteractionBot.add_message_command` are deprecated. +|commands| :meth:`.ext.commands.InteractionBot.add_slash_command`, :meth:`.ext.commands.InteractionBot.add_user_command`, :meth:`.ext.commands.InteractionBot.add_message_command` are deprecated. diff --git a/changelog/260.deprecate.2.rst b/changelog/260.deprecate.2.rst index 35848c65b7..bf20c12a10 100644 --- a/changelog/260.deprecate.2.rst +++ b/changelog/260.deprecate.2.rst @@ -1,3 +1 @@ -|commands| :meth:`InteractionBot.remove_slash_command`, -:meth:`InteractionBot.remove_user_command`, -:meth:`InteractionBot.remove_message_command` are deprecated. +|commands| :meth:`.ext.commands.InteractionBot.remove_slash_command`, :meth:`.ext.commands.InteractionBot.remove_user_command`, :meth:`.ext.commands.InteractionBot.remove_message_command` are deprecated. diff --git a/changelog/260.feature.0.rst b/changelog/260.feature.0.rst index a9d250f21d..2c5ae38652 100644 --- a/changelog/260.feature.0.rst +++ b/changelog/260.feature.0.rst @@ -1 +1 @@ -|commands| Add :meth:`InteractionBot.add_app_command` and :meth:`InteractionBot.remove_app_command`. +|commands| Add :meth:`.ext.commands.InteractionBot.add_app_command` and :meth:`.ext.commands.InteractionBot.remove_app_command`. diff --git a/changelog/260.feature.1.rst b/changelog/260.feature.1.rst index 494986d99b..e48cb5a22e 100644 --- a/changelog/260.feature.1.rst +++ b/changelog/260.feature.1.rst @@ -1,2 +1 @@ -|commands| Add ``guild_id`` parameter to :meth:`InteractionBot.get_slash_command`, -:meth:`InteractionBot.get_user_command`, and :meth:`InteractionBot.get_message_command`. +|commands| Add ``guild_id`` parameter to :meth:`.ext.commands.InteractionBot.get_slash_command`, :meth:`.ext.commands.InteractionBot.get_user_command`, and :meth:`.ext.commands.InteractionBot.get_message_command`. diff --git a/changelog/260.feature.2.rst b/changelog/260.feature.2.rst index 7abeb8d277..191f973b7e 100644 --- a/changelog/260.feature.2.rst +++ b/changelog/260.feature.2.rst @@ -1 +1 @@ -|commands| Add :class:`ApplicationCommandRegistrationError`. +|commands| Add :class:`.ext.commands.ApplicationCommandRegistrationError`. From 8b5916c266581c3e877651299b648b5a9afe44f8 Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Sat, 23 Sep 2023 02:41:41 +0300 Subject: [PATCH 13/34] docs: hide the `ext.commands` prefix in changelog entries --- changelog/260.deprecate.0.rst | 2 +- changelog/260.deprecate.1.rst | 2 +- changelog/260.deprecate.2.rst | 2 +- changelog/260.feature.0.rst | 2 +- changelog/260.feature.1.rst | 2 +- changelog/260.feature.2.rst | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/changelog/260.deprecate.0.rst b/changelog/260.deprecate.0.rst index 51658a46cd..d268a8b7c0 100644 --- a/changelog/260.deprecate.0.rst +++ b/changelog/260.deprecate.0.rst @@ -1 +1 @@ -|commands| :attr:`.ext.commands.InteractionBot.all_slash_commands`, :attr:`.ext.commands.InteractionBot.all_user_commands`, :attr:`.ext.commands.InteractionBot.all_message_commands` are deprecated. +|commands| :attr:`~disnake.ext.commands.InteractionBot.all_slash_commands`, :attr:`~disnake.ext.commands.InteractionBot.all_user_commands`, :attr:`~disnake.ext.commands.InteractionBot.all_message_commands` are deprecated. diff --git a/changelog/260.deprecate.1.rst b/changelog/260.deprecate.1.rst index d383c8394b..016ce80e2a 100644 --- a/changelog/260.deprecate.1.rst +++ b/changelog/260.deprecate.1.rst @@ -1 +1 @@ -|commands| :meth:`.ext.commands.InteractionBot.add_slash_command`, :meth:`.ext.commands.InteractionBot.add_user_command`, :meth:`.ext.commands.InteractionBot.add_message_command` are deprecated. +|commands| :meth:`~disnake.ext.commands.InteractionBot.add_slash_command`, :meth:`~disnake.ext.commands.InteractionBot.add_user_command`, :meth:`~disnake.ext.commands.InteractionBot.add_message_command` are deprecated. diff --git a/changelog/260.deprecate.2.rst b/changelog/260.deprecate.2.rst index bf20c12a10..d59cb2a5da 100644 --- a/changelog/260.deprecate.2.rst +++ b/changelog/260.deprecate.2.rst @@ -1 +1 @@ -|commands| :meth:`.ext.commands.InteractionBot.remove_slash_command`, :meth:`.ext.commands.InteractionBot.remove_user_command`, :meth:`.ext.commands.InteractionBot.remove_message_command` are deprecated. +|commands| :meth:`~disnake.ext.commands.InteractionBot.remove_slash_command`, :meth:`~disnake.ext.commands.InteractionBot.remove_user_command`, :meth:`~disnake.ext.commands.InteractionBot.remove_message_command` are deprecated. diff --git a/changelog/260.feature.0.rst b/changelog/260.feature.0.rst index 2c5ae38652..2d7b1e472c 100644 --- a/changelog/260.feature.0.rst +++ b/changelog/260.feature.0.rst @@ -1 +1 @@ -|commands| Add :meth:`.ext.commands.InteractionBot.add_app_command` and :meth:`.ext.commands.InteractionBot.remove_app_command`. +|commands| Add :meth:`~disnake.ext.commands.InteractionBot.add_app_command` and :meth:`~disnake.ext.commands.InteractionBot.remove_app_command`. diff --git a/changelog/260.feature.1.rst b/changelog/260.feature.1.rst index e48cb5a22e..6961119fd3 100644 --- a/changelog/260.feature.1.rst +++ b/changelog/260.feature.1.rst @@ -1 +1 @@ -|commands| Add ``guild_id`` parameter to :meth:`.ext.commands.InteractionBot.get_slash_command`, :meth:`.ext.commands.InteractionBot.get_user_command`, and :meth:`.ext.commands.InteractionBot.get_message_command`. +|commands| Add ``guild_id`` parameter to :meth:`~disnake.ext.commands.InteractionBot.get_slash_command`, :meth:`~disnake.ext.commands.InteractionBot.get_user_command`, and :meth:`~disnake.ext.commands.InteractionBot.get_message_command`. diff --git a/changelog/260.feature.2.rst b/changelog/260.feature.2.rst index 191f973b7e..b04d1d380d 100644 --- a/changelog/260.feature.2.rst +++ b/changelog/260.feature.2.rst @@ -1 +1 @@ -|commands| Add :class:`.ext.commands.ApplicationCommandRegistrationError`. +|commands| Add :class:`~disnake.ext.commands.ApplicationCommandRegistrationError`. From 88584c71087b971ddacb291f32fa49fee4955697 Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Sat, 23 Sep 2023 10:58:03 +0300 Subject: [PATCH 14/34] docs: revert the removal of `ext.commands` prefix --- changelog/260.deprecate.0.rst | 2 +- changelog/260.deprecate.1.rst | 2 +- changelog/260.deprecate.2.rst | 2 +- changelog/260.feature.0.rst | 2 +- changelog/260.feature.1.rst | 2 +- changelog/260.feature.2.rst | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/changelog/260.deprecate.0.rst b/changelog/260.deprecate.0.rst index d268a8b7c0..51658a46cd 100644 --- a/changelog/260.deprecate.0.rst +++ b/changelog/260.deprecate.0.rst @@ -1 +1 @@ -|commands| :attr:`~disnake.ext.commands.InteractionBot.all_slash_commands`, :attr:`~disnake.ext.commands.InteractionBot.all_user_commands`, :attr:`~disnake.ext.commands.InteractionBot.all_message_commands` are deprecated. +|commands| :attr:`.ext.commands.InteractionBot.all_slash_commands`, :attr:`.ext.commands.InteractionBot.all_user_commands`, :attr:`.ext.commands.InteractionBot.all_message_commands` are deprecated. diff --git a/changelog/260.deprecate.1.rst b/changelog/260.deprecate.1.rst index 016ce80e2a..d383c8394b 100644 --- a/changelog/260.deprecate.1.rst +++ b/changelog/260.deprecate.1.rst @@ -1 +1 @@ -|commands| :meth:`~disnake.ext.commands.InteractionBot.add_slash_command`, :meth:`~disnake.ext.commands.InteractionBot.add_user_command`, :meth:`~disnake.ext.commands.InteractionBot.add_message_command` are deprecated. +|commands| :meth:`.ext.commands.InteractionBot.add_slash_command`, :meth:`.ext.commands.InteractionBot.add_user_command`, :meth:`.ext.commands.InteractionBot.add_message_command` are deprecated. diff --git a/changelog/260.deprecate.2.rst b/changelog/260.deprecate.2.rst index d59cb2a5da..bf20c12a10 100644 --- a/changelog/260.deprecate.2.rst +++ b/changelog/260.deprecate.2.rst @@ -1 +1 @@ -|commands| :meth:`~disnake.ext.commands.InteractionBot.remove_slash_command`, :meth:`~disnake.ext.commands.InteractionBot.remove_user_command`, :meth:`~disnake.ext.commands.InteractionBot.remove_message_command` are deprecated. +|commands| :meth:`.ext.commands.InteractionBot.remove_slash_command`, :meth:`.ext.commands.InteractionBot.remove_user_command`, :meth:`.ext.commands.InteractionBot.remove_message_command` are deprecated. diff --git a/changelog/260.feature.0.rst b/changelog/260.feature.0.rst index 2d7b1e472c..2c5ae38652 100644 --- a/changelog/260.feature.0.rst +++ b/changelog/260.feature.0.rst @@ -1 +1 @@ -|commands| Add :meth:`~disnake.ext.commands.InteractionBot.add_app_command` and :meth:`~disnake.ext.commands.InteractionBot.remove_app_command`. +|commands| Add :meth:`.ext.commands.InteractionBot.add_app_command` and :meth:`.ext.commands.InteractionBot.remove_app_command`. diff --git a/changelog/260.feature.1.rst b/changelog/260.feature.1.rst index 6961119fd3..e48cb5a22e 100644 --- a/changelog/260.feature.1.rst +++ b/changelog/260.feature.1.rst @@ -1 +1 @@ -|commands| Add ``guild_id`` parameter to :meth:`~disnake.ext.commands.InteractionBot.get_slash_command`, :meth:`~disnake.ext.commands.InteractionBot.get_user_command`, and :meth:`~disnake.ext.commands.InteractionBot.get_message_command`. +|commands| Add ``guild_id`` parameter to :meth:`.ext.commands.InteractionBot.get_slash_command`, :meth:`.ext.commands.InteractionBot.get_user_command`, and :meth:`.ext.commands.InteractionBot.get_message_command`. diff --git a/changelog/260.feature.2.rst b/changelog/260.feature.2.rst index b04d1d380d..191f973b7e 100644 --- a/changelog/260.feature.2.rst +++ b/changelog/260.feature.2.rst @@ -1 +1 @@ -|commands| Add :class:`~disnake.ext.commands.ApplicationCommandRegistrationError`. +|commands| Add :class:`.ext.commands.ApplicationCommandRegistrationError`. From 1f10a8df8f67b47d2ebb5257b82a2bd346227421 Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Sun, 24 Sep 2023 01:39:14 +0300 Subject: [PATCH 15/34] fix: subcommands trying to be top level commands --- disnake/ext/commands/cog.py | 4 +++- disnake/ext/commands/interaction_bot_base.py | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/disnake/ext/commands/cog.py b/disnake/ext/commands/cog.py index 6c40a4bd6e..b21a1375bd 100644 --- a/disnake/ext/commands/cog.py +++ b/disnake/ext/commands/cog.py @@ -27,7 +27,7 @@ from ._types import _BaseCommand from .base_core import InvokableApplicationCommand from .ctx_menus_core import InvokableMessageCommand, InvokableUserCommand -from .slash_core import InvokableSlashCommand +from .slash_core import InvokableSlashCommand, SubCommand, SubCommandGroup if TYPE_CHECKING: from typing_extensions import Self @@ -753,6 +753,8 @@ def _inject(self, bot: AnyBot) -> Self: for index, command in enumerate(self.__cog_app_commands__): command.cog = self + if isinstance(command, (SubCommand, SubCommandGroup)): + continue try: bot.add_app_command(command) except Exception: diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 4c69f06335..35792f9941 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -359,6 +359,11 @@ def add_app_command(self, app_command: InvokableApplicationCommand) -> None: raise TypeError( "The app_command passed must be an instance of InvokableApplicationCommand" ) + if isinstance(app_command, (SubCommand, SubCommandGroup)): + raise TypeError( + "The app_command passed must be a top level command, " + "not an instance of SubCommand or SubCommandGroup" + ) if app_command.guild_ids is None: # if test_guilds are specified then we add the same command for each test guild From f5d5d5777a9abb4ca7f6122c28293076b1329d7c Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Sun, 24 Sep 2023 01:46:41 +0300 Subject: [PATCH 16/34] chore: remove deprecated undocumented things --- disnake/ext/commands/interaction_bot_base.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 35792f9941..4aaf725ab1 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -254,14 +254,6 @@ def command_sync_flags(self) -> CommandSyncFlags: """ return CommandSyncFlags._from_value(self._command_sync_flags.value) - def application_commands_iterator(self) -> Iterable[InvokableApplicationCommand]: - warn_deprecated( - "application_commands_iterator is deprecated and will be removed in a future version. " - "Use all_app_commands.values() instead.", - stacklevel=3, - ) - return self.all_app_commands.values() - @property def application_commands(self) -> Set[InvokableApplicationCommand]: """Set[:class:`InvokableApplicationCommand`]: A set of all application commands the bot has.""" @@ -983,15 +975,8 @@ def decorator( # command synchronisation def _ordered_unsynced_commands( - self, test_guilds: Optional[Sequence[int]] = MISSING + self ) -> Tuple[List[ApplicationCommand], Dict[int, List[ApplicationCommand]]]: - if test_guilds is not MISSING: - warn_deprecated( - "Argument test_guilds of _ordered_unsynced_commands is deprecated " - "and will be removed in a future version.", - stacklevel=3, - ) - global_cmds = [] guilds = {} From 164df505900bc1ec5d4436a87a69d7658a191236 Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Sun, 24 Sep 2023 01:48:39 +0300 Subject: [PATCH 17/34] chore: fix a small linting issue --- disnake/ext/commands/interaction_bot_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 4aaf725ab1..fb1e910f14 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -975,7 +975,7 @@ def decorator( # command synchronisation def _ordered_unsynced_commands( - self + self, ) -> Tuple[List[ApplicationCommand], Dict[int, List[ApplicationCommand]]]: global_cmds = [] guilds = {} From 0064e9a0f037381cef3d249e0fc41b2470eadbe1 Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Sun, 24 Sep 2023 23:44:49 +0300 Subject: [PATCH 18/34] chore: replace `warn_deprecated` with `deprecated ` --- disnake/ext/commands/interaction_bot_base.py | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index fb1e910f14..cfd9613619 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -29,7 +29,7 @@ from disnake.app_commands import ApplicationCommand, Option from disnake.custom_warnings import SyncWarning from disnake.enums import ApplicationCommandType -from disnake.utils import warn_deprecated +from disnake.utils import deprecated, warn_deprecated from . import errors from .base_core import InvokableApplicationCommand @@ -283,13 +283,9 @@ def message_commands(self) -> Set[InvokableMessageCommand]: } @property + @deprecated("slash_commands") def all_slash_commands(self) -> Dict[str, InvokableSlashCommand]: # no docstring because it was an attribute and now it's deprecated - warn_deprecated( - "all_slash_commands is deprecated and will be removed in a future version. " - "Use all_app_commands as a replacement.", - stacklevel=3, - ) return { cmd.name: cmd for cmd in self.all_app_commands.values() @@ -297,13 +293,9 @@ def all_slash_commands(self) -> Dict[str, InvokableSlashCommand]: } @property + @deprecated("user_commands") def all_user_commands(self) -> Dict[str, InvokableUserCommand]: # no docstring because it was an attribute and now it's deprecated - warn_deprecated( - "all_user_commands is deprecated and will be removed in a future version. " - "Use all_app_commands as a replacement.", - stacklevel=3, - ) return { cmd.name: cmd for cmd in self.all_app_commands.values() @@ -311,13 +303,9 @@ def all_user_commands(self) -> Dict[str, InvokableUserCommand]: } @property + @deprecated("message_commands") def all_message_commands(self) -> Dict[str, InvokableMessageCommand]: # no docstring because it was an attribute and now it's deprecated - warn_deprecated( - "all_message_commands is deprecated and will be removed in a future version. " - "Use all_app_commands as a replacement.", - stacklevel=3, - ) return { cmd.name: cmd for cmd in self.all_app_commands.values() From 4736dad01e6f79345cca53816fe5aa8cda0828c6 Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Mon, 25 Sep 2023 00:31:32 +0300 Subject: [PATCH 19/34] chore: resolve `remove_app_command` nits --- disnake/ext/commands/cog.py | 6 ++++-- disnake/ext/commands/interaction_bot_base.py | 9 +++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/disnake/ext/commands/cog.py b/disnake/ext/commands/cog.py index b21a1375bd..53a2799bf4 100644 --- a/disnake/ext/commands/cog.py +++ b/disnake/ext/commands/cog.py @@ -760,7 +760,9 @@ def _inject(self, bot: AnyBot) -> Self: except Exception: # undo our additions for to_undo in self.__cog_app_commands__[:index]: - bot.remove_app_command(to_undo.body.type, to_undo.name, to_undo.guild_ids) + bot.remove_app_command( + to_undo.body.type, to_undo.name, guild_ids=to_undo.guild_ids + ) raise if not hasattr(self.cog_load.__func__, "__cog_special_method__"): @@ -834,7 +836,7 @@ def _eject(self, bot: AnyBot) -> None: for app_command in self.__cog_app_commands__: bot.remove_app_command( - app_command.body.type, app_command.name, app_command.guild_ids + app_command.body.type, app_command.name, guild_ids=app_command.guild_ids ) for name, method_name in self.__cog_listeners__: diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index cfd9613619..83cc6e73ce 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -443,7 +443,7 @@ def add_message_command(self, message_command: InvokableMessageCommand) -> None: self.add_app_command(message_command) def remove_app_command( - self, cmd_type: ApplicationCommandType, name: str, guild_ids: Optional[Tuple[int, ...]] + self, cmd_type: ApplicationCommandType, name: str, *, guild_ids: Optional[Sequence[int]] ) -> Optional[InvokableApplicationCommand]: """Removes an :class:`InvokableApplicationCommand` from the internal list of app commands. @@ -455,13 +455,14 @@ def remove_app_command( The type of the app command to remove. name: :class:`str` The name of the app command to remove. - guild_ids: Optional[Tuple[:class:`str`, ...]] - The IDs of the guilds from which the command should be removed. + guild_ids: Optional[Sequence[:class:`int`]] + The IDs of the guilds from which the command should be removed, + or ``None`` if global. Defaults to ``None``. Returns ------- Optional[:class:`InvokableApplicationCommand`] - The app command that was removed. If the key data was not valid then ``None`` is returned instead. + The app command that was removed. If no matching command was found, then ``None`` is returned instead. """ if guild_ids is None: # a global command may end up being a local command if test_guilds were specified From 8c6bb90659ee0f8a54722938cff65773420d0fb0 Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Mon, 25 Sep 2023 00:42:07 +0300 Subject: [PATCH 20/34] chore: add suggested typings --- disnake/ext/commands/interaction_bot_base.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 83cc6e73ce..e1c5835774 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -490,12 +490,12 @@ def _emulate_old_app_command_remove(self, cmd_type: ApplicationCommandType, name "Use remove_app_command instead.", stacklevel=3, ) - bad_keys = [] + bad_keys: List[AppCmdIndex] = [] for key in self.all_app_commands.keys(): if key.type is cmd_type and key.name == name: bad_keys.append(key) - result = None + result: Optional[InvokableApplicationCommand] = None for key in bad_keys: cmd = self.all_app_commands.pop(key, None) if result is None: @@ -517,7 +517,7 @@ def remove_slash_command(self, name: str) -> Optional[InvokableSlashCommand]: Optional[:class:`InvokableSlashCommand`] The slash command that was removed. If the name is not valid then ``None`` is returned instead. """ - self._emulate_old_app_command_remove(ApplicationCommandType.chat_input, name) + return self._emulate_old_app_command_remove(ApplicationCommandType.chat_input, name) def remove_user_command(self, name: str) -> Optional[InvokableUserCommand]: """Removes an :class:`InvokableUserCommand` from the internal list @@ -533,7 +533,7 @@ def remove_user_command(self, name: str) -> Optional[InvokableUserCommand]: Optional[:class:`InvokableUserCommand`] The user command that was removed. If the name is not valid then ``None`` is returned instead. """ - self._emulate_old_app_command_remove(ApplicationCommandType.user, name) + return self._emulate_old_app_command_remove(ApplicationCommandType.user, name) def remove_message_command(self, name: str) -> Optional[InvokableMessageCommand]: """Removes an :class:`InvokableMessageCommand` from the internal list @@ -549,7 +549,7 @@ def remove_message_command(self, name: str) -> Optional[InvokableMessageCommand] Optional[:class:`InvokableMessageCommand`] The message command that was removed. If the name is not valid then ``None`` is returned instead. """ - self._emulate_old_app_command_remove(ApplicationCommandType.message, name) + return self._emulate_old_app_command_remove(ApplicationCommandType.message, name) def get_slash_command( self, name: str, guild_id: Optional[int] = MISSING @@ -966,8 +966,8 @@ def decorator( def _ordered_unsynced_commands( self, ) -> Tuple[List[ApplicationCommand], Dict[int, List[ApplicationCommand]]]: - global_cmds = [] - guilds = {} + global_cmds: List[ApplicationCommand] = [] + guilds: Dict[int, List[ApplicationCommand]] = {} for key, cmd in self.all_app_commands.items(): if not cmd.auto_sync: From c44806b42cbd6a815d2343cbc41313231c57a887 Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Mon, 25 Sep 2023 00:49:20 +0300 Subject: [PATCH 21/34] docs: fix typos and improve comments --- disnake/ext/commands/interaction_bot_base.py | 4 ++++ disnake/interactions/application_command.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index e1c5835774..9f122e2eba 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -1509,6 +1509,8 @@ async def process_app_command_autocompletion( inter: :class:`disnake.ApplicationCommandInteraction` The interaction to process. """ + # `inter.data.guild_id` is the guild ID the command is registered to, + # so this is correct even when a global command is called from a guild cmd_index = AppCmdIndex( type=inter.data.type, name=inter.data.name, guild_id=inter.data.guild_id ) @@ -1587,6 +1589,8 @@ async def process_application_commands( command_type = interaction.data.type event_name = None + # `inter.data.guild_id` is the guild ID the command is registered to, + # so this is correct even when a global command is called from a guild cmd_index = AppCmdIndex( type=command_type, name=interaction.data.name, guild_id=interaction.data.guild_id ) diff --git a/disnake/interactions/application_command.py b/disnake/interactions/application_command.py index 8fb465d7c6..d5248176fd 100644 --- a/disnake/interactions/application_command.py +++ b/disnake/interactions/application_command.py @@ -177,9 +177,9 @@ class ApplicationCommandInteractionData(Dict[str, Any]): guild_id: Optional[:class:`int`] ID of the guild the command is registered to. target_id: :class:`int` - ID of the user or message targetted by a user or message command. + ID of the user or message targeted by a user or message command. target: Union[:class:`User`, :class:`Member`, :class:`Message`] - The user or message targetted by a user or message command. + The user or message targeted by a user or message command. """ __slots__ = ( From ef591e697034296f44319adcc3abbe2255a6017b Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Mon, 25 Sep 2023 00:58:23 +0300 Subject: [PATCH 22/34] chore: simplify ambiguity checks --- disnake/ext/commands/interaction_bot_base.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 9f122e2eba..e3a1200d9d 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -605,11 +605,8 @@ def get_slash_command( continue if result is None: result = chain_match - continue # we should check whether there's an ambiguity in command search - result_guild_ids = (result.root_parent or result).guild_ids - match_guild_ids = (chain_match.root_parent or chain_match).guild_ids - if result_guild_ids != match_guild_ids: + elif chain_match is not result: raise ValueError( "Argument guild_id must be provided if there're different slash commands " "with the same name but different guilds or one of them is global." @@ -656,7 +653,7 @@ def get_user_command( if result is None: result = command # we should check whether there's an ambiguity in command search - elif result.guild_ids != command.guild_ids: + elif command is not result: raise ValueError( "Argument guild_id must be provided if there're different user commands " "with the same name but different guilds or one of them is global." @@ -705,7 +702,7 @@ def get_message_command( if result is None: result = command # we should check whether there's an ambiguity in command search - elif result.guild_ids != command.guild_ids: + elif command is not result: raise ValueError( "Argument guild_id must be provided if there're different message commands " "with the same name but different guilds or one of them is global." From 42c7e6f0e09911c4786e581653b885c2023b4bc4 Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Tue, 26 Sep 2023 00:24:22 +0300 Subject: [PATCH 23/34] docs: explain command location priorities --- disnake/ext/commands/ctx_menus_core.py | 16 +++++++---- disnake/ext/commands/interaction_bot_base.py | 29 ++++++++++++++------ disnake/ext/commands/slash_core.py | 8 ++++-- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/disnake/ext/commands/ctx_menus_core.py b/disnake/ext/commands/ctx_menus_core.py index 835bd674e6..cee4073d23 100644 --- a/disnake/ext/commands/ctx_menus_core.py +++ b/disnake/ext/commands/ctx_menus_core.py @@ -266,9 +266,11 @@ def user_command( auto_sync: :class:`bool` Whether to automatically register the command. Defaults to ``True``. - guild_ids: Sequence[:class:`int`] - If specified, the client will register the command in these guilds. - Otherwise, this command will be registered globally. + guild_ids: Optional[Sequence[:class:`int`]] + If specified, the client will register the command to these guilds. + Otherwise the command will be registered globally, unless + parameter ``test_guilds`` is specified in the bot constructor, in which case + this command will be registered to those guilds. extras: Dict[:class:`str`, Any] A dict of user provided extras to attach to the command. @@ -348,9 +350,11 @@ def message_command( auto_sync: :class:`bool` Whether to automatically register the command. Defaults to ``True``. - guild_ids: Sequence[:class:`int`] - If specified, the client will register the command in these guilds. - Otherwise, this command will be registered globally. + guild_ids: Optional[Sequence[:class:`int`]] + If specified, the client will register the command to these guilds. + Otherwise the command will be registered globally, unless + parameter ``test_guilds`` is specified in the bot constructor, in which case + this command will be registered to those guilds. extras: Dict[:class:`str`, Any] A dict of user provided extras to attach to the command. diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index e3a1200d9d..7700e0b7f2 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -318,6 +318,11 @@ def add_app_command(self, app_command: InvokableApplicationCommand) -> None: This is usually not called, instead shortcut decorators are used, such as :meth:`.slash_command`, :meth:`.user_command` or :meth:`.message_command`. + The app command is registered to guilds specified in the ``guild_ids`` attribute. + If this attribute is ``None`` then the command is registered globally, unless + parameter ``test_guilds`` is specified in the bot constructor, in which case + this command is registered to those guilds. + .. versionadded:: 2.10 Parameters @@ -762,9 +767,11 @@ def slash_command( auto_sync: :class:`bool` Whether to automatically register the command. Defaults to ``True`` - guild_ids: Sequence[:class:`int`] - If specified, the client will register the command in these guilds. - Otherwise, this command will be registered globally. + guild_ids: Optional[Sequence[:class:`int`]] + If specified, the client will register the command to these guilds. + Otherwise the command will be registered globally, unless + parameter ``test_guilds`` is specified in the bot constructor, in which case + this command will be registered to those guilds. connectors: Dict[:class:`str`, :class:`str`] Binds function names to option names. If the name of an option already matches the corresponding function param, @@ -846,9 +853,11 @@ def user_command( auto_sync: :class:`bool` Whether to automatically register the command. Defaults to ``True``. - guild_ids: Sequence[:class:`int`] - If specified, the client will register the command in these guilds. - Otherwise, this command will be registered globally. + guild_ids: Optional[Sequence[:class:`int`]] + If specified, the client will register the command to these guilds. + Otherwise the command will be registered globally, unless + parameter ``test_guilds`` is specified in the bot constructor, in which case + this command will be registered to those guilds. extras: Dict[:class:`str`, Any] A dict of user provided extras to attach to the command. @@ -923,9 +932,11 @@ def message_command( auto_sync: :class:`bool` Whether to automatically register the command. Defaults to ``True`` - guild_ids: Sequence[:class:`int`] - If specified, the client will register the command in these guilds. - Otherwise, this command will be registered globally. + guild_ids: Optional[Sequence[:class:`int`]] + If specified, the client will register the command to these guilds. + Otherwise the command will be registered globally, unless + parameter ``test_guilds`` is specified in the bot constructor, in which case + this command will be registered to those guilds. extras: Dict[:class:`str`, Any] A dict of user provided extras to attach to the command. diff --git a/disnake/ext/commands/slash_core.py b/disnake/ext/commands/slash_core.py index 1b318a21d0..7895851717 100644 --- a/disnake/ext/commands/slash_core.py +++ b/disnake/ext/commands/slash_core.py @@ -792,9 +792,11 @@ def slash_command( .. versionadded:: 2.5 - guild_ids: List[:class:`int`] - If specified, the client will register the command in these guilds. - Otherwise, this command will be registered globally. + guild_ids: Optional[Sequence[:class:`int`]] + If specified, the client will register the command to these guilds. + Otherwise the command will be registered globally, unless + parameter ``test_guilds`` is specified in the bot constructor, in which case + this command will be registered to those guilds. connectors: Dict[:class:`str`, :class:`str`] Binds function names to option names. If the name of an option already matches the corresponding function param, From 9a4d1aa5bb59efba893aba68a01c65dbc8079d65 Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Fri, 3 Nov 2023 18:31:47 +0300 Subject: [PATCH 24/34] Make `bot.all_app_commands` a mapping proxy --- disnake/ext/commands/interaction_bot_base.py | 51 +++++++++++--------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 7700e0b7f2..5f4bb7e5e3 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -7,6 +7,7 @@ import sys import traceback import warnings +from types import MappingProxyType from typing import ( TYPE_CHECKING, Any, @@ -238,7 +239,7 @@ def __init__( self._before_message_command_invoke = None self._after_message_command_invoke = None - self.all_app_commands: Dict[AppCmdIndex, InvokableApplicationCommand] = {} + self._all_app_commands: Dict[AppCmdIndex, InvokableApplicationCommand] = {} @disnake.utils.copy_doc(disnake.Client.login) async def login(self, token: str) -> None: @@ -254,23 +255,29 @@ def command_sync_flags(self) -> CommandSyncFlags: """ return CommandSyncFlags._from_value(self._command_sync_flags.value) + @property + def all_app_commands(self) -> MappingProxyType[AppCmdIndex, InvokableApplicationCommand]: + """MappingProxyType[:class:`AppCmdIndex`, :class:`InvokableApplicationCommand`]: + A mapping proxy with all application commands the bot has.""" + return MappingProxyType(self._all_app_commands) + @property def application_commands(self) -> Set[InvokableApplicationCommand]: """Set[:class:`InvokableApplicationCommand`]: A set of all application commands the bot has.""" - return set(self.all_app_commands.values()) + return set(self._all_app_commands.values()) @property def slash_commands(self) -> Set[InvokableSlashCommand]: """Set[:class:`InvokableSlashCommand`]: A set of all slash commands the bot has.""" return { - cmd for cmd in self.all_app_commands.values() if isinstance(cmd, InvokableSlashCommand) + cmd for cmd in self._all_app_commands.values() if isinstance(cmd, InvokableSlashCommand) } @property def user_commands(self) -> Set[InvokableUserCommand]: """Set[:class:`InvokableUserCommand`]: A set of all user commands the bot has.""" return { - cmd for cmd in self.all_app_commands.values() if isinstance(cmd, InvokableUserCommand) + cmd for cmd in self._all_app_commands.values() if isinstance(cmd, InvokableUserCommand) } @property @@ -278,7 +285,7 @@ def message_commands(self) -> Set[InvokableMessageCommand]: """Set[:class:`InvokableMessageCommand`]: A set of all message commands the bot has.""" return { cmd - for cmd in self.all_app_commands.values() + for cmd in self._all_app_commands.values() if isinstance(cmd, InvokableMessageCommand) } @@ -288,7 +295,7 @@ def all_slash_commands(self) -> Dict[str, InvokableSlashCommand]: # no docstring because it was an attribute and now it's deprecated return { cmd.name: cmd - for cmd in self.all_app_commands.values() + for cmd in self._all_app_commands.values() if isinstance(cmd, InvokableSlashCommand) } @@ -298,7 +305,7 @@ def all_user_commands(self) -> Dict[str, InvokableUserCommand]: # no docstring because it was an attribute and now it's deprecated return { cmd.name: cmd - for cmd in self.all_app_commands.values() + for cmd in self._all_app_commands.values() if isinstance(cmd, InvokableUserCommand) } @@ -308,7 +315,7 @@ def all_message_commands(self) -> Dict[str, InvokableMessageCommand]: # no docstring because it was an attribute and now it's deprecated return { cmd.name: cmd - for cmd in self.all_app_commands.values() + for cmd in self._all_app_commands.values() if isinstance(cmd, InvokableMessageCommand) } @@ -360,7 +367,7 @@ def add_app_command(self, app_command: InvokableApplicationCommand) -> None: cmd_index = AppCmdIndex( type=app_command.body.type, name=app_command.name, guild_id=guild_id ) - if cmd_index in self.all_app_commands: + if cmd_index in self._all_app_commands: raise ApplicationCommandRegistrationError( cmd_index.type, cmd_index.name, cmd_index.guild_id ) @@ -370,7 +377,7 @@ def add_app_command(self, app_command: InvokableApplicationCommand) -> None: # note that we're adding the same command object for each guild_id # this ensures that any changes that happen to app_command after add_app_command # (such as hook attachments or permission modifications) apply properly - self.all_app_commands[cmd_index] = app_command + self._all_app_commands[cmd_index] = app_command def add_slash_command(self, slash_command: InvokableSlashCommand) -> None: """Adds an :class:`InvokableSlashCommand` into the internal list of slash commands. @@ -482,7 +489,7 @@ def remove_app_command( result = None for guild_id in extended_guild_ids: cmd_index = AppCmdIndex(type=cmd_type, name=name, guild_id=guild_id) - cmd = self.all_app_commands.pop(cmd_index, None) + cmd = self._all_app_commands.pop(cmd_index, None) if result is None: result = cmd @@ -496,13 +503,13 @@ def _emulate_old_app_command_remove(self, cmd_type: ApplicationCommandType, name stacklevel=3, ) bad_keys: List[AppCmdIndex] = [] - for key in self.all_app_commands.keys(): + for key in self._all_app_commands.keys(): if key.type is cmd_type and key.name == name: bad_keys.append(key) result: Optional[InvokableApplicationCommand] = None for key in bad_keys: - cmd = self.all_app_commands.pop(key, None) + cmd = self._all_app_commands.pop(key, None) if result is None: result = cmd @@ -595,14 +602,14 @@ def get_slash_command( cmd_index = AppCmdIndex( type=ApplicationCommandType.chat_input, name=chain[0], guild_id=guild_id ) - command = self.all_app_commands.get(cmd_index) + command = self._all_app_commands.get(cmd_index) if command is None: return None return _match_subcommand_chain(command, chain) # type: ignore # this is mostly for backwards compatibility, as previously guild_id arg didn't exist result = None - for command in self.all_app_commands.values(): + for command in self._all_app_commands.values(): if not isinstance(command, InvokableSlashCommand): continue chain_match = _match_subcommand_chain(command, chain) @@ -646,13 +653,13 @@ def get_user_command( """ if guild_id is not MISSING: cmd_index = AppCmdIndex(type=ApplicationCommandType.user, name=name, guild_id=guild_id) - command = self.all_app_commands.get(cmd_index) + command = self._all_app_commands.get(cmd_index) if command is None: return None return command # type: ignore # this is mostly for backwards compatibility, as previously guild_id arg didn't exist result = None - for command in self.all_app_commands.values(): + for command in self._all_app_commands.values(): if not isinstance(command, InvokableUserCommand) or command.name != name: continue if result is None: @@ -695,13 +702,13 @@ def get_message_command( cmd_index = AppCmdIndex( type=ApplicationCommandType.message, name=name, guild_id=guild_id ) - command = self.all_app_commands.get(cmd_index) + command = self._all_app_commands.get(cmd_index) if command is None: return None return command # type: ignore # this is mostly for backwards compatibility, as previously guild_id arg didn't exist result = None - for command in self.all_app_commands.values(): + for command in self._all_app_commands.values(): if not isinstance(command, InvokableMessageCommand) or command.name != name: continue if result is None: @@ -977,7 +984,7 @@ def _ordered_unsynced_commands( global_cmds: List[ApplicationCommand] = [] guilds: Dict[int, List[ApplicationCommand]] = {} - for key, cmd in self.all_app_commands.items(): + for key, cmd in self._all_app_commands.items(): if not cmd.auto_sync: cmd.body._always_synced = True @@ -1523,7 +1530,7 @@ async def process_app_command_autocompletion( type=inter.data.type, name=inter.data.name, guild_id=inter.data.guild_id ) # this happens to always be a slash command - slash_command = self.all_app_commands.get(cmd_index) + slash_command = self._all_app_commands.get(cmd_index) if slash_command is None: return @@ -1602,7 +1609,7 @@ async def process_application_commands( cmd_index = AppCmdIndex( type=command_type, name=interaction.data.name, guild_id=interaction.data.guild_id ) - app_command = self.all_app_commands.get(cmd_index) + app_command = self._all_app_commands.get(cmd_index) if command_type is ApplicationCommandType.chat_input: event_name = "slash_command" From 55299b25c5a52bddbd2d2987943aa590b6400dc6 Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Fri, 3 Nov 2023 18:44:07 +0300 Subject: [PATCH 25/34] fix: unintended breaking change (handling `guild_ids=[]`) --- disnake/ext/commands/interaction_bot_base.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 5f4bb7e5e3..b2868f53dc 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -258,7 +258,8 @@ def command_sync_flags(self) -> CommandSyncFlags: @property def all_app_commands(self) -> MappingProxyType[AppCmdIndex, InvokableApplicationCommand]: """MappingProxyType[:class:`AppCmdIndex`, :class:`InvokableApplicationCommand`]: - A mapping proxy with all application commands the bot has.""" + A mapping proxy with all application commands the bot has. + """ return MappingProxyType(self._all_app_commands) @property @@ -357,11 +358,8 @@ def add_app_command(self, app_command: InvokableApplicationCommand) -> None: "not an instance of SubCommand or SubCommandGroup" ) - if app_command.guild_ids is None: - # if test_guilds are specified then we add the same command for each test guild - guild_ids = (None,) if self._test_guilds is None else self._test_guilds - else: - guild_ids = app_command.guild_ids + test_guilds = (None,) if self._test_guilds is None else self._test_guilds + guild_ids = app_command.guild_ids or test_guilds for guild_id in guild_ids: cmd_index = AppCmdIndex( From 8dc756e68b81a99285d7d3fadc04299faf4aa327 Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Fri, 3 Nov 2023 18:55:24 +0300 Subject: [PATCH 26/34] docs: `AppCmdIndex` --- disnake/ext/commands/interaction_bot_base.py | 13 +++++++++++++ docs/ext/commands/api/app_commands.rst | 7 +++++++ 2 files changed, 20 insertions(+) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index b2868f53dc..2720602a09 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -82,6 +82,19 @@ class _Diff(TypedDict): class AppCmdIndex(NamedTuple): + """A named tuple used for indexation of :class:`InvokableApplicationCommand`s + stored in bot's cache. + + Attributes + ---------- + type: :class:`ApplicationCommandType` + The type of the application command being stored. + name: :class:`str` + The name of the application command being stored. + guild_id: :class:`int` + One of the guild IDs this command should be registered to, + or ``None`` if it's a global command. + """ type: ApplicationCommandType name: str guild_id: Optional[int] diff --git a/docs/ext/commands/api/app_commands.rst b/docs/ext/commands/api/app_commands.rst index f4d2d6f290..67a78846f4 100644 --- a/docs/ext/commands/api/app_commands.rst +++ b/docs/ext/commands/api/app_commands.rst @@ -138,6 +138,13 @@ CommandSyncFlags .. autoclass:: CommandSyncFlags() :members: +AppCmdIndex +~~~~~~~~~~~ + +.. attributetable:: AppCmdIndex + +.. autoclass:: AppCmdIndex + Injection ~~~~~~~~~ From d5197117dcb5fb10935ebcb845d4b2edfca998d5 Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Fri, 3 Nov 2023 19:02:22 +0300 Subject: [PATCH 27/34] fix: docs and linting --- disnake/ext/commands/interaction_bot_base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 2720602a09..bea7259e22 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -62,7 +62,7 @@ P = ParamSpec("P") -__all__ = ("InteractionBotBase",) +__all__ = ("InteractionBotBase", "AppCmdIndex") MISSING: Any = disnake.utils.MISSING @@ -95,6 +95,7 @@ class AppCmdIndex(NamedTuple): One of the guild IDs this command should be registered to, or ``None`` if it's a global command. """ + type: ApplicationCommandType name: str guild_id: Optional[int] From 413f75923956536dd8da0c87ceedd585638f90ef Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Fri, 3 Nov 2023 19:12:36 +0300 Subject: [PATCH 28/34] Make `AppCmdIndex` public --- disnake/ext/commands/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/disnake/ext/commands/__init__.py b/disnake/ext/commands/__init__.py index ea897b384e..edc00a72d1 100644 --- a/disnake/ext/commands/__init__.py +++ b/disnake/ext/commands/__init__.py @@ -22,5 +22,6 @@ from .flag_converter import * from .flags import * from .help import * +from .interaction_bot_base import * from .params import * from .slash_core import * From 304a829521ed2e3c25a25a32bf9d43796e4af45e Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Fri, 3 Nov 2023 19:34:32 +0300 Subject: [PATCH 29/34] docs: fix a reference --- disnake/ext/commands/interaction_bot_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index bea7259e22..63e7f8009f 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -87,7 +87,7 @@ class AppCmdIndex(NamedTuple): Attributes ---------- - type: :class:`ApplicationCommandType` + type: :class:`disnake.ApplicationCommandType` The type of the application command being stored. name: :class:`str` The name of the application command being stored. From 63ce00788463e05abf846853600c7193935a6d98 Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Fri, 3 Nov 2023 20:45:48 +0300 Subject: [PATCH 30/34] docs: improve AppCmdIndex docstring --- disnake/ext/commands/interaction_bot_base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 63e7f8009f..213245a451 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -82,8 +82,8 @@ class _Diff(TypedDict): class AppCmdIndex(NamedTuple): - """A named tuple used for indexation of :class:`InvokableApplicationCommand`s - stored in bot's cache. + """A named tuple used for indexation of :class:`InvokableApplicationCommand` + objects stored in bot's cache. Attributes ---------- From a5802385de007644aacf5d5a03a3b2c685ea1b41 Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Mon, 11 Dec 2023 18:14:00 +0300 Subject: [PATCH 31/34] docs: small fixes; chore: move `AppCmdIndex` to a different file --- disnake/ext/commands/__init__.py | 1 - disnake/ext/commands/base_core.py | 23 +++++++++++++++- disnake/ext/commands/interaction_bot_base.py | 29 +++----------------- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/disnake/ext/commands/__init__.py b/disnake/ext/commands/__init__.py index edc00a72d1..ea897b384e 100644 --- a/disnake/ext/commands/__init__.py +++ b/disnake/ext/commands/__init__.py @@ -22,6 +22,5 @@ from .flag_converter import * from .flags import * from .help import * -from .interaction_bot_base import * from .params import * from .slash_core import * diff --git a/disnake/ext/commands/base_core.py b/disnake/ext/commands/base_core.py index c21cc5f2d1..bfd5822efb 100644 --- a/disnake/ext/commands/base_core.py +++ b/disnake/ext/commands/base_core.py @@ -12,6 +12,7 @@ Callable, Dict, List, + NamedTuple, Optional, Tuple, TypeVar, @@ -49,7 +50,7 @@ ] -__all__ = ("InvokableApplicationCommand", "default_member_permissions") +__all__ = ("AppCmdIndex", "InvokableApplicationCommand", "default_member_permissions") T = TypeVar("T") @@ -79,6 +80,26 @@ async def wrapped(*args, **kwargs): return wrapped +class AppCmdIndex(NamedTuple): + """A named tuple used for indexation of :class:`InvokableApplicationCommand` + objects stored in bot's cache. + + Attributes + ---------- + type: :class:`disnake.ApplicationCommandType` + The type of the application command being stored. + name: :class:`str` + The name of the application command being stored. + guild_id: Optional[:class:`int`] + One of the guild IDs this command should be registered to, + or ``None`` if it's a global command. + """ + + type: ApplicationCommandType + name: str + guild_id: Optional[int] + + class InvokableApplicationCommand(ABC): """A base class that implements the protocol for a bot application command. diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 213245a451..a5949d2d19 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -15,7 +15,6 @@ Dict, Iterable, List, - NamedTuple, Optional, Sequence, Set, @@ -33,7 +32,7 @@ from disnake.utils import deprecated, warn_deprecated from . import errors -from .base_core import InvokableApplicationCommand +from .base_core import AppCmdIndex, InvokableApplicationCommand from .common_bot_base import CommonBotBase from .ctx_menus_core import ( InvokableMessageCommand, @@ -62,7 +61,7 @@ P = ParamSpec("P") -__all__ = ("InteractionBotBase", "AppCmdIndex") +__all__ = ("InteractionBotBase",) MISSING: Any = disnake.utils.MISSING @@ -81,26 +80,6 @@ class _Diff(TypedDict): delete_ignored: NotRequired[List[ApplicationCommand]] -class AppCmdIndex(NamedTuple): - """A named tuple used for indexation of :class:`InvokableApplicationCommand` - objects stored in bot's cache. - - Attributes - ---------- - type: :class:`disnake.ApplicationCommandType` - The type of the application command being stored. - name: :class:`str` - The name of the application command being stored. - guild_id: :class:`int` - One of the guild IDs this command should be registered to, - or ``None`` if it's a global command. - """ - - type: ApplicationCommandType - name: str - guild_id: Optional[int] - - def _get_to_send_from_diff(diff: _Diff): return diff["no_changes"] + diff["upsert"] + diff["edit"] + diff.get("delete_ignored", []) @@ -271,8 +250,8 @@ def command_sync_flags(self) -> CommandSyncFlags: @property def all_app_commands(self) -> MappingProxyType[AppCmdIndex, InvokableApplicationCommand]: - """MappingProxyType[:class:`AppCmdIndex`, :class:`InvokableApplicationCommand`]: - A mapping proxy with all application commands the bot has. + """Mapping[:class:`AppCmdIndex`, :class:`InvokableApplicationCommand`]: + A read-only mapping with all application commands the bot has. """ return MappingProxyType(self._all_app_commands) From ebdeb33dbc1f26f8ccd74a239157f6cd3bb94c8e Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Mon, 11 Dec 2023 18:40:53 +0300 Subject: [PATCH 32/34] fix: prevent an unintended breaking change --- disnake/ext/commands/interaction_bot_base.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 132a910cf3..5a1b5a7d93 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -467,15 +467,9 @@ def remove_app_command( Optional[:class:`InvokableApplicationCommand`] The app command that was removed. If no matching command was found, then ``None`` is returned instead. """ - if guild_ids is None: - # a global command may end up being a local command if test_guilds were specified - # so we should remove this "global" command from each test guild - if self._test_guilds is not None: - extended_guild_ids = self._test_guilds - else: - extended_guild_ids = (None,) - else: - extended_guild_ids = guild_ids + test_guilds = (None,) if self._test_guilds is None else self._test_guilds + # this is consistent with the behavior of command synchronisation + extended_guild_ids = guild_ids or test_guilds result = None for guild_id in extended_guild_ids: From 93880cffb149490166e1c3d9c3d98b77d59a2107 Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:13:06 +0300 Subject: [PATCH 33/34] refactor: `remove_app_command` now takes a single `guild_id` --- disnake/ext/commands/cog.py | 4 +-- disnake/ext/commands/interaction_bot_base.py | 27 ++++++++++++++------ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/disnake/ext/commands/cog.py b/disnake/ext/commands/cog.py index 452ca98bde..e353435f30 100644 --- a/disnake/ext/commands/cog.py +++ b/disnake/ext/commands/cog.py @@ -760,7 +760,7 @@ def _inject(self, bot: AnyBot) -> Self: except Exception: # undo our additions for to_undo in self.__cog_app_commands__[:index]: - bot.remove_app_command( + bot._remove_app_commands( to_undo.body.type, to_undo.name, guild_ids=to_undo.guild_ids ) raise @@ -835,7 +835,7 @@ def _eject(self, bot: AnyBot) -> None: bot.remove_command(command.name) # type: ignore for app_command in self.__cog_app_commands__: - bot.remove_app_command( + bot._remove_app_commands( app_command.body.type, app_command.name, guild_ids=app_command.guild_ids ) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 5a1b5a7d93..7f7e4321a4 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -446,7 +446,7 @@ def add_message_command(self, message_command: InvokableMessageCommand) -> None: self.add_app_command(message_command) def remove_app_command( - self, cmd_type: ApplicationCommandType, name: str, *, guild_ids: Optional[Sequence[int]] + self, cmd_type: ApplicationCommandType, name: str, *, guild_id: Optional[int] ) -> Optional[InvokableApplicationCommand]: """Removes an :class:`InvokableApplicationCommand` from the internal list of app commands. @@ -458,21 +458,21 @@ def remove_app_command( The type of the app command to remove. name: :class:`str` The name of the app command to remove. - guild_ids: Optional[Sequence[:class:`int`]] - The IDs of the guilds from which the command should be removed, - or ``None`` if global. Defaults to ``None``. + guild_id: Optional[:class:`int`] + The ID of the guild from which this command should be removed, + or ``None`` if it's global. Returns ------- Optional[:class:`InvokableApplicationCommand`] The app command that was removed. If no matching command was found, then ``None`` is returned instead. """ - test_guilds = (None,) if self._test_guilds is None else self._test_guilds - # this is consistent with the behavior of command synchronisation - extended_guild_ids = guild_ids or test_guilds + if guild_id is not None or self._test_guilds is None: + cmd_index = AppCmdIndex(type=cmd_type, name=name, guild_id=guild_id) + return self._all_app_commands.pop(cmd_index, None) result = None - for guild_id in extended_guild_ids: + for guild_id in self._test_guilds: cmd_index = AppCmdIndex(type=cmd_type, name=name, guild_id=guild_id) cmd = self._all_app_commands.pop(cmd_index, None) if result is None: @@ -480,6 +480,17 @@ def remove_app_command( return result + def _remove_app_commands( + self, cmd_type: ApplicationCommandType, name: str, *, guild_ids: Optional[Sequence[int]] + ) -> None: + test_guilds = (None,) if self._test_guilds is None else self._test_guilds + # this is consistent with the behavior of command synchronisation + final_guild_ids = guild_ids or test_guilds + + for guild_id in final_guild_ids: + cmd_index = AppCmdIndex(type=cmd_type, name=name, guild_id=guild_id) + self._all_app_commands.pop(cmd_index, None) + def _emulate_old_app_command_remove(self, cmd_type: ApplicationCommandType, name: str) -> Any: type_info = "slash" if cmd_type is ApplicationCommandType.chat_input else cmd_type.name warn_deprecated( From 84eff4224bf1cab26b338b1c451c28c51e8bdf7a Mon Sep 17 00:00:00 2001 From: EQUENOS <50338932+EQUENOS@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:17:13 +0300 Subject: [PATCH 34/34] refactor: use `@deprecated` for `add_xxx_command` --- disnake/ext/commands/interaction_bot_base.py | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 7f7e4321a4..92b1805473 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -370,6 +370,7 @@ def add_app_command(self, app_command: InvokableApplicationCommand) -> None: # (such as hook attachments or permission modifications) apply properly self._all_app_commands[cmd_index] = app_command + @deprecated("add_app_command") def add_slash_command(self, slash_command: InvokableSlashCommand) -> None: """Adds an :class:`InvokableSlashCommand` into the internal list of slash commands. @@ -388,13 +389,9 @@ def add_slash_command(self, slash_command: InvokableSlashCommand) -> None: TypeError The slash command passed is not an instance of :class:`InvokableSlashCommand`. """ - warn_deprecated( - "add_slash_command is deprecated and will be removed in a future version. " - "Use add_app_command instead.", - stacklevel=3, - ) self.add_app_command(slash_command) + @deprecated("add_app_command") def add_user_command(self, user_command: InvokableUserCommand) -> None: """Adds an :class:`InvokableUserCommand` into the internal list of user commands. @@ -413,13 +410,9 @@ def add_user_command(self, user_command: InvokableUserCommand) -> None: TypeError The user command passed is not an instance of :class:`InvokableUserCommand`. """ - warn_deprecated( - "add_user_command is deprecated and will be removed in a future version. " - "Use add_app_command instead.", - stacklevel=3, - ) self.add_app_command(user_command) + @deprecated("add_app_command") def add_message_command(self, message_command: InvokableMessageCommand) -> None: """Adds an :class:`InvokableMessageCommand` into the internal list of message commands. @@ -438,11 +431,6 @@ def add_message_command(self, message_command: InvokableMessageCommand) -> None: TypeError The message command passed is not an instance of :class:`InvokableMessageCommand`. """ - warn_deprecated( - "add_message_command is deprecated and will be removed in a future version. " - "Use add_app_command instead.", - stacklevel=3, - ) self.add_app_command(message_command) def remove_app_command(