diff --git a/docs/framework_context.rst b/docs/framework_context.rst new file mode 100644 index 000000000..10a8229e4 --- /dev/null +++ b/docs/framework_context.rst @@ -0,0 +1,10 @@ +.. red invocation context documentation + +========================== +Command Invocation Context +========================== + +.. automodule:: redbot.core.context + +.. autoclass:: redbot.core.RedContext + :members: diff --git a/docs/index.rst b/docs/index.rst index 8b161e278..6b8c1ee7f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -20,6 +20,7 @@ Welcome to Red - Discord Bot's documentation! framework_cogmanager framework_config framework_downloader + framework_context Indices and tables diff --git a/redbot/cogs/admin/admin.py b/redbot/cogs/admin/admin.py index 182948a21..df516d2e6 100644 --- a/redbot/cogs/admin/admin.py +++ b/redbot/cogs/admin/admin.py @@ -167,7 +167,7 @@ class Admin: async def editrole(self, ctx: commands.Context): """Edits roles settings""" if ctx.invoked_subcommand is None: - await ctx.bot.send_cmd_help(ctx) + await ctx.send_help() @editrole.command(name="colour", aliases=["color", ]) async def editrole_colour(self, ctx: commands.Context, role: discord.Role, diff --git a/redbot/cogs/alias/alias.py b/redbot/cogs/alias/alias.py index 72ea9bcdd..fa066bd8d 100644 --- a/redbot/cogs/alias/alias.py +++ b/redbot/cogs/alias/alias.py @@ -184,7 +184,7 @@ class Alias: async def alias(self, ctx: commands.Context): """Manage per-server aliases for commands""" if ctx.invoked_subcommand is None: - await self.bot.send_cmd_help(ctx) + await ctx.send_help() @alias.group(name="global") async def global_(self, ctx: commands.Context): @@ -193,7 +193,7 @@ class Alias: """ if ctx.invoked_subcommand is None or \ isinstance(ctx.invoked_subcommand, commands.Group): - await self.bot.send_cmd_help(ctx) + await ctx.send_help() @alias.command(name="add") @commands.guild_only() diff --git a/redbot/cogs/bank/bank.py b/redbot/cogs/bank/bank.py index de1552b94..0317ad534 100644 --- a/redbot/cogs/bank/bank.py +++ b/redbot/cogs/bank/bank.py @@ -54,7 +54,7 @@ class Bank: async def bankset(self, ctx: commands.Context): """Base command for bank settings""" if ctx.invoked_subcommand is None: - await self.bot.send_cmd_help(ctx) + await ctx.send_help() @bankset.command(name="toggleglobal") @checks.is_owner() diff --git a/redbot/cogs/customcom/customcom.py b/redbot/cogs/customcom/customcom.py index a17018010..acbd5c9e2 100644 --- a/redbot/cogs/customcom/customcom.py +++ b/redbot/cogs/customcom/customcom.py @@ -164,7 +164,7 @@ class CustomCommands: ctx: commands.Context): """Custom commands management""" if not ctx.invoked_subcommand: - await self.bot.send_cmd_help(ctx) + await ctx.send_help() @customcom.group(name="add") @checks.mod_or_permissions(administrator=True) @@ -176,7 +176,7 @@ class CustomCommands: """ if not ctx.invoked_subcommand or isinstance(ctx.invoked_subcommand, commands.Group): - await self.bot.send_cmd_help(ctx) + await ctx.send_help() @cc_add.command(name='random') @checks.mod_or_permissions(administrator=True) diff --git a/redbot/cogs/downloader/downloader.py b/redbot/cogs/downloader/downloader.py index 74a01305e..fc5a1e486 100644 --- a/redbot/cogs/downloader/downloader.py +++ b/redbot/cogs/downloader/downloader.py @@ -179,7 +179,7 @@ class Downloader: Command group for managing Downloader repos. """ if ctx.invoked_subcommand is None: - await self.bot.send_cmd_help(ctx) + await ctx.send_help() @repo.command(name="add") @install_agreement() @@ -231,7 +231,7 @@ class Downloader: Command group for managing installable Cogs. """ if ctx.invoked_subcommand is None: - await self.bot.send_cmd_help(ctx) + await ctx.send_help() @cog.command(name="install") async def _cog_install(self, ctx, repo_name: Repo, cog_name: str): diff --git a/redbot/cogs/economy/economy.py b/redbot/cogs/economy/economy.py index da4d0a8b4..feeeba944 100644 --- a/redbot/cogs/economy/economy.py +++ b/redbot/cogs/economy/economy.py @@ -141,7 +141,7 @@ class Economy: async def _bank(self, ctx: commands.Context): """Bank operations""" if ctx.invoked_subcommand is None: - await self.bot.send_cmd_help(ctx) + await ctx.send_help() @_bank.command() async def balance(self, ctx: commands.Context, user: discord.Member = None): @@ -405,7 +405,7 @@ class Economy: """Changes economy module settings""" guild = ctx.guild if ctx.invoked_subcommand is None: - await self.bot.send_cmd_help(ctx) + await ctx.send_help() if await bank.is_global(): slot_min = await self.config.SLOT_MIN() slot_max = await self.config.SLOT_MAX() diff --git a/redbot/cogs/image/image.py b/redbot/cogs/image/image.py index 9ad68a807..dae3b552d 100644 --- a/redbot/cogs/image/image.py +++ b/redbot/cogs/image/image.py @@ -32,7 +32,7 @@ class Image: Make sure to set the client ID using [p]imgurcreds""" if ctx.invoked_subcommand is None: - await self.bot.send_cmd_help(ctx) + await ctx.send_help() @_imgur.command(name="search") async def imgur_search(self, ctx, *, term: str): @@ -70,7 +70,7 @@ class Image: await ctx.send(_("Only 'new' and 'top' are a valid sort type.")) return elif window not in ("day", "week", "month", "year", "all"): - await self.bot.send_cmd_help(ctx) + await ctx.send_help() return if sort_type == "new": @@ -120,7 +120,7 @@ class Image: if keywords: keywords = "+".join(keywords) else: - await self.bot.send_cmd_help(ctx) + await ctx.send_help() return url = ("http://api.giphy.com/v1/gifs/search?&api_key={}&q={}" @@ -142,7 +142,7 @@ class Image: if keywords: keywords = "+".join(keywords) else: - await self.bot.send_cmd_help(ctx) + await ctx.send_help() return url = ("http://api.giphy.com/v1/gifs/random?&api_key={}&tag={}" diff --git a/redbot/core/__init__.py b/redbot/core/__init__.py index 81b524fcd..7060c1627 100644 --- a/redbot/core/__init__.py +++ b/redbot/core/__init__.py @@ -1,7 +1,8 @@ import pkg_resources from .config import Config +from .context import RedContext -__all__ = ["Config", "__version__"] +__all__ = ["Config", "RedContext", "__version__"] __version__ = version = pkg_resources.require("Red-DiscordBot")[0].version diff --git a/redbot/core/bot.py b/redbot/core/bot.py index f0d31bb0e..7bf564f7c 100644 --- a/redbot/core/bot.py +++ b/redbot/core/bot.py @@ -10,8 +10,7 @@ from discord.ext import commands from discord.ext.commands import GroupMixin from .cog_manager import CogManager -from . import Config -from . import i18n +from . import Config, i18n, RedContext class Red(commands.Bot): @@ -84,15 +83,8 @@ class Red(commands.Bot): return True return await super().is_owner(user) - async def send_cmd_help(self, ctx): - if ctx.invoked_subcommand: - pages = await self.formatter.format_help_for(ctx, ctx.invoked_subcommand) - for page in pages: - await ctx.send(page) - else: - pages = await self.formatter.format_help_for(ctx, ctx.command) - for page in pages: - await ctx.send(page) + async def get_context(self, message, *, cls=RedContext): + return await super().get_context(message, cls=cls) async def shutdown(self, *, restart=False): """Gracefully quits Red with exit code 0 diff --git a/redbot/core/context.py b/redbot/core/context.py new file mode 100644 index 000000000..af7c96a64 --- /dev/null +++ b/redbot/core/context.py @@ -0,0 +1,42 @@ +"""The purpose of this module is to allow for Red to +further customise the command invocation context provided +by discord.py. +""" + +import discord +from discord.ext import commands + +__all__ = ["RedContext"] + +TICK = "\N{WHITE HEAVY CHECK MARK}" + + +class RedContext(commands.Context): + """ + Command invocation context for Red. + + All context passed into commands will be of this type. + + This class inherits from + :py:class:`commands.Context `. + """ + + async def send_help(self): + """Send the command help message.""" + command = self.invoked_subcommand or self.command + pages = await self.bot.formatter.format_help_for(self, command) + for page in pages: + await self.send(page) + + async def tick(self): + """Add a tick reaction to the command message. + + :return: ``True`` if adding the reaction succeeded. + :rtype: bool + """ + try: + await self.message.add_reaction(TICK) + except discord.HTTPException: + return False + else: + return True diff --git a/redbot/core/core_commands.py b/redbot/core/core_commands.py index 1a356b199..8c31d048a 100644 --- a/redbot/core/core_commands.py +++ b/redbot/core/core_commands.py @@ -136,7 +136,7 @@ class Core: async def _set(self, ctx): """Changes Red's settings""" if ctx.invoked_subcommand is None: - await ctx.bot.send_cmd_help(ctx) + await ctx.send_help() @_set.command() @checks.guildowner() @@ -207,7 +207,7 @@ class Core: try: status = statuses[status.lower()] except KeyError: - await ctx.bot.send_cmd_help(ctx) + await ctx.send_help() else: await ctx.bot.change_presence(status=status, game=game) @@ -229,7 +229,7 @@ class Core: game = discord.Game(type=1, url=streamer, name=stream_title) await ctx.bot.change_presence(game=game, status=status) elif streamer is not None: - await ctx.bot.send_cmd_help(ctx) + await ctx.send_help() return else: await ctx.bot.change_presence(game=None, status=status) @@ -267,7 +267,7 @@ class Core: async def prefix(self, ctx, *prefixes): """Sets Red's global prefix(es)""" if not prefixes: - await ctx.bot.send_cmd_help(ctx) + await ctx.send_help() return prefixes = sorted(prefixes, reverse=True) await ctx.bot.db.prefix.set(prefixes) diff --git a/redbot/core/events.py b/redbot/core/events.py index 5ff09bdf2..4771e5ec3 100644 --- a/redbot/core/events.py +++ b/redbot/core/events.py @@ -74,9 +74,9 @@ def init_events(bot, cli_flags): @bot.event async def on_command_error(ctx, error): if isinstance(error, commands.MissingRequiredArgument): - await bot.send_cmd_help(ctx) + await ctx.send_help() elif isinstance(error, commands.BadArgument): - await bot.send_cmd_help(ctx) + await ctx.send_help() elif isinstance(error, commands.DisabledCommand): await ctx.send("That command is disabled.") elif isinstance(error, commands.CommandInvokeError):