diff options
Diffstat (limited to 'bot/exts/evergreen/error_handler.py')
-rw-r--r-- | bot/exts/evergreen/error_handler.py | 77 |
1 files changed, 62 insertions, 15 deletions
diff --git a/bot/exts/evergreen/error_handler.py b/bot/exts/evergreen/error_handler.py index 28902503..a280c725 100644 --- a/bot/exts/evergreen/error_handler.py +++ b/bot/exts/evergreen/error_handler.py @@ -1,3 +1,4 @@ +import difflib import logging import math import random @@ -7,17 +8,21 @@ from discord import Embed, Message from discord.ext import commands from sentry_sdk import push_scope -from bot.constants import Channels, Colours, ERROR_REPLIES, NEGATIVE_REPLIES +from bot.bot import Bot +from bot.constants import Channels, Colours, ERROR_REPLIES, NEGATIVE_REPLIES, RedirectOutput from bot.utils.decorators import InChannelCheckFailure, InMonthCheckFailure -from bot.utils.exceptions import UserNotPlayingError +from bot.utils.exceptions import APIError, UserNotPlayingError log = logging.getLogger(__name__) +QUESTION_MARK_ICON = "https://cdn.discordapp.com/emojis/512367613339369475.png" + + class CommandErrorHandler(commands.Cog): """A error handler for the PythonDiscord server.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot) -> None: self.bot = bot @staticmethod @@ -41,12 +46,17 @@ class CommandErrorHandler(commands.Cog): @commands.Cog.listener() async def on_command_error(self, ctx: commands.Context, error: commands.CommandError) -> None: - """Activates when a command opens an error.""" - if getattr(error, 'handled', False): + """Activates when a command raises an error.""" + if getattr(error, "handled", False): logging.debug(f"Command {ctx.command} had its error already handled locally; ignoring.") return - error = getattr(error, 'original', error) + parent_command = "" + if subctx := getattr(ctx, "subcontext", None): + parent_command = f"{ctx.command} " + ctx = subctx + + error = getattr(error, "original", error) logging.debug( f"Error Encountered: {type(error).__name__} - {str(error)}, " f"Command: {ctx.command}, " @@ -55,6 +65,7 @@ class CommandErrorHandler(commands.Cog): ) if isinstance(error, commands.CommandNotFound): + await self.send_command_suggestion(ctx, ctx.invoked_with) return if isinstance(error, (InChannelCheckFailure, InMonthCheckFailure)): @@ -63,8 +74,9 @@ class CommandErrorHandler(commands.Cog): if isinstance(error, commands.UserInputError): self.revert_cooldown_counter(ctx.command, ctx.message) + usage = f"```{ctx.prefix}{parent_command}{ctx.command} {ctx.command.signature}```" embed = self.error_embed( - f"Your input was invalid: {error}\n\nUsage:\n```{ctx.prefix}{ctx.command} {ctx.command.signature}```" + f"Your input was invalid: {error}\n\nUsage:{usage}" ) await ctx.send(embed=embed) return @@ -95,7 +107,7 @@ class CommandErrorHandler(commands.Cog): self.revert_cooldown_counter(ctx.command, ctx.message) embed = self.error_embed( "The argument you provided was invalid: " - f"{error}\n\nUsage:\n```{ctx.prefix}{ctx.command} {ctx.command.signature}```" + f"{error}\n\nUsage:\n```{ctx.prefix}{parent_command}{ctx.command} {ctx.command.signature}```" ) await ctx.send(embed=embed) return @@ -108,6 +120,15 @@ class CommandErrorHandler(commands.Cog): await ctx.send("Game not found.") return + if isinstance(error, APIError): + await ctx.send( + embed=self.error_embed( + f"There was an error when communicating with the {error.api}", + NEGATIVE_REPLIES + ) + ) + return + with push_scope() as scope: scope.user = { "id": ctx.author.id, @@ -121,14 +142,40 @@ class CommandErrorHandler(commands.Cog): scope.set_extra("full_message", ctx.message.content) if ctx.guild is not None: - scope.set_extra( - "jump_to", - f"https://discordapp.com/channels/{ctx.guild.id}/{ctx.channel.id}/{ctx.message.id}" - ) + scope.set_extra("jump_to", ctx.message.jump_url) log.exception(f"Unhandled command error: {str(error)}", exc_info=error) - -def setup(bot: commands.Bot) -> None: - """Error handler Cog load.""" + async def send_command_suggestion(self, ctx: commands.Context, command_name: str) -> None: + """Sends user similar commands if any can be found.""" + raw_commands = [] + for cmd in self.bot.walk_commands(): + if not cmd.hidden: + raw_commands += (cmd.name, *cmd.aliases) + if similar_command_data := difflib.get_close_matches(command_name, raw_commands, 1): + similar_command_name = similar_command_data[0] + similar_command = self.bot.get_command(similar_command_name) + + if not similar_command: + return + + log_msg = "Cancelling attempt to suggest a command due to failed checks." + try: + if not await similar_command.can_run(ctx): + log.debug(log_msg) + return + except commands.errors.CommandError as cmd_error: + log.debug(log_msg) + await self.on_command_error(ctx, cmd_error) + return + + misspelled_content = ctx.message.content + e = Embed() + e.set_author(name="Did you mean:", icon_url=QUESTION_MARK_ICON) + e.description = misspelled_content.replace(command_name, similar_command_name, 1) + await ctx.send(embed=e, delete_after=RedirectOutput.delete_delay) + + +def setup(bot: Bot) -> None: + """Load the ErrorHandler cog.""" bot.add_cog(CommandErrorHandler(bot)) |