From 12c2a706f31dbb07bde08c5219f93ede47878912 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Mon, 17 Oct 2022 22:53:55 -0400 Subject: [PATCH 01/44] feat: implement new command sync behavior --- disnake/ext/commands/__init__.py | 1 + disnake/ext/commands/bot.py | 35 ++-- disnake/ext/commands/cog.py | 4 +- disnake/ext/commands/flags.py | 152 +++++++++++++++++ disnake/ext/commands/interaction_bot_base.py | 163 +++++++++++++------ docs/ext/commands/api.rst | 12 ++ test_bot/__main__.py | 2 +- 7 files changed, 303 insertions(+), 66 deletions(-) create mode 100644 disnake/ext/commands/flags.py diff --git a/disnake/ext/commands/__init__.py b/disnake/ext/commands/__init__.py index c97b3a0ebf..6a59dde1c9 100644 --- a/disnake/ext/commands/__init__.py +++ b/disnake/ext/commands/__init__.py @@ -21,6 +21,7 @@ from .custom_warnings import * from .errors import * from .flag_converter import * +from .flags import * from .help import * from .params import * from .slash_core import * diff --git a/disnake/ext/commands/bot.py b/disnake/ext/commands/bot.py index 3b0d82cb25..7fd0b5ebca 100644 --- a/disnake/ext/commands/bot.py +++ b/disnake/ext/commands/bot.py @@ -26,6 +26,7 @@ from ._types import MaybeCoro from .bot_base import PrefixType + from .flags import ApplicationCommandSyncFlags from .help import HelpCommand @@ -68,11 +69,17 @@ class Bot(BotBase, InteractionBotBase, disnake.Client): .. versionadded:: 2.1 + .. deprecated:: 2.7 + Replaced with ``command_sync``. + sync_commands_on_cog_unload: :class:`bool` Whether to sync the application commands on cog unload / reload. Defaults to ``True``. .. versionadded:: 2.1 + .. deprecated:: 2.7 + Replaced with ``command_sync``. + sync_commands_debug: :class:`bool` Whether to always show sync debug logs (uses ``INFO`` log level if it's enabled, prints otherwise). If disabled, uses the default ``DEBUG`` log level which isn't shown unless the log level is changed manually. @@ -85,6 +92,9 @@ class Bot(BotBase, InteractionBotBase, disnake.Client): Changes the log level of corresponding messages from ``DEBUG`` to ``INFO`` or ``print``\\s them, instead of controlling whether they are enabled at all. + .. deprecated:: 2.7 + Replaced with ``command_sync``. + localization_provider: :class:`.LocalizationProtocol` An implementation of :class:`.LocalizationProtocol` to use for localization of application commands. @@ -216,9 +226,7 @@ def __init__( owner_ids: Optional[Set[int]] = None, reload: bool = False, case_insensitive: bool = False, - sync_commands: bool = True, - sync_commands_debug: bool = False, - sync_commands_on_cog_unload: bool = True, + command_sync: ApplicationCommandSyncFlags = ..., test_guilds: Optional[Sequence[int]] = None, asyncio_debug: bool = False, loop: Optional[asyncio.AbstractEventLoop] = None, @@ -267,9 +275,7 @@ def __init__( owner_ids: Optional[Set[int]] = None, reload: bool = False, case_insensitive: bool = False, - sync_commands: bool = True, - sync_commands_debug: bool = False, - sync_commands_on_cog_unload: bool = True, + command_sync: ApplicationCommandSyncFlags = ..., test_guilds: Optional[Sequence[int]] = None, asyncio_debug: bool = False, loop: Optional[asyncio.AbstractEventLoop] = None, @@ -324,11 +330,17 @@ class InteractionBot(InteractionBotBase, disnake.Client): .. versionadded:: 2.1 + .. deprecated:: 2.7 + Replaced with ``command_sync``. + sync_commands_on_cog_unload: :class:`bool` Whether to sync the application commands on cog unload / reload. Defaults to ``True``. .. versionadded:: 2.1 + .. deprecated:: 2.7 + Replaced with ``command_sync``. + sync_commands_debug: :class:`bool` Whether to always show sync debug logs (uses ``INFO`` log level if it's enabled, prints otherwise). If disabled, uses the default ``DEBUG`` log level which isn't shown unless the log level is changed manually. @@ -341,6 +353,9 @@ class InteractionBot(InteractionBotBase, disnake.Client): Changes the log level of corresponding messages from ``DEBUG`` to ``INFO`` or ``print``\\s them, instead of controlling whether they are enabled at all. + .. deprecated:: 2.7 + Replaced with ``command_sync``. + localization_provider: :class:`.LocalizationProtocol` An implementation of :class:`.LocalizationProtocol` to use for localization of application commands. @@ -399,9 +414,7 @@ def __init__( owner_id: Optional[int] = None, owner_ids: Optional[Set[int]] = None, reload: bool = False, - sync_commands: bool = True, - sync_commands_debug: bool = False, - sync_commands_on_cog_unload: bool = True, + command_sync: ApplicationCommandSyncFlags = ..., test_guilds: Optional[Sequence[int]] = None, asyncio_debug: bool = False, loop: Optional[asyncio.AbstractEventLoop] = None, @@ -443,9 +456,7 @@ def __init__( owner_id: Optional[int] = None, owner_ids: Optional[Set[int]] = None, reload: bool = False, - sync_commands: bool = True, - sync_commands_debug: bool = False, - sync_commands_on_cog_unload: bool = True, + command_sync: ApplicationCommandSyncFlags = ..., test_guilds: Optional[Sequence[int]] = None, asyncio_debug: bool = False, loop: Optional[asyncio.AbstractEventLoop] = None, diff --git a/disnake/ext/commands/cog.py b/disnake/ext/commands/cog.py index 54b33e9c55..b493edf685 100644 --- a/disnake/ext/commands/cog.py +++ b/disnake/ext/commands/cog.py @@ -808,7 +808,7 @@ def _inject(self, bot: AnyBot) -> Self: bot.add_listener(getattr(self, method_name), name) try: - if bot._sync_commands_on_cog_unload: + if bot._command_sync.on_cog_unload: bot._schedule_delayed_command_sync() except NotImplementedError: pass @@ -874,7 +874,7 @@ def _eject(self, bot: AnyBot) -> None: finally: try: - if bot._sync_commands_on_cog_unload: + if bot._command_sync.on_cog_unload: bot._schedule_delayed_command_sync() except NotImplementedError: pass diff --git a/disnake/ext/commands/flags.py b/disnake/ext/commands/flags.py new file mode 100644 index 0000000000..047f985c94 --- /dev/null +++ b/disnake/ext/commands/flags.py @@ -0,0 +1,152 @@ +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from disnake.flags import BaseFlags, alias_flag_value, all_flags_value, flag_value + +if TYPE_CHECKING: + from typing_extensions import Self + +__all__ = ("ApplicationCommandSyncFlags",) + + +class ApplicationCommandSyncFlags(BaseFlags): + """Controls the library's application command syncing policy. + + This allows for finer grained control over what commands are synced automatically and in what cases. + + To construct an object you can pass keyword arguments denoting the flags + to enable or disable. + + The default value is all flags enabled. + + .. versionadded:: 2.7 + + .. container:: operations + + .. describe:: x == y + + Checks if two ApplicationCommandSyncFlags instances are equal. + .. describe:: x != y + + Checks if two ApplicationCommandSyncFlags instances are not equal. + .. describe:: x <= y + + Checks if an ApplicationCommandSyncFlags instance is a subset of another ApplicationCommandSyncFlags instance. + .. describe:: x >= y + + Checks if an ApplicationCommandSyncFlags instance is a superset of another ApplicationCommandSyncFlags instance. + .. describe:: x < y + + Checks if an ApplicationCommandSyncFlags instance is a strict subset of another ApplicationCommandSyncFlags instance. + .. describe:: x > y + + Checks if an ApplicationCommandSyncFlags instance is a strict superset of another ApplicationCommandSyncFlags instance. + .. describe:: x | y, x |= y + + Returns a new ApplicationCommandSyncFlags instance with all enabled flags from both x and y. + (Using ``|=`` will update in place). + .. describe:: x & y, x &= y + + Returns a new ApplicationCommandSyncFlags instance with only flags enabled on both x and y. + (Using ``&=`` will update in place). + .. describe:: x ^ y, x ^= y + + Returns a new ApplicationCommandSyncFlags instance with only flags enabled on one of x or y, but not both. + (Using ``^=`` will update in place). + .. describe:: ~x + + Returns a new ApplicationCommandSyncFlags instance with all flags from x inverted. + .. describe:: hash(x) + + Return the flag's hash. + .. describe:: iter(x) + + Returns an iterator of ``(name, value)`` pairs. This allows it + to be, for example, constructed as a dict or a list of pairs. + Note that aliases are not shown. + + + Additionally supported are a few operations on class attributes. + + .. describe:: ApplicationCommandSyncFlags.y | ApplicationCommandSyncFlags.z, ApplicationCommandSyncFlags(y=True) | ApplicationCommandSyncFlags.z + + Returns a ApplicationCommandSyncFlags instance with all provided flags enabled. + + .. describe:: ~ApplicationCommandSyncFlags.y + + Returns a ApplicationCommandSyncFlags instance with all flags except ``y`` inverted from their default value. + + Attributes + ---------- + value: :class:`int` + The raw value. You should query flags via the properties + rather than using this raw value. + """ + + __slots__ = () + + def __init__(self, **kwargs: bool): + self.value = all_flags_value(self.VALID_FLAGS) + for key, value in kwargs.items(): + if key not in self.VALID_FLAGS: + raise TypeError(f"{key!r} is not a valid flag name.") + setattr(self, key, value) + + @classmethod + def all(cls) -> Self: + """A factory method that creates a :class:`ApplicationCommandSyncFlags` with everything enabled.""" + self = cls.__new__(cls) + self.value = all_flags_value(cls.VALID_FLAGS) + return self + + @classmethod + def none(cls) -> Self: + """A factory method that creates a :class:`ApplicationCommandSyncFlags` with everything disabled.""" + self = cls.__new__(cls) + self.value = self.DEFAULT_VALUE + return self + + @classmethod + def default(cls) -> Self: + """A factory method that creates a :class:`ApplicationCommandSyncFlags` with the default settings.""" + instance = cls.all() + instance.sync_commands_debug = False + return instance + + @alias_flag_value + def sync_commands(self): + """:class:`bool`: Whether to sync app commands at all.""" + return 1 << 5 | 1 << 6 + + @flag_value + def sync_commands_debug(self): + """:class:`bool`: Whether or not to show app command sync debug messages""" + return 1 << 1 + + @alias_flag_value + def on_cog_actions(self): + """:class:`bool`: Whether or not to sync app commands on cog load, unload, or reload.""" + return 1 << 2 | 1 << 4 + + @flag_value + def on_cog_unload(self): + """:class:`bool`: Whether or not to sync app commands on cog unload or reload.""" + return 1 << 2 + + @flag_value + def allow_command_deletion(self): + """:class:`bool`: Whether to allow commands to be deleted by automatic command sync.""" + return 1 << 3 + + @flag_value + def sync_global_commands(self): + """:class:`bool`: Whether to sync global commands.""" + return 1 << 5 + + @flag_value + def sync_guild_commands(self): + """:class:`bool`: Whether to sync per-guild commands.""" + return 1 << 6 diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 2577260cc1..1de2afa47d 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -15,10 +15,12 @@ Dict, Iterable, List, + NamedTuple, Optional, Sequence, Set, Tuple, + TypedDict, TypeVar, Union, ) @@ -27,6 +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 . import errors from .base_core import InvokableApplicationCommand @@ -38,6 +41,7 @@ user_command, ) from .errors import CommandRegistrationError +from .flags import ApplicationCommandSyncFlags from .slash_core import InvokableSlashCommand, SubCommand, SubCommandGroup, slash_command if TYPE_CHECKING: @@ -68,14 +72,27 @@ _log = logging.getLogger(__name__) +class _Diff(TypedDict): + no_changes: List[ApplicationCommand] + upsert: List[ApplicationCommand] + edit: List[ApplicationCommand] + delete: List[ApplicationCommand] + + +class AppCommandMetadata(NamedTuple): + name: str + guild_id: Optional[int] + type: ApplicationCommandType + + def _app_commands_diff( new_commands: Iterable[ApplicationCommand], old_commands: Iterable[ApplicationCommand], -) -> Dict[str, List[ApplicationCommand]]: +) -> _Diff: new_cmds = {(cmd.name, cmd.type): cmd for cmd in new_commands} old_cmds = {(cmd.name, cmd.type): cmd for cmd in old_commands} - diff = { + diff: _Diff = { "no_changes": [], "upsert": [], "edit": [], @@ -109,7 +126,7 @@ def _app_commands_diff( } -def _format_diff(diff: Dict[str, List[ApplicationCommand]]) -> str: +def _format_diff(diff: _Diff) -> str: lines: List[str] = [] for key, label in _diff_map.items(): lines.append(label) @@ -125,9 +142,10 @@ class InteractionBotBase(CommonBotBase): def __init__( self, *, - sync_commands: bool = True, - sync_commands_debug: bool = False, - sync_commands_on_cog_unload: bool = True, + command_sync: Optional[ApplicationCommandSyncFlags] = None, + sync_commands: bool = MISSING, + sync_commands_debug: bool = MISSING, + sync_commands_on_cog_unload: bool = MISSING, test_guilds: Optional[Sequence[int]] = None, **options: Any, ): @@ -138,9 +156,42 @@ def __init__( test_guilds = None if test_guilds is None else tuple(test_guilds) self._test_guilds: Optional[Tuple[int, ...]] = test_guilds - self._sync_commands: bool = sync_commands - self._sync_commands_debug: bool = sync_commands_debug - self._sync_commands_on_cog_unload = sync_commands_on_cog_unload + + if command_sync is not None and ( + sync_commands is not MISSING + or sync_commands_debug is not MISSING + or sync_commands_on_cog_unload is not MISSING + ): + raise TypeError( + "cannot set 'command_sync' and any of 'sync_commands', 'sync_commands_debug', 'sync_commands_on_cog_unload' at the same time." + ) + if command_sync is None: + command_sync = ApplicationCommandSyncFlags.default() + + if sync_commands is not MISSING: + warn_deprecated( + "sync_commands is deprecated and will be removed in version 2.8. " + "Use `command_sync` with an `ApplicationCommandSyncFlags` instance as a replacement.", + stacklevel=3, + ) + command_sync.sync_commands = sync_commands + if sync_commands_debug is not MISSING: + warn_deprecated( + "sync_commands_debug is deprecated and will be removed in version 2.8. " + "Use `command_sync` with an `ApplicationCommandSyncFlags` instance as a replacement.", + stacklevel=3, + ) + command_sync.sync_commands_debug = sync_commands_debug + + if sync_commands_on_cog_unload is not MISSING: + warn_deprecated( + "sync_commands_on_cog_unload is deprecated and will be removed in version 2.8. " + "Use `command_sync` with an `ApplicationCommandSyncFlags` instance as a replacement.", + stacklevel=3, + ) + command_sync.on_cog_unload = sync_commands_on_cog_unload + + self._command_sync = command_sync self._sync_queued: bool = False self._slash_command_checks = [] @@ -698,65 +749,75 @@ async def _sync_application_commands(self) -> None: if not isinstance(self, disnake.Client): raise NotImplementedError("This method is only usable in disnake.Client subclasses") - if not self._sync_commands or self._is_closed or self.loop.is_closed(): + if not self._command_sync.sync_commands or self._is_closed or self.loop.is_closed(): 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) - if global_cmds is None: - return - - # Update global commands first - diff = _app_commands_diff( - global_cmds, self._connection._global_application_commands.values() - ) - update_required = bool(diff["upsert"]) or bool(diff["edit"]) or bool(diff["delete"]) - - # Show the difference - self._log_sync_debug( - "Application command synchronization:\n" - "GLOBAL COMMANDS\n" - "===============\n" - f"| Update is required: {update_required}\n{_format_diff(diff)}" - ) - if update_required: - # Notice that we don't do any API requests if there're no changes. - try: - to_send = diff["no_changes"] + diff["edit"] + diff["upsert"] - await self.bulk_overwrite_global_commands(to_send) - except Exception as e: - warnings.warn(f"Failed to overwrite global commands due to {e}", SyncWarning) - # Same process but for each specified guild individually. - # Notice that we're not doing this for every single guild for optimisation purposes. - # See the note in :meth:`_cache_application_commands` about guild app commands. - for guild_id, cmds in guild_cmds.items(): - current_guild_cmds = self._connection._guild_application_commands.get(guild_id, {}) - diff = _app_commands_diff(cmds, current_guild_cmds.values()) + if global_cmds is not None and self._command_sync.sync_global_commands: + # Update global commands first + diff = _app_commands_diff( + global_cmds, self._connection._global_application_commands.values() + ) + if not self._command_sync.allow_command_deletion: + # because allow_command_deletion is disabled, we want to never delete a command, so we move the delete commands to no_changes + diff["no_changes"] += diff["delete"] + diff["delete"].clear() update_required = bool(diff["upsert"]) or bool(diff["edit"]) or bool(diff["delete"]) - # Show diff + + # Show the difference self._log_sync_debug( "Application command synchronization:\n" - f"COMMANDS IN {guild_id}\n" - "===============================\n" + "GLOBAL COMMANDS\n" + "===============\n" f"| Update is required: {update_required}\n{_format_diff(diff)}" ) - # Do API requests and cache + if update_required: + # Notice that we don't do any API requests if there're no changes. try: to_send = diff["no_changes"] + diff["edit"] + diff["upsert"] - await self.bulk_overwrite_guild_commands(guild_id, to_send) + await self.bulk_overwrite_global_commands(to_send) except Exception as e: - warnings.warn( - f"Failed to overwrite commands in due to {e}", - SyncWarning, - ) + warnings.warn(f"Failed to overwrite global commands due to {e}", SyncWarning) + # Same process but for each specified guild individually. + # Notice that we're not doing this for every single guild for optimisation purposes. + # See the note in :meth:`_cache_application_commands` about guild app commands. + if guild_cmds is not None and self._command_sync.sync_guild_commands: + for guild_id, cmds in guild_cmds.items(): + current_guild_cmds = self._connection._guild_application_commands.get(guild_id, {}) + diff = _app_commands_diff(cmds, current_guild_cmds.values()) + if not self._command_sync.allow_command_deletion: + # because allow_command_deletion is disabled, we want to never delete a command, so we move the delete commands to no_changes + diff["no_changes"] += diff["delete"] + diff["delete"].clear() + update_required = bool(diff["upsert"]) or bool(diff["edit"]) or bool(diff["delete"]) + + # Show diff + self._log_sync_debug( + "Application command synchronization:\n" + f"COMMANDS IN {guild_id}\n" + "===============================\n" + f"| Update is required: {update_required}\n{_format_diff(diff)}" + ) + + # Do API requests and cache + if update_required: + try: + to_send = diff["no_changes"] + diff["edit"] + diff["upsert"] + await self.bulk_overwrite_guild_commands(guild_id, to_send) + except Exception as e: + warnings.warn( + f"Failed to overwrite commands in due to {e}", + SyncWarning, + ) # Last debug message self._log_sync_debug("Command synchronization task has finished") def _log_sync_debug(self, text: str) -> None: - if self._sync_commands_debug: + if self._command_sync.sync_commands_debug: # if sync debugging is enabled, *always* output logs if _log.isEnabledFor(logging.INFO): # if the log level is `INFO` or higher, use that @@ -783,7 +844,7 @@ async def _delayed_command_sync(self) -> None: raise NotImplementedError("This method is only usable in disnake.Client subclasses") if ( - not self._sync_commands + not self._command_sync.sync_commands or self._sync_queued or not self.is_ready() or self._is_closed @@ -1201,7 +1262,7 @@ async def process_application_commands( interaction: :class:`disnake.ApplicationCommandInteraction` The interaction to process commands for. """ - if self._sync_commands and not self._sync_queued: + if self._command_sync.sync_commands and not self._sync_queued: known_command = self.get_global_command(interaction.data.id) # type: ignore if known_command is None: diff --git a/docs/ext/commands/api.rst b/docs/ext/commands/api.rst index 7d40793f13..194a856da8 100644 --- a/docs/ext/commands/api.rst +++ b/docs/ext/commands/api.rst @@ -153,6 +153,18 @@ AutoShardedInteractionBot .. autoclass:: disnake.ext.commands.AutoShardedInteractionBot :members: +Command Sync +------------- + +ApplicationCommandSyncFlags +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: disnake.ext.commands.ApplicationCommandSyncFlags + +.. autoclass:: disnake.ext.commands.ApplicationCommandSyncFlags() + :members: + + Prefix Helpers ---------------- diff --git a/test_bot/__main__.py b/test_bot/__main__.py index 5e084fdcb3..3e4e7babae 100644 --- a/test_bot/__main__.py +++ b/test_bot/__main__.py @@ -32,7 +32,7 @@ def __init__(self): command_prefix=Config.prefix, intents=disnake.Intents.all(), help_command=None, # type: ignore - sync_commands_debug=Config.sync_commands_debug, + command_sync=commands.ApplicationCommandSyncFlags.all(), strict_localization=Config.strict_localization, test_guilds=Config.test_guilds, reload=Config.auto_reload, From fc36d511dcd818531b939062bddf52f1a8d36928 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Mon, 17 Oct 2022 16:30:03 -0400 Subject: [PATCH 02/44] docs: update the documentation on command sync --- docs/ext/commands/additional_info.rst | 6 +++--- docs/ext/commands/slash_commands.rst | 15 +++++++++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/ext/commands/additional_info.rst b/docs/ext/commands/additional_info.rst index 37db81dabe..f2bde47d4b 100644 --- a/docs/ext/commands/additional_info.rst +++ b/docs/ext/commands/additional_info.rst @@ -15,9 +15,9 @@ App command sync ---------------- If you're using :ref:`discord_ext_commands` for application commands (slash commands, context menus) you should -understand how your commands show up in Discord. If ``sync_commands`` kwarg of :class:`Bot ` (or a similar class) is set to ``True`` (which is the default value) -the library registers / updates all commands automatically. Based on the application commands defined in your code it decides -which commands should be registered, edited or deleted but there're some edge cases you should keep in mind. +understand how your commands show up in Discord. By default, the library registers / updates all commands automatically. +Based on the application commands defined in your code the library automatically determines +which commands should be registered, edited or deleted, but there're some edge cases you should keep in mind. Changing test guilds ++++++++++++++++++++ diff --git a/docs/ext/commands/slash_commands.rst b/docs/ext/commands/slash_commands.rst index 9a60a99486..707f6080d2 100644 --- a/docs/ext/commands/slash_commands.rst +++ b/docs/ext/commands/slash_commands.rst @@ -1,6 +1,6 @@ .. SPDX-License-Identifier: MIT -.. currentmodule:: disnake +.. currentmodule:: disnake.ext.commands .. _ext_commands_slash_commands: @@ -49,21 +49,28 @@ This is useful if you want to figure out some registration details: from disnake.ext import commands + command_sync = commands.ApplicationCommandSyncFlags.default() + command_sync.sync_commands_debug = True + bot = commands.Bot( command_prefix='!', test_guilds=[123456789], # Optional - sync_commands_debug=True + command_sync=command_sync, ) -If you want to disable the automatic registration, set ``sync_commands`` to ``False``: +If you want to disable the automatic registration, set :attr:`ApplicationCommandSyncFlags.sync_commands` +to ``False``, or use the classmethod :meth:`none() ` .. code-block:: python3 from disnake.ext import commands + command_sync = commands.ApplicationCommandSyncFlags.none() + command_sync.sync_commands = False + bot = commands.Bot( command_prefix='!', - sync_commands=False + command_sync=command_sync, ) Basic Slash Command From 6359147967c4d54f592bd0b6717e7d2342b6d6ae Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Mon, 17 Oct 2022 16:55:43 -0400 Subject: [PATCH 03/44] chore: enable codemodding on new flags file --- disnake/ext/commands/flags.py | 23 ++++++++++++++++++++++- scripts/codemods/typed_flags.py | 5 ++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/disnake/ext/commands/flags.py b/disnake/ext/commands/flags.py index 047f985c94..d6d02f3f31 100644 --- a/disnake/ext/commands/flags.py +++ b/disnake/ext/commands/flags.py @@ -2,9 +2,10 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, NoReturn, overload from disnake.flags import BaseFlags, alias_flag_value, all_flags_value, flag_value +from disnake.utils import _generated if TYPE_CHECKING: from typing_extensions import Self @@ -88,6 +89,26 @@ class ApplicationCommandSyncFlags(BaseFlags): __slots__ = () + @overload + @_generated + def __init__( + self, + *, + allow_command_deletion: bool = ..., + on_cog_actions: bool = ..., + on_cog_unload: bool = ..., + sync_commands: bool = ..., + sync_commands_debug: bool = ..., + sync_global_commands: bool = ..., + sync_guild_commands: bool = ..., + ): + ... + + @overload + @_generated + def __init__(self: NoReturn): + ... + def __init__(self, **kwargs: bool): self.value = all_flags_value(self.VALID_FLAGS) for key, value in kwargs.items(): diff --git a/scripts/codemods/typed_flags.py b/scripts/codemods/typed_flags.py index a1beaebdfe..823ff4a62c 100644 --- a/scripts/codemods/typed_flags.py +++ b/scripts/codemods/typed_flags.py @@ -14,7 +14,10 @@ BASE_FLAG_CLASSES = (flags.BaseFlags, flags.ListBaseFlags) -MODULES = ("disnake.flags",) +MODULES = ( + "disnake.flags", + "disnake.ext.commands.flags", +) class FlagTypings(codemod.VisitorBasedCodemodCommand): From ca970cffef5e6a7620929fa86980389500146c19 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Mon, 17 Oct 2022 17:32:00 -0400 Subject: [PATCH 04/44] chore: add property for command sync --- disnake/ext/commands/interaction_bot_base.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 1de2afa47d..1c8c9d49ac 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -214,6 +214,16 @@ def __init__( self._schedule_app_command_preparation() + @property + def command_sync(self) -> ApplicationCommandSyncFlags: + """:class:`~commands.ApplicationCommandSyncFlags`: The command sync configured for this connection. + + .. versionadded:: 2.7 + + """ + + return ApplicationCommandSyncFlags._from_value(self._command_sync.value) + def application_commands_iterator(self) -> Iterable[InvokableApplicationCommand]: return chain( self.all_slash_commands.values(), From 311dcfd0810c31652ef3459bbd7481726275c4ba Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Mon, 17 Oct 2022 17:32:45 -0400 Subject: [PATCH 05/44] chore: copy command sync before setting it --- disnake/ext/commands/interaction_bot_base.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 1c8c9d49ac..fce90a6c72 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -165,6 +165,10 @@ def __init__( raise TypeError( "cannot set 'command_sync' and any of 'sync_commands', 'sync_commands_debug', 'sync_commands_on_cog_unload' at the same time." ) + + if command_sync is not None: + # this makes a copy so it cannot be changed after setting + command_sync = ApplicationCommandSyncFlags._from_value(command_sync.value) if command_sync is None: command_sync = ApplicationCommandSyncFlags.default() From de8b449d421999474f052b16ffcc664b12ccb98e Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Tue, 18 Oct 2022 14:01:58 -0400 Subject: [PATCH 06/44] docs: fix references to ApplicationCommandSyncFlags --- disnake/ext/commands/bot.py | 7 +++++++ disnake/ext/commands/interaction_bot_base.py | 2 +- docs/ext/commands/slash_commands.rst | 15 +++++++++------ 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/disnake/ext/commands/bot.py b/disnake/ext/commands/bot.py index 7fd0b5ebca..037434ecb0 100644 --- a/disnake/ext/commands/bot.py +++ b/disnake/ext/commands/bot.py @@ -323,6 +323,13 @@ class InteractionBot(InteractionBotBase, disnake.Client): .. versionadded:: 2.1 + command_sync: :class:`.ext.commands.ApplicationCommandSyncFlags` + The command sync flags that you want to enable for the session. This is a way of + controlling when and how application commands will be synced with the API. + If not given, automatic command synchronization is enabled by default. + + .. versionadded:: 2.7 + sync_commands: :class:`bool` Whether to enable automatic synchronization of application commands in your code. Defaults to ``True``, which means that commands in API are automatically synced diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index fce90a6c72..5d1ff6f16c 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -220,7 +220,7 @@ def __init__( @property def command_sync(self) -> ApplicationCommandSyncFlags: - """:class:`~commands.ApplicationCommandSyncFlags`: The command sync configured for this connection. + """:class:`~.ext.commands.ApplicationCommandSyncFlags`: The command sync configured for this connection. .. versionadded:: 2.7 diff --git a/docs/ext/commands/slash_commands.rst b/docs/ext/commands/slash_commands.rst index 707f6080d2..6449ecc6c2 100644 --- a/docs/ext/commands/slash_commands.rst +++ b/docs/ext/commands/slash_commands.rst @@ -1,6 +1,6 @@ .. SPDX-License-Identifier: MIT -.. currentmodule:: disnake.ext.commands +.. currentmodule:: disnake .. _ext_commands_slash_commands: @@ -40,9 +40,12 @@ This code sample shows how to set the registration to be local: For global registration, don't specify this parameter. -Another useful parameter is ``sync_commands_debug``. If set to ``True``, you receive debug messages related to the -app command registration by default, without having to change the log level of any loggers -(see the documentation on :class:`Bot ` for more info). +In order to configure specific properties about command sync, we have a configuration +class which may be passed to the Bot, :class:`~.ext.commands.ApplicationCommandSyncFlags`. + +Setting :attr:`ApplicationCommandSyncFlags.sync_commands_debug <.ext.commands.ApplicationCommandSyncFlags.sync_commands_debug>` to ``True``, will print debug messages related to the +app command registration to the console, without having to change the log level of any loggers. + This is useful if you want to figure out some registration details: .. code-block:: python3 @@ -58,8 +61,8 @@ This is useful if you want to figure out some registration details: command_sync=command_sync, ) -If you want to disable the automatic registration, set :attr:`ApplicationCommandSyncFlags.sync_commands` -to ``False``, or use the classmethod :meth:`none() ` +If you want to disable the automatic registration, set :attr:`ApplicationCommandSyncFlags.sync_commands <.ext.commands.ApplicationCommandSyncFlags.sync_commands>` +to ``False``, or use the classmethod :meth:`none() <.ext.commands.ApplicationCommandSyncFlags.none>` .. code-block:: python3 From 07535d3c70ed523f870a403fd657e9cea8c51b26 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Mon, 17 Oct 2022 23:51:22 -0400 Subject: [PATCH 07/44] fix: don't delete guild commands if delete is disabled --- disnake/ext/commands/interaction_bot_base.py | 30 +++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 5d1ff6f16c..2d7f6e56ab 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -1283,19 +1283,35 @@ async def process_application_commands( known_command = self.get_guild_command(interaction.guild_id, interaction.data.id) # type: ignore if known_command is None: + # don't do anything if we aren't allowed to disable them # This usually comes from the blind spots of the sync algorithm. # Since not all guild commands are cached, it is possible to experience such issues. # In this case, the blind spot is the interaction guild, let's fix it: - try: - await self.bulk_overwrite_guild_commands(interaction.guild_id, []) # type: ignore - except disnake.HTTPException: - pass + if self._command_sync.allow_command_deletion: + try: + await self.bulk_overwrite_guild_commands(interaction.guild_id, []) # type: ignore + except disnake.HTTPException: + message = ( + "This command does not exist locally. More information about this: " + f"https://docs.disnake.dev/en/v{disnake.__version__}/ext/commands/additional_info.html" + "#app-command-sync." + ) + else: + message = ( + "This command has just been synced. More information about this: " + f"https://docs.disnake.dev/en/v{disnake.__version__}/ext/commands/additional_info.html" + "#app-command-sync." + ) + else: + message = ( + "This command does not exist locally. More information about this: " + f"https://docs.disnake.dev/en/v{disnake.__version__}/ext/commands/additional_info.html" + "#app-command-sync." + ) try: # This part is in a separate try-except because we still should respond to the interaction await interaction.response.send_message( - "This command has just been synced. More information about this: " - "https://docs.disnake.dev/en/latest/ext/commands/additional_info.html" - "#app-command-sync.", + message, ephemeral=True, ) except disnake.HTTPException: From 4199ab1a14efa42e885be5eea3d66654c767db85 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Mon, 17 Oct 2022 23:51:49 -0400 Subject: [PATCH 08/44] docs: better explain lazy command sync for guild commands --- docs/ext/commands/additional_info.rst | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/docs/ext/commands/additional_info.rst b/docs/ext/commands/additional_info.rst index f2bde47d4b..65648aec8f 100644 --- a/docs/ext/commands/additional_info.rst +++ b/docs/ext/commands/additional_info.rst @@ -19,20 +19,30 @@ understand how your commands show up in Discord. By default, the library registe Based on the application commands defined in your code the library automatically determines which commands should be registered, edited or deleted, but there're some edge cases you should keep in mind. -Changing test guilds -++++++++++++++++++++ +Unknown Commands ++++++++++++++++++ + +Unlike global commands, per-guild application commands are synced in a lazy fashion. This is due to Discord ratelimits, +as checking all guilds for application commands is infeasible past two or three guilds. +This can lead to situations where a command no longer exists in the code but still exists in a server. + +To rectify this, just run the command. It will automatically be deleted. + +Changing Guild Commands +++++++++++++++++++++++++ If you remove some IDs from the ``test_guilds`` kwarg of :class:`Bot ` (or a similar class) or from the ``guild_ids`` kwarg of :func:`slash_command ` (:func:`user_command `, :func:`message_command `) -the commands in those guilds won't be deleted instantly. Instead, they'll be deleted as soon as one of the deprecated commands is invoked. Your bot will send a message +the commands in those guilds won't be deleted instantly. As explained above, they'll be deleted as soon as one of the deprecated commands is invoked. Your bot will send a message like "This command has just been synced ...". -Hosting the bot on multiple machines + +Command Sync with Multiple Clusters ++++++++++++++++++++++++++++++++++++ -If your bot requires shard distribution across several machines, you should set ``sync_commands`` kwarg to ``False`` everywhere except 1 machine. +If your bot requires shard distribution across several clusters, you should disable command_sync on all clusters except one. This will prevent conflicts and race conditions. Discord API doesn't provide users with events related to application command updates, -so it's impossible to keep the cache of multiple machines synced. Having only 1 machine with ``sync_commands`` set to ``True`` is enough +so it's impossible to keep the cache of multiple machines synced. Having only 1 cluster with ``sync_commands`` set to ``True`` is enough because global registration of application commands doesn't depend on sharding. .. _why_params_and_injections_return_any: From 1ac09eae29a3a9faca2f15f079257360e64c3eee Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Tue, 18 Oct 2022 00:37:49 -0400 Subject: [PATCH 09/44] chore: update debug diff shown when commands are not deleted --- disnake/ext/commands/interaction_bot_base.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 2d7f6e56ab..ae71b4f947 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -45,7 +45,7 @@ from .slash_core import InvokableSlashCommand, SubCommand, SubCommandGroup, slash_command if TYPE_CHECKING: - from typing_extensions import ParamSpec + from typing_extensions import NotRequired, ParamSpec from disnake.i18n import LocalizedOptional from disnake.interactions import ( @@ -77,6 +77,7 @@ class _Diff(TypedDict): upsert: List[ApplicationCommand] edit: List[ApplicationCommand] delete: List[ApplicationCommand] + delete_ignored: NotRequired[List[ApplicationCommand]] class AppCommandMetadata(NamedTuple): @@ -123,6 +124,7 @@ def _app_commands_diff( "edit": "To edit:", "delete": "To delete:", "no_changes": "No changes:", + "delete_ignored": "Ignored due to delete flags:", } @@ -777,9 +779,9 @@ async def _sync_application_commands(self) -> None: ) if not self._command_sync.allow_command_deletion: # because allow_command_deletion is disabled, we want to never delete a command, so we move the delete commands to no_changes - diff["no_changes"] += diff["delete"] + diff["delete_ignored"] = diff["delete"].copy() diff["delete"].clear() - update_required = bool(diff["upsert"]) or bool(diff["edit"]) or bool(diff["delete"]) + update_required = bool(diff["upsert"] or diff["edit"] or diff["delete"]) # Show the difference self._log_sync_debug( From 40720b410e0b170664f752851feb8d64ba01f13c Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Tue, 18 Oct 2022 01:48:49 -0400 Subject: [PATCH 10/44] fix: check for the key in the diff --- disnake/ext/commands/interaction_bot_base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index ae71b4f947..b1c5986b3c 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -131,6 +131,8 @@ def _app_commands_diff( def _format_diff(diff: _Diff) -> str: lines: List[str] = [] for key, label in _diff_map.items(): + if key not in diff: + continue lines.append(label) if changes := diff[key]: lines.extend(f" <{type(cmd).__name__} name={cmd.name!r}>" for cmd in changes) From 9d5c2686bd792e8ab30ecd92c8f135853b1a9104 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Tue, 18 Oct 2022 01:53:11 -0400 Subject: [PATCH 11/44] chore: remove unused flag value --- disnake/ext/commands/flags.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/disnake/ext/commands/flags.py b/disnake/ext/commands/flags.py index d6d02f3f31..c01597b739 100644 --- a/disnake/ext/commands/flags.py +++ b/disnake/ext/commands/flags.py @@ -95,7 +95,6 @@ def __init__( self, *, allow_command_deletion: bool = ..., - on_cog_actions: bool = ..., on_cog_unload: bool = ..., sync_commands: bool = ..., sync_commands_debug: bool = ..., @@ -147,11 +146,6 @@ def sync_commands_debug(self): """:class:`bool`: Whether or not to show app command sync debug messages""" return 1 << 1 - @alias_flag_value - def on_cog_actions(self): - """:class:`bool`: Whether or not to sync app commands on cog load, unload, or reload.""" - return 1 << 2 | 1 << 4 - @flag_value def on_cog_unload(self): """:class:`bool`: Whether or not to sync app commands on cog unload or reload.""" From 0ba772877a5b6e06713de91dc78b47503d3d5be8 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Tue, 18 Oct 2022 22:15:03 -0400 Subject: [PATCH 12/44] docs: update default value --- disnake/flags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/flags.py b/disnake/flags.py index 7c5b40440c..527fc372b2 100644 --- a/disnake/flags.py +++ b/disnake/flags.py @@ -1544,7 +1544,7 @@ class MemberCacheFlags(BaseFlags): to enable or disable. Arguments are applied in order, similar to :class:`Permissions`. - The default value is all flags enabled. + The default value is all flags enabled except for debug messages. .. versionadded:: 1.5 From e8554dc2553e6886a3f4820e0025d93550e2c1e4 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Wed, 19 Oct 2022 00:07:39 -0400 Subject: [PATCH 13/44] docs: add preliminary changelogs --- changelog/806.deprecate.rst | 1 + changelog/806.feature.rst | 1 + 2 files changed, 2 insertions(+) create mode 100644 changelog/806.deprecate.rst create mode 100644 changelog/806.feature.rst diff --git a/changelog/806.deprecate.rst b/changelog/806.deprecate.rst new file mode 100644 index 0000000000..c958b9dc6b --- /dev/null +++ b/changelog/806.deprecate.rst @@ -0,0 +1 @@ +|commands| Deprecate the ``sync_commands``, ``sync_commands_debug``, and ``sync_commands_on_cog_unload`` parameters of :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot`. These have been replaced with the ``command_sync`` parameter which takes a :class:`~disnake.ext.commands.ApplicationCommandSyncFlags` instance. diff --git a/changelog/806.feature.rst b/changelog/806.feature.rst new file mode 100644 index 0000000000..8d6aedc7e9 --- /dev/null +++ b/changelog/806.feature.rst @@ -0,0 +1 @@ +Add :class:`~disnake.ext.commands.ApplicationCommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded varients) as ``command_sync``. From 9511e5558cdf084b8a8e2f9723a0640c7546dd77 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Wed, 19 Oct 2022 00:26:26 -0400 Subject: [PATCH 14/44] fix: actually allow syncing just global or guild commands --- disnake/ext/commands/flags.py | 4 ++++ disnake/ext/commands/interaction_bot_base.py | 10 +++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/disnake/ext/commands/flags.py b/disnake/ext/commands/flags.py index c01597b739..d6b31419cd 100644 --- a/disnake/ext/commands/flags.py +++ b/disnake/ext/commands/flags.py @@ -136,6 +136,10 @@ def default(cls) -> Self: instance.sync_commands_debug = False return instance + @property + def _sync_enabled(self): + return self.sync_global_commands or self.sync_guild_commands + @alias_flag_value def sync_commands(self): """:class:`bool`: Whether to sync app commands at all.""" diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index b1c5986b3c..fc10bfa5df 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -767,14 +767,14 @@ async def _sync_application_commands(self) -> None: if not isinstance(self, disnake.Client): raise NotImplementedError("This method is only usable in disnake.Client subclasses") - if not self._command_sync.sync_commands or self._is_closed or self.loop.is_closed(): + if not self._command_sync._sync_enabled or self._is_closed or self.loop.is_closed(): 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) - if global_cmds is not None and self._command_sync.sync_global_commands: + if self._command_sync.sync_global_commands and global_cmds is not None: # Update global commands first diff = _app_commands_diff( global_cmds, self._connection._global_application_commands.values() @@ -803,7 +803,7 @@ async def _sync_application_commands(self) -> None: # Same process but for each specified guild individually. # Notice that we're not doing this for every single guild for optimisation purposes. # See the note in :meth:`_cache_application_commands` about guild app commands. - if guild_cmds is not None and self._command_sync.sync_guild_commands: + if self._command_sync.sync_guild_commands and guild_cmds is not None: for guild_id, cmds in guild_cmds.items(): current_guild_cmds = self._connection._guild_application_commands.get(guild_id, {}) diff = _app_commands_diff(cmds, current_guild_cmds.values()) @@ -862,7 +862,7 @@ async def _delayed_command_sync(self) -> None: raise NotImplementedError("This method is only usable in disnake.Client subclasses") if ( - not self._command_sync.sync_commands + not self._command_sync._sync_enabled or self._sync_queued or not self.is_ready() or self._is_closed @@ -1280,7 +1280,7 @@ async def process_application_commands( interaction: :class:`disnake.ApplicationCommandInteraction` The interaction to process commands for. """ - if self._command_sync.sync_commands and not self._sync_queued: + if self._command_sync._sync_enabled and not self._sync_queued: known_command = self.get_global_command(interaction.data.id) # type: ignore if known_command is None: From e25f9c43d354918ca0ef22e59c733388fc7d4488 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Wed, 19 Oct 2022 01:24:53 -0400 Subject: [PATCH 15/44] docs: add other changelogs --- changelog/265.feature.rst | 1 + changelog/433.feature.rst | 1 + changelog/468.feature.rst | 1 + 3 files changed, 3 insertions(+) create mode 100644 changelog/265.feature.rst create mode 100644 changelog/433.feature.rst create mode 100644 changelog/468.feature.rst diff --git a/changelog/265.feature.rst b/changelog/265.feature.rst new file mode 100644 index 0000000000..8d6aedc7e9 --- /dev/null +++ b/changelog/265.feature.rst @@ -0,0 +1 @@ +Add :class:`~disnake.ext.commands.ApplicationCommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded varients) as ``command_sync``. diff --git a/changelog/433.feature.rst b/changelog/433.feature.rst new file mode 100644 index 0000000000..8d6aedc7e9 --- /dev/null +++ b/changelog/433.feature.rst @@ -0,0 +1 @@ +Add :class:`~disnake.ext.commands.ApplicationCommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded varients) as ``command_sync``. diff --git a/changelog/468.feature.rst b/changelog/468.feature.rst new file mode 100644 index 0000000000..8d6aedc7e9 --- /dev/null +++ b/changelog/468.feature.rst @@ -0,0 +1 @@ +Add :class:`~disnake.ext.commands.ApplicationCommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded varients) as ``command_sync``. From 9a6d7751fb8092a3ae752662ba678e7300fb3835 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Fri, 21 Oct 2022 15:34:17 -0400 Subject: [PATCH 16/44] docs: add missing command_sync documentation --- disnake/ext/commands/bot.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/disnake/ext/commands/bot.py b/disnake/ext/commands/bot.py index 037434ecb0..23bc63e014 100644 --- a/disnake/ext/commands/bot.py +++ b/disnake/ext/commands/bot.py @@ -62,6 +62,13 @@ class Bot(BotBase, InteractionBotBase, disnake.Client): .. versionadded:: 2.1 + command_sync: :class:`.ext.commands.ApplicationCommandSyncFlags` + The command sync flags that you want to enable for the session. This is a way of + controlling when and how application commands will be synced with the API. + If not given, automatic command synchronization is enabled by default. + + .. versionadded:: 2.7 + sync_commands: :class:`bool` Whether to enable automatic synchronization of application commands in your code. Defaults to ``True``, which means that commands in API are automatically synced From 34095b50f7b7021e1f18ddc55f60ebde30c1ed4b Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Fri, 21 Oct 2022 15:36:05 -0400 Subject: [PATCH 17/44] fix: correct comments --- disnake/ext/commands/interaction_bot_base.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index fc10bfa5df..b0160b1b1f 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -780,7 +780,8 @@ async def _sync_application_commands(self) -> None: global_cmds, self._connection._global_application_commands.values() ) if not self._command_sync.allow_command_deletion: - # because allow_command_deletion is disabled, we want to never delete a command, so we move the delete commands to no_changes + # because allow_command_deletion is disabled, we want to never automatically delete a command + # so we move the delete commands to delete_ignored diff["delete_ignored"] = diff["delete"].copy() diff["delete"].clear() update_required = bool(diff["upsert"] or diff["edit"] or diff["delete"]) @@ -808,7 +809,8 @@ async def _sync_application_commands(self) -> None: current_guild_cmds = self._connection._guild_application_commands.get(guild_id, {}) diff = _app_commands_diff(cmds, current_guild_cmds.values()) if not self._command_sync.allow_command_deletion: - # because allow_command_deletion is disabled, we want to never delete a command, so we move the delete commands to no_changes + # because allow_command_deletion is disabled, we want to never automatically delete a command + # so we move the delete commands to delete_ignored diff["no_changes"] += diff["delete"] diff["delete"].clear() update_required = bool(diff["upsert"]) or bool(diff["edit"]) or bool(diff["delete"]) From 334b19171a8a55404f5d7e3353031677a0b9c590 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Fri, 21 Oct 2022 15:37:05 -0400 Subject: [PATCH 18/44] docs: varients -> variants --- changelog/265.feature.rst | 2 +- changelog/433.feature.rst | 2 +- changelog/468.feature.rst | 2 +- changelog/806.feature.rst | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/changelog/265.feature.rst b/changelog/265.feature.rst index 8d6aedc7e9..08e9f29525 100644 --- a/changelog/265.feature.rst +++ b/changelog/265.feature.rst @@ -1 +1 @@ -Add :class:`~disnake.ext.commands.ApplicationCommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded varients) as ``command_sync``. +Add :class:`~disnake.ext.commands.ApplicationCommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync``. diff --git a/changelog/433.feature.rst b/changelog/433.feature.rst index 8d6aedc7e9..08e9f29525 100644 --- a/changelog/433.feature.rst +++ b/changelog/433.feature.rst @@ -1 +1 @@ -Add :class:`~disnake.ext.commands.ApplicationCommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded varients) as ``command_sync``. +Add :class:`~disnake.ext.commands.ApplicationCommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync``. diff --git a/changelog/468.feature.rst b/changelog/468.feature.rst index 8d6aedc7e9..08e9f29525 100644 --- a/changelog/468.feature.rst +++ b/changelog/468.feature.rst @@ -1 +1 @@ -Add :class:`~disnake.ext.commands.ApplicationCommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded varients) as ``command_sync``. +Add :class:`~disnake.ext.commands.ApplicationCommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync``. diff --git a/changelog/806.feature.rst b/changelog/806.feature.rst index 8d6aedc7e9..08e9f29525 100644 --- a/changelog/806.feature.rst +++ b/changelog/806.feature.rst @@ -1 +1 @@ -Add :class:`~disnake.ext.commands.ApplicationCommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded varients) as ``command_sync``. +Add :class:`~disnake.ext.commands.ApplicationCommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync``. From b6df4910977e7cca86b7a4f703202c94cdb4c40e Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Fri, 21 Oct 2022 15:48:22 -0400 Subject: [PATCH 19/44] fix: remove unused class --- disnake/ext/commands/interaction_bot_base.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index b0160b1b1f..56983294ea 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -80,12 +80,6 @@ class _Diff(TypedDict): delete_ignored: NotRequired[List[ApplicationCommand]] -class AppCommandMetadata(NamedTuple): - name: str - guild_id: Optional[int] - type: ApplicationCommandType - - def _app_commands_diff( new_commands: Iterable[ApplicationCommand], old_commands: Iterable[ApplicationCommand], From 4fd2b7fe714a807077aec72cce6ff0c599db6d29 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Fri, 21 Oct 2022 15:49:08 -0400 Subject: [PATCH 20/44] fix: copy to delete_ignored --- 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 56983294ea..03025f315b 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -805,8 +805,8 @@ async def _sync_application_commands(self) -> None: if not self._command_sync.allow_command_deletion: # because allow_command_deletion is disabled, we want to never automatically delete a command # so we move the delete commands to delete_ignored - diff["no_changes"] += diff["delete"] - diff["delete"].clear() + diff["delete_ignored"] = diff["delete"].copy() + diff["delete"] = [] update_required = bool(diff["upsert"]) or bool(diff["edit"]) or bool(diff["delete"]) # Show diff From 90909cc296ea632d2ab92a7ca6c9f2876902a656 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Sat, 22 Oct 2022 11:31:19 -0400 Subject: [PATCH 21/44] chore: rename ApplicationCommandSyncFlags to CommandSyncFlags --- changelog/265.feature.rst | 2 +- changelog/433.feature.rst | 2 +- changelog/468.feature.rst | 2 +- changelog/806.deprecate.rst | 2 +- changelog/806.feature.rst | 2 +- disnake/ext/commands/bot.py | 14 ++++---- disnake/ext/commands/flags.py | 38 ++++++++++---------- disnake/ext/commands/interaction_bot_base.py | 20 +++++------ docs/ext/commands/api.rst | 6 ++-- docs/ext/commands/slash_commands.rst | 12 +++---- test_bot/__main__.py | 2 +- 11 files changed, 51 insertions(+), 51 deletions(-) diff --git a/changelog/265.feature.rst b/changelog/265.feature.rst index 08e9f29525..81ada4f981 100644 --- a/changelog/265.feature.rst +++ b/changelog/265.feature.rst @@ -1 +1 @@ -Add :class:`~disnake.ext.commands.ApplicationCommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync``. +Add :class:`~disnake.ext.commands.CommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync``. diff --git a/changelog/433.feature.rst b/changelog/433.feature.rst index 08e9f29525..81ada4f981 100644 --- a/changelog/433.feature.rst +++ b/changelog/433.feature.rst @@ -1 +1 @@ -Add :class:`~disnake.ext.commands.ApplicationCommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync``. +Add :class:`~disnake.ext.commands.CommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync``. diff --git a/changelog/468.feature.rst b/changelog/468.feature.rst index 08e9f29525..81ada4f981 100644 --- a/changelog/468.feature.rst +++ b/changelog/468.feature.rst @@ -1 +1 @@ -Add :class:`~disnake.ext.commands.ApplicationCommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync``. +Add :class:`~disnake.ext.commands.CommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync``. diff --git a/changelog/806.deprecate.rst b/changelog/806.deprecate.rst index c958b9dc6b..e3d52d9267 100644 --- a/changelog/806.deprecate.rst +++ b/changelog/806.deprecate.rst @@ -1 +1 @@ -|commands| Deprecate the ``sync_commands``, ``sync_commands_debug``, and ``sync_commands_on_cog_unload`` parameters of :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot`. These have been replaced with the ``command_sync`` parameter which takes a :class:`~disnake.ext.commands.ApplicationCommandSyncFlags` instance. +|commands| Deprecate the ``sync_commands``, ``sync_commands_debug``, and ``sync_commands_on_cog_unload`` parameters of :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot`. These have been replaced with the ``command_sync`` parameter which takes a :class:`~disnake.ext.commands.CommandSyncFlags` instance. diff --git a/changelog/806.feature.rst b/changelog/806.feature.rst index 08e9f29525..81ada4f981 100644 --- a/changelog/806.feature.rst +++ b/changelog/806.feature.rst @@ -1 +1 @@ -Add :class:`~disnake.ext.commands.ApplicationCommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync``. +Add :class:`~disnake.ext.commands.CommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync``. diff --git a/disnake/ext/commands/bot.py b/disnake/ext/commands/bot.py index 23bc63e014..b05eff1628 100644 --- a/disnake/ext/commands/bot.py +++ b/disnake/ext/commands/bot.py @@ -26,7 +26,7 @@ from ._types import MaybeCoro from .bot_base import PrefixType - from .flags import ApplicationCommandSyncFlags + from .flags import CommandSyncFlags from .help import HelpCommand @@ -62,7 +62,7 @@ class Bot(BotBase, InteractionBotBase, disnake.Client): .. versionadded:: 2.1 - command_sync: :class:`.ext.commands.ApplicationCommandSyncFlags` + command_sync: :class:`.ext.commands.CommandSyncFlags` The command sync flags that you want to enable for the session. This is a way of controlling when and how application commands will be synced with the API. If not given, automatic command synchronization is enabled by default. @@ -233,7 +233,7 @@ def __init__( owner_ids: Optional[Set[int]] = None, reload: bool = False, case_insensitive: bool = False, - command_sync: ApplicationCommandSyncFlags = ..., + command_sync: CommandSyncFlags = ..., test_guilds: Optional[Sequence[int]] = None, asyncio_debug: bool = False, loop: Optional[asyncio.AbstractEventLoop] = None, @@ -282,7 +282,7 @@ def __init__( owner_ids: Optional[Set[int]] = None, reload: bool = False, case_insensitive: bool = False, - command_sync: ApplicationCommandSyncFlags = ..., + command_sync: CommandSyncFlags = ..., test_guilds: Optional[Sequence[int]] = None, asyncio_debug: bool = False, loop: Optional[asyncio.AbstractEventLoop] = None, @@ -330,7 +330,7 @@ class InteractionBot(InteractionBotBase, disnake.Client): .. versionadded:: 2.1 - command_sync: :class:`.ext.commands.ApplicationCommandSyncFlags` + command_sync: :class:`.ext.commands.CommandSyncFlags` The command sync flags that you want to enable for the session. This is a way of controlling when and how application commands will be synced with the API. If not given, automatic command synchronization is enabled by default. @@ -428,7 +428,7 @@ def __init__( owner_id: Optional[int] = None, owner_ids: Optional[Set[int]] = None, reload: bool = False, - command_sync: ApplicationCommandSyncFlags = ..., + command_sync: CommandSyncFlags = ..., test_guilds: Optional[Sequence[int]] = None, asyncio_debug: bool = False, loop: Optional[asyncio.AbstractEventLoop] = None, @@ -470,7 +470,7 @@ def __init__( owner_id: Optional[int] = None, owner_ids: Optional[Set[int]] = None, reload: bool = False, - command_sync: ApplicationCommandSyncFlags = ..., + command_sync: CommandSyncFlags = ..., test_guilds: Optional[Sequence[int]] = None, asyncio_debug: bool = False, loop: Optional[asyncio.AbstractEventLoop] = None, diff --git a/disnake/ext/commands/flags.py b/disnake/ext/commands/flags.py index d6b31419cd..fce8a2dee1 100644 --- a/disnake/ext/commands/flags.py +++ b/disnake/ext/commands/flags.py @@ -10,10 +10,10 @@ if TYPE_CHECKING: from typing_extensions import Self -__all__ = ("ApplicationCommandSyncFlags",) +__all__ = ("CommandSyncFlags",) -class ApplicationCommandSyncFlags(BaseFlags): +class CommandSyncFlags(BaseFlags): """Controls the library's application command syncing policy. This allows for finer grained control over what commands are synced automatically and in what cases. @@ -29,37 +29,37 @@ class ApplicationCommandSyncFlags(BaseFlags): .. describe:: x == y - Checks if two ApplicationCommandSyncFlags instances are equal. + Checks if two CommandSyncFlags instances are equal. .. describe:: x != y - Checks if two ApplicationCommandSyncFlags instances are not equal. + Checks if two CommandSyncFlags instances are not equal. .. describe:: x <= y - Checks if an ApplicationCommandSyncFlags instance is a subset of another ApplicationCommandSyncFlags instance. + Checks if an CommandSyncFlags instance is a subset of another CommandSyncFlags instance. .. describe:: x >= y - Checks if an ApplicationCommandSyncFlags instance is a superset of another ApplicationCommandSyncFlags instance. + Checks if an CommandSyncFlags instance is a superset of another CommandSyncFlags instance. .. describe:: x < y - Checks if an ApplicationCommandSyncFlags instance is a strict subset of another ApplicationCommandSyncFlags instance. + Checks if an CommandSyncFlags instance is a strict subset of another CommandSyncFlags instance. .. describe:: x > y - Checks if an ApplicationCommandSyncFlags instance is a strict superset of another ApplicationCommandSyncFlags instance. + Checks if an CommandSyncFlags instance is a strict superset of another CommandSyncFlags instance. .. describe:: x | y, x |= y - Returns a new ApplicationCommandSyncFlags instance with all enabled flags from both x and y. + Returns a new CommandSyncFlags instance with all enabled flags from both x and y. (Using ``|=`` will update in place). .. describe:: x & y, x &= y - Returns a new ApplicationCommandSyncFlags instance with only flags enabled on both x and y. + Returns a new CommandSyncFlags instance with only flags enabled on both x and y. (Using ``&=`` will update in place). .. describe:: x ^ y, x ^= y - Returns a new ApplicationCommandSyncFlags instance with only flags enabled on one of x or y, but not both. + Returns a new CommandSyncFlags instance with only flags enabled on one of x or y, but not both. (Using ``^=`` will update in place). .. describe:: ~x - Returns a new ApplicationCommandSyncFlags instance with all flags from x inverted. + Returns a new CommandSyncFlags instance with all flags from x inverted. .. describe:: hash(x) Return the flag's hash. @@ -72,13 +72,13 @@ class ApplicationCommandSyncFlags(BaseFlags): Additionally supported are a few operations on class attributes. - .. describe:: ApplicationCommandSyncFlags.y | ApplicationCommandSyncFlags.z, ApplicationCommandSyncFlags(y=True) | ApplicationCommandSyncFlags.z + .. describe:: CommandSyncFlags.y | CommandSyncFlags.z, CommandSyncFlags(y=True) | CommandSyncFlags.z - Returns a ApplicationCommandSyncFlags instance with all provided flags enabled. + Returns a CommandSyncFlags instance with all provided flags enabled. - .. describe:: ~ApplicationCommandSyncFlags.y + .. describe:: ~CommandSyncFlags.y - Returns a ApplicationCommandSyncFlags instance with all flags except ``y`` inverted from their default value. + Returns a CommandSyncFlags instance with all flags except ``y`` inverted from their default value. Attributes ---------- @@ -117,21 +117,21 @@ def __init__(self, **kwargs: bool): @classmethod def all(cls) -> Self: - """A factory method that creates a :class:`ApplicationCommandSyncFlags` with everything enabled.""" + """A factory method that creates a :class:`CommandSyncFlags` with everything enabled.""" self = cls.__new__(cls) self.value = all_flags_value(cls.VALID_FLAGS) return self @classmethod def none(cls) -> Self: - """A factory method that creates a :class:`ApplicationCommandSyncFlags` with everything disabled.""" + """A factory method that creates a :class:`CommandSyncFlags` with everything disabled.""" self = cls.__new__(cls) self.value = self.DEFAULT_VALUE return self @classmethod def default(cls) -> Self: - """A factory method that creates a :class:`ApplicationCommandSyncFlags` with the default settings.""" + """A factory method that creates a :class:`CommandSyncFlags` with the default settings.""" instance = cls.all() instance.sync_commands_debug = False return instance diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 03025f315b..d4f29cc8d6 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -41,7 +41,7 @@ user_command, ) from .errors import CommandRegistrationError -from .flags import ApplicationCommandSyncFlags +from .flags import CommandSyncFlags from .slash_core import InvokableSlashCommand, SubCommand, SubCommandGroup, slash_command if TYPE_CHECKING: @@ -140,7 +140,7 @@ class InteractionBotBase(CommonBotBase): def __init__( self, *, - command_sync: Optional[ApplicationCommandSyncFlags] = None, + command_sync: Optional[CommandSyncFlags] = None, sync_commands: bool = MISSING, sync_commands_debug: bool = MISSING, sync_commands_on_cog_unload: bool = MISSING, @@ -166,21 +166,21 @@ def __init__( if command_sync is not None: # this makes a copy so it cannot be changed after setting - command_sync = ApplicationCommandSyncFlags._from_value(command_sync.value) + command_sync = CommandSyncFlags._from_value(command_sync.value) if command_sync is None: - command_sync = ApplicationCommandSyncFlags.default() + command_sync = CommandSyncFlags.default() if sync_commands is not MISSING: warn_deprecated( "sync_commands is deprecated and will be removed in version 2.8. " - "Use `command_sync` with an `ApplicationCommandSyncFlags` instance as a replacement.", + "Use `command_sync` with an `CommandSyncFlags` instance as a replacement.", stacklevel=3, ) command_sync.sync_commands = sync_commands if sync_commands_debug is not MISSING: warn_deprecated( "sync_commands_debug is deprecated and will be removed in version 2.8. " - "Use `command_sync` with an `ApplicationCommandSyncFlags` instance as a replacement.", + "Use `command_sync` with an `CommandSyncFlags` instance as a replacement.", stacklevel=3, ) command_sync.sync_commands_debug = sync_commands_debug @@ -188,7 +188,7 @@ def __init__( if sync_commands_on_cog_unload is not MISSING: warn_deprecated( "sync_commands_on_cog_unload is deprecated and will be removed in version 2.8. " - "Use `command_sync` with an `ApplicationCommandSyncFlags` instance as a replacement.", + "Use `command_sync` with an `CommandSyncFlags` instance as a replacement.", stacklevel=3, ) command_sync.on_cog_unload = sync_commands_on_cog_unload @@ -217,14 +217,14 @@ def __init__( self._schedule_app_command_preparation() @property - def command_sync(self) -> ApplicationCommandSyncFlags: - """:class:`~.ext.commands.ApplicationCommandSyncFlags`: The command sync configured for this connection. + def command_sync(self) -> CommandSyncFlags: + """:class:`~.ext.commands.CommandSyncFlags`: The command sync configured for this connection. .. versionadded:: 2.7 """ - return ApplicationCommandSyncFlags._from_value(self._command_sync.value) + return CommandSyncFlags._from_value(self._command_sync.value) def application_commands_iterator(self) -> Iterable[InvokableApplicationCommand]: return chain( diff --git a/docs/ext/commands/api.rst b/docs/ext/commands/api.rst index 194a856da8..7a999aafab 100644 --- a/docs/ext/commands/api.rst +++ b/docs/ext/commands/api.rst @@ -156,12 +156,12 @@ AutoShardedInteractionBot Command Sync ------------- -ApplicationCommandSyncFlags +CommandSyncFlags ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. attributetable:: disnake.ext.commands.ApplicationCommandSyncFlags +.. attributetable:: disnake.ext.commands.CommandSyncFlags -.. autoclass:: disnake.ext.commands.ApplicationCommandSyncFlags() +.. autoclass:: disnake.ext.commands.CommandSyncFlags() :members: diff --git a/docs/ext/commands/slash_commands.rst b/docs/ext/commands/slash_commands.rst index 6449ecc6c2..7eb5603e86 100644 --- a/docs/ext/commands/slash_commands.rst +++ b/docs/ext/commands/slash_commands.rst @@ -41,9 +41,9 @@ This code sample shows how to set the registration to be local: For global registration, don't specify this parameter. In order to configure specific properties about command sync, we have a configuration -class which may be passed to the Bot, :class:`~.ext.commands.ApplicationCommandSyncFlags`. +class which may be passed to the Bot, :class:`~.ext.commands.CommandSyncFlags`. -Setting :attr:`ApplicationCommandSyncFlags.sync_commands_debug <.ext.commands.ApplicationCommandSyncFlags.sync_commands_debug>` to ``True``, will print debug messages related to the +Setting :attr:`CommandSyncFlags.sync_commands_debug <.ext.commands.CommandSyncFlags.sync_commands_debug>` to ``True``, will print debug messages related to the app command registration to the console, without having to change the log level of any loggers. This is useful if you want to figure out some registration details: @@ -52,7 +52,7 @@ This is useful if you want to figure out some registration details: from disnake.ext import commands - command_sync = commands.ApplicationCommandSyncFlags.default() + command_sync = commands.CommandSyncFlags.default() command_sync.sync_commands_debug = True bot = commands.Bot( @@ -61,14 +61,14 @@ This is useful if you want to figure out some registration details: command_sync=command_sync, ) -If you want to disable the automatic registration, set :attr:`ApplicationCommandSyncFlags.sync_commands <.ext.commands.ApplicationCommandSyncFlags.sync_commands>` -to ``False``, or use the classmethod :meth:`none() <.ext.commands.ApplicationCommandSyncFlags.none>` +If you want to disable the automatic registration, set :attr:`CommandSyncFlags.sync_commands <.ext.commands.CommandSyncFlags.sync_commands>` +to ``False``, or use the classmethod :meth:`none() <.ext.commands.CommandSyncFlags.none>` .. code-block:: python3 from disnake.ext import commands - command_sync = commands.ApplicationCommandSyncFlags.none() + command_sync = commands.CommandSyncFlags.none() command_sync.sync_commands = False bot = commands.Bot( diff --git a/test_bot/__main__.py b/test_bot/__main__.py index 3e4e7babae..ea46793dcc 100644 --- a/test_bot/__main__.py +++ b/test_bot/__main__.py @@ -32,7 +32,7 @@ def __init__(self): command_prefix=Config.prefix, intents=disnake.Intents.all(), help_command=None, # type: ignore - command_sync=commands.ApplicationCommandSyncFlags.all(), + command_sync=commands.CommandSyncFlags.all(), strict_localization=Config.strict_localization, test_guilds=Config.test_guilds, reload=Config.auto_reload, From 16cf03f60821f6b909c054ba0cd5f18af5d90673 Mon Sep 17 00:00:00 2001 From: arl Date: Sun, 23 Oct 2022 23:55:23 -0400 Subject: [PATCH 22/44] remove unused import Co-authored-by: shiftinv <8530778+shiftinv@users.noreply.github.com> Signed-off-by: arl --- disnake/ext/commands/interaction_bot_base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index d4f29cc8d6..87373cdfa7 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, From f6994223aed69b0f67968833bfb87ae1c6a3221c Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Sun, 23 Oct 2022 23:52:42 -0400 Subject: [PATCH 23/44] docs: add |commands| prefix to changelog --- changelog/265.feature.rst | 2 +- changelog/433.feature.rst | 2 +- changelog/468.feature.rst | 2 +- changelog/806.feature.rst | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/changelog/265.feature.rst b/changelog/265.feature.rst index 81ada4f981..08766d9177 100644 --- a/changelog/265.feature.rst +++ b/changelog/265.feature.rst @@ -1 +1 @@ -Add :class:`~disnake.ext.commands.CommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync``. +|commands| Add :class:`~disnake.ext.commands.CommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync``. diff --git a/changelog/433.feature.rst b/changelog/433.feature.rst index 81ada4f981..08766d9177 100644 --- a/changelog/433.feature.rst +++ b/changelog/433.feature.rst @@ -1 +1 @@ -Add :class:`~disnake.ext.commands.CommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync``. +|commands| Add :class:`~disnake.ext.commands.CommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync``. diff --git a/changelog/468.feature.rst b/changelog/468.feature.rst index 81ada4f981..08766d9177 100644 --- a/changelog/468.feature.rst +++ b/changelog/468.feature.rst @@ -1 +1 @@ -Add :class:`~disnake.ext.commands.CommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync``. +|commands| Add :class:`~disnake.ext.commands.CommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync``. diff --git a/changelog/806.feature.rst b/changelog/806.feature.rst index 81ada4f981..08766d9177 100644 --- a/changelog/806.feature.rst +++ b/changelog/806.feature.rst @@ -1 +1 @@ -Add :class:`~disnake.ext.commands.CommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync``. +|commands| Add :class:`~disnake.ext.commands.CommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync``. From c7dfaea526f783a95989d76a30d8e0fc6090d75f Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Mon, 24 Oct 2022 00:01:27 -0400 Subject: [PATCH 24/44] docs: address all doc comments --- disnake/ext/commands/bot.py | 12 ++++++------ disnake/ext/commands/flags.py | 2 +- disnake/ext/commands/interaction_bot_base.py | 3 +-- disnake/flags.py | 2 +- docs/ext/commands/api.rst | 2 +- docs/ext/commands/slash_commands.rst | 6 +++--- 6 files changed, 13 insertions(+), 14 deletions(-) diff --git a/disnake/ext/commands/bot.py b/disnake/ext/commands/bot.py index b05eff1628..8a75b595d3 100644 --- a/disnake/ext/commands/bot.py +++ b/disnake/ext/commands/bot.py @@ -63,9 +63,9 @@ class Bot(BotBase, InteractionBotBase, disnake.Client): .. versionadded:: 2.1 command_sync: :class:`.ext.commands.CommandSyncFlags` - The command sync flags that you want to enable for the session. This is a way of - controlling when and how application commands will be synced with the API. - If not given, automatic command synchronization is enabled by default. + The command sync flags for the session. This is a way of + controlling when and how application commands will be synced with the Discord API. + If not given, defaults to :func:`CommandSyncFlags.default`. .. versionadded:: 2.7 @@ -331,9 +331,9 @@ class InteractionBot(InteractionBotBase, disnake.Client): .. versionadded:: 2.1 command_sync: :class:`.ext.commands.CommandSyncFlags` - The command sync flags that you want to enable for the session. This is a way of - controlling when and how application commands will be synced with the API. - If not given, automatic command synchronization is enabled by default. + The command sync flags for the session. This is a way of + controlling when and how application commands will be synced with the Discord API. + If not given, defaults to :func:`CommandSyncFlags.default`. .. versionadded:: 2.7 diff --git a/disnake/ext/commands/flags.py b/disnake/ext/commands/flags.py index fce8a2dee1..8a06284897 100644 --- a/disnake/ext/commands/flags.py +++ b/disnake/ext/commands/flags.py @@ -21,7 +21,7 @@ class CommandSyncFlags(BaseFlags): To construct an object you can pass keyword arguments denoting the flags to enable or disable. - The default value is all flags enabled. + The default value is all flags enabled except for debug messages. .. versionadded:: 2.7 diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 87373cdfa7..108cbeab2c 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -217,10 +217,9 @@ def __init__( @property def command_sync(self) -> CommandSyncFlags: - """:class:`~.ext.commands.CommandSyncFlags`: The command sync configured for this connection. + """:class:`~.ext.commands.CommandSyncFlags`: The command sync flags configured for this bot. .. versionadded:: 2.7 - """ return CommandSyncFlags._from_value(self._command_sync.value) diff --git a/disnake/flags.py b/disnake/flags.py index 527fc372b2..7c5b40440c 100644 --- a/disnake/flags.py +++ b/disnake/flags.py @@ -1544,7 +1544,7 @@ class MemberCacheFlags(BaseFlags): to enable or disable. Arguments are applied in order, similar to :class:`Permissions`. - The default value is all flags enabled except for debug messages. + The default value is all flags enabled. .. versionadded:: 1.5 diff --git a/docs/ext/commands/api.rst b/docs/ext/commands/api.rst index 7a999aafab..49da475f08 100644 --- a/docs/ext/commands/api.rst +++ b/docs/ext/commands/api.rst @@ -157,7 +157,7 @@ Command Sync ------------- CommandSyncFlags -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~ .. attributetable:: disnake.ext.commands.CommandSyncFlags diff --git a/docs/ext/commands/slash_commands.rst b/docs/ext/commands/slash_commands.rst index 7eb5603e86..3f74514c44 100644 --- a/docs/ext/commands/slash_commands.rst +++ b/docs/ext/commands/slash_commands.rst @@ -40,11 +40,11 @@ This code sample shows how to set the registration to be local: For global registration, don't specify this parameter. -In order to configure specific properties about command sync, we have a configuration +In order to configure specific properties about command sync, there's a configuration class which may be passed to the Bot, :class:`~.ext.commands.CommandSyncFlags`. Setting :attr:`CommandSyncFlags.sync_commands_debug <.ext.commands.CommandSyncFlags.sync_commands_debug>` to ``True``, will print debug messages related to the -app command registration to the console, without having to change the log level of any loggers. +app command registration to the console (or logger if enabled). This is useful if you want to figure out some registration details: @@ -62,7 +62,7 @@ This is useful if you want to figure out some registration details: ) If you want to disable the automatic registration, set :attr:`CommandSyncFlags.sync_commands <.ext.commands.CommandSyncFlags.sync_commands>` -to ``False``, or use the classmethod :meth:`none() <.ext.commands.CommandSyncFlags.none>` +to ``False``, or use :meth:`CommandSyncFlags.none() <.ext.commands.CommandSyncFlags.none>` .. code-block:: python3 From 003da6cc623ee5988915962c5236c10b61e01f62 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Mon, 24 Oct 2022 00:11:54 -0400 Subject: [PATCH 25/44] chore: rename command_sync to command_sync_flags --- changelog/265.feature.rst | 2 +- changelog/433.feature.rst | 2 +- changelog/468.feature.rst | 2 +- changelog/806.deprecate.rst | 2 +- changelog/806.feature.rst | 2 +- disnake/ext/commands/bot.py | 24 ++++++++--------- disnake/ext/commands/interaction_bot_base.py | 28 ++++++++++---------- docs/ext/commands/slash_commands.rst | 12 ++++----- test_bot/__main__.py | 2 +- 9 files changed, 38 insertions(+), 38 deletions(-) diff --git a/changelog/265.feature.rst b/changelog/265.feature.rst index 08766d9177..eca3e2027b 100644 --- a/changelog/265.feature.rst +++ b/changelog/265.feature.rst @@ -1 +1 @@ -|commands| Add :class:`~disnake.ext.commands.CommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync``. +|commands| Add :class:`~disnake.ext.commands.CommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync_flags``. diff --git a/changelog/433.feature.rst b/changelog/433.feature.rst index 08766d9177..eca3e2027b 100644 --- a/changelog/433.feature.rst +++ b/changelog/433.feature.rst @@ -1 +1 @@ -|commands| Add :class:`~disnake.ext.commands.CommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync``. +|commands| Add :class:`~disnake.ext.commands.CommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync_flags``. diff --git a/changelog/468.feature.rst b/changelog/468.feature.rst index 08766d9177..eca3e2027b 100644 --- a/changelog/468.feature.rst +++ b/changelog/468.feature.rst @@ -1 +1 @@ -|commands| Add :class:`~disnake.ext.commands.CommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync``. +|commands| Add :class:`~disnake.ext.commands.CommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync_flags``. diff --git a/changelog/806.deprecate.rst b/changelog/806.deprecate.rst index e3d52d9267..32b45acaf6 100644 --- a/changelog/806.deprecate.rst +++ b/changelog/806.deprecate.rst @@ -1 +1 @@ -|commands| Deprecate the ``sync_commands``, ``sync_commands_debug``, and ``sync_commands_on_cog_unload`` parameters of :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot`. These have been replaced with the ``command_sync`` parameter which takes a :class:`~disnake.ext.commands.CommandSyncFlags` instance. +|commands| Deprecate the ``sync_commands``, ``sync_commands_debug``, and ``sync_commands_on_cog_unload`` parameters of :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot`. These have been replaced with the ``command_sync_flags`` parameter which takes a :class:`~disnake.ext.commands.CommandSyncFlags` instance. diff --git a/changelog/806.feature.rst b/changelog/806.feature.rst index 08766d9177..eca3e2027b 100644 --- a/changelog/806.feature.rst +++ b/changelog/806.feature.rst @@ -1 +1 @@ -|commands| Add :class:`~disnake.ext.commands.CommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync``. +|commands| Add :class:`~disnake.ext.commands.CommandSyncFlags` to provide sync configuration to :class:`~disnake.ext.commands.Bot` and :class:`~disnake.ext.commands.InteractionBot` (and their autosharded variants) as ``command_sync_flags``. diff --git a/disnake/ext/commands/bot.py b/disnake/ext/commands/bot.py index 8a75b595d3..dadd4c068f 100644 --- a/disnake/ext/commands/bot.py +++ b/disnake/ext/commands/bot.py @@ -62,7 +62,7 @@ class Bot(BotBase, InteractionBotBase, disnake.Client): .. versionadded:: 2.1 - command_sync: :class:`.ext.commands.CommandSyncFlags` + command_sync_flags: :class:`.ext.commands.CommandSyncFlags` The command sync flags for the session. This is a way of controlling when and how application commands will be synced with the Discord API. If not given, defaults to :func:`CommandSyncFlags.default`. @@ -77,7 +77,7 @@ class Bot(BotBase, InteractionBotBase, disnake.Client): .. versionadded:: 2.1 .. deprecated:: 2.7 - Replaced with ``command_sync``. + Replaced with ``command_sync_flags``. sync_commands_on_cog_unload: :class:`bool` Whether to sync the application commands on cog unload / reload. Defaults to ``True``. @@ -85,7 +85,7 @@ class Bot(BotBase, InteractionBotBase, disnake.Client): .. versionadded:: 2.1 .. deprecated:: 2.7 - Replaced with ``command_sync``. + Replaced with ``command_sync_flags``. sync_commands_debug: :class:`bool` Whether to always show sync debug logs (uses ``INFO`` log level if it's enabled, prints otherwise). @@ -100,7 +100,7 @@ class Bot(BotBase, InteractionBotBase, disnake.Client): instead of controlling whether they are enabled at all. .. deprecated:: 2.7 - Replaced with ``command_sync``. + Replaced with ``command_sync_flags``. localization_provider: :class:`.LocalizationProtocol` An implementation of :class:`.LocalizationProtocol` to use for localization of @@ -233,7 +233,7 @@ def __init__( owner_ids: Optional[Set[int]] = None, reload: bool = False, case_insensitive: bool = False, - command_sync: CommandSyncFlags = ..., + command_sync_flags: CommandSyncFlags = ..., test_guilds: Optional[Sequence[int]] = None, asyncio_debug: bool = False, loop: Optional[asyncio.AbstractEventLoop] = None, @@ -282,7 +282,7 @@ def __init__( owner_ids: Optional[Set[int]] = None, reload: bool = False, case_insensitive: bool = False, - command_sync: CommandSyncFlags = ..., + command_sync_flags: CommandSyncFlags = ..., test_guilds: Optional[Sequence[int]] = None, asyncio_debug: bool = False, loop: Optional[asyncio.AbstractEventLoop] = None, @@ -330,7 +330,7 @@ class InteractionBot(InteractionBotBase, disnake.Client): .. versionadded:: 2.1 - command_sync: :class:`.ext.commands.CommandSyncFlags` + command_sync_flags: :class:`.ext.commands.CommandSyncFlags` The command sync flags for the session. This is a way of controlling when and how application commands will be synced with the Discord API. If not given, defaults to :func:`CommandSyncFlags.default`. @@ -345,7 +345,7 @@ class InteractionBot(InteractionBotBase, disnake.Client): .. versionadded:: 2.1 .. deprecated:: 2.7 - Replaced with ``command_sync``. + Replaced with ``command_sync_flags``. sync_commands_on_cog_unload: :class:`bool` Whether to sync the application commands on cog unload / reload. Defaults to ``True``. @@ -353,7 +353,7 @@ class InteractionBot(InteractionBotBase, disnake.Client): .. versionadded:: 2.1 .. deprecated:: 2.7 - Replaced with ``command_sync``. + Replaced with ``command_sync_flags``. sync_commands_debug: :class:`bool` Whether to always show sync debug logs (uses ``INFO`` log level if it's enabled, prints otherwise). @@ -368,7 +368,7 @@ class InteractionBot(InteractionBotBase, disnake.Client): instead of controlling whether they are enabled at all. .. deprecated:: 2.7 - Replaced with ``command_sync``. + Replaced with ``command_sync_flags``. localization_provider: :class:`.LocalizationProtocol` An implementation of :class:`.LocalizationProtocol` to use for localization of @@ -428,7 +428,7 @@ def __init__( owner_id: Optional[int] = None, owner_ids: Optional[Set[int]] = None, reload: bool = False, - command_sync: CommandSyncFlags = ..., + command_sync_flags: CommandSyncFlags = ..., test_guilds: Optional[Sequence[int]] = None, asyncio_debug: bool = False, loop: Optional[asyncio.AbstractEventLoop] = None, @@ -470,7 +470,7 @@ def __init__( owner_id: Optional[int] = None, owner_ids: Optional[Set[int]] = None, reload: bool = False, - command_sync: CommandSyncFlags = ..., + command_sync_flags: CommandSyncFlags = ..., test_guilds: Optional[Sequence[int]] = None, asyncio_debug: bool = False, loop: Optional[asyncio.AbstractEventLoop] = None, diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 108cbeab2c..11aa5de788 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -139,7 +139,7 @@ class InteractionBotBase(CommonBotBase): def __init__( self, *, - command_sync: Optional[CommandSyncFlags] = None, + command_sync_flags: Optional[CommandSyncFlags] = None, sync_commands: bool = MISSING, sync_commands_debug: bool = MISSING, sync_commands_on_cog_unload: bool = MISSING, @@ -154,45 +154,45 @@ def __init__( test_guilds = None if test_guilds is None else tuple(test_guilds) self._test_guilds: Optional[Tuple[int, ...]] = test_guilds - if command_sync is not None and ( + if command_sync_flags is not None and ( sync_commands is not MISSING or sync_commands_debug is not MISSING or sync_commands_on_cog_unload is not MISSING ): raise TypeError( - "cannot set 'command_sync' and any of 'sync_commands', 'sync_commands_debug', 'sync_commands_on_cog_unload' at the same time." + "cannot set 'command_sync_flags' and any of 'sync_commands', 'sync_commands_debug', 'sync_commands_on_cog_unload' at the same time." ) - if command_sync is not None: + if command_sync_flags is not None: # this makes a copy so it cannot be changed after setting - command_sync = CommandSyncFlags._from_value(command_sync.value) - if command_sync is None: - command_sync = CommandSyncFlags.default() + command_sync_flags = CommandSyncFlags._from_value(command_sync_flags.value) + if command_sync_flags is None: + command_sync_flags = CommandSyncFlags.default() if sync_commands is not MISSING: warn_deprecated( "sync_commands is deprecated and will be removed in version 2.8. " - "Use `command_sync` with an `CommandSyncFlags` instance as a replacement.", + "Use `command_sync_flags` with an `CommandSyncFlags` instance as a replacement.", stacklevel=3, ) - command_sync.sync_commands = sync_commands + command_sync_flags.sync_commands = sync_commands if sync_commands_debug is not MISSING: warn_deprecated( "sync_commands_debug is deprecated and will be removed in version 2.8. " - "Use `command_sync` with an `CommandSyncFlags` instance as a replacement.", + "Use `command_sync_flags` with an `CommandSyncFlags` instance as a replacement.", stacklevel=3, ) - command_sync.sync_commands_debug = sync_commands_debug + command_sync_flags.sync_commands_debug = sync_commands_debug if sync_commands_on_cog_unload is not MISSING: warn_deprecated( "sync_commands_on_cog_unload is deprecated and will be removed in version 2.8. " - "Use `command_sync` with an `CommandSyncFlags` instance as a replacement.", + "Use `command_sync_flags` with an `CommandSyncFlags` instance as a replacement.", stacklevel=3, ) - command_sync.on_cog_unload = sync_commands_on_cog_unload + command_sync_flags.on_cog_unload = sync_commands_on_cog_unload - self._command_sync = command_sync + self._command_sync = command_sync_flags self._sync_queued: bool = False self._slash_command_checks = [] diff --git a/docs/ext/commands/slash_commands.rst b/docs/ext/commands/slash_commands.rst index 3f74514c44..d1b16e4469 100644 --- a/docs/ext/commands/slash_commands.rst +++ b/docs/ext/commands/slash_commands.rst @@ -52,13 +52,13 @@ This is useful if you want to figure out some registration details: from disnake.ext import commands - command_sync = commands.CommandSyncFlags.default() - command_sync.sync_commands_debug = True + command_sync_flags = commands.CommandSyncFlags.default() + command_sync_flags.sync_commands_debug = True bot = commands.Bot( command_prefix='!', test_guilds=[123456789], # Optional - command_sync=command_sync, + command_sync_flags=command_sync_flags, ) If you want to disable the automatic registration, set :attr:`CommandSyncFlags.sync_commands <.ext.commands.CommandSyncFlags.sync_commands>` @@ -68,12 +68,12 @@ to ``False``, or use :meth:`CommandSyncFlags.none() <.ext.commands.CommandSyncFl from disnake.ext import commands - command_sync = commands.CommandSyncFlags.none() - command_sync.sync_commands = False + command_sync_flags = commands.CommandSyncFlags.none() + command_sync_flags.sync_commands = False bot = commands.Bot( command_prefix='!', - command_sync=command_sync, + command_sync_flags=command_sync_flags, ) Basic Slash Command diff --git a/test_bot/__main__.py b/test_bot/__main__.py index ea46793dcc..687e682d7c 100644 --- a/test_bot/__main__.py +++ b/test_bot/__main__.py @@ -32,7 +32,7 @@ def __init__(self): command_prefix=Config.prefix, intents=disnake.Intents.all(), help_command=None, # type: ignore - command_sync=commands.CommandSyncFlags.all(), + command_sync_flags=commands.CommandSyncFlags.all(), strict_localization=Config.strict_localization, test_guilds=Config.test_guilds, reload=Config.auto_reload, From 3924c951e3cb4e2118f45e43419466bb45739975 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Mon, 24 Oct 2022 00:12:48 -0400 Subject: [PATCH 26/44] chore: change deprecations to not explicity state v2.8 --- disnake/ext/commands/interaction_bot_base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 11aa5de788..b839645335 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -171,14 +171,14 @@ def __init__( if sync_commands is not MISSING: warn_deprecated( - "sync_commands is deprecated and will be removed in version 2.8. " + "sync_commands is deprecated and will be removed in a future version. " "Use `command_sync_flags` with an `CommandSyncFlags` instance as a replacement.", stacklevel=3, ) command_sync_flags.sync_commands = sync_commands if sync_commands_debug is not MISSING: warn_deprecated( - "sync_commands_debug is deprecated and will be removed in version 2.8. " + "sync_commands_debug is deprecated and will be removed in a future version. " "Use `command_sync_flags` with an `CommandSyncFlags` instance as a replacement.", stacklevel=3, ) @@ -186,7 +186,7 @@ def __init__( if sync_commands_on_cog_unload is not MISSING: warn_deprecated( - "sync_commands_on_cog_unload is deprecated and will be removed in version 2.8. " + "sync_commands_on_cog_unload is deprecated and will be removed in a future version. " "Use `command_sync_flags` with an `CommandSyncFlags` instance as a replacement.", stacklevel=3, ) From 04639b9af15aac8a07e4d28b6fc46fadeebbf2dd Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Mon, 24 Oct 2022 00:20:06 -0400 Subject: [PATCH 27/44] clean up sync --- disnake/ext/commands/interaction_bot_base.py | 23 ++++++++++---------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index b839645335..fce970cc95 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -766,7 +766,7 @@ async def _sync_application_commands(self) -> None: # Sort all invokable commands between guild IDs: global_cmds, guild_cmds = self._ordered_unsynced_commands(self._test_guilds) - if self._command_sync.sync_global_commands and global_cmds is not None: + if self._command_sync.sync_global_commands: # Update global commands first diff = _app_commands_diff( global_cmds, self._connection._global_application_commands.values() @@ -774,8 +774,8 @@ async def _sync_application_commands(self) -> None: if not self._command_sync.allow_command_deletion: # because allow_command_deletion is disabled, we want to never automatically delete a command # so we move the delete commands to delete_ignored - diff["delete_ignored"] = diff["delete"].copy() - diff["delete"].clear() + diff["delete_ignored"] = diff["delete"] + diff["delete"] = [] update_required = bool(diff["upsert"] or diff["edit"] or diff["delete"]) # Show the difference @@ -793,19 +793,20 @@ async def _sync_application_commands(self) -> None: await self.bulk_overwrite_global_commands(to_send) except Exception as e: warnings.warn(f"Failed to overwrite global commands due to {e}", SyncWarning) + # Same process but for each specified guild individually. # Notice that we're not doing this for every single guild for optimisation purposes. # See the note in :meth:`_cache_application_commands` about guild app commands. - if self._command_sync.sync_guild_commands and guild_cmds is not None: + if self._command_sync.sync_guild_commands: for guild_id, cmds in guild_cmds.items(): current_guild_cmds = self._connection._guild_application_commands.get(guild_id, {}) diff = _app_commands_diff(cmds, current_guild_cmds.values()) if not self._command_sync.allow_command_deletion: # because allow_command_deletion is disabled, we want to never automatically delete a command # so we move the delete commands to delete_ignored - diff["delete_ignored"] = diff["delete"].copy() + diff["delete_ignored"] = diff["delete"] diff["delete"] = [] - update_required = bool(diff["upsert"]) or bool(diff["edit"]) or bool(diff["delete"]) + update_required = bool(diff["upsert"] or diff["edit"] or diff["delete"]) # Show diff self._log_sync_debug( @@ -1290,20 +1291,20 @@ async def process_application_commands( await self.bulk_overwrite_guild_commands(interaction.guild_id, []) # type: ignore except disnake.HTTPException: message = ( - "This command does not exist locally. More information about this: " - f"https://docs.disnake.dev/en/v{disnake.__version__}/ext/commands/additional_info.html" + "This command is not defined. More information about this: " + "https://docs.disnake.dev/page/ext/commands/additional_info.html" "#app-command-sync." ) else: message = ( "This command has just been synced. More information about this: " - f"https://docs.disnake.dev/en/v{disnake.__version__}/ext/commands/additional_info.html" + "https://docs.disnake.dev/page/ext/commands/additional_info.html" "#app-command-sync." ) else: message = ( - "This command does not exist locally. More information about this: " - f"https://docs.disnake.dev/en/v{disnake.__version__}/ext/commands/additional_info.html" + "This command is not defined. More information about this: " + "https://docs.disnake.dev/page/ext/commands/additional_info.html" "#app-command-sync." ) try: From 18de61d5df12753c340d90f51ca8007f15fc3856 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Mon, 24 Oct 2022 00:23:21 -0400 Subject: [PATCH 28/44] chore: clean up the flag code --- disnake/ext/commands/flags.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/disnake/ext/commands/flags.py b/disnake/ext/commands/flags.py index 8a06284897..8372da114d 100644 --- a/disnake/ext/commands/flags.py +++ b/disnake/ext/commands/flags.py @@ -21,7 +21,7 @@ class CommandSyncFlags(BaseFlags): To construct an object you can pass keyword arguments denoting the flags to enable or disable. - The default value is all flags enabled except for debug messages. + If command sync is disabled (see :attr:`sync_commands`), other options will have no effect. .. versionadded:: 2.7 @@ -131,7 +131,10 @@ def none(cls) -> Self: @classmethod def default(cls) -> Self: - """A factory method that creates a :class:`CommandSyncFlags` with the default settings.""" + """A factory method that creates a :class:`CommandSyncFlags` with the default settings. + + The default is all flags enabled except for debug messages. + """ instance = cls.all() instance.sync_commands_debug = False return instance @@ -142,30 +145,32 @@ def _sync_enabled(self): @alias_flag_value def sync_commands(self): - """:class:`bool`: Whether to sync app commands at all.""" - return 1 << 5 | 1 << 6 + """:class:`bool`: Whether to sync app commands at all. + + This controls the :attr:`sync_global_commands` and :attr:`sync_guild_commands` attributes.""" + return 1 << 3 | 1 << 4 @flag_value def sync_commands_debug(self): - """:class:`bool`: Whether or not to show app command sync debug messages""" - return 1 << 1 + """:class:`bool`: Whether or not to show app command sync debug messages.""" + return 1 << 0 @flag_value def on_cog_unload(self): """:class:`bool`: Whether or not to sync app commands on cog unload or reload.""" - return 1 << 2 + return 1 << 1 @flag_value def allow_command_deletion(self): """:class:`bool`: Whether to allow commands to be deleted by automatic command sync.""" - return 1 << 3 + return 1 << 2 @flag_value def sync_global_commands(self): """:class:`bool`: Whether to sync global commands.""" - return 1 << 5 + return 1 << 3 @flag_value def sync_guild_commands(self): """:class:`bool`: Whether to sync per-guild commands.""" - return 1 << 6 + return 1 << 4 From 8884f97a83c9ef9a2f9c99117cdccde30e6b1749 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Mon, 24 Oct 2022 00:29:59 -0400 Subject: [PATCH 29/44] rename on_cog_unload to sync_on_cog_unload --- disnake/ext/commands/cog.py | 4 ++-- disnake/ext/commands/flags.py | 4 ++-- disnake/ext/commands/interaction_bot_base.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/disnake/ext/commands/cog.py b/disnake/ext/commands/cog.py index b493edf685..a368f15fe8 100644 --- a/disnake/ext/commands/cog.py +++ b/disnake/ext/commands/cog.py @@ -808,7 +808,7 @@ def _inject(self, bot: AnyBot) -> Self: bot.add_listener(getattr(self, method_name), name) try: - if bot._command_sync.on_cog_unload: + if bot._command_sync.sync_on_cog_unload: bot._schedule_delayed_command_sync() except NotImplementedError: pass @@ -874,7 +874,7 @@ def _eject(self, bot: AnyBot) -> None: finally: try: - if bot._command_sync.on_cog_unload: + if bot._command_sync.sync_on_cog_unload: bot._schedule_delayed_command_sync() except NotImplementedError: pass diff --git a/disnake/ext/commands/flags.py b/disnake/ext/commands/flags.py index 8372da114d..9ecf7e5be6 100644 --- a/disnake/ext/commands/flags.py +++ b/disnake/ext/commands/flags.py @@ -95,11 +95,11 @@ def __init__( self, *, allow_command_deletion: bool = ..., - on_cog_unload: bool = ..., sync_commands: bool = ..., sync_commands_debug: bool = ..., sync_global_commands: bool = ..., sync_guild_commands: bool = ..., + sync_on_cog_unload: bool = ..., ): ... @@ -156,7 +156,7 @@ def sync_commands_debug(self): return 1 << 0 @flag_value - def on_cog_unload(self): + def sync_on_cog_unload(self): """:class:`bool`: Whether or not to sync app commands on cog unload or reload.""" return 1 << 1 diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index fce970cc95..835a22ea68 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -190,7 +190,7 @@ def __init__( "Use `command_sync_flags` with an `CommandSyncFlags` instance as a replacement.", stacklevel=3, ) - command_sync_flags.on_cog_unload = sync_commands_on_cog_unload + command_sync_flags.sync_on_cog_unload = sync_commands_on_cog_unload self._command_sync = command_sync_flags self._sync_queued: bool = False From 981057f20ba372c951586ddbd4891bafdb651982 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Mon, 24 Oct 2022 00:34:47 -0400 Subject: [PATCH 30/44] readd sync_commands and others to overloads --- disnake/ext/commands/bot.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/disnake/ext/commands/bot.py b/disnake/ext/commands/bot.py index dadd4c068f..e9760d5f8d 100644 --- a/disnake/ext/commands/bot.py +++ b/disnake/ext/commands/bot.py @@ -235,6 +235,9 @@ def __init__( case_insensitive: bool = False, command_sync_flags: CommandSyncFlags = ..., test_guilds: Optional[Sequence[int]] = None, + sync_commands: bool = ..., + sync_commands_debug: bool = ..., + sync_commands_on_cog_unload: bool = ..., asyncio_debug: bool = False, loop: Optional[asyncio.AbstractEventLoop] = None, shard_id: Optional[int] = None, @@ -284,6 +287,9 @@ def __init__( case_insensitive: bool = False, command_sync_flags: CommandSyncFlags = ..., test_guilds: Optional[Sequence[int]] = None, + sync_commands: bool = ..., + sync_commands_debug: bool = ..., + sync_commands_on_cog_unload: bool = ..., asyncio_debug: bool = False, loop: Optional[asyncio.AbstractEventLoop] = None, shard_ids: Optional[List[int]] = None, # instead of shard_id @@ -430,6 +436,9 @@ def __init__( reload: bool = False, command_sync_flags: CommandSyncFlags = ..., test_guilds: Optional[Sequence[int]] = None, + sync_commands: bool = ..., + sync_commands_debug: bool = ..., + sync_commands_on_cog_unload: bool = ..., asyncio_debug: bool = False, loop: Optional[asyncio.AbstractEventLoop] = None, shard_id: Optional[int] = None, @@ -472,6 +481,9 @@ def __init__( reload: bool = False, command_sync_flags: CommandSyncFlags = ..., test_guilds: Optional[Sequence[int]] = None, + sync_commands: bool = ..., + sync_commands_debug: bool = ..., + sync_commands_on_cog_unload: bool = ..., asyncio_debug: bool = False, loop: Optional[asyncio.AbstractEventLoop] = None, shard_ids: Optional[List[int]] = None, # instead of shard_id From 43161f204f0aa1c4995999f116edde5ea49309a4 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Mon, 24 Oct 2022 03:13:53 -0400 Subject: [PATCH 31/44] remove duplicated section --- disnake/ext/commands/interaction_bot_base.py | 6 +++--- docs/ext/commands/additional_info.rst | 10 +++------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 835a22ea68..992b42b45a 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -1293,19 +1293,19 @@ async def process_application_commands( message = ( "This command is not defined. More information about this: " "https://docs.disnake.dev/page/ext/commands/additional_info.html" - "#app-command-sync." + "#unknown-commands." ) else: message = ( "This command has just been synced. More information about this: " "https://docs.disnake.dev/page/ext/commands/additional_info.html" - "#app-command-sync." + "#unknown-commands." ) else: message = ( "This command is not defined. More information about this: " "https://docs.disnake.dev/page/ext/commands/additional_info.html" - "#app-command-sync." + "#unknown-commands." ) try: # This part is in a separate try-except because we still should respond to the interaction diff --git a/docs/ext/commands/additional_info.rst b/docs/ext/commands/additional_info.rst index 65648aec8f..d6b98e4146 100644 --- a/docs/ext/commands/additional_info.rst +++ b/docs/ext/commands/additional_info.rst @@ -28,14 +28,10 @@ This can lead to situations where a command no longer exists in the code but sti To rectify this, just run the command. It will automatically be deleted. -Changing Guild Commands -++++++++++++++++++++++++ - -If you remove some IDs from the ``test_guilds`` kwarg of :class:`Bot ` (or a similar class) or from the ``guild_ids`` kwarg of -:func:`slash_command ` (:func:`user_command `, :func:`message_command `) -the commands in those guilds won't be deleted instantly. As explained above, they'll be deleted as soon as one of the deprecated commands is invoked. Your bot will send a message -like "This command has just been synced ...". +.. _changing-test-guilds: +This will also occur when IDs are removed from the ``test_guilds`` kwarg of :class:`Bot ` (or a similar class) or from the ``guild_ids`` kwarg of +:func:`slash_command `, :func:`user_command `, or :func:`message_command `. Command Sync with Multiple Clusters ++++++++++++++++++++++++++++++++++++ From 4433b1600310c67ea478e9282a1c3934bc1552a9 Mon Sep 17 00:00:00 2001 From: arl Date: Mon, 24 Oct 2022 18:41:01 -0400 Subject: [PATCH 32/44] add suggestion documentation changes Co-authored-by: shiftinv <8530778+shiftinv@users.noreply.github.com> Signed-off-by: arl --- disnake/ext/commands/flags.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/disnake/ext/commands/flags.py b/disnake/ext/commands/flags.py index 9ecf7e5be6..9b0f7970bf 100644 --- a/disnake/ext/commands/flags.py +++ b/disnake/ext/commands/flags.py @@ -133,7 +133,7 @@ def none(cls) -> Self: def default(cls) -> Self: """A factory method that creates a :class:`CommandSyncFlags` with the default settings. - The default is all flags enabled except for debug messages. + The default is all flags enabled except for :attr:`sync_commands_debug`. """ instance = cls.all() instance.sync_commands_debug = False @@ -145,7 +145,7 @@ def _sync_enabled(self): @alias_flag_value def sync_commands(self): - """:class:`bool`: Whether to sync app commands at all. + """:class:`bool`: Whether to sync global and guild app commands. This controls the :attr:`sync_global_commands` and :attr:`sync_guild_commands` attributes.""" return 1 << 3 | 1 << 4 From c69ada2a98a659776150afc98072ed1e5b262a4e Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Mon, 24 Oct 2022 19:07:11 -0400 Subject: [PATCH 33/44] chore: rename command_sync to command_sync_flags --- disnake/ext/commands/cog.py | 4 +-- disnake/ext/commands/interaction_bot_base.py | 31 ++++++++++++-------- docs/ext/commands/additional_info.rst | 2 +- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/disnake/ext/commands/cog.py b/disnake/ext/commands/cog.py index a368f15fe8..0e99e177a9 100644 --- a/disnake/ext/commands/cog.py +++ b/disnake/ext/commands/cog.py @@ -808,7 +808,7 @@ def _inject(self, bot: AnyBot) -> Self: bot.add_listener(getattr(self, method_name), name) try: - if bot._command_sync.sync_on_cog_unload: + if bot._command_sync_flags.sync_on_cog_unload: bot._schedule_delayed_command_sync() except NotImplementedError: pass @@ -874,7 +874,7 @@ def _eject(self, bot: AnyBot) -> None: finally: try: - if bot._command_sync.sync_on_cog_unload: + if bot._command_sync_flags.sync_on_cog_unload: bot._schedule_delayed_command_sync() except NotImplementedError: pass diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 992b42b45a..31c5a1cfa9 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -192,7 +192,7 @@ def __init__( ) command_sync_flags.sync_on_cog_unload = sync_commands_on_cog_unload - self._command_sync = command_sync_flags + self._command_sync_flags = command_sync_flags self._sync_queued: bool = False self._slash_command_checks = [] @@ -216,13 +216,13 @@ def __init__( self._schedule_app_command_preparation() @property - def command_sync(self) -> CommandSyncFlags: + def command_sync_flags(self) -> CommandSyncFlags: """:class:`~.ext.commands.CommandSyncFlags`: The command sync flags configured for this bot. .. versionadded:: 2.7 """ - return CommandSyncFlags._from_value(self._command_sync.value) + return CommandSyncFlags._from_value(self._command_sync_flags.value) def application_commands_iterator(self) -> Iterable[InvokableApplicationCommand]: return chain( @@ -759,19 +759,19 @@ async def _sync_application_commands(self) -> None: if not isinstance(self, disnake.Client): raise NotImplementedError("This method is only usable in disnake.Client subclasses") - if not self._command_sync._sync_enabled or self._is_closed or self.loop.is_closed(): + if not self._command_sync_flags._sync_enabled or self._is_closed or self.loop.is_closed(): 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) - if self._command_sync.sync_global_commands: + if self._command_sync_flags.sync_global_commands: # Update global commands first diff = _app_commands_diff( global_cmds, self._connection._global_application_commands.values() ) - if not self._command_sync.allow_command_deletion: + if not self._command_sync_flags.allow_command_deletion: # because allow_command_deletion is disabled, we want to never automatically delete a command # so we move the delete commands to delete_ignored diff["delete_ignored"] = diff["delete"] @@ -797,11 +797,11 @@ async def _sync_application_commands(self) -> None: # Same process but for each specified guild individually. # Notice that we're not doing this for every single guild for optimisation purposes. # See the note in :meth:`_cache_application_commands` about guild app commands. - if self._command_sync.sync_guild_commands: + if self._command_sync_flags.sync_guild_commands: for guild_id, cmds in guild_cmds.items(): current_guild_cmds = self._connection._guild_application_commands.get(guild_id, {}) diff = _app_commands_diff(cmds, current_guild_cmds.values()) - if not self._command_sync.allow_command_deletion: + if not self._command_sync_flags.allow_command_deletion: # because allow_command_deletion is disabled, we want to never automatically delete a command # so we move the delete commands to delete_ignored diff["delete_ignored"] = diff["delete"] @@ -830,7 +830,7 @@ async def _sync_application_commands(self) -> None: self._log_sync_debug("Command synchronization task has finished") def _log_sync_debug(self, text: str) -> None: - if self._command_sync.sync_commands_debug: + if self._command_sync_flags.sync_commands_debug: # if sync debugging is enabled, *always* output logs if _log.isEnabledFor(logging.INFO): # if the log level is `INFO` or higher, use that @@ -857,7 +857,7 @@ async def _delayed_command_sync(self) -> None: raise NotImplementedError("This method is only usable in disnake.Client subclasses") if ( - not self._command_sync._sync_enabled + not self._command_sync_flags._sync_enabled or self._sync_queued or not self.is_ready() or self._is_closed @@ -1275,7 +1275,7 @@ async def process_application_commands( interaction: :class:`disnake.ApplicationCommandInteraction` The interaction to process commands for. """ - if self._command_sync._sync_enabled and not self._sync_queued: + if self._command_sync_flags._sync_enabled and not self._sync_queued: known_command = self.get_global_command(interaction.data.id) # type: ignore if known_command is None: @@ -1286,12 +1286,17 @@ async def process_application_commands( # This usually comes from the blind spots of the sync algorithm. # Since not all guild commands are cached, it is possible to experience such issues. # In this case, the blind spot is the interaction guild, let's fix it: - if self._command_sync.allow_command_deletion: + if self._command_sync_flags.allow_command_deletion: try: await self.bulk_overwrite_guild_commands(interaction.guild_id, []) # type: ignore except disnake.HTTPException: + # for some reason we were unable to sync the command + # either malformed API request, or some other error + # in theory this will never error: if a command exists the bot has authorisation + # in practice this is not the case, the API could change valid requests at any time message = ( - "This command is not defined. More information about this: " + "This comand could not be processed. Additionally, an error occured when trying to sync commands. " + "More information about this: " "https://docs.disnake.dev/page/ext/commands/additional_info.html" "#unknown-commands." ) diff --git a/docs/ext/commands/additional_info.rst b/docs/ext/commands/additional_info.rst index d6b98e4146..8631e3fc05 100644 --- a/docs/ext/commands/additional_info.rst +++ b/docs/ext/commands/additional_info.rst @@ -36,7 +36,7 @@ This will also occur when IDs are removed from the ``test_guilds`` kwarg of :cla Command Sync with Multiple Clusters ++++++++++++++++++++++++++++++++++++ -If your bot requires shard distribution across several clusters, you should disable command_sync on all clusters except one. +If your bot requires shard distribution across several clusters, you should disable command sync on all clusters except one. This will prevent conflicts and race conditions. Discord API doesn't provide users with events related to application command updates, so it's impossible to keep the cache of multiple machines synced. Having only 1 cluster with ``sync_commands`` set to ``True`` is enough because global registration of application commands doesn't depend on sharding. From 856efd2e9d9095d721f96cc1822d7cca9799dd06 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Mon, 24 Oct 2022 19:13:45 -0400 Subject: [PATCH 34/44] consistency --- disnake/ext/commands/flags.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/disnake/ext/commands/flags.py b/disnake/ext/commands/flags.py index 9b0f7970bf..0dfad668ad 100644 --- a/disnake/ext/commands/flags.py +++ b/disnake/ext/commands/flags.py @@ -135,9 +135,9 @@ def default(cls) -> Self: The default is all flags enabled except for :attr:`sync_commands_debug`. """ - instance = cls.all() - instance.sync_commands_debug = False - return instance + self = cls.all() + self.sync_commands_debug = False + return self @property def _sync_enabled(self): From 7667389188bf40b819f5b9f002245995219a48e2 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Mon, 24 Oct 2022 19:15:52 -0400 Subject: [PATCH 35/44] expand on sync_commands shortcut --- disnake/ext/commands/flags.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/disnake/ext/commands/flags.py b/disnake/ext/commands/flags.py index 0dfad668ad..211c43a373 100644 --- a/disnake/ext/commands/flags.py +++ b/disnake/ext/commands/flags.py @@ -21,7 +21,7 @@ class CommandSyncFlags(BaseFlags): To construct an object you can pass keyword arguments denoting the flags to enable or disable. - If command sync is disabled (see :attr:`sync_commands`), other options will have no effect. + If command sync is disabled (see the docs of :attr:`sync_commands` for more info), other options will have no effect. .. versionadded:: 2.7 @@ -147,7 +147,10 @@ def _sync_enabled(self): def sync_commands(self): """:class:`bool`: Whether to sync global and guild app commands. - This controls the :attr:`sync_global_commands` and :attr:`sync_guild_commands` attributes.""" + This controls the :attr:`sync_global_commands` and :attr:`sync_guild_commands` attributes. + + Note that it is possible for sync to be enabled for guild *or* global commands yet this will return ``False``. + """ return 1 << 3 | 1 << 4 @flag_value From fbf1df9cc954f5880c1215d41f24e51ef54f68fa Mon Sep 17 00:00:00 2001 From: shiftinv Date: Tue, 25 Oct 2022 16:06:21 +0200 Subject: [PATCH 36/44] nit: fix typo --- 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 31c5a1cfa9..236cc38eeb 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -1295,7 +1295,7 @@ async def process_application_commands( # in theory this will never error: if a command exists the bot has authorisation # in practice this is not the case, the API could change valid requests at any time message = ( - "This comand could not be processed. Additionally, an error occured when trying to sync commands. " + "This command could not be processed. Additionally, an error occured when trying to sync commands. " "More information about this: " "https://docs.disnake.dev/page/ext/commands/additional_info.html" "#unknown-commands." From 740aecdafda4048e2b90ebc9e4f715fe94de2b40 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Wed, 26 Oct 2022 11:10:04 -0400 Subject: [PATCH 37/44] fix: actually send the delete_ignored commands instead of deleting them --- disnake/ext/commands/interaction_bot_base.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 236cc38eeb..68bf077a03 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -79,6 +79,10 @@ class _Diff(TypedDict): delete_ignored: NotRequired[List[ApplicationCommand]] +def _get_to_send_from_diff(diff: _Diff): + return diff["no_changes"] + diff["upsert"] + diff["edit"] + diff.get("delete_ignored", []) + + def _app_commands_diff( new_commands: Iterable[ApplicationCommand], old_commands: Iterable[ApplicationCommand], @@ -788,8 +792,8 @@ async def _sync_application_commands(self) -> None: if update_required: # Notice that we don't do any API requests if there're no changes. + to_send = _get_to_send_from_diff(diff) try: - to_send = diff["no_changes"] + diff["edit"] + diff["upsert"] await self.bulk_overwrite_global_commands(to_send) except Exception as e: warnings.warn(f"Failed to overwrite global commands due to {e}", SyncWarning) @@ -818,8 +822,8 @@ async def _sync_application_commands(self) -> None: # Do API requests and cache if update_required: + to_send = _get_to_send_from_diff(diff) try: - to_send = diff["no_changes"] + diff["edit"] + diff["upsert"] await self.bulk_overwrite_guild_commands(guild_id, to_send) except Exception as e: warnings.warn( From 9030d0951374b5c34d115f67e7128e4a26731c96 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Wed, 26 Oct 2022 11:12:15 -0400 Subject: [PATCH 38/44] chore: rename sync_on_cog_unload to sync_on_cog_actions --- disnake/ext/commands/cog.py | 4 ++-- disnake/ext/commands/flags.py | 6 +++--- disnake/ext/commands/interaction_bot_base.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/disnake/ext/commands/cog.py b/disnake/ext/commands/cog.py index 0e99e177a9..8dc2ff626e 100644 --- a/disnake/ext/commands/cog.py +++ b/disnake/ext/commands/cog.py @@ -808,7 +808,7 @@ def _inject(self, bot: AnyBot) -> Self: bot.add_listener(getattr(self, method_name), name) try: - if bot._command_sync_flags.sync_on_cog_unload: + if bot._command_sync_flags.sync_on_cog_actions: bot._schedule_delayed_command_sync() except NotImplementedError: pass @@ -874,7 +874,7 @@ def _eject(self, bot: AnyBot) -> None: finally: try: - if bot._command_sync_flags.sync_on_cog_unload: + if bot._command_sync_flags.sync_on_cog_actions: bot._schedule_delayed_command_sync() except NotImplementedError: pass diff --git a/disnake/ext/commands/flags.py b/disnake/ext/commands/flags.py index 211c43a373..c29ba4a679 100644 --- a/disnake/ext/commands/flags.py +++ b/disnake/ext/commands/flags.py @@ -99,7 +99,7 @@ def __init__( sync_commands_debug: bool = ..., sync_global_commands: bool = ..., sync_guild_commands: bool = ..., - sync_on_cog_unload: bool = ..., + sync_on_cog_actions: bool = ..., ): ... @@ -159,8 +159,8 @@ def sync_commands_debug(self): return 1 << 0 @flag_value - def sync_on_cog_unload(self): - """:class:`bool`: Whether or not to sync app commands on cog unload or reload.""" + def sync_on_cog_actions(self): + """:class:`bool`: Whether or not to sync app commands on cog load, unload, or reload.""" return 1 << 1 @flag_value diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 68bf077a03..b1b185a510 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -194,7 +194,7 @@ def __init__( "Use `command_sync_flags` with an `CommandSyncFlags` instance as a replacement.", stacklevel=3, ) - command_sync_flags.sync_on_cog_unload = sync_commands_on_cog_unload + command_sync_flags.sync_on_cog_actions = sync_commands_on_cog_unload self._command_sync_flags = command_sync_flags self._sync_queued: bool = False From 8d102dcd061fc367b07499b1331b3b8ec7799a60 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Wed, 26 Oct 2022 11:12:43 -0400 Subject: [PATCH 39/44] doc: update allow_command_deletion to explain how renames work --- disnake/ext/commands/flags.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/disnake/ext/commands/flags.py b/disnake/ext/commands/flags.py index c29ba4a679..823228e372 100644 --- a/disnake/ext/commands/flags.py +++ b/disnake/ext/commands/flags.py @@ -165,7 +165,11 @@ def sync_on_cog_actions(self): @flag_value def allow_command_deletion(self): - """:class:`bool`: Whether to allow commands to be deleted by automatic command sync.""" + """:class:`bool`: Whether to allow commands to be deleted by automatic command sync. + + Current implementation of commands sync of renamed commands means that a rename of a command *will* result + in the old one being deleted and a new command being created. + """ return 1 << 2 @flag_value From 4d3c7e48c74dab2e95627ef869a7157ffebf4fbd Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Wed, 26 Oct 2022 12:07:30 -0400 Subject: [PATCH 40/44] fix: require sync guild commands to be enabled as well --- disnake/ext/commands/interaction_bot_base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index b1b185a510..8eef2ac3d1 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -1290,7 +1290,10 @@ async def process_application_commands( # This usually comes from the blind spots of the sync algorithm. # Since not all guild commands are cached, it is possible to experience such issues. # In this case, the blind spot is the interaction guild, let's fix it: - if self._command_sync_flags.allow_command_deletion: + if ( + self._command_sync_flags.sync_guild_commands + and self._command_sync_flags.allow_command_deletion + ): try: await self.bulk_overwrite_guild_commands(interaction.guild_id, []) # type: ignore except disnake.HTTPException: From bd6e394b32d31b66af166913c15124bff85748cb Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Wed, 26 Oct 2022 13:20:02 -0400 Subject: [PATCH 41/44] refactor interaction handling --- disnake/ext/commands/interaction_bot_base.py | 77 ++++++++++---------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index 8eef2ac3d1..2372db42e2 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -1280,54 +1280,57 @@ async def process_application_commands( The interaction to process commands for. """ if self._command_sync_flags._sync_enabled and not self._sync_queued: - known_command = self.get_global_command(interaction.data.id) # type: ignore - - if known_command is None: + if guild_id := interaction.data.get( + "guild_id", None + ): # this means that a guild command was invoked known_command = self.get_guild_command(interaction.guild_id, interaction.data.id) # type: ignore + else: + known_command = self.get_global_command(interaction.data.id) # type: ignore - if known_command is None: + # this command is only a guild command if guild_id is truthy + if known_command is None and guild_id: # don't do anything if we aren't allowed to disable them # This usually comes from the blind spots of the sync algorithm. # Since not all guild commands are cached, it is possible to experience such issues. # In this case, the blind spot is the interaction guild, let's fix it: - if ( - self._command_sync_flags.sync_guild_commands - and self._command_sync_flags.allow_command_deletion - ): - try: - await self.bulk_overwrite_guild_commands(interaction.guild_id, []) # type: ignore - except disnake.HTTPException: - # for some reason we were unable to sync the command - # either malformed API request, or some other error - # in theory this will never error: if a command exists the bot has authorisation - # in practice this is not the case, the API could change valid requests at any time - message = ( - "This command could not be processed. Additionally, an error occured when trying to sync commands. " - "More information about this: " - "https://docs.disnake.dev/page/ext/commands/additional_info.html" - "#unknown-commands." - ) + if self._command_sync_flags.sync_guild_commands: + if self._command_sync_flags.allow_command_deletion: + try: + await self.bulk_overwrite_guild_commands(interaction.guild_id, []) # type: ignore + except disnake.HTTPException: + # for some reason we were unable to sync the command + # either malformed API request, or some other error + # in theory this will never error: if a command exists the bot has authorisation + # in practice this is not the case, the API could change valid requests at any time + message = ( + "This command could not be processed. Additionally, an error occured when trying to sync commands. " + "More information about this: " + "https://docs.disnake.dev/page/ext/commands/additional_info.html" + "#unknown-commands." + ) + else: + message = ( + "This command has just been synced. More information about this: " + "https://docs.disnake.dev/page/ext/commands/additional_info.html" + "#unknown-commands." + ) else: + # this block is responsible for responding to guild commands that we don't delete + # this could be changed to not respond but that behavior is undecided message = ( - "This command has just been synced. More information about this: " + "This command could not be processed. More information about this: " "https://docs.disnake.dev/page/ext/commands/additional_info.html" "#unknown-commands." ) - else: - message = ( - "This command is not defined. More information about this: " - "https://docs.disnake.dev/page/ext/commands/additional_info.html" - "#unknown-commands." - ) - try: - # This part is in a separate try-except because we still should respond to the interaction - await interaction.response.send_message( - message, - ephemeral=True, - ) - except disnake.HTTPException: - pass - return + try: + # This part is in a separate try-except because we still should respond to the interaction + await interaction.response.send_message( + message, + ephemeral=True, + ) + except disnake.HTTPException: + pass + return command_type = interaction.data.type command_name = interaction.data.name From 5b1326ff352bf330e107414238d91cafc9b322f6 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Wed, 26 Oct 2022 13:33:09 -0400 Subject: [PATCH 42/44] fix: catch interaction timed out errors --- 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 2372db42e2..e7297615cd 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -1328,7 +1328,7 @@ async def process_application_commands( message, ephemeral=True, ) - except disnake.HTTPException: + except (disnake.HTTPException, disnake.InteractionTimedOut): pass return From 2baf2b7758f7556acb521af1c97b6556fa78196d Mon Sep 17 00:00:00 2001 From: shiftinv Date: Wed, 26 Oct 2022 20:23:03 +0200 Subject: [PATCH 43/44] refactor: clean up unknown command handling --- disnake/ext/commands/interaction_bot_base.py | 93 +++++++++----------- 1 file changed, 42 insertions(+), 51 deletions(-) diff --git a/disnake/ext/commands/interaction_bot_base.py b/disnake/ext/commands/interaction_bot_base.py index e7297615cd..d05106e0e6 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -1279,58 +1279,49 @@ async def process_application_commands( interaction: :class:`disnake.ApplicationCommandInteraction` The interaction to process commands for. """ - if self._command_sync_flags._sync_enabled and not self._sync_queued: - if guild_id := interaction.data.get( - "guild_id", None - ): # this means that a guild command was invoked - known_command = self.get_guild_command(interaction.guild_id, interaction.data.id) # type: ignore + + # This usually comes from the blind spots of the sync algorithm. + # Since not all guild commands are cached, it is possible to experience such issues. + # In this case, the blind spot is the interaction guild, let's fix it: + if ( + # if we're not currently syncing, + not self._sync_queued + # 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 we don't know the command + and not self.get_guild_command(interaction.guild_id, interaction.data.id) # type: ignore + ): + # don't do anything if we aren't allowed to disable them + if self._command_sync_flags.allow_command_deletion: + try: + await self.bulk_overwrite_guild_commands(interaction.guild_id, []) # type: ignore + except disnake.HTTPException: + # for some reason we were unable to sync the command + # either malformed API request, or some other error + # in theory this will never error: if a command exists the bot has authorisation + # in practice this is not the case, the API could change valid requests at any time + message = "This command could not be processed. Additionally, an error occured when trying to sync commands." + else: + message = "This command has just been synced" else: - known_command = self.get_global_command(interaction.data.id) # type: ignore - - # this command is only a guild command if guild_id is truthy - if known_command is None and guild_id: - # don't do anything if we aren't allowed to disable them - # This usually comes from the blind spots of the sync algorithm. - # Since not all guild commands are cached, it is possible to experience such issues. - # In this case, the blind spot is the interaction guild, let's fix it: - if self._command_sync_flags.sync_guild_commands: - if self._command_sync_flags.allow_command_deletion: - try: - await self.bulk_overwrite_guild_commands(interaction.guild_id, []) # type: ignore - except disnake.HTTPException: - # for some reason we were unable to sync the command - # either malformed API request, or some other error - # in theory this will never error: if a command exists the bot has authorisation - # in practice this is not the case, the API could change valid requests at any time - message = ( - "This command could not be processed. Additionally, an error occured when trying to sync commands. " - "More information about this: " - "https://docs.disnake.dev/page/ext/commands/additional_info.html" - "#unknown-commands." - ) - else: - message = ( - "This command has just been synced. More information about this: " - "https://docs.disnake.dev/page/ext/commands/additional_info.html" - "#unknown-commands." - ) - else: - # this block is responsible for responding to guild commands that we don't delete - # this could be changed to not respond but that behavior is undecided - message = ( - "This command could not be processed. More information about this: " - "https://docs.disnake.dev/page/ext/commands/additional_info.html" - "#unknown-commands." - ) - try: - # This part is in a separate try-except because we still should respond to the interaction - await interaction.response.send_message( - message, - ephemeral=True, - ) - except (disnake.HTTPException, disnake.InteractionTimedOut): - pass - return + # this block is responsible for responding to guild commands that we don't delete + # this could be changed to not respond but that behavior is undecided + message = "This command could not be processed." + try: + # This part is in a separate try-except because we still should respond to the interaction + message += ( + " More information about this: " + "https://docs.disnake.dev/page/ext/commands/additional_info.html#unknown-commands." + ) + await interaction.response.send_message( + message, + ephemeral=True, + ) + except (disnake.HTTPException, disnake.InteractionTimedOut): + pass + return command_type = interaction.data.type command_name = interaction.data.name From 7f54110875c831f8a7630fb6d2d755ea5db84f3c Mon Sep 17 00:00:00 2001 From: shiftinv Date: Wed, 26 Oct 2022 20:56:26 +0200 Subject: [PATCH 44/44] nit: add dot --- 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 d05106e0e6..b3d9c3aa25 100644 --- a/disnake/ext/commands/interaction_bot_base.py +++ b/disnake/ext/commands/interaction_bot_base.py @@ -1304,7 +1304,7 @@ async def process_application_commands( # in practice this is not the case, the API could change valid requests at any time message = "This command could not be processed. Additionally, an error occured when trying to sync commands." else: - message = "This command has just been synced" + message = "This command has just been synced." else: # this block is responsible for responding to guild commands that we don't delete # this could be changed to not respond but that behavior is undecided