From f1d7d353b5f47fbb5f1f6187c5074e5e771f0b21 Mon Sep 17 00:00:00 2001 From: Gnome Date: Wed, 1 Sep 2021 17:00:37 +0100 Subject: [PATCH] Add slash_command_guilds to bot and decos --- discord/ext/commands/bot.py | 56 +++++++++++++++++++++++++++++++----- discord/ext/commands/core.py | 12 ++++++-- 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/discord/ext/commands/bot.py b/discord/ext/commands/bot.py index d901586e..75ca3e2d 100644 --- a/discord/ext/commands/bot.py +++ b/discord/ext/commands/bot.py @@ -35,11 +35,13 @@ import importlib.util import sys import traceback import types -from typing import Any, Callable, cast, Mapping, List, Dict, TYPE_CHECKING, Optional, TypeVar, Type, Union +from collections import defaultdict +from typing import Any, Callable, Iterable, cast, Mapping, List, Dict, TYPE_CHECKING, Optional, TypeVar, Type, Union import discord from discord.types.interactions import ( ApplicationCommandInteractionData, + EditApplicationCommand, _ApplicationCommandInteractionDataOptionString ) @@ -174,7 +176,7 @@ class BotBase(GroupMixin): if not (message_commands or slash_commands): raise TypeError("Both message_commands and slash_commands are disabled.") elif slash_commands: - self.slash_command_guild = options.get('slash_command_guild', None) + self.slash_command_guilds: Optional[Iterable[int]] = options.get('slash_command_guilds', None) if help_command is _default: self.help_command = DefaultHelpCommand() @@ -192,12 +194,44 @@ class BotBase(GroupMixin): self._schedule_event(event, ev, *args, **kwargs) # type: ignore async def _create_application_commands(self, application_id: int, http: HTTPClient): - commands = [scmd for cmd in self.commands if not cmd.hidden and (scmd := cmd.to_application_command()) is not None] + commands: defaultdict[Optional[int], List[EditApplicationCommand]] = defaultdict(list) + for command in self.commands: + if command.hidden: + continue - if self.slash_command_guild is None: - await http.bulk_upsert_global_commands(application_id, payload=commands) - else: - await http.bulk_upsert_guild_commands(application_id, self.slash_command_guild, payload=commands) + payload = command.to_application_command() + if payload is None: + continue + + guilds = command.slash_command_guilds or self.slash_command_guilds + if guilds is None: + commands[None].append(payload) + else: + for guild in guilds: + commands[guild].append(payload) + + global_commands = commands.pop(None, None) + if global_commands is not None: + if self.slash_command_guilds is None: + await http.bulk_upsert_global_commands( + payload=global_commands, + application_id=application_id, + ) + else: + for guild in self.slash_command_guilds: + await http.bulk_upsert_guild_commands( + guild_id=guild, + payload=global_commands, + application_id=application_id, + ) + + for guild, guild_commands in commands.items(): + assert guild is not None + await http.bulk_upsert_guild_commands( + guild_id=guild, + payload=guild_commands, + application_id=application_id, + ) @discord.utils.copy_doc(discord.Client.close) @@ -1242,6 +1276,14 @@ class Bot(BotBase, discord.Client): a :class:`Command` object via the ``slash_command`` parameter .. versionadded:: 2.0 + slash_command_guilds: Optional[:class:`List[int]`] + If this is set, only upload slash commands to these guild IDs. + + Can be overwritten per command in the command decorators or when making + a :class:`Command` object via the ``slash_command_guilds`` parameter + + .. versionadded:: 2.0 + """ async def setup(self): if not self.slash_commands: diff --git a/discord/ext/commands/core.py b/discord/ext/commands/core.py index 098c9440..c672d478 100644 --- a/discord/ext/commands/core.py +++ b/discord/ext/commands/core.py @@ -30,6 +30,7 @@ from typing import ( Dict, Generator, Generic, + Iterable, Literal, List, Optional, @@ -296,21 +297,24 @@ class Command(_BaseCommand, Generic[CogT, P, T]): .. note:: This object may be copied by the library. - message_command: Optional[:class:`bool`] Whether to process this command based on messages. This overwrites the global ``message_commands`` parameter of :class:`.Bot`. .. versionadded:: 2.0 - slash_command: Optional[:class:`bool`] Whether to upload and process this command as a slash command. This overwrites the global ``slash_commands`` parameter of :class:`.Bot`. .. versionadded:: 2.0 + slash_command_guilds: Optional[:class:`List[int]`] + If this is set, only upload this slash command to these guild IDs. + This overwrites the global ``slash_command_guilds`` parameter of :class:`.Bot`. + + .. versionadded:: 2.0 """ __original_kwargs__: Dict[str, Any] @@ -344,8 +348,10 @@ class Command(_BaseCommand, Generic[CogT, P, T]): self.callback = func self.enabled: bool = kwargs.get('enabled', True) + self.slash_command: Optional[bool] = kwargs.get("slash_command", None) self.message_command: Optional[bool] = kwargs.get("message_command", None) + self.slash_command_guilds: Optional[Iterable[int]] = kwargs.get("slash_command_guilds", None) help_doc = kwargs.get('help') if help_doc is not None: @@ -405,6 +411,8 @@ class Command(_BaseCommand, Generic[CogT, P, T]): # bandaid for the fact that sometimes parent can be the bot instance parent = kwargs.get('parent') self.parent: Optional[GroupMixin] = parent if isinstance(parent, _BaseCommand) else None # type: ignore + if self.slash_command_guilds is not None and self.parent is not None: + raise TypeError("Cannot set specific guilds for a subcommand. They are inherited from the top level group.") self._before_invoke: Optional[Hook] = None try: