From 3186c57200e59a491cdb3835a48037f10fe57882 Mon Sep 17 00:00:00 2001 From: NikitaBylnov Date: Mon, 24 Jan 2022 19:56:47 +0300 Subject: [PATCH 1/8] Fix error with option default params --- discord/commands/core.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/discord/commands/core.py b/discord/commands/core.py index a029301d06..be5e519999 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -554,10 +554,7 @@ def __init__(self, func: Callable, *args, **kwargs) -> None: if self.permissions and self.default_permission: self.default_permission = False - def _parse_options(self, params) -> List[Option]: - final_options = [] - if list(params.items())[0][0] == "self": temp = list(params.items()) temp.pop(0) @@ -573,7 +570,6 @@ def _parse_options(self, params) -> List[Option]: ) final_options = [] - for p_name, p_obj in params: option = p_obj.annotation @@ -592,13 +588,13 @@ def _parse_options(self, params) -> List[Option]: if not isinstance(option, Option): option = Option(option, "No description provided") - if p_obj.default != inspect.Parameter.empty: - option.required = False - option.default = option.default if option.default is not None else p_obj.default - - if option.default == inspect.Parameter.empty: - option.default = None + if option.default is None: + if p_obj.default == inspect.Parameter.empty: + option.default = None + else: + option.default = p_obj.default + option.required = False if option.name is None: option.name = p_name @@ -611,7 +607,6 @@ def _parse_options(self, params) -> List[Option]: return final_options - def _match_option_param_names(self, params, options): if list(params.items())[0][0] == "self": temp = list(params.items()) From 0eb628e18af90e6ab3d4e5f07a13c9f929f59917 Mon Sep 17 00:00:00 2001 From: NikitaBylnov Date: Thu, 10 Feb 2022 22:29:02 +0300 Subject: [PATCH 2/8] Fix get_desynced_commands function --- discord/bot.py | 112 +++++++++++++++++++++++++------------------------ 1 file changed, 57 insertions(+), 55 deletions(-) diff --git a/discord/bot.py b/discord/bot.py index b013992a0e..ae4fbd0863 100644 --- a/discord/bot.py +++ b/discord/bot.py @@ -44,6 +44,8 @@ Dict, ) +import discord + from .client import Client from .cog import CogMixin from .commands import ( @@ -181,10 +183,10 @@ def get_command(self): return self.get_application_command def get_application_command( - self, - name: str, - guild_ids: Optional[List[int]] = None, - type: Type[ApplicationCommand] = SlashCommand, + self, + name: str, + guild_ids: Optional[List[int]] = None, + type: Type[ApplicationCommand] = SlashCommand ) -> Optional[ApplicationCommand]: """Get a :class:`.ApplicationCommand` from the internal list of commands. @@ -208,8 +210,8 @@ def get_application_command( for command in self._application_commands.values(): if ( - command.name == name - and isinstance(command, type) + command.name == name + and isinstance(command, type) ): if guild_ids is not None and command.guild_ids != guild_ids: return @@ -242,6 +244,35 @@ async def get_desynced_commands(self, guild_id: Optional[int] = None) -> List[Di """ # We can suggest the user to upsert, edit, delete, or bulk upsert the commands + def _check_command(cmd, match) -> bool: + if isinstance(cmd, SlashCommandGroup): + if len(cmd.subcommands) != len(match["options"]): + return True + for i, subcommand in enumerate(cmd.subcommands): + match_ = next((data for data in match["options"] if data["name"] == subcommand.name), None) + if match_ is not None and _check_command(subcommand, match_): + return True + else: + as_dict = cmd.to_dict() + for check in to_check: + if isinstance(to_check[check], list): + for opt in to_check[check]: + cmd_vals = [val.get(opt, MISSING) for val in as_dict[check]] if check in as_dict else [] + for i, val in enumerate(cmd_vals): + # We need to do some falsy conversion here + # The API considers False (autocomplete) and [] (choices) to be falsy values + if val in (False, []): + cmd_vals[i] = MISSING + if ( + match.get(check, MISSING) is not MISSING + and cmd_vals != [val.get(opt, MISSING) for val in match[check]] + ): + return True # We have a difference + else: + if check in match and getattr(cmd, check) != match[check]: + return True # We have a difference + return False + return_value = [] cmds = self.pending_application_commands.copy() @@ -274,39 +305,12 @@ async def get_desynced_commands(self, guild_id: Optional[int] = None) -> List[Di "command": cmd, "action": "upsert" }) - continue - - as_dict = cmd.to_dict() - - for check in to_check: - if type(to_check[check]) == list: - for opt in to_check[check]: - - cmd_vals = [val.get(opt, MISSING) for val in as_dict[check]] if check in as_dict else [] - for i, val in enumerate(cmd_vals): - # We need to do some falsy conversion here - # The API considers False (autocomplete) and [] (choices) to be falsy values - falsy_vals = (False, []) - if val in falsy_vals: - cmd_vals[i] = MISSING - if ((not match.get(check, MISSING) is MISSING) - and cmd_vals != [val.get(opt, MISSING) for val in match[check]]): - # We have a difference - return_value.append({ - "command": cmd, - "action": "edit", - "id": int(registered_commands_dict[cmd.name]["id"]) - }) - break - else: - if getattr(cmd, check) != match[check]: - # We have a difference - return_value.append({ - "command": cmd, - "action": "edit", - "id": int(registered_commands_dict[cmd.name]["id"]) - }) - break + elif _check_command(cmd, match): + return_value.append({ + "command": cmd, + "action": "edit", + "id": int(registered_commands_dict[cmd.name]["id"]) + }) # Now let's see if there are any commands on discord that we need to delete for cmd in registered_commands_dict: @@ -318,15 +322,13 @@ async def get_desynced_commands(self, guild_id: Optional[int] = None) -> List[Di "id": int(registered_commands_dict[cmd]["id"]), "action": "delete" }) - continue - return return_value async def register_command( - self, - command: ApplicationCommand, - force: bool = True, - guild_ids: List[int] = None + self, + command: ApplicationCommand, + force: bool = True, + guild_ids: List[int] = None ) -> None: """|coro| @@ -353,10 +355,10 @@ async def register_command( raise NotImplementedError("This function has not been implemented yet") async def register_commands( - self, - commands: Optional[List[ApplicationCommand]] = None, - guild_id: Optional[int] = None, - force: bool = False + self, + commands: Optional[List[ApplicationCommand]] = None, + guild_id: Optional[int] = None, + force: bool = False ) -> List[interactions.ApplicationCommand]: """|coro| @@ -487,12 +489,12 @@ def register(method: str, *args, **kwargs): return registered async def sync_commands( - self, - commands: Optional[List[ApplicationCommand]] = None, - force: bool = False, - guild_ids: Optional[List[int]] = None, - register_guild_commands: bool = True, - unregister_guilds: Optional[List[int]] = None, + self, + commands: Optional[List[ApplicationCommand]] = None, + force: bool = False, + guild_ids: Optional[List[int]] = None, + register_guild_commands: bool = True, + unregister_guilds: Optional[List[int]] = None ) -> None: """|coro| From 60af04a99d29ca0055c29936f3aea78725cd61d8 Mon Sep 17 00:00:00 2001 From: NikitaBylnov Date: Sat, 12 Feb 2022 18:59:17 +0300 Subject: [PATCH 3/8] Fix codestyle --- discord/bot.py | 47 ++++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/discord/bot.py b/discord/bot.py index 032142bd19..ab03edc0cc 100644 --- a/discord/bot.py +++ b/discord/bot.py @@ -184,7 +184,7 @@ def get_application_command( self, name: str, guild_ids: Optional[List[int]] = None, - type: Type[ApplicationCommand] = SlashCommand + type: Type[ApplicationCommand] = SlashCommand, ) -> Optional[ApplicationCommand]: """Get a :class:`.ApplicationCommand` from the internal list of commands. @@ -246,7 +246,14 @@ def _check_command(cmd: ApplicationCommand, match: Dict) -> bool: if len(cmd.subcommands) != len(match["options"]): return True for i, subcommand in enumerate(cmd.subcommands): - match_ = next((data for data in match["options"] if data["name"] == subcommand.name), None) + match_ = next( + ( + data + for data in match["options"] + if data["name"] == subcommand.name + ), + None, + ) if match_ is not None and _check_command(subcommand, match_): return True else: @@ -254,16 +261,21 @@ def _check_command(cmd: ApplicationCommand, match: Dict) -> bool: for check in to_check: if isinstance(to_check[check], list): for opt in to_check[check]: - cmd_vals = [val.get(opt, MISSING) for val in as_dict[check]] if check in as_dict else [] + cmd_vals = ( + [val.get(opt, MISSING) for val in as_dict[check]] + if check in as_dict + else [] + ) for i, val in enumerate(cmd_vals): # We need to do some falsy conversion here # The API considers False (autocomplete) and [] (choices) to be falsy values if val in (False, []): cmd_vals[i] = MISSING - if ( - match.get(check, MISSING) is not MISSING - and cmd_vals != [val.get(opt, MISSING) for val in match[check]] - ): + if match.get( + check, MISSING + ) is not MISSING and cmd_vals != [ + val.get(opt, MISSING) for val in match[check] + ]: return True # We have a difference else: if check in match and getattr(cmd, check) != match[check]: @@ -298,18 +310,13 @@ def _check_command(cmd: ApplicationCommand, match: Dict) -> bool: match = registered_commands_dict.get(cmd.name) if match is None: # We don't have this command registered - return_value.append( - { - "command": cmd, - "action": "upsert" - } - ) + return_value.append({"command": cmd, "action": "upsert"}) elif _check_command(cmd, match): return_value.append( { "command": cmd, "action": "edit", - "id": int(registered_commands_dict[cmd.name]["id"]) + "id": int(registered_commands_dict[cmd.name]["id"]), } ) @@ -326,15 +333,13 @@ def _check_command(cmd: ApplicationCommand, match: Dict) -> bool: } ) - continue - return return_value async def register_command( self, command: ApplicationCommand, force: bool = True, - guild_ids: List[int] = None + guild_ids: List[int] = None, ) -> None: """|coro| @@ -364,7 +369,7 @@ async def register_commands( self, commands: Optional[List[ApplicationCommand]] = None, guild_id: Optional[int] = None, - force: bool = False + force: bool = False, ) -> List[interactions.ApplicationCommand]: """|coro| @@ -474,7 +479,7 @@ def register(method: str, *args, **kwargs): data = [cmd["command"].to_dict() for cmd in filtered_deleted] registered = await register("bulk", data) else: - if len(pending_actions) == 0: + if not pending_actions: registered = [] for cmd in pending_actions: if cmd["action"] == "delete": @@ -521,7 +526,7 @@ async def sync_commands( force: bool = False, guild_ids: Optional[List[int]] = None, register_guild_commands: bool = True, - unregister_guilds: Optional[List[int]] = None + unregister_guilds: Optional[List[int]] = None, ) -> None: """|coro| @@ -1212,7 +1217,7 @@ async def can_run( ) -> bool: data = self._check_once if call_once else self._checks - if len(data) == 0: + if not data: return True # type-checker doesn't distinguish between functions and methods From 9f4bb9eaf38a8df6188145c6c30f6829871909ae Mon Sep 17 00:00:00 2001 From: NikitaBylnov Date: Sat, 12 Feb 2022 21:07:41 +0300 Subject: [PATCH 4/8] Fix bugs in get_desynced_commands --- discord/bot.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/discord/bot.py b/discord/bot.py index ab03edc0cc..45010f566e 100644 --- a/discord/bot.py +++ b/discord/bot.py @@ -243,7 +243,7 @@ async def get_desynced_commands( def _check_command(cmd: ApplicationCommand, match: Dict) -> bool: if isinstance(cmd, SlashCommandGroup): - if len(cmd.subcommands) != len(match["options"]): + if len(cmd.subcommands) != len(match.get("options", [])): return True for i, subcommand in enumerate(cmd.subcommands): match_ = next( @@ -271,10 +271,8 @@ def _check_command(cmd: ApplicationCommand, match: Dict) -> bool: # The API considers False (autocomplete) and [] (choices) to be falsy values if val in (False, []): cmd_vals[i] = MISSING - if match.get( - check, MISSING - ) is not MISSING and cmd_vals != [ - val.get(opt, MISSING) for val in match[check] + if cmd_vals != [ + val.get(opt, MISSING) for val in match.get(check, []) ]: return True # We have a difference else: From d97421fa8b05fa3efc1969b3a722a8ea2eb91f53 Mon Sep 17 00:00:00 2001 From: Ratery <72614905+Ratery@users.noreply.github.com> Date: Mon, 14 Feb 2022 19:57:25 +0300 Subject: [PATCH 5/8] Remove unused import --- discord/bot.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/discord/bot.py b/discord/bot.py index 45010f566e..7d034aa30b 100644 --- a/discord/bot.py +++ b/discord/bot.py @@ -44,8 +44,6 @@ Union, ) -import discord - from .client import Client from .cog import CogMixin from .commands import ( From 8d769323aec9def8edfb5283e6103bafdf11cb7b Mon Sep 17 00:00:00 2001 From: BobDotCom <71356958+BobDotCom@users.noreply.github.com> Date: Mon, 4 Apr 2022 11:03:27 -0500 Subject: [PATCH 6/8] Fix check --- discord/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/bot.py b/discord/bot.py index ba87e20853..b13102b8df 100644 --- a/discord/bot.py +++ b/discord/bot.py @@ -289,7 +289,7 @@ def _check_command(cmd: ApplicationCommand, match: Dict) -> bool: ]: # We have a difference return True - elif getattr(cmd, check) != match[check]: + elif getattr(cmd, check) != match.get(check): # We have a difference return True return False From ba7c7af0f5691287c1d1bc075d3c6ecc2524d2ee Mon Sep 17 00:00:00 2001 From: BobDotCom <71356958+BobDotCom@users.noreply.github.com> Date: Mon, 4 Apr 2022 12:46:02 -0500 Subject: [PATCH 7/8] Fix a couple errors --- discord/bot.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/discord/bot.py b/discord/bot.py index b13102b8df..8f98a1023c 100644 --- a/discord/bot.py +++ b/discord/bot.py @@ -255,9 +255,7 @@ def _check_command(cmd: ApplicationCommand, match: Dict) -> bool: ), MISSING, ) - if match_ is MISSING: - return True - elif _check_command(subcommand, match_): + if match_ is not MISSING and _check_command(subcommand, match_): return True else: as_dict = cmd.to_dict() @@ -291,6 +289,10 @@ def _check_command(cmd: ApplicationCommand, match: Dict) -> bool: return True elif getattr(cmd, check) != match.get(check): # We have a difference + if check == "default_permission" and getattr(cmd, check) is True and match.get(check) is None: + # This is a special case + # TODO: Remove for perms v2 + continue return True return False @@ -325,6 +327,9 @@ def _check_command(cmd: ApplicationCommand, match: Dict) -> bool: "id": int(registered_commands_dict[cmd.name]["id"]), } ) + else: + # We have this command registered but it's the same + return_value.append({"command": cmd, "action": None, "id": int(match["id"])}) # Now let's see if there are any commands on discord that we need to delete for cmd, value_ in registered_commands_dict.items(): @@ -473,8 +478,8 @@ def register(method: Literal["bulk", "upsert", "delete", "edit"], *args, **kwarg pending_actions.append( { "action": "delete" if delete_existing else None, - "command": cmd["id"], - "name": cmd["command"], + "command": collections.namedtuple("Command", ["name"])(name=cmd["command"]), + "id": cmd["id"], } ) continue From 96b656b3b959bdc460b6fca7d704ac93cfab47bd Mon Sep 17 00:00:00 2001 From: BobDotCom <71356958+BobDotCom@users.noreply.github.com> Date: Mon, 4 Apr 2022 12:56:48 -0500 Subject: [PATCH 8/8] Fix group localizations --- discord/bot.py | 5 ++++- discord/commands/core.py | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/discord/bot.py b/discord/bot.py index 8f98a1023c..26e501e4af 100644 --- a/discord/bot.py +++ b/discord/bot.py @@ -939,6 +939,7 @@ def create_group( name: str, description: Optional[str] = None, guild_ids: Optional[List[int]] = None, + **kwargs ) -> SlashCommandGroup: """A shortcut method that creates a slash command group with no subcommands and adds it to the internal command list via :meth:`~.ApplicationCommandMixin.add_application_command`. @@ -954,6 +955,8 @@ def create_group( guild_ids: Optional[List[:class:`int`]] A list of the IDs of each guild this group should be added to, making it a guild command. This will be a global command if ``None`` is passed. + kwargs: + Any additional keyword arguments to pass to :class:`.SlashCommandGroup`. Returns -------- @@ -961,7 +964,7 @@ def create_group( The slash command group that was created. """ description = description or "No description provided." - group = SlashCommandGroup(name, description, guild_ids) + group = SlashCommandGroup(name, description, guild_ids, **kwargs) self.add_application_command(group) return group diff --git a/discord/commands/core.py b/discord/commands/core.py index ebf9f4803a..315cc332cf 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -940,6 +940,8 @@ def __init__( self.permissions: List[CommandPermission] = kwargs.get("permissions", []) if self.permissions and self.default_permission: self.default_permission = False + self.name_localizations: Optional[Dict[str, str]] = kwargs.get("name_localizations", None) + self.description_localizations: Optional[Dict[str, str]] = kwargs.get("description_localizations", None) @property def module(self) -> Optional[str]: @@ -952,6 +954,10 @@ def to_dict(self) -> Dict: "options": [c.to_dict() for c in self.subcommands], "default_permission": self.default_permission, } + if self.name_localizations is not None: + as_dict["name_localizations"] = self.name_localizations + if self.description_localizations is not None: + as_dict["description_localizations"] = self.description_localizations if self.parent is not None: as_dict["type"] = self.input_type.value