From 6f25814aee1794fdabce6fef97d0d776121d5535 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sun, 16 Feb 2020 11:41:56 -0800 Subject: Moderation: fix member not found error not being shown --- bot/cogs/moderation/infractions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/moderation/infractions.py b/bot/cogs/moderation/infractions.py index f4e296df9..9ea17b2b3 100644 --- a/bot/cogs/moderation/infractions.py +++ b/bot/cogs/moderation/infractions.py @@ -313,6 +313,6 @@ class Infractions(InfractionScheduler, commands.Cog): async def cog_command_error(self, ctx: Context, error: Exception) -> None: """Send a notification to the invoking context on a Union failure.""" if isinstance(error, commands.BadUnionArgument): - if discord.User in error.converters: + if discord.User in error.converters or discord.Member in error.converters: await ctx.send(str(error.errors[0])) error.handled = True -- cgit v1.2.3 From a83d2683f72a750b1946df913749a5c4257ebb16 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sun, 16 Feb 2020 11:52:54 -0800 Subject: Error handler: create separate function to handle CheckFailure --- bot/cogs/error_handler.py | 44 +++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/bot/cogs/error_handler.py b/bot/cogs/error_handler.py index 52893b2ee..bd47eecf8 100644 --- a/bot/cogs/error_handler.py +++ b/bot/cogs/error_handler.py @@ -100,21 +100,9 @@ class ErrorHandler(Cog): f"Command {command} invoked by {ctx.message.author} with error " f"{e.__class__.__name__}: {e}" ) - elif isinstance(e, NoPrivateMessage): - await ctx.send("Sorry, this command can't be used in a private message!") - elif isinstance(e, BotMissingPermissions): - await ctx.send(f"Sorry, it looks like I don't have the permissions I need to do that.") - log.warning( - f"The bot is missing permissions to execute command {command}: {e.missing_perms}" - ) - elif isinstance(e, MissingPermissions): - log.debug( - f"{ctx.message.author} is missing permissions to invoke command {command}: " - f"{e.missing_perms}" - ) - elif isinstance(e, InChannelCheckFailure): - await ctx.send(e) - elif isinstance(e, (CheckFailure, CommandOnCooldown, DisabledCommand)): + elif isinstance(e, CheckFailure): + await self.handle_check_failure(ctx, e) + elif isinstance(e, (CommandOnCooldown, DisabledCommand)): log.debug( f"Command {command} invoked by {ctx.message.author} with error " f"{e.__class__.__name__}: {e}" @@ -138,8 +126,34 @@ class ErrorHandler(Cog): else: await self.handle_unexpected_error(ctx, e.original) else: + # MaxConcurrencyReached, ExtensionError await self.handle_unexpected_error(ctx, e) + @staticmethod + async def handle_check_failure(ctx: Context, e: CheckFailure) -> None: + """Handle CheckFailure exceptions and its children.""" + command = ctx.command + + if isinstance(e, NoPrivateMessage): + await ctx.send("Sorry, this command can't be used in a private message!") + elif isinstance(e, BotMissingPermissions): + await ctx.send(f"Sorry, it looks like I don't have the permissions I need to do that.") + log.warning( + f"The bot is missing permissions to execute command {command}: {e.missing_perms}" + ) + elif isinstance(e, MissingPermissions): + log.debug( + f"{ctx.message.author} is missing permissions to invoke command {command}: " + f"{e.missing_perms}" + ) + elif isinstance(e, InChannelCheckFailure): + await ctx.send(e) + else: + log.debug( + f"Command {command} invoked by {ctx.message.author} with error " + f"{e.__class__.__name__}: {e}" + ) + @staticmethod async def handle_unexpected_error(ctx: Context, e: CommandError) -> None: """Generic handler for errors without an explicit handler.""" -- cgit v1.2.3 From eab4b16ccb2d5b6f3d0a8765e8741fe88fb03e27 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sun, 16 Feb 2020 11:58:48 -0800 Subject: Error handler: create separate function to handle ResponseCodeError --- bot/cogs/error_handler.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/bot/cogs/error_handler.py b/bot/cogs/error_handler.py index bd47eecf8..97124cb15 100644 --- a/bot/cogs/error_handler.py +++ b/bot/cogs/error_handler.py @@ -109,20 +109,7 @@ class ErrorHandler(Cog): ) elif isinstance(e, CommandInvokeError): if isinstance(e.original, ResponseCodeError): - status = e.original.response.status - - if status == 404: - await ctx.send("There does not seem to be anything matching your query.") - elif status == 400: - content = await e.original.response.json() - log.debug(f"API responded with 400 for command {command}: %r.", content) - await ctx.send("According to the API, your request is malformed.") - elif 500 <= status < 600: - await ctx.send("Sorry, there seems to be an internal issue with the API.") - log.warning(f"API responded with {status} for command {command}") - else: - await ctx.send(f"Got an unexpected status code from the API (`{status}`).") - log.warning(f"Unexpected API response for command {command}: {status}") + await self.handle_api_error(ctx, e.original) else: await self.handle_unexpected_error(ctx, e.original) else: @@ -154,6 +141,22 @@ class ErrorHandler(Cog): f"{e.__class__.__name__}: {e}" ) + @staticmethod + async def handle_api_error(ctx: Context, e: ResponseCodeError) -> None: + """Handle ResponseCodeError exceptions.""" + if e.status == 404: + await ctx.send("There does not seem to be anything matching your query.") + elif e.status == 400: + content = await e.response.json() + log.debug(f"API responded with 400 for command {ctx.command}: %r.", content) + await ctx.send("According to the API, your request is malformed.") + elif 500 <= e.status < 600: + await ctx.send("Sorry, there seems to be an internal issue with the API.") + log.warning(f"API responded with {e.status} for command {ctx.command}") + else: + await ctx.send(f"Got an unexpected status code from the API (`{e.status}`).") + log.warning(f"Unexpected API response for command {ctx.command}: {e.status}") + @staticmethod async def handle_unexpected_error(ctx: Context, e: CommandError) -> None: """Generic handler for errors without an explicit handler.""" -- cgit v1.2.3 From 29e3c3e46242866820b9c4461378ed4b2e3afb47 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sun, 16 Feb 2020 12:09:24 -0800 Subject: Error handler: log unhandled exceptions instead of re-raising --- bot/cogs/error_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/error_handler.py b/bot/cogs/error_handler.py index 97124cb15..5eef045e8 100644 --- a/bot/cogs/error_handler.py +++ b/bot/cogs/error_handler.py @@ -165,9 +165,9 @@ class ErrorHandler(Cog): f"```{e.__class__.__name__}: {e}```" ) log.error( - f"Error executing command invoked by {ctx.message.author}: {ctx.message.content}" + f"Error executing command invoked by {ctx.message.author}: {ctx.message.content}", + exc_info=e ) - raise e def setup(bot: Bot) -> None: -- cgit v1.2.3 From 806c69f78c5751f6dc93bd8dcc6fff95436fe0ed Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sun, 16 Feb 2020 12:19:52 -0800 Subject: Error handler: move tag retrieval to a separate function --- bot/cogs/error_handler.py | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/bot/cogs/error_handler.py b/bot/cogs/error_handler.py index 5eef045e8..7078d425d 100644 --- a/bot/cogs/error_handler.py +++ b/bot/cogs/error_handler.py @@ -72,24 +72,8 @@ class ErrorHandler(Cog): # Try to look for a tag with the command's name if the command isn't found. if isinstance(e, CommandNotFound) and not hasattr(ctx, "invoked_from_error_handler"): - if not ctx.channel.id == Channels.verification: - tags_get_command = self.bot.get_command("tags get") - ctx.invoked_from_error_handler = True - - log_msg = "Cancelling attempt to fall back to a tag due to failed checks." - try: - if not await tags_get_command.can_run(ctx): - log.debug(log_msg) - return - except CommandError as tag_error: - log.debug(log_msg) - await self.on_command_error(ctx, tag_error) - return - - # Return to not raise the exception - with contextlib.suppress(ResponseCodeError): - await ctx.invoke(tags_get_command, tag_name=ctx.invoked_with) - return + if ctx.channel.id != Channels.verification: + await self.try_get_tag(ctx) elif isinstance(e, BadArgument): await ctx.send(f"Bad argument: {e}\n") await ctx.invoke(*help_command) @@ -116,6 +100,32 @@ class ErrorHandler(Cog): # MaxConcurrencyReached, ExtensionError await self.handle_unexpected_error(ctx, e) + async def try_get_tag(self, ctx: Context) -> None: + """ + Attempt to display a tag by interpreting the command name as a tag name. + + The invocation of tags get respects its checks. Any CommandErrors raised will be handled + by `on_command_error`, but the `invoked_from_error_handler` attribute will be added to + the context to prevent infinite recursion in the case of a CommandNotFound exception. + """ + tags_get_command = self.bot.get_command("tags get") + ctx.invoked_from_error_handler = True + + log_msg = "Cancelling attempt to fall back to a tag due to failed checks." + try: + if not await tags_get_command.can_run(ctx): + log.debug(log_msg) + return + except CommandError as tag_error: + log.debug(log_msg) + await self.on_command_error(ctx, tag_error) + return + + # Return to not raise the exception + with contextlib.suppress(ResponseCodeError): + await ctx.invoke(tags_get_command, tag_name=ctx.invoked_with) + return + @staticmethod async def handle_check_failure(ctx: Context, e: CheckFailure) -> None: """Handle CheckFailure exceptions and its children.""" -- cgit v1.2.3 From fb30fb1427fa26d6cfd54fdb6a80e4e7552d808f Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sun, 16 Feb 2020 12:31:42 -0800 Subject: Error handler: move help command retrieval to a separate function --- bot/cogs/error_handler.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/bot/cogs/error_handler.py b/bot/cogs/error_handler.py index 7078d425d..6a0aef13e 100644 --- a/bot/cogs/error_handler.py +++ b/bot/cogs/error_handler.py @@ -1,10 +1,12 @@ import contextlib import logging +import typing as t from discord.ext.commands import ( BadArgument, BotMissingPermissions, CheckFailure, + Command, CommandError, CommandInvokeError, CommandNotFound, @@ -53,18 +55,9 @@ class ErrorHandler(Cog): 10. Otherwise, handling is deferred to `handle_unexpected_error` """ command = ctx.command - parent = None - if command is not None: - parent = command.parent - - # Retrieve the help command for the invoked command. - if parent and command: - help_command = (self.bot.get_command("help"), parent.name, command.name) - elif command: - help_command = (self.bot.get_command("help"), command.name) - else: - help_command = (self.bot.get_command("help"),) + # TODO: use ctx.send_help() once PR #519 is merged. + help_command = await self.get_help_command(command) if hasattr(e, "handled"): log.trace(f"Command {command} had its error already handled locally; ignoring.") @@ -100,6 +93,20 @@ class ErrorHandler(Cog): # MaxConcurrencyReached, ExtensionError await self.handle_unexpected_error(ctx, e) + async def get_help_command(self, command: t.Optional[Command]) -> t.Tuple: + """Return the help command invocation args to display help for `command`.""" + parent = None + if command is not None: + parent = command.parent + + # Retrieve the help command for the invoked command. + if parent and command: + return self.bot.get_command("help"), parent.name, command.name + elif command: + return self.bot.get_command("help"), command.name + else: + return self.bot.get_command("help") + async def try_get_tag(self, ctx: Context) -> None: """ Attempt to display a tag by interpreting the command name as a tag name. -- cgit v1.2.3 From d263f948e57a71e23cf4e04d678a880a130f3884 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sun, 16 Feb 2020 12:44:58 -0800 Subject: Error handler: create separate function to handle UserInputError --- bot/cogs/error_handler.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/bot/cogs/error_handler.py b/bot/cogs/error_handler.py index 6a0aef13e..c7758d946 100644 --- a/bot/cogs/error_handler.py +++ b/bot/cogs/error_handler.py @@ -56,9 +56,6 @@ class ErrorHandler(Cog): """ command = ctx.command - # TODO: use ctx.send_help() once PR #519 is merged. - help_command = await self.get_help_command(command) - if hasattr(e, "handled"): log.trace(f"Command {command} had its error already handled locally; ignoring.") return @@ -67,16 +64,8 @@ class ErrorHandler(Cog): if isinstance(e, CommandNotFound) and not hasattr(ctx, "invoked_from_error_handler"): if ctx.channel.id != Channels.verification: await self.try_get_tag(ctx) - elif isinstance(e, BadArgument): - await ctx.send(f"Bad argument: {e}\n") - await ctx.invoke(*help_command) elif isinstance(e, UserInputError): - await ctx.send("Something about your input seems off. Check the arguments:") - await ctx.invoke(*help_command) - log.debug( - f"Command {command} invoked by {ctx.message.author} with error " - f"{e.__class__.__name__}: {e}" - ) + await self.handle_user_input_error(ctx, e) elif isinstance(e, CheckFailure): await self.handle_check_failure(ctx, e) elif isinstance(e, (CommandOnCooldown, DisabledCommand)): @@ -133,6 +122,22 @@ class ErrorHandler(Cog): await ctx.invoke(tags_get_command, tag_name=ctx.invoked_with) return + async def handle_user_input_error(self, ctx: Context, e: UserInputError) -> None: + """Handle UserInputError exceptions and its children.""" + # TODO: use ctx.send_help() once PR #519 is merged. + help_command = await self.get_help_command(ctx.command) + + if isinstance(e, BadArgument): + await ctx.send(f"Bad argument: {e}\n") + await ctx.invoke(*help_command) + else: + await ctx.send("Something about your input seems off. Check the arguments:") + await ctx.invoke(*help_command) + log.debug( + f"Command {ctx.command} invoked by {ctx.message.author} with error " + f"{e.__class__.__name__}: {e}" + ) + @staticmethod async def handle_check_failure(ctx: Context, e: CheckFailure) -> None: """Handle CheckFailure exceptions and its children.""" -- cgit v1.2.3 From dbd879e715fe9eadee33d098282cb7b4d941df26 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sun, 16 Feb 2020 13:09:05 -0800 Subject: Error handler: simplify error imports Import the errors module and qualify the error types with it rather than importing a large list of error types. --- bot/cogs/error_handler.py | 44 +++++++++++++++----------------------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/bot/cogs/error_handler.py b/bot/cogs/error_handler.py index c7758d946..c65ada344 100644 --- a/bot/cogs/error_handler.py +++ b/bot/cogs/error_handler.py @@ -2,21 +2,7 @@ import contextlib import logging import typing as t -from discord.ext.commands import ( - BadArgument, - BotMissingPermissions, - CheckFailure, - Command, - CommandError, - CommandInvokeError, - CommandNotFound, - CommandOnCooldown, - DisabledCommand, - MissingPermissions, - NoPrivateMessage, - UserInputError, -) -from discord.ext.commands import Cog, Context +from discord.ext.commands import Cog, Command, Context, errors from bot.api import ResponseCodeError from bot.bot import Bot @@ -33,7 +19,7 @@ class ErrorHandler(Cog): self.bot = bot @Cog.listener() - async def on_command_error(self, ctx: Context, e: CommandError) -> None: + async def on_command_error(self, ctx: Context, e: errors.CommandError) -> None: """ Provide generic command error handling. @@ -61,19 +47,19 @@ class ErrorHandler(Cog): return # Try to look for a tag with the command's name if the command isn't found. - if isinstance(e, CommandNotFound) and not hasattr(ctx, "invoked_from_error_handler"): + if isinstance(e, errors.CommandNotFound) and not hasattr(ctx, "invoked_from_error_handler"): if ctx.channel.id != Channels.verification: await self.try_get_tag(ctx) - elif isinstance(e, UserInputError): + elif isinstance(e, errors.UserInputError): await self.handle_user_input_error(ctx, e) - elif isinstance(e, CheckFailure): + elif isinstance(e, errors.CheckFailure): await self.handle_check_failure(ctx, e) - elif isinstance(e, (CommandOnCooldown, DisabledCommand)): + elif isinstance(e, (errors.CommandOnCooldown, errors.DisabledCommand)): log.debug( f"Command {command} invoked by {ctx.message.author} with error " f"{e.__class__.__name__}: {e}" ) - elif isinstance(e, CommandInvokeError): + elif isinstance(e, errors.CommandInvokeError): if isinstance(e.original, ResponseCodeError): await self.handle_api_error(ctx, e.original) else: @@ -112,7 +98,7 @@ class ErrorHandler(Cog): if not await tags_get_command.can_run(ctx): log.debug(log_msg) return - except CommandError as tag_error: + except errors.CommandError as tag_error: log.debug(log_msg) await self.on_command_error(ctx, tag_error) return @@ -122,12 +108,12 @@ class ErrorHandler(Cog): await ctx.invoke(tags_get_command, tag_name=ctx.invoked_with) return - async def handle_user_input_error(self, ctx: Context, e: UserInputError) -> None: + async def handle_user_input_error(self, ctx: Context, e: errors.UserInputError) -> None: """Handle UserInputError exceptions and its children.""" # TODO: use ctx.send_help() once PR #519 is merged. help_command = await self.get_help_command(ctx.command) - if isinstance(e, BadArgument): + if isinstance(e, errors.BadArgument): await ctx.send(f"Bad argument: {e}\n") await ctx.invoke(*help_command) else: @@ -139,18 +125,18 @@ class ErrorHandler(Cog): ) @staticmethod - async def handle_check_failure(ctx: Context, e: CheckFailure) -> None: + async def handle_check_failure(ctx: Context, e: errors.CheckFailure) -> None: """Handle CheckFailure exceptions and its children.""" command = ctx.command - if isinstance(e, NoPrivateMessage): + if isinstance(e, errors.NoPrivateMessage): await ctx.send("Sorry, this command can't be used in a private message!") - elif isinstance(e, BotMissingPermissions): + elif isinstance(e, errors.BotMissingPermissions): await ctx.send(f"Sorry, it looks like I don't have the permissions I need to do that.") log.warning( f"The bot is missing permissions to execute command {command}: {e.missing_perms}" ) - elif isinstance(e, MissingPermissions): + elif isinstance(e, errors.MissingPermissions): log.debug( f"{ctx.message.author} is missing permissions to invoke command {command}: " f"{e.missing_perms}" @@ -180,7 +166,7 @@ class ErrorHandler(Cog): log.warning(f"Unexpected API response for command {ctx.command}: {e.status}") @staticmethod - async def handle_unexpected_error(ctx: Context, e: CommandError) -> None: + async def handle_unexpected_error(ctx: Context, e: errors.CommandError) -> None: """Generic handler for errors without an explicit handler.""" await ctx.send( f"Sorry, an unexpected error occurred. Please let us know!\n\n" -- cgit v1.2.3 From d2f94f4c1280716e32e13611f6c778f9d9d4efd3 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sun, 16 Feb 2020 13:15:55 -0800 Subject: Error handler: handle MissingRequiredArgument Send a message indicating which argument is missing. --- bot/cogs/error_handler.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bot/cogs/error_handler.py b/bot/cogs/error_handler.py index c65ada344..ffb36d10a 100644 --- a/bot/cogs/error_handler.py +++ b/bot/cogs/error_handler.py @@ -113,7 +113,10 @@ class ErrorHandler(Cog): # TODO: use ctx.send_help() once PR #519 is merged. help_command = await self.get_help_command(ctx.command) - if isinstance(e, errors.BadArgument): + if isinstance(e, errors.MissingRequiredArgument): + await ctx.send(f"Missing required argument `{e.param.name}`.") + await ctx.invoke(*help_command) + elif isinstance(e, errors.BadArgument): await ctx.send(f"Bad argument: {e}\n") await ctx.invoke(*help_command) else: -- cgit v1.2.3 From 6fa0ba18a6b4daa265e6716f9d360117378c67ab Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sun, 16 Feb 2020 13:17:03 -0800 Subject: Error handler: handle TooManyArguments Send a message specifying the error reason. --- bot/cogs/error_handler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bot/cogs/error_handler.py b/bot/cogs/error_handler.py index ffb36d10a..5cf95e71a 100644 --- a/bot/cogs/error_handler.py +++ b/bot/cogs/error_handler.py @@ -116,6 +116,9 @@ class ErrorHandler(Cog): if isinstance(e, errors.MissingRequiredArgument): await ctx.send(f"Missing required argument `{e.param.name}`.") await ctx.invoke(*help_command) + elif isinstance(e, errors.TooManyArguments): + await ctx.send(f"Too many arguments provided.") + await ctx.invoke(*help_command) elif isinstance(e, errors.BadArgument): await ctx.send(f"Bad argument: {e}\n") await ctx.invoke(*help_command) -- cgit v1.2.3 From 8cdbec386d50e5866dcfd7cc0aeee359bb182317 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sun, 16 Feb 2020 13:23:56 -0800 Subject: Error handler: handle BadUnionArgument Send a message specifying the parameter name, the converters used, and the last error message from the converters. --- bot/cogs/error_handler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bot/cogs/error_handler.py b/bot/cogs/error_handler.py index 5cf95e71a..d67261fc6 100644 --- a/bot/cogs/error_handler.py +++ b/bot/cogs/error_handler.py @@ -122,6 +122,8 @@ class ErrorHandler(Cog): elif isinstance(e, errors.BadArgument): await ctx.send(f"Bad argument: {e}\n") await ctx.invoke(*help_command) + elif isinstance(e, errors.BadUnionArgument): + await ctx.send(f"Bad argument: {e}\n```{e.errors[-1]}```") else: await ctx.send("Something about your input seems off. Check the arguments:") await ctx.invoke(*help_command) -- cgit v1.2.3 From 4116aca0218138dd1a97db39b942a886945fa05b Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sun, 16 Feb 2020 17:23:43 -0800 Subject: Error handler: handle ArgumentParsingError Simply send the error message with the help command. --- bot/cogs/error_handler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bot/cogs/error_handler.py b/bot/cogs/error_handler.py index d67261fc6..07b93283d 100644 --- a/bot/cogs/error_handler.py +++ b/bot/cogs/error_handler.py @@ -124,6 +124,8 @@ class ErrorHandler(Cog): await ctx.invoke(*help_command) elif isinstance(e, errors.BadUnionArgument): await ctx.send(f"Bad argument: {e}\n```{e.errors[-1]}```") + elif isinstance(e, errors.ArgumentParsingError): + await ctx.send(f"Argument parsing error: {e}") else: await ctx.send("Something about your input seems off. Check the arguments:") await ctx.invoke(*help_command) -- cgit v1.2.3 From 476d5e7851f5b53ece319f023eeca88ae5c345eb Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sun, 16 Feb 2020 17:38:41 -0800 Subject: Error handler: (almost) always log the error being handled The log level is debug for most errors and it's mainly useful for precisely that - debugging. This is why some "useless" errors are also logged e.g. CommandNotFound. Unexpected errors and some API errors will still have higher levels. * Add a single log statement to the end of the handler to cover UserInputError, CheckFailure, and CommandNotFound (when it's not trying to get a tag) * Log 404s from API --- bot/cogs/error_handler.py | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/bot/cogs/error_handler.py b/bot/cogs/error_handler.py index 07b93283d..ff8b36ddc 100644 --- a/bot/cogs/error_handler.py +++ b/bot/cogs/error_handler.py @@ -50,23 +50,26 @@ class ErrorHandler(Cog): if isinstance(e, errors.CommandNotFound) and not hasattr(ctx, "invoked_from_error_handler"): if ctx.channel.id != Channels.verification: await self.try_get_tag(ctx) + return # Exit early to avoid logging. elif isinstance(e, errors.UserInputError): await self.handle_user_input_error(ctx, e) elif isinstance(e, errors.CheckFailure): await self.handle_check_failure(ctx, e) - elif isinstance(e, (errors.CommandOnCooldown, errors.DisabledCommand)): - log.debug( - f"Command {command} invoked by {ctx.message.author} with error " - f"{e.__class__.__name__}: {e}" - ) elif isinstance(e, errors.CommandInvokeError): if isinstance(e.original, ResponseCodeError): await self.handle_api_error(ctx, e.original) else: await self.handle_unexpected_error(ctx, e.original) - else: - # MaxConcurrencyReached, ExtensionError + return # Exit early to avoid logging. + elif not isinstance(e, (errors.CommandOnCooldown, errors.DisabledCommand)): + # ConversionError, MaxConcurrencyReached, ExtensionError await self.handle_unexpected_error(ctx, e) + return # Exit early to avoid logging. + + log.debug( + f"Command {command} invoked by {ctx.message.author} with error " + f"{e.__class__.__name__}: {e}" + ) async def get_help_command(self, command: t.Optional[Command]) -> t.Tuple: """Return the help command invocation args to display help for `command`.""" @@ -129,10 +132,6 @@ class ErrorHandler(Cog): else: await ctx.send("Something about your input seems off. Check the arguments:") await ctx.invoke(*help_command) - log.debug( - f"Command {ctx.command} invoked by {ctx.message.author} with error " - f"{e.__class__.__name__}: {e}" - ) @staticmethod async def handle_check_failure(ctx: Context, e: errors.CheckFailure) -> None: @@ -153,17 +152,13 @@ class ErrorHandler(Cog): ) elif isinstance(e, InChannelCheckFailure): await ctx.send(e) - else: - log.debug( - f"Command {command} invoked by {ctx.message.author} with error " - f"{e.__class__.__name__}: {e}" - ) @staticmethod async def handle_api_error(ctx: Context, e: ResponseCodeError) -> None: """Handle ResponseCodeError exceptions.""" if e.status == 404: await ctx.send("There does not seem to be anything matching your query.") + log.debug(f"API responded with 404 for command {ctx.command}") elif e.status == 400: content = await e.response.json() log.debug(f"API responded with 400 for command {ctx.command}: %r.", content) -- cgit v1.2.3 From 9bfdf7e3e95c07a1b0369c9fa8bdb4c91339732f Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sun, 16 Feb 2020 18:09:08 -0800 Subject: Error handler: simplify check failure handler & handle bot missing roles discord.py's default error messages are quite descriptive already so there really isn't a need to write our own. Therefore, the log calls were removed so that the generic debug log message is used in the on_command_error. In addition to handling missing bot permissions, missing bot roles are also handled. The message doesn't specify which because it doesn't really matter to the end-user. The logs will use the default error messages as described above, and those will contain the specific roles or permissions that are missing. --- bot/cogs/error_handler.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/bot/cogs/error_handler.py b/bot/cogs/error_handler.py index ff8b36ddc..6c4074e3a 100644 --- a/bot/cogs/error_handler.py +++ b/bot/cogs/error_handler.py @@ -136,21 +136,17 @@ class ErrorHandler(Cog): @staticmethod async def handle_check_failure(ctx: Context, e: errors.CheckFailure) -> None: """Handle CheckFailure exceptions and its children.""" - command = ctx.command + bot_missing_errors = ( + errors.BotMissingPermissions, + errors.BotMissingRole, + errors.BotMissingAnyRole + ) - if isinstance(e, errors.NoPrivateMessage): - await ctx.send("Sorry, this command can't be used in a private message!") - elif isinstance(e, errors.BotMissingPermissions): - await ctx.send(f"Sorry, it looks like I don't have the permissions I need to do that.") - log.warning( - f"The bot is missing permissions to execute command {command}: {e.missing_perms}" - ) - elif isinstance(e, errors.MissingPermissions): - log.debug( - f"{ctx.message.author} is missing permissions to invoke command {command}: " - f"{e.missing_perms}" + if isinstance(e, bot_missing_errors): + await ctx.send( + f"Sorry, it looks like I don't have the permissions or roles I need to do that." ) - elif isinstance(e, InChannelCheckFailure): + elif isinstance(e, (InChannelCheckFailure, errors.NoPrivateMessage)): await ctx.send(e) @staticmethod -- cgit v1.2.3 From aa84854f942d68f5245d2ca99612dfdd6ad167ce Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sun, 16 Feb 2020 19:01:42 -0800 Subject: Error handler: update docstrings to reflect recent changes --- bot/cogs/error_handler.py | 59 +++++++++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/bot/cogs/error_handler.py b/bot/cogs/error_handler.py index 6c4074e3a..d2c806566 100644 --- a/bot/cogs/error_handler.py +++ b/bot/cogs/error_handler.py @@ -23,22 +23,22 @@ class ErrorHandler(Cog): """ Provide generic command error handling. - Error handling is deferred to any local error handler, if present. - - Error handling emits a single error response, prioritized as follows: - 1. If the name fails to match a command but matches a tag, the tag is invoked - 2. Send a BadArgument error message to the invoking context & invoke the command's help - 3. Send a UserInputError error message to the invoking context & invoke the command's help - 4. Send a NoPrivateMessage error message to the invoking context - 5. Send a BotMissingPermissions error message to the invoking context - 6. Log a MissingPermissions error, no message is sent - 7. Send a InChannelCheckFailure error message to the invoking context - 8. Log CheckFailure, CommandOnCooldown, and DisabledCommand errors, no message is sent - 9. For CommandInvokeErrors, response is based on the type of error: - * 404: Error message is sent to the invoking context - * 400: Log the resopnse JSON, no message is sent - * 500 <= status <= 600: Error message is sent to the invoking context - 10. Otherwise, handling is deferred to `handle_unexpected_error` + Error handling is deferred to any local error handler, if present. This is done by + checking for the presence of a `handled` attribute on the error. + + Error handling emits a single error message in the invoking context `ctx` and a log message, + prioritised as follows: + + 1. If the name fails to match a command but matches a tag, the tag is invoked + * If CommandNotFound is raised when invoking the tag (determined by the presence of the + `invoked_from_error_handler` attribute), this error is treated as being unexpected + and therefore sends an error message + * Commands in the verification channel are ignored + 2. UserInputError: see `handle_user_input_error` + 3. CheckFailure: see `handle_check_failure` + 4. ResponseCodeError: see `handle_api_error` + 5. Otherwise, if not a CommandOnCooldown or DisabledCommand, handling is deferred to + `handle_unexpected_error` """ command = ctx.command @@ -112,7 +112,16 @@ class ErrorHandler(Cog): return async def handle_user_input_error(self, ctx: Context, e: errors.UserInputError) -> None: - """Handle UserInputError exceptions and its children.""" + """ + Send an error message in `ctx` for UserInputError, sometimes invoking the help command too. + + * MissingRequiredArgument: send an error message with arg name and the help command + * TooManyArguments: send an error message and the help command + * BadArgument: send an error message and the help command + * BadUnionArgument: send an error message including the error produced by the last converter + * ArgumentParsingError: send an error message + * Other: send an error message and the help command + """ # TODO: use ctx.send_help() once PR #519 is merged. help_command = await self.get_help_command(ctx.command) @@ -135,7 +144,17 @@ class ErrorHandler(Cog): @staticmethod async def handle_check_failure(ctx: Context, e: errors.CheckFailure) -> None: - """Handle CheckFailure exceptions and its children.""" + """ + Send an error message in `ctx` for certain types of CheckFailure. + + The following types are handled: + + * BotMissingPermissions + * BotMissingRole + * BotMissingAnyRole + * NoPrivateMessage + * InChannelCheckFailure + """ bot_missing_errors = ( errors.BotMissingPermissions, errors.BotMissingRole, @@ -151,7 +170,7 @@ class ErrorHandler(Cog): @staticmethod async def handle_api_error(ctx: Context, e: ResponseCodeError) -> None: - """Handle ResponseCodeError exceptions.""" + """Send an error message in `ctx` for ResponseCodeError and log it.""" if e.status == 404: await ctx.send("There does not seem to be anything matching your query.") log.debug(f"API responded with 404 for command {ctx.command}") @@ -168,7 +187,7 @@ class ErrorHandler(Cog): @staticmethod async def handle_unexpected_error(ctx: Context, e: errors.CommandError) -> None: - """Generic handler for errors without an explicit handler.""" + """Send a generic error message in `ctx` and log the exception as an error with exc_info.""" await ctx.send( f"Sorry, an unexpected error occurred. Please let us know!\n\n" f"```{e.__class__.__name__}: {e}```" -- cgit v1.2.3 From b3fdfa71ceeecbd9fe62cadc240fcad27bad7a32 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sun, 16 Feb 2020 19:06:22 -0800 Subject: Error handler: handle CommandOnCooldown errors Simply send the error's default message to the invoking context. --- bot/cogs/error_handler.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bot/cogs/error_handler.py b/bot/cogs/error_handler.py index d2c806566..347ce93ae 100644 --- a/bot/cogs/error_handler.py +++ b/bot/cogs/error_handler.py @@ -36,9 +36,9 @@ class ErrorHandler(Cog): * Commands in the verification channel are ignored 2. UserInputError: see `handle_user_input_error` 3. CheckFailure: see `handle_check_failure` - 4. ResponseCodeError: see `handle_api_error` - 5. Otherwise, if not a CommandOnCooldown or DisabledCommand, handling is deferred to - `handle_unexpected_error` + 4. CommandOnCooldown: send an error message in the invoking context + 5. ResponseCodeError: see `handle_api_error` + 6. Otherwise, if not a DisabledCommand, handling is deferred to `handle_unexpected_error` """ command = ctx.command @@ -55,13 +55,15 @@ class ErrorHandler(Cog): await self.handle_user_input_error(ctx, e) elif isinstance(e, errors.CheckFailure): await self.handle_check_failure(ctx, e) + elif isinstance(e, errors.CommandOnCooldown): + await ctx.send(e) elif isinstance(e, errors.CommandInvokeError): if isinstance(e.original, ResponseCodeError): await self.handle_api_error(ctx, e.original) else: await self.handle_unexpected_error(ctx, e.original) return # Exit early to avoid logging. - elif not isinstance(e, (errors.CommandOnCooldown, errors.DisabledCommand)): + elif not isinstance(e, errors.DisabledCommand): # ConversionError, MaxConcurrencyReached, ExtensionError await self.handle_unexpected_error(ctx, e) return # Exit early to avoid logging. -- cgit v1.2.3