From e5b236fb1c1a69ad0428c770c78a069953008734 Mon Sep 17 00:00:00 2001 From: PhenoM4n4n <61065078+phenom4n4n@users.noreply.github.com> Date: Tue, 7 Sep 2021 14:43:29 -0700 Subject: [PATCH] Add CommandConverter and CogConverter + add usage in Core (#5037) * add commands, cog converter * properly use type_checking * make core commands use command converter * update commands to use cogconverter * fix undefined variable name, style * Update redbot/core/commands/converter.py Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com> * Update redbot/core/commands/converter.py Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com> * Update redbot/core/core_commands.py Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com> * Update redbot/core/core_commands.py Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com> * Update redbot/core/core_commands.py Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com> * Update redbot/core/core_commands.py Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com> * Update redbot/core/core_commands.py Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com> * update cog argument names * update documentation arg names * update more docs * This new Sphinx is annoying about this... * I'm questioning my skills * Fix name error in `[p]embedset showsettings` when command is not given * Do not use the new cog converter in `[p]command enablecog` This is needed so that a cog that isn't loaded but was disabled can be enabled back. Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com> --- docs/cog_guides/core.rst | 26 ++--- redbot/core/commands/__init__.py | 2 + redbot/core/commands/converter.py | 28 ++++++ redbot/core/core_commands.py | 154 ++++++++++-------------------- 4 files changed, 95 insertions(+), 115 deletions(-) diff --git a/docs/cog_guides/core.rst b/docs/cog_guides/core.rst index dd82fd6c2..d4a69d87a 100644 --- a/docs/cog_guides/core.rst +++ b/docs/cog_guides/core.rst @@ -383,7 +383,7 @@ command defaultdisablecog .. code-block:: none - [p]command defaultdisablecog + [p]command defaultdisablecog **Description** @@ -400,7 +400,7 @@ To override it, use ``[p]command enablecog`` on the servers you want to allow us - ``[p]command defaultdisablecog ModLog`` **Arguments:** - - ```` - The name of the cog to make disabled by default. Must be title-case. + - ```` - The name of the cog to make disabled by default. Must be title-case. .. _core-command-command-defaultenablecog: @@ -414,7 +414,7 @@ command defaultenablecog .. code-block:: none - [p]command defaultenablecog + [p]command defaultenablecog **Description** @@ -431,7 +431,7 @@ To override it, use ``[p]command disablecog`` on the servers you want to disallo - ``[p]command defaultenablecog ModLog`` **Arguments:** - - ```` - The name of the cog to make enabled by default. Must be title-case. + - ```` - The name of the cog to make enabled by default. Must be title-case. .. _core-command-command-disable: @@ -519,7 +519,7 @@ command disablecog .. code-block:: none - [p]command disablecog + [p]command disablecog **Description** @@ -533,7 +533,7 @@ Disable a cog in this server. - ``[p]command disablecog ModLog`` **Arguments:** - - ```` - The name of the cog to disable on this server. Must be title-case. + - ```` - The name of the cog to disable on this server. Must be title-case. .. _core-command-command-disabledmsg: @@ -651,7 +651,7 @@ command enablecog .. code-block:: none - [p]command enablecog + [p]command enablecog **Description** @@ -665,7 +665,7 @@ Enable a cog in this server. - ``[p]command enablecog ModLog`` **Arguments:** - - ```` - The name of the cog to enable on this server. Must be title-case. + - ```` - The name of the cog to enable on this server. Must be title-case. .. _core-command-command-listdisabled: @@ -902,7 +902,7 @@ embedset command .. code-block:: none - [p]embedset command [enabled] + [p]embedset command [enabled] **Description** @@ -935,7 +935,7 @@ embedset command global .. code-block:: none - [p]embedset command global [enabled] + [p]embedset command global [enabled] **Description** @@ -965,7 +965,7 @@ embedset command server .. code-block:: none - [p]embedset command server [enabled] + [p]embedset command server [enabled] .. tip:: Alias: ``embedset command guild`` @@ -1057,7 +1057,7 @@ embedset showsettings .. code-block:: none - [p]embedset showsettings [command_name] + [p]embedset showsettings [command] **Description** @@ -1071,7 +1071,7 @@ Provide a command name to check for command specific embed settings. - ``[p]embedset showsettings "ignore list"`` - Checking subcommands requires quotes. **Arguments:** - - ``[command_name]`` - Checks this command for command specific embed settings. + - ``[command]`` - Checks this command for command specific embed settings. .. _core-command-embedset-user: diff --git a/redbot/core/commands/__init__.py b/redbot/core/commands/__init__.py index 36700c0bf..852dd57ee 100644 --- a/redbot/core/commands/__init__.py +++ b/redbot/core/commands/__init__.py @@ -30,6 +30,8 @@ from .converter import ( NoParseOptional as NoParseOptional, UserInputOptional as UserInputOptional, Literal as Literal, + CogConverter as CogConverter, + CommandConverter as CommandConverter, ) from .errors import ( ConversionFailure as ConversionFailure, diff --git a/redbot/core/commands/converter.py b/redbot/core/commands/converter.py index 7eb1dd9f0..a34ba2a7d 100644 --- a/redbot/core/commands/converter.py +++ b/redbot/core/commands/converter.py @@ -45,6 +45,8 @@ __all__ = [ "parse_relativedelta", "parse_timedelta", "Literal", + "CommandConverter", + "CogConverter", ] _ = Translator("commands.converter", __file__) @@ -526,3 +528,29 @@ if not TYPE_CHECKING: return cls(k) else: return cls((k,)) + + +if TYPE_CHECKING: + CommandConverter = dpy_commands.Command + CogConverter = dpy_commands.Cog +else: + + class CommandConverter(dpy_commands.Converter): + """Converts a command name to the matching `redbot.core.commands.Command` object.""" + + async def convert(self, ctx: "Context", argument: str): + arg = argument.strip() + command = ctx.bot.get_command(arg) + if not command: + raise BadArgument(_('Command "{arg}" not found.').format(arg=arg)) + return command + + class CogConverter(dpy_commands.Converter): + """Converts a cog name to the matching `redbot.core.commands.Cog` object.""" + + async def convert(self, ctx: "Context", argument: str): + arg = argument.strip() + cog = ctx.bot.get_cog(arg) + if not cog: + raise BadArgument(_('Cog "{arg}" not found.').format(arg=arg)) + return cog diff --git a/redbot/core/core_commands.py b/redbot/core/core_commands.py index bc0ec9e19..890fd0b78 100644 --- a/redbot/core/core_commands.py +++ b/redbot/core/core_commands.py @@ -49,6 +49,7 @@ from .utils.chat_formatting import ( inline, pagify, ) +from .commands import CommandConverter, CogConverter from .commands.requires import PrivilegeLevel _entities = { @@ -1139,7 +1140,9 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): """ @embedset.command(name="showsettings") - async def embedset_showsettings(self, ctx: commands.Context, command_name: str = None) -> None: + async def embedset_showsettings( + self, ctx: commands.Context, command: CommandConverter = None + ) -> None: """ Show the current embed settings. @@ -1151,17 +1154,10 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): - `[p]embedset showsettings "ignore list"` - Checking subcommands requires quotes. **Arguments:** - - `[command_name]` - Checks this command for command specific embed settings. + - `[command]` - Checks this command for command specific embed settings. """ - if command_name is not None: - command_obj: Optional[commands.Command] = ctx.bot.get_command(command_name) - if command_obj is None: - await ctx.send( - _("I couldn't find that command. Please note that it is case sensitive.") - ) - return - # qualified name might be different if alias was passed to this command - command_name = command_obj.qualified_name + # qualified name might be different if alias was passed to this command + command_name = command and command.qualified_name text = _("Embed settings:\n\n") global_default = await self.bot._config.embeds() @@ -1251,7 +1247,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): @checks.guildowner_or_permissions(administrator=True) @embedset.group(name="command", invoke_without_command=True) async def embedset_command( - self, ctx: commands.Context, command_name: str, enabled: bool = None + self, ctx: commands.Context, command: CommandConverter, enabled: bool = None ) -> None: """ Sets a command's embed setting. @@ -1273,9 +1269,9 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): """ # Select the scope based on the author's privileges if await ctx.bot.is_owner(ctx.author): - await self.embedset_command_global(ctx, command_name, enabled) + await self.embedset_command_global(ctx, command, enabled) else: - await self.embedset_command_guild(ctx, command_name, enabled) + await self.embedset_command_guild(ctx, command, enabled) def _check_if_command_requires_embed_links(self, command_obj: commands.Command) -> None: for command in itertools.chain((command_obj,), command_obj.parents): @@ -1291,7 +1287,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): @commands.is_owner() @embedset_command.command(name="global") async def embedset_command_global( - self, ctx: commands.Context, command_name: str, enabled: bool = None + self, ctx: commands.Context, command: CommandConverter, enabled: bool = None ): """ Sets a command's embed setting globally. @@ -1310,15 +1306,9 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): **Arguments:** - `[enabled]` - Whether to use embeds for this command. Leave blank to reset to default. """ - command_obj: Optional[commands.Command] = ctx.bot.get_command(command_name) - if command_obj is None: - await ctx.send( - _("I couldn't find that command. Please note that it is case sensitive.") - ) - return - self._check_if_command_requires_embed_links(command_obj) + self._check_if_command_requires_embed_links(command) # qualified name might be different if alias was passed to this command - command_name = command_obj.qualified_name + command_name = command.qualified_name if enabled is None: await self.bot._config.custom("COMMAND", command_name, 0).embeds.clear() @@ -1342,7 +1332,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): @commands.guild_only() @embedset_command.command(name="server", aliases=["guild"]) async def embedset_command_guild( - self, ctx: commands.GuildContext, command_name: str, enabled: bool = None + self, ctx: commands.GuildContext, command: CommandConverter, enabled: bool = None ): """ Sets a commmand's embed setting for the current server. @@ -1361,15 +1351,9 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): **Arguments:** - `[enabled]` - Whether to use embeds for this command. Leave blank to reset to default. """ - command_obj: Optional[commands.Command] = ctx.bot.get_command(command_name) - if command_obj is None: - await ctx.send( - _("I couldn't find that command. Please note that it is case sensitive.") - ) - return - self._check_if_command_requires_embed_links(command_obj) + self._check_if_command_requires_embed_links(command) # qualified name might be different if alias was passed to this command - command_name = command_obj.qualified_name + command_name = command.qualified_name if enabled is None: await self.bot._config.custom("COMMAND", command_name, ctx.guild.id).embeds.clear() @@ -4159,7 +4143,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): @checks.is_owner() @command_manager.command(name="defaultdisablecog") - async def command_default_disable_cog(self, ctx: commands.Context, *, cogname: str): + async def command_default_disable_cog(self, ctx: commands.Context, *, cog: CogConverter): """Set the default state for a cog as disabled. This will disable the cog for all servers by default. @@ -4172,11 +4156,9 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): - `[p]command defaultdisablecog ModLog` **Arguments:** - - `` - The name of the cog to make disabled by default. Must be title-case. + - `` - The name of the cog to make disabled by default. Must be title-case. """ - cog = self.bot.get_cog(cogname) - if not cog: - return await ctx.send(_("Cog with the given name doesn't exist.")) + cogname = cog.qualified_name if isinstance(cog, commands.commands._RuleDropper): return await ctx.send(_("You can't disable this cog by default.")) await self.bot._disabled_cog_cache.default_disable(cogname) @@ -4184,7 +4166,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): @checks.is_owner() @command_manager.command(name="defaultenablecog") - async def command_default_enable_cog(self, ctx: commands.Context, *, cogname: str): + async def command_default_enable_cog(self, ctx: commands.Context, *, cog: CogConverter): """Set the default state for a cog as enabled. This will re-enable the cog for all servers by default. @@ -4197,17 +4179,15 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): - `[p]command defaultenablecog ModLog` **Arguments:** - - `` - The name of the cog to make enabled by default. Must be title-case. + - `` - The name of the cog to make enabled by default. Must be title-case. """ - cog = self.bot.get_cog(cogname) - if not cog: - return await ctx.send(_("Cog with the given name doesn't exist.")) + cogname = cog.qualified_name await self.bot._disabled_cog_cache.default_enable(cogname) await ctx.send(_("{cogname} has been set as enabled by default.").format(cogname=cogname)) @commands.guild_only() @command_manager.command(name="disablecog") - async def command_disable_cog(self, ctx: commands.Context, *, cogname: str): + async def command_disable_cog(self, ctx: commands.Context, *, cog: CogConverter): """Disable a cog in this server. Note: This will only work on loaded cogs, and must reference the title-case cog name. @@ -4217,11 +4197,9 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): - `[p]command disablecog ModLog` **Arguments:** - - `` - The name of the cog to disable on this server. Must be title-case. + - `` - The name of the cog to disable on this server. Must be title-case. """ - cog = self.bot.get_cog(cogname) - if not cog: - return await ctx.send(_("Cog with the given name doesn't exist.")) + cogname = cog.qualified_name if isinstance(cog, commands.commands._RuleDropper): return await ctx.send(_("You can't disable this cog as you would lock yourself out.")) if await self.bot._disabled_cog_cache.disable_cog_in_guild(cogname, ctx.guild.id): @@ -4232,7 +4210,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): ) @commands.guild_only() - @command_manager.command(name="enablecog") + @command_manager.command(name="enablecog", usage="") async def command_enable_cog(self, ctx: commands.Context, *, cogname: str): """Enable a cog in this server. @@ -4243,7 +4221,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): - `[p]command enablecog ModLog` **Arguments:** - - `` - The name of the cog to enable on this server. Must be title-case. + - `` - The name of the cog to enable on this server. Must be title-case. """ if await self.bot._disabled_cog_cache.enable_cog_in_guild(cogname, ctx.guild.id): await ctx.send(_("{cogname} has been enabled in this guild.").format(cogname=cogname)) @@ -4251,7 +4229,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): # putting this here allows enabling a cog that isn't loaded but was disabled. cog = self.bot.get_cog(cogname) if not cog: - return await ctx.send(_("Cog with the given name doesn't exist.")) + return await ctx.send(_('Cog "{arg}" not found.').format(arg=cogname)) await ctx.send( _("{cogname} was not disabled (nothing to do).").format(cogname=cogname) @@ -4342,7 +4320,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): await ctx.send_interactive(paged) @command_manager.group(name="disable", invoke_without_command=True) - async def command_disable(self, ctx: commands.Context, *, command: str): + async def command_disable(self, ctx: commands.Context, *, command: CommandConverter): """ Disable a command. @@ -4364,7 +4342,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): @checks.is_owner() @command_disable.command(name="global") - async def command_disable_global(self, ctx: commands.Context, *, command: str): + async def command_disable_global(self, ctx: commands.Context, *, command: CommandConverter): """ Disable a command globally. @@ -4375,39 +4353,32 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): **Arguments:** - `` - The command to disable globally. """ - command_obj: Optional[commands.Command] = ctx.bot.get_command(command) - if command_obj is None: - await ctx.send( - _("I couldn't find that command. Please note that it is case sensitive.") - ) - return - - if self.command_manager in command_obj.parents or self.command_manager == command_obj: + if self.command_manager in command.parents or self.command_manager == command: await ctx.send( _("The command to disable cannot be `command` or any of its subcommands.") ) return - if isinstance(command_obj, commands.commands._RuleDropper): + if isinstance(command, commands.commands._RuleDropper): await ctx.send( _("This command is designated as being always available and cannot be disabled.") ) return async with ctx.bot._config.disabled_commands() as disabled_commands: - if command not in disabled_commands: - disabled_commands.append(command_obj.qualified_name) + if command.qualified_name not in disabled_commands: + disabled_commands.append(command.qualified_name) - if not command_obj.enabled: + if not command.enabled: await ctx.send(_("That command is already disabled globally.")) return - command_obj.enabled = False + command.enabled = False await ctx.tick() @commands.guild_only() @command_disable.command(name="server", aliases=["guild"]) - async def command_disable_guild(self, ctx: commands.Context, *, command: str): + async def command_disable_guild(self, ctx: commands.Context, *, command: CommandConverter): """ Disable a command in this server only. @@ -4418,34 +4389,27 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): **Arguments:** - `` - The command to disable for the current server. """ - command_obj: Optional[commands.Command] = ctx.bot.get_command(command) - if command_obj is None: - await ctx.send( - _("I couldn't find that command. Please note that it is case sensitive.") - ) - return - - if self.command_manager in command_obj.parents or self.command_manager == command_obj: + if self.command_manager in command.parents or self.command_manager == command: await ctx.send( _("The command to disable cannot be `command` or any of its subcommands.") ) return - if isinstance(command_obj, commands.commands._RuleDropper): + if isinstance(command, commands.commands._RuleDropper): await ctx.send( _("This command is designated as being always available and cannot be disabled.") ) return - if command_obj.requires.privilege_level > await PrivilegeLevel.from_ctx(ctx): + if command.requires.privilege_level > await PrivilegeLevel.from_ctx(ctx): await ctx.send(_("You are not allowed to disable that command.")) return async with ctx.bot._config.guild(ctx.guild).disabled_commands() as disabled_commands: - if command not in disabled_commands: - disabled_commands.append(command_obj.qualified_name) + if command.qualified_name not in disabled_commands: + disabled_commands.append(command.qualified_name) - done = command_obj.disable_in(ctx.guild) + done = command.disable_in(ctx.guild) if not done: await ctx.send(_("That command is already disabled in this server.")) @@ -4453,7 +4417,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): await ctx.tick() @command_manager.group(name="enable", invoke_without_command=True) - async def command_enable(self, ctx: commands.Context, *, command: str): + async def command_enable(self, ctx: commands.Context, *, command: CommandConverter): """Enable a command. If you're the bot owner, this will try to enable a globally disabled command by default. @@ -4473,7 +4437,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): @commands.is_owner() @command_enable.command(name="global") - async def command_enable_global(self, ctx: commands.Context, *, command: str): + async def command_enable_global(self, ctx: commands.Context, *, command: CommandConverter): """ Enable a command globally. @@ -4484,27 +4448,20 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): **Arguments:** - `` - The command to enable globally. """ - command_obj: Optional[commands.Command] = ctx.bot.get_command(command) - if command_obj is None: - await ctx.send( - _("I couldn't find that command. Please note that it is case sensitive.") - ) - return - async with ctx.bot._config.disabled_commands() as disabled_commands: with contextlib.suppress(ValueError): - disabled_commands.remove(command_obj.qualified_name) + disabled_commands.remove(command.qualified_name) - if command_obj.enabled: + if command.enabled: await ctx.send(_("That command is already enabled globally.")) return - command_obj.enabled = True + command.enabled = True await ctx.tick() @commands.guild_only() @command_enable.command(name="server", aliases=["guild"]) - async def command_enable_guild(self, ctx: commands.Context, *, command: str): + async def command_enable_guild(self, ctx: commands.Context, *, command: CommandConverter): """ Enable a command in this server. @@ -4515,22 +4472,15 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): **Arguments:** - `` - The command to enable for the current server. """ - command_obj: Optional[commands.Command] = ctx.bot.get_command(command) - if command_obj is None: - await ctx.send( - _("I couldn't find that command. Please note that it is case sensitive.") - ) - return - - if command_obj.requires.privilege_level > await PrivilegeLevel.from_ctx(ctx): + if command.requires.privilege_level > await PrivilegeLevel.from_ctx(ctx): await ctx.send(_("You are not allowed to enable that command.")) return async with ctx.bot._config.guild(ctx.guild).disabled_commands() as disabled_commands: with contextlib.suppress(ValueError): - disabled_commands.remove(command_obj.qualified_name) + disabled_commands.remove(command.qualified_name) - done = command_obj.enable_in(ctx.guild) + done = command.enable_in(ctx.guild) if not done: await ctx.send(_("That command is already enabled in this server."))