aboutsummaryrefslogtreecommitdiffstats
path: root/bot/cogs/error_handler.py
blob: e74030c16a9971eb399a20d4e5b027f2e6d22e44 (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
123
124
125
126
127
128
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, Cog, Context

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

log = logging.getLogger(__name__)


class ErrorHandler(Cog):
    """Handles errors emitted from commands."""

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

    @Cog.listener()
    async def on_command_error(self, ctx: Context, e: CommandError) -> None:
        """Provide command error handling."""
        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"),)

        if hasattr(e, "handled"):
            log.trace(f"Command {command} had its error already handled locally; 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, InChannelCheckFailure):
            await ctx.send(e)
        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) -> None:
        """Generic handler for errors without an explicit handler."""
        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) -> None:
    """Error handler cog load."""
    bot.add_cog(ErrorHandler(bot))
    log.info("Cog loaded: Events")