Repository: Cog-Creators/Red-DiscordBot

Issue:
V3 Handler not shutting up after custom error handler.
# Other bugs

<!-- 
Did you find a bug with something other than a command? Fill out the following:
-->

#### What were you trying to do?

Replace a redbot error message (in this case the message displayed after CommandOnCooldown was triggered) with a custom message via @my_function.error:

    @my_function.error
    async def myfunction_error(self, ctx, error):
        if isinstance(error, commands.CommandOnCooldown):
             await ctx.send("I feel a little tired now. Ask me again in {:.0f}s".format(error.retry_after))

#### What were you expecting to happen?

To only see my custom on_cooldown message from the event above.

#### What actually happened?

The bot wrote:
I feel a little tired now. Ask me again in 117s
This command is on cooldown. Try again in 116.69s

#### How can we reproduce this issue?

Add cooldown to any command and extend the @functionname.error handler like I described for your command. Then cause the command cooldown and try to use the command again while on cooldown.



Discussion:
This should probably be handled, but I did forget an option when discussing this with you in discord.

using the special named cog level: `async def __error(self, ctx: commands.Context, error)` will prevent default red messages. since you want this for the specific command, you can make this `pass` if you will handle each command separately, or do all of your command error handling for the cog in here (check ctx for what failed)
Replacing the @myfunction.error decorator with this function led to the same result:

    async def __error(self, ctx: commands.Context, error):
        if isinstance(error, commands.CommandOnCooldown):
            await ctx.send("I feel a little tired now. Ask me again in {:.0f}s".format(error.retry_after))

Resolving patch:
diff --git a/redbot/core/commands/commands.py b/redbot/core/commands/commands.py
--- a/redbot/core/commands/commands.py
+++ b/redbot/core/commands/commands.py
@@ -397,6 +397,45 @@ def clear_rule_for(
                     break
         return old_rule, new_rule
 
+    async def error(self, coro):
+        """
+        A decorator that registers a coroutine as a local error handler.
+
+        A local error handler is an :func:`.on_command_error` event limited to
+        a single command.
+
+        The on_command_error event is still dispatched
+        for commands with a dedicated error handler.
+
+        Red's global error handler will ignore commands with a registered error handler.
+
+        To have red handle specific errors with the default behavior,
+        call ``Red.on_command_error`` with ``unhandled_by_cog`` set to True.
+
+        For example:
+
+            .. code-block:: python
+
+                @a_command.error
+                async def a_command_error_handler(self, ctx, error):
+
+                    if isisntance(error, MyErrrorType):
+                        self.log_exception(error)
+                    else:
+                        await ctx.bot.on_command_error(ctx, error, unhandled_by_cog=True)
+
+        Parameters
+        -----------
+        coro : :term:`coroutine function`
+            The coroutine to register as the local error handler.
+
+        Raises
+        -------
+        discord.ClientException
+            The coroutine is not actually a coroutine.
+        """
+        return await super().error(coro)
+
 
 class GroupMixin(discord.ext.commands.GroupMixin):
     """Mixin for `Group` and `Red` classes.
diff --git a/redbot/core/events.py b/redbot/core/events.py
--- a/redbot/core/events.py
+++ b/redbot/core/events.py
@@ -160,7 +160,15 @@ async def on_ready():
         bot.color = discord.Colour(await bot.db.color())
 
     @bot.event
-    async def on_command_error(ctx, error):
+    async def on_command_error(ctx, error, unhandled_by_cog=False):
+
+        if not unhandled_by_cog:
+            if hasattr(ctx.command, "on_error"):
+                return
+
+            if ctx.cog and hasattr(ctx.cog, f"_{ctx.cog.__class__.__name__}__error"):
+                return
+
         if isinstance(error, commands.MissingRequiredArgument):
             await ctx.send_help()
         elif isinstance(error, commands.ConversionFailure):
@@ -188,8 +196,7 @@ async def on_command_error(ctx, error):
                 traceback.format_exception(type(error), error, error.__traceback__)
             )
             bot._last_exception = exception_log
-            if not hasattr(ctx.cog, "_{0.command.cog_name}__error".format(ctx)):
-                await ctx.send(inline(message))
+            await ctx.send(inline(message))
         elif isinstance(error, commands.CommandNotFound):
             fuzzy_commands = await fuzzy_command_search(ctx)
             if not fuzzy_commands:


Test patch: