From aa51fd9ad1463484039f1578ae71210a516000b8 Mon Sep 17 00:00:00 2001 From: Flame442 <34169552+Flame442@users.noreply.github.com> Date: Fri, 14 Apr 2023 17:59:21 -0400 Subject: [PATCH] Allow force enabling app commands using flag in extras (#6018) --- docs/cog_guides/core.rst | 9 ++++- redbot/core/bot.py | 2 ++ redbot/core/core_commands.py | 65 ++++++++++++++++++++++++++++++------ redbot/core/tree.py | 47 ++++++++++++++++++-------- 4 files changed, 98 insertions(+), 25 deletions(-) diff --git a/docs/cog_guides/core.rst b/docs/cog_guides/core.rst index ae72dc5a4..1a66dfef5 100644 --- a/docs/cog_guides/core.rst +++ b/docs/cog_guides/core.rst @@ -4168,7 +4168,14 @@ slash list **Description** List the slash commands the bot can see, and whether or not they are enabled. + This command shows the state that will be changed to when ``[p]slash sync`` is run. +Commands from the same cog are grouped, with the cog name as the header. + +The prefix denotes the state of the command: +- Commands starting with ``- `` have not yet been enabled. +- Commands starting with ``+ `` have been manually enabled. +- Commands starting with ``++`` have been enabled by the cog author, and cannot be disabled. .. _core-command-slash-sync: @@ -4330,4 +4337,4 @@ uptime **Description** -Shows Red's uptime. +Shows Red's uptime. \ No newline at end of file diff --git a/redbot/core/bot.py b/redbot/core/bot.py index 49a339fdd..c40e6e514 100644 --- a/redbot/core/bot.py +++ b/redbot/core/bot.py @@ -1930,6 +1930,8 @@ class Red( self.dispatch("command_add", subcommand) if permissions_not_loaded: subcommand.requires.ready_event.set() + if isinstance(command, (commands.HybridCommand, commands.HybridGroup)): + command.app_command.extras = command.extras def remove_command(self, name: str, /) -> Optional[commands.Command]: command = super().remove_command(name) diff --git a/redbot/core/core_commands.py b/redbot/core/core_commands.py index 08241cf89..e246ce6f6 100644 --- a/redbot/core/core_commands.py +++ b/redbot/core/core_commands.py @@ -2045,11 +2045,22 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): await ctx.send(_("Command type must be one of `slash`, `message`, or `user`.")) return + existing = self.bot.tree.get_command(command_name, type=raw_type) + if existing is not None and existing.extras.get("red_force_enable", False): + await ctx.send( + _( + "That application command has been set as required for the cog to function " + "by the author, and cannot be disabled. " + "The cog must be unloaded to remove the command." + ) + ) + return + current_settings = await self.bot.list_enabled_app_commands() current_settings = current_settings[command_type] if command_name not in current_settings: - await ctx.send(_("That application command is already disabled.")) + await ctx.send(_("That application command is already disabled or does not exist.")) return await self.bot.disable_app_command(command_name, raw_type) @@ -2198,6 +2209,12 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): """List the slash commands the bot can see, and whether or not they are enabled. This command shows the state that will be changed to when `[p]slash sync` is run. + Commands from the same cog are grouped, with the cog name as the header. + + The prefix denotes the state of the command: + - Commands starting with `- ` have not yet been enabled. + - Commands starting with `+ ` have been manually enabled. + - Commands starting with `++` have been enabled by the cog author, and cannot be disabled. """ cog_commands = defaultdict(list) slash_command_names = set() @@ -2208,13 +2225,27 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): module = command.module if "." in module: module = module[: module.find(".")] - cog_commands[module].append((command.name, discord.AppCommandType.chat_input, True)) + cog_commands[module].append( + ( + command.name, + discord.AppCommandType.chat_input, + True, + command.extras.get("red_force_enable", False), + ) + ) slash_command_names.add(command.name) for command in self.bot.tree._disabled_global_commands.values(): module = command.module if "." in module: module = module[: module.find(".")] - cog_commands[module].append((command.name, discord.AppCommandType.chat_input, False)) + cog_commands[module].append( + ( + command.name, + discord.AppCommandType.chat_input, + False, + command.extras.get("red_force_enable", False), + ) + ) for key, command in self.bot.tree._context_menus.items(): # Filter out guild context menus if key[1] is not None: @@ -2222,7 +2253,9 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): module = command.module if "." in module: module = module[: module.find(".")] - cog_commands[module].append((command.name, command.type, True)) + cog_commands[module].append( + (command.name, command.type, True, command.extras.get("red_force_enable", False)) + ) if command.type is discord.AppCommandType.message: message_command_names.add(command.name) elif command.type is discord.AppCommandType.user: @@ -2231,7 +2264,9 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): module = command.module if "." in module: module = module[: module.find(".")] - cog_commands[module].append((command.name, command.type, False)) + cog_commands[module].append( + (command.name, command.type, False, command.extras.get("red_force_enable", False)) + ) # Commands added with evals will come from __main__, make them unknown instead if "__main__" in cog_commands: @@ -2245,9 +2280,13 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): unknown_message = set(enabled_commands["message"]) - message_command_names unknown_user = set(enabled_commands["user"]) - user_command_names - unknown_slash = [(n, discord.AppCommandType.chat_input, True) for n in unknown_slash] - unknown_message = [(n, discord.AppCommandType.message, True) for n in unknown_message] - unknown_user = [(n, discord.AppCommandType.user, True) for n in unknown_user] + unknown_slash = [ + (n, discord.AppCommandType.chat_input, True, False) for n in unknown_slash + ] + unknown_message = [ + (n, discord.AppCommandType.message, True, False) for n in unknown_message + ] + unknown_user = [(n, discord.AppCommandType.user, True, False) for n in unknown_user] cog_commands["(unknown)"].extend(unknown_slash) cog_commands["(unknown)"].extend(unknown_message) @@ -2263,8 +2302,14 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): msg = "" for cog in sorted(cog_commands.keys()): msg += cog + "\n" - for name, raw_command_type, enabled in sorted(cog_commands[cog], key=lambda v: v[0]): - diff = "+ " if enabled else "- " + for name, raw_command_type, enabled, forced in sorted( + cog_commands[cog], key=lambda v: v[0] + ): + diff = "- " + if forced: + diff = "++ " + elif enabled: + diff = "+ " command_type = "unknown" if raw_command_type is discord.AppCommandType.chat_input: command_type = "slash" diff --git a/redbot/core/tree.py b/redbot/core/tree.py index 2d97b3e3d..cd93ca11d 100644 --- a/redbot/core/tree.py +++ b/redbot/core/tree.py @@ -61,7 +61,11 @@ class RedTree(CommandTree): Commands will be internally stored until enabled by ``[p]slash enable``. """ # Allow guild specific commands to bypass the internals for development - if guild is not MISSING or guilds is not MISSING: + if ( + guild is not MISSING + or guilds is not MISSING + or command.extras.get("red_force_enable", False) + ): return super().add_command( command, *args, guild=guild, guilds=guilds, override=override, **kwargs ) @@ -171,45 +175,60 @@ class RedTree(CommandTree): """ enabled_commands = await self.client.list_enabled_app_commands() - to_add_commands = [] - to_add_context = [] - to_remove_commands = [] - to_remove_context = [] + to_add_commands = set() + to_add_context = set() + to_remove_commands = set() + to_remove_context = set() # Add commands for command in enabled_commands["slash"]: if command in self._disabled_global_commands: - to_add_commands.append(command) + to_add_commands.add(command) # Add context for command in enabled_commands["message"]: key = (command, None, discord.AppCommandType.message.value) if key in self._disabled_context_menus: - to_add_context.append(key) + to_add_context.add(key) for command in enabled_commands["user"]: key = (command, None, discord.AppCommandType.user.value) if key in self._disabled_context_menus: - to_add_context.append(key) + to_add_context.add(key) + + # Add force enabled commands + for command, command_obj in self._disabled_global_commands.items(): + if command_obj.extras.get("red_force_enable", False): + to_add_commands.add(command) + + # Add force enabled context + for key, command_obj in self._disabled_context_menus.items(): + if command_obj.extras.get("red_force_enable", False): + to_add_context.add(key) # Remove commands - for command in self._global_commands: - if command not in enabled_commands["slash"]: - to_remove_commands.append((command, discord.AppCommandType.chat_input)) + for command, command_obj in self._global_commands.items(): + if command not in enabled_commands["slash"] and not command_obj.extras.get( + "red_force_enable", False + ): + to_remove_commands.add((command, discord.AppCommandType.chat_input)) # Remove context - for command, guild_id, command_type in self._context_menus: + for key, command_obj in self._context_menus.items(): + command, guild_id, command_type = key if guild_id is not None: continue if ( discord.AppCommandType(command_type) is discord.AppCommandType.message and command not in enabled_commands["message"] + and not command_obj.extras.get("red_force_enable", False) ): - to_remove_context.append((command, discord.AppCommandType.message)) + to_remove_context.add((command, discord.AppCommandType.message)) elif ( discord.AppCommandType(command_type) is discord.AppCommandType.user and command not in enabled_commands["user"] + and not command_obj.extras.get("red_force_enable", False) ): - to_remove_context.append((command, discord.AppCommandType.user)) + to_remove_context.add((command, discord.AppCommandType.user)) # Actually add/remove for command in to_add_commands: