diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c459cd514..4cc5b7eee8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ These changes are available on the `master` branch, but have not yet been releas ([#3320](https://github.com/Pycord-Development/pycord/pull/3320)) - Fix `SyntaxWarning` about `return` in a `finally` block raised on Python 3.14+ ([#3332](https://github.com/Pycord-Development/pycord/pull/3334)) +- Fix `AttributeError` when setting `delete_existing=False` in `register_commands()`. + ([#3325](https://github.com/Pycord-Development/pycord/pull/3325)) ### Deprecated diff --git a/discord/bot.py b/discord/bot.py index 0910f5d46e..780cf939eb 100644 --- a/discord/bot.py +++ b/discord/bot.py @@ -39,6 +39,7 @@ TYPE_CHECKING, Any, Literal, + TypedDict, TypeVar, ) @@ -58,12 +59,12 @@ from .errors import CheckFailure, DiscordException from .interactions import Interaction from .shard import AutoShardedClient -from .types import interactions +from .types.interactions import ApplicationCommand as ApplicationCommandPayload from .user import User from .utils import MISSING, async_all, find, get if TYPE_CHECKING: - from typing_extensions import Never + from typing_extensions import Never, NotRequired from .cog import Cog from .commands import Option @@ -99,8 +100,8 @@ class ApplicationCommandMixin(ABC): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - self._pending_application_commands = [] - self._application_commands = {} + self._pending_application_commands: list[ApplicationCommand] = [] + self._application_commands: dict[str, ApplicationCommand] = {} @property def all_commands(self): @@ -245,8 +246,8 @@ def get_application_command( async def get_desynced_commands( self, guild_id: int | None = None, - prefetched: list[interactions.ApplicationCommand] | None = None, - ) -> list[dict[str, Any]]: + prefetched: list[ApplicationCommandPayload] | None = None, + ) -> list[_CommandSyncAction]: """|coro| Gets the list of commands that are desynced from discord. If ``guild_id`` is specified, it will only return @@ -350,7 +351,7 @@ def _check_command(cmd: ApplicationCommand, match: Mapping[str, Any]) -> bool: return True return False - return_value = [] + return_value: list[_CommandSyncAction] = [] cmds = self.pending_application_commands.copy() if guild_id is None: @@ -362,7 +363,7 @@ def _check_command(cmd: ApplicationCommand, match: Mapping[str, Any]) -> bool: if cmd.guild_ids is not None and guild_id in cmd.guild_ids ] - registered_commands: list[interactions.ApplicationCommand] = [] + registered_commands: list[ApplicationCommandPayload] = [] if prefetched is not None: registered_commands = prefetched elif self._bot.user: @@ -403,7 +404,7 @@ def _check_command(cmd: ApplicationCommand, match: Mapping[str, Any]) -> bool: # We have this command registered but not in our list return_value.append( { - "command": registered_commands_dict[cmd]["name"], + "command": registered_commands_dict[cmd], "id": int(value_["id"]), "action": "delete", } @@ -450,7 +451,7 @@ async def register_commands( method: Literal["individual", "bulk", "auto"] = "bulk", force: bool = False, delete_existing: bool = True, - ) -> list[interactions.ApplicationCommand]: + ) -> list[ApplicationCommandPayload]: """|coro| Register a list of commands. @@ -546,10 +547,10 @@ def register( _log.debug(f"Deleting command {cmd_name} for guild {guild_id}") # type: ignore return _register(method, *args, **kwargs) - pending_actions = [] + pending_actions: list[_CommandSyncAction] = [] if not force: - prefetched_commands: list[interactions.ApplicationCommand] = [] + prefetched_commands: list[ApplicationCommandPayload] = [] if self._bot.user: if guild_id is None: prefetched_commands = await self._bot.http.get_global_commands( @@ -568,9 +569,7 @@ def register( pending_actions.append( { "action": "delete" if delete_existing else None, - "command": collections.namedtuple("Command", ["name"])( - name=cmd["command"] - ), + "command": cmd["command"], "id": cmd["id"], } ) @@ -613,7 +612,14 @@ def register( method == "auto" and len(filtered_deleted) == len(pending) ): # Either the method is bulk or all the commands need to be modified, so we can just do a bulk upsert - data = [cmd["command"].to_dict() for cmd in filtered_deleted] + data = [ + ( + cmd["command"] + if isinstance(cmd["command"], dict) + else cmd["command"].to_dict() + ) + for cmd in filtered_deleted + ] # If there's nothing to update, don't bother if len(filtered_no_action) == 0: _log.debug("Skipping bulk command update: Commands are up to date") @@ -621,7 +627,14 @@ def register( else: _log.debug( "Bulk updating commands %s for guild %s", - {c["command"].name: c["action"] for c in pending_actions}, + { + ( + c["command"]["name"] + if isinstance(c["command"], dict) + else c["command"].name + ): c["action"] + for c in pending_actions + }, guild_id, ) registered = await register("bulk", data, _log=False) @@ -633,7 +646,7 @@ def register( await register( "delete", cmd["id"], - cmd_name=cmd["command"].name, + cmd_name=cmd["command"]["name"], guild_id=guild_id, ) continue @@ -681,10 +694,11 @@ def register( type=i.get("type"), ) if not cmd: - raise ValueError( + _log.warning( f"Registered command {i['name']}, type {i.get('type')} not found in" - " pending commands" + " pending commands. It's interactions will be ignored." ) + continue cmd.id = i["id"] self._application_commands[cmd.id] = cmd @@ -765,7 +779,7 @@ async def on_connect(): global_commands, method=method, force=force, delete_existing=delete_existing ) - registered_guild_commands: dict[int, list[interactions.ApplicationCommand]] = {} + registered_guild_commands: dict[int, list[ApplicationCommandPayload]] = {} if register_guild_commands: cmd_guild_ids: list[int] = [] @@ -1313,6 +1327,14 @@ async def invoke_application_command(self, ctx: ApplicationContext) -> None: def _bot(self) -> Bot | AutoShardedBot: ... +class _CommandSyncAction(TypedDict): + """Used internally for passing metadata about the commands status for syncing with Discord.""" + + id: NotRequired[int] + action: Literal["upsert", "edit", "delete", None] + command: ApplicationCommand | ApplicationCommandPayload + + class BotBase(ApplicationCommandMixin, CogMixin, ABC): _supports_prefixed_commands = False