aboutsummaryrefslogtreecommitdiffstats
path: root/bot/cogs/error_handler.py
blob: 1f0700f280e7eff3cde960c314ff3d746a876eab (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import contextlib
import logging

from discord.ext.commands import (
    BadArgument,
    BotMissingPermissions,
    CheckFailure,
    CommandError,
    CommandInvokeError,
    CommandNotFound,
    CommandOnCooldown,
    DisabledCommand,
    MissingPermissions,
    NoPrivateMessage,
    UserInputError,
)
from discord.ext.commands import Bot, Context

from bot.api import ResponseCodeError
from bot.constants import Channels

log = logging.getLogger(__name__)


class ErrorHandler:
    """Handles errors emitted from commands."""

    def __init__(self, bot: Bot):
        self.bot = bot

    async def on_command_error(self, ctx: Context, e: CommandError):
        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"),)

        cog_has_handler = command and hasattr(ctx.cog, f"_{command.cog_name}__error")
        if hasattr(command, "on_error") or cog_has_handler:
            log.debug(f"Command {command} has a local error handler; ignoring.")
            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 not ctx.channel.id == Channels.verification:
                tags_get_command = self.bot.get_command("tags get")
                ctx.invoked_from_error_handler = True

                # Return to not raise the exception
                with contextlib.suppress(ResponseCodeError):
                    return await ctx.invoke(tags_get_command, tag_name=ctx.invoked_with)
        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}"
            )
        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, (CheckFailure, CommandOnCooldown, DisabledCommand)):
            log.debug(
                f"Command {command} invoked by {ctx.message.author} with error "
                f"{e.__class__.__name__}: {e}"
            )
        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}")
            else:
                await self.handle_unexpected_error(ctx, e.original)
        else:
            await self.handle_unexpected_error(ctx, e)

    @staticmethod
    async def handle_unexpected_error(ctx: Context, e: CommandError):
        await ctx.send(
            f"Sorry, an unexpected error occurred. Please let us know!\n\n"
            f"```{e.__class__.__name__}: {e}```"
        )
        log.error(
            f"Error executing command invoked by {ctx.message.author}: {ctx.message.content}"
        )
        raise e


def setup(bot: Bot):
    bot.add_cog(ErrorHandler(bot))
    log.info("Cog loaded: Events")