[V3] Custom context class (#988)

* Create custom context class

* Documentation

* Remove old help method, replace with new

* Update from rebase
This commit is contained in:
Tobotimus 2017-10-16 12:02:51 +11:00 committed by Will
parent 36b3fbd5bc
commit 86b18c702c
14 changed files with 78 additions and 32 deletions

View File

@ -0,0 +1,10 @@
.. red invocation context documentation
==========================
Command Invocation Context
==========================
.. automodule:: redbot.core.context
.. autoclass:: redbot.core.RedContext
:members:

View File

@ -20,6 +20,7 @@ Welcome to Red - Discord Bot's documentation!
framework_cogmanager framework_cogmanager
framework_config framework_config
framework_downloader framework_downloader
framework_context
Indices and tables Indices and tables

View File

@ -167,7 +167,7 @@ class Admin:
async def editrole(self, ctx: commands.Context): async def editrole(self, ctx: commands.Context):
"""Edits roles settings""" """Edits roles settings"""
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
await ctx.bot.send_cmd_help(ctx) await ctx.send_help()
@editrole.command(name="colour", aliases=["color", ]) @editrole.command(name="colour", aliases=["color", ])
async def editrole_colour(self, ctx: commands.Context, role: discord.Role, async def editrole_colour(self, ctx: commands.Context, role: discord.Role,

View File

@ -184,7 +184,7 @@ class Alias:
async def alias(self, ctx: commands.Context): async def alias(self, ctx: commands.Context):
"""Manage per-server aliases for commands""" """Manage per-server aliases for commands"""
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
await self.bot.send_cmd_help(ctx) await ctx.send_help()
@alias.group(name="global") @alias.group(name="global")
async def global_(self, ctx: commands.Context): async def global_(self, ctx: commands.Context):
@ -193,7 +193,7 @@ class Alias:
""" """
if ctx.invoked_subcommand is None or \ if ctx.invoked_subcommand is None or \
isinstance(ctx.invoked_subcommand, commands.Group): isinstance(ctx.invoked_subcommand, commands.Group):
await self.bot.send_cmd_help(ctx) await ctx.send_help()
@alias.command(name="add") @alias.command(name="add")
@commands.guild_only() @commands.guild_only()

View File

@ -54,7 +54,7 @@ class Bank:
async def bankset(self, ctx: commands.Context): async def bankset(self, ctx: commands.Context):
"""Base command for bank settings""" """Base command for bank settings"""
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
await self.bot.send_cmd_help(ctx) await ctx.send_help()
@bankset.command(name="toggleglobal") @bankset.command(name="toggleglobal")
@checks.is_owner() @checks.is_owner()

View File

@ -164,7 +164,7 @@ class CustomCommands:
ctx: commands.Context): ctx: commands.Context):
"""Custom commands management""" """Custom commands management"""
if not ctx.invoked_subcommand: if not ctx.invoked_subcommand:
await self.bot.send_cmd_help(ctx) await ctx.send_help()
@customcom.group(name="add") @customcom.group(name="add")
@checks.mod_or_permissions(administrator=True) @checks.mod_or_permissions(administrator=True)
@ -176,7 +176,7 @@ class CustomCommands:
""" """
if not ctx.invoked_subcommand or isinstance(ctx.invoked_subcommand, if not ctx.invoked_subcommand or isinstance(ctx.invoked_subcommand,
commands.Group): commands.Group):
await self.bot.send_cmd_help(ctx) await ctx.send_help()
@cc_add.command(name='random') @cc_add.command(name='random')
@checks.mod_or_permissions(administrator=True) @checks.mod_or_permissions(administrator=True)

View File

@ -179,7 +179,7 @@ class Downloader:
Command group for managing Downloader repos. Command group for managing Downloader repos.
""" """
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
await self.bot.send_cmd_help(ctx) await ctx.send_help()
@repo.command(name="add") @repo.command(name="add")
@install_agreement() @install_agreement()
@ -231,7 +231,7 @@ class Downloader:
Command group for managing installable Cogs. Command group for managing installable Cogs.
""" """
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
await self.bot.send_cmd_help(ctx) await ctx.send_help()
@cog.command(name="install") @cog.command(name="install")
async def _cog_install(self, ctx, repo_name: Repo, cog_name: str): async def _cog_install(self, ctx, repo_name: Repo, cog_name: str):

View File

@ -141,7 +141,7 @@ class Economy:
async def _bank(self, ctx: commands.Context): async def _bank(self, ctx: commands.Context):
"""Bank operations""" """Bank operations"""
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
await self.bot.send_cmd_help(ctx) await ctx.send_help()
@_bank.command() @_bank.command()
async def balance(self, ctx: commands.Context, user: discord.Member = None): async def balance(self, ctx: commands.Context, user: discord.Member = None):
@ -405,7 +405,7 @@ class Economy:
"""Changes economy module settings""" """Changes economy module settings"""
guild = ctx.guild guild = ctx.guild
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
await self.bot.send_cmd_help(ctx) await ctx.send_help()
if await bank.is_global(): if await bank.is_global():
slot_min = await self.config.SLOT_MIN() slot_min = await self.config.SLOT_MIN()
slot_max = await self.config.SLOT_MAX() slot_max = await self.config.SLOT_MAX()

View File

@ -32,7 +32,7 @@ class Image:
Make sure to set the client ID using Make sure to set the client ID using
[p]imgurcreds""" [p]imgurcreds"""
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
await self.bot.send_cmd_help(ctx) await ctx.send_help()
@_imgur.command(name="search") @_imgur.command(name="search")
async def imgur_search(self, ctx, *, term: str): 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.")) await ctx.send(_("Only 'new' and 'top' are a valid sort type."))
return return
elif window not in ("day", "week", "month", "year", "all"): elif window not in ("day", "week", "month", "year", "all"):
await self.bot.send_cmd_help(ctx) await ctx.send_help()
return return
if sort_type == "new": if sort_type == "new":
@ -120,7 +120,7 @@ class Image:
if keywords: if keywords:
keywords = "+".join(keywords) keywords = "+".join(keywords)
else: else:
await self.bot.send_cmd_help(ctx) await ctx.send_help()
return return
url = ("http://api.giphy.com/v1/gifs/search?&api_key={}&q={}" url = ("http://api.giphy.com/v1/gifs/search?&api_key={}&q={}"
@ -142,7 +142,7 @@ class Image:
if keywords: if keywords:
keywords = "+".join(keywords) keywords = "+".join(keywords)
else: else:
await self.bot.send_cmd_help(ctx) await ctx.send_help()
return return
url = ("http://api.giphy.com/v1/gifs/random?&api_key={}&tag={}" url = ("http://api.giphy.com/v1/gifs/random?&api_key={}&tag={}"

View File

@ -1,7 +1,8 @@
import pkg_resources import pkg_resources
from .config import Config from .config import Config
from .context import RedContext
__all__ = ["Config", "__version__"] __all__ = ["Config", "RedContext", "__version__"]
__version__ = version = pkg_resources.require("Red-DiscordBot")[0].version __version__ = version = pkg_resources.require("Red-DiscordBot")[0].version

View File

@ -10,8 +10,7 @@ from discord.ext import commands
from discord.ext.commands import GroupMixin from discord.ext.commands import GroupMixin
from .cog_manager import CogManager from .cog_manager import CogManager
from . import Config from . import Config, i18n, RedContext
from . import i18n
class Red(commands.Bot): class Red(commands.Bot):
@ -84,15 +83,8 @@ class Red(commands.Bot):
return True return True
return await super().is_owner(user) return await super().is_owner(user)
async def send_cmd_help(self, ctx): async def get_context(self, message, *, cls=RedContext):
if ctx.invoked_subcommand: return await super().get_context(message, cls=cls)
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 shutdown(self, *, restart=False): async def shutdown(self, *, restart=False):
"""Gracefully quits Red with exit code 0 """Gracefully quits Red with exit code 0

42
redbot/core/context.py Normal file
View File

@ -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 <discord.ext.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

View File

@ -136,7 +136,7 @@ class Core:
async def _set(self, ctx): async def _set(self, ctx):
"""Changes Red's settings""" """Changes Red's settings"""
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
await ctx.bot.send_cmd_help(ctx) await ctx.send_help()
@_set.command() @_set.command()
@checks.guildowner() @checks.guildowner()
@ -207,7 +207,7 @@ class Core:
try: try:
status = statuses[status.lower()] status = statuses[status.lower()]
except KeyError: except KeyError:
await ctx.bot.send_cmd_help(ctx) await ctx.send_help()
else: else:
await ctx.bot.change_presence(status=status, await ctx.bot.change_presence(status=status,
game=game) game=game)
@ -229,7 +229,7 @@ class Core:
game = discord.Game(type=1, url=streamer, name=stream_title) game = discord.Game(type=1, url=streamer, name=stream_title)
await ctx.bot.change_presence(game=game, status=status) await ctx.bot.change_presence(game=game, status=status)
elif streamer is not None: elif streamer is not None:
await ctx.bot.send_cmd_help(ctx) await ctx.send_help()
return return
else: else:
await ctx.bot.change_presence(game=None, status=status) await ctx.bot.change_presence(game=None, status=status)
@ -267,7 +267,7 @@ class Core:
async def prefix(self, ctx, *prefixes): async def prefix(self, ctx, *prefixes):
"""Sets Red's global prefix(es)""" """Sets Red's global prefix(es)"""
if not prefixes: if not prefixes:
await ctx.bot.send_cmd_help(ctx) await ctx.send_help()
return return
prefixes = sorted(prefixes, reverse=True) prefixes = sorted(prefixes, reverse=True)
await ctx.bot.db.prefix.set(prefixes) await ctx.bot.db.prefix.set(prefixes)

View File

@ -74,9 +74,9 @@ def init_events(bot, cli_flags):
@bot.event @bot.event
async def on_command_error(ctx, error): async def on_command_error(ctx, error):
if isinstance(error, commands.MissingRequiredArgument): if isinstance(error, commands.MissingRequiredArgument):
await bot.send_cmd_help(ctx) await ctx.send_help()
elif isinstance(error, commands.BadArgument): elif isinstance(error, commands.BadArgument):
await bot.send_cmd_help(ctx) await ctx.send_help()
elif isinstance(error, commands.DisabledCommand): elif isinstance(error, commands.DisabledCommand):
await ctx.send("That command is disabled.") await ctx.send("That command is disabled.")
elif isinstance(error, commands.CommandInvokeError): elif isinstance(error, commands.CommandInvokeError):