diff options
| author | NCPlayz <[email protected]> | 2019-12-30 19:10:16 +0000 |
|---|---|---|
| committer | Rapptz <[email protected]> | 2020-04-04 02:57:20 -0400 |
| commit | 1b0e80624556faa1577647d7d04b8343694bbd47 (patch) | |
| tree | 2ebcbb73cc5673524187185cc0a59626e3e490ca | |
| parent | Fix documentation problem with color (diff) | |
| download | discord.py-1b0e80624556faa1577647d7d04b8343694bbd47.tar.xz discord.py-1b0e80624556faa1577647d7d04b8343694bbd47.zip | |
[commands] Implement `commands.before/after_invoke`
| -rw-r--r-- | discord/ext/commands/core.py | 105 | ||||
| -rw-r--r-- | docs/ext/commands/api.rst | 4 |
2 files changed, 101 insertions, 8 deletions
diff --git a/discord/ext/commands/core.py b/discord/ext/commands/core.py index e1e12cc5..03ba6b54 100644 --- a/discord/ext/commands/core.py +++ b/discord/ext/commands/core.py @@ -49,6 +49,8 @@ __all__ = ( 'has_any_role', 'check', 'check_any', + 'before_invoke', + 'after_invoke', 'bot_has_role', 'bot_has_permissions', 'bot_has_any_role', @@ -266,8 +268,20 @@ class Command(_BaseCommand): # bandaid for the fact that sometimes parent can be the bot instance parent = kwargs.get('parent') self.parent = parent if isinstance(parent, _BaseCommand) else None - self._before_invoke = None - self._after_invoke = None + + try: + before_invoke = func.__before_invoke__ + except AttributeError: + self._before_invoke = None + else: + self.before_invoke(before_invoke) + + try: + after_invoke = func.__after_invoke__ + except AttributeError: + self._after_invoke = None + else: + self.after_invoke(after_invoke) @property def callback(self): @@ -695,10 +709,18 @@ class Command(_BaseCommand): # first, call the command local hook: cog = self.cog if self._before_invoke is not None: - if cog is None: - await self._before_invoke(ctx) + try: + instance = self._before_invoke.__self__ + # should be cog if @commands.before_invoke is used + except AttributeError: + # __self__ only exists for methods, not functions + # however, if @command.before_invoke is used, it will be a function + if self.cog: + await self._before_invoke(cog, ctx) + else: + await self._before_invoke(ctx) else: - await self._before_invoke(cog, ctx) + await self._before_invoke(instance, ctx) # call the cog local hook if applicable: if cog is not None: @@ -714,10 +736,15 @@ class Command(_BaseCommand): async def call_after_hooks(self, ctx): cog = self.cog if self._after_invoke is not None: - if cog is None: - await self._after_invoke(ctx) + try: + instance = self._after_invoke.__self__ + except AttributeError: + if self.cog: + await self._after_invoke(cog, ctx) + else: + await self._after_invoke(ctx) else: - await self._after_invoke(cog, ctx) + await self._after_invoke(instance, ctx) # call the cog local hook if applicable: if cog is not None: @@ -1888,3 +1915,65 @@ def max_concurrency(number, per=BucketType.default, *, wait=False): func.__commands_max_concurrency__ = value return func return decorator + +def before_invoke(coro): + """A decorator that registers a coroutine as a pre-invoke hook. + + This allows you to refer to one before invoke hook for several commands that + do not have to be within the same cog. + + .. versionadded:: 1.4 + + Example + --------- + + .. code-block:: python3 + + async def record_usage(ctx): + print(ctx.author, 'used', ctx.command, 'at', ctx.message.created_at) + + @bot.command() + @commands.before_invoke(record_usage) + async def who(ctx): # Output: <User> used who at <Time> + await ctx.send('i am a bot') + + class What(commands.Cog): + + @commands.before_invoke(record_usage) + @commands.command() + async def when(self, ctx): # Output: <User> used when at <Time> + await ctx.send('and i have existed since {}'.format(ctx.bot.user.created_at)) + + @commands.command() + async def where(self, ctx): # Output: <Nothing> + await ctx.send('on Discord') + + @commands.command() + async def why(self, ctx): # Output: <Nothing> + await ctx.send('because someone made me') + + bot.add_cog(What()) + """ + def decorator(func): + if isinstance(func, Command): + func.before_invoke(coro) + else: + func.__before_invoke__ = coro + return func + return decorator + +def after_invoke(coro): + """A decorator that registers a coroutine as a post-invoke hook. + + This allows you to refer to one after invoke hook for several commands that + do not have to be within the same cog. + + .. versionadded:: 1.4 + """ + def decorator(func): + if isinstance(func, Command): + func.after_invoke(coro) + else: + func.__after_invoke__ = coro + return func + return decorator diff --git a/docs/ext/commands/api.rst b/docs/ext/commands/api.rst index bfc07067..830509dc 100644 --- a/docs/ext/commands/api.rst +++ b/docs/ext/commands/api.rst @@ -173,6 +173,10 @@ Checks .. autofunction:: discord.ext.commands.max_concurrency +.. autofunction:: discord.ext.commands.before_invoke + +.. autofunction:: discord.ext.commands.after_invoke + .. autofunction:: discord.ext.commands.guild_only .. autofunction:: discord.ext.commands.dm_only |