From 8c5b4ec59fec3c415247b79d8642a8b68b9d8040 Mon Sep 17 00:00:00 2001 From: ddorn Date: Wed, 29 Apr 2020 15:14:35 +0200 Subject: [PATCH] :recycle: more flexible error handling --- src/cogs/errors.py | 94 +++++++++++++++++++++++++++++++++++++++++ src/tfjm_discord_bot.py | 42 +++--------------- 2 files changed, 99 insertions(+), 37 deletions(-) diff --git a/src/cogs/errors.py b/src/cogs/errors.py index e69de29..df560f3 100644 --- a/src/cogs/errors.py +++ b/src/cogs/errors.py @@ -0,0 +1,94 @@ +import sys +import traceback + +import discord +from discord.ext.commands import * +from discord.utils import maybe_coroutine + +from src.errors import UnwantedCommand + + +# Global variable and function because I'm too lazy to make a metaclass +handlers = {} + + +def handles(error_type): + """ + This registers an error handler. + + Error handlers can be coroutines or functions. + """ + + def decorator(f): + handlers[error_type] = f + return f + + return decorator + + +class ErrorsCog(Cog): + """This cog defines all the handles for errors.""" + + @Cog.listener() + async def on_command_error(self, ctx: Context, error: CommandError): + print(repr(error), file=sys.stderr) + + # We take the first superclass with an handler defined + handler = None + for type_ in error.__class__.__mro__: + handler = handlers.get(type_) + if handler: + break + + if handler is None: + # Default handling + msg = repr(error) + else: + msg = await maybe_coroutine(handler, self, ctx, error) + + if msg: + await ctx.send(msg) + + @handles(UnwantedCommand) + async def on_unwanted_command(self, ctx, error): + await ctx.message.delete() + author: discord.Message + await ctx.author.send( + "J'ai supprimé ton message:\n> " + + ctx.message.clean_content + + "\nC'est pas grave, c'est juste pour ne pas encombrer " + "le chat lors du tirage." + ) + await ctx.author.send("Raison: " + error.original.msg) + + @handles(CommandInvokeError) + async def on_command_invoke_error(self, ctx, error): + specific_handler = handlers.get(type(error.original)) + + if specific_handler: + return await specific_handler(ctx, error) + + traceback.print_tb(error.original.__traceback__, file=sys.stderr) + return ( + error.original.__class__.__name__ + + ": " + + (str(error.original) or str(error)) + ) + + @handles(CommandNotFound) + def on_command_not_found(self, ctx, error): + + # Here we just take advantage that the error is formatted this way: + # 'Command "NAME" is not found' + name = str(error).partition('"')[2].rpartition('"')[0] + return f"La commande {name} n'éxiste pas. Pour une liste des commandes, envoie `!help`." + + @handles(MissingRole) + def on_missing_role(self, ctx, error): + return ( + f"Il te faut le role de {error.missing_role} pour utiliser cette commande." + ) + + +def setup(bot): + bot.add_cog(ErrorsCog()) diff --git a/src/tfjm_discord_bot.py b/src/tfjm_discord_bot.py index 44d6e7a..ba4cdc3 100644 --- a/src/tfjm_discord_bot.py +++ b/src/tfjm_discord_bot.py @@ -18,6 +18,8 @@ from src.errors import TfjmError, UnwantedCommand bot = commands.Bot(("! ", "!"), help_command=TfjmHelpCommand()) # Variable globale qui contient les tirages. +# We *want* it to be global so we can reload the tirages cog without +# removing all the running tirages tirages = {} @@ -26,46 +28,12 @@ async def on_ready(): print(f"{bot.user} has connected to Discord!") -@bot.event -async def on_command_error(ctx: Context, error, *args, **kwargs): - if isinstance(error, commands.CommandInvokeError): - if isinstance(error.original, UnwantedCommand): - await ctx.message.delete() - author: discord.Message - await ctx.author.send( - "J'ai supprimé ton message:\n> " - + ctx.message.clean_content - + "\nC'est pas grave, c'est juste pour ne pas encombrer " - "le chat lors du tirage." - ) - await ctx.author.send("Raison: " + error.original.msg) - return - else: - msg = ( - error.original.__class__.__name__ - + ": " - + (str(error.original) or str(error)) - ) - traceback.print_tb(error.original.__traceback__, file=sys.stderr) - elif isinstance(error, commands.CommandNotFound): - # Here we just take adventage that the error is formatted this way: - # 'Command "NAME" is not found' - name = str(error).partition('"')[2].rpartition('"')[0] - msg = f"La commande {name} n'éxiste pas. Pour un liste des commandes, envoie `!help`." - elif isinstance(error, commands.MissingRole): - msg = f"Il te faut le role de {error.missing_role} pour utiliser cette commande" - else: - msg = repr(error) - - print(repr(error), dir(error), file=sys.stderr) - await ctx.send(msg) - - bot.remove_command("help") -bot.load_extension("src.cogs.tirages") -bot.load_extension("src.cogs.teams") bot.load_extension("src.cogs.dev") +bot.load_extension("src.cogs.errors") bot.load_extension("src.cogs.misc") +bot.load_extension("src.cogs.teams") +bot.load_extension("src.cogs.tirages") if __name__ == "__main__":