Edit on GitHub

tickets_plus.cogs.errors

A cog that handles errors from app commands globally.

We use this cog to handle errors from app commands globally. This is to actually handle and respond to errors. It's nice to not leave the user confused.

Typical usage example:
from tickets_plus import bot
bot_instance = bot.TicketsPlusBot(...)
await bot_instance.load_extension("tickets_plus.cogs.errors")
  1"""A cog that handles errors from app commands globally.
  2
  3We use this cog to handle errors from app commands globally.
  4This is to actually handle and respond to errors.
  5It's nice to not leave the user confused.
  6
  7Typical usage example:
  8    ```py
  9    from tickets_plus import bot
 10    bot_instance = bot.TicketsPlusBot(...)
 11    await bot_instance.load_extension("tickets_plus.cogs.errors")
 12    ```
 13"""
 14# License: EPL-2.0
 15# SPDX-License-Identifier: EPL-2.0
 16# Copyright (c) 2021-present The Tickets+ Contributors
 17# This Source Code may also be made available under the following
 18# Secondary Licenses when the conditions for such availability set forth
 19# in the Eclipse Public License, v. 2.0 are satisfied: GPL-3.0-only OR
 20# If later approved by the Initial Contributor, GPL-3.0-or-later.
 21
 22import logging
 23
 24import discord
 25from discord import app_commands, utils
 26from discord.ext import commands
 27from sqlalchemy import exc
 28
 29from tickets_plus import bot
 30from tickets_plus.ext import exceptions
 31
 32
 33class ErrorHandling(commands.Cog, name="AppCommandErrorHandler"):
 34    """Error handling for Tickets+.
 35
 36    This cog is used to handle errors from app commands globally.
 37    This is to actually handle and respond to errors.
 38
 39    Attributes:
 40        old_error_handler: The old error handler.
 41            This is used to restore the old error handler.
 42    """
 43
 44    def __init__(self, bot_instance: bot.TicketsPlusBot) -> None:
 45        """Initialises the cog instance.
 46
 47        We store some attributes here for later use.
 48
 49        Args:
 50            bot_instance: The bot instance.
 51        """
 52        self._bt = bot_instance
 53        logging.info("Loaded %s", self.__class__.__name__)
 54
 55    async def cog_load(self) -> None:
 56        """Adds the error handler to the bot.
 57
 58        Should not be called manually.
 59        """
 60        tree = self._bt.tree
 61        self.old_error_handler = tree.on_error
 62        tree.on_error = self.on_app_command_error
 63        logging.info("Error handling ready.")
 64
 65    async def cog_unload(self) -> None:
 66        """Removes the error handler from the bot.
 67
 68        Should not be called manually.
 69        """
 70        tree = self._bt.tree
 71        tree.on_error = self.old_error_handler
 72        logging.info("Error handling unloaded.")
 73
 74    @staticmethod
 75    async def on_app_command_error(ctx: discord.Interaction, error: app_commands.AppCommandError) -> None:
 76        """Handles errors from app commands globally.
 77
 78        This function is automatically called when an error is raised
 79        from an app command.
 80        This is used to handle and respond to errors.
 81        To not leave the user confused.
 82
 83        Args:
 84            ctx: The interaction that raised the error.
 85            error: The error that was raised.
 86        """
 87        if not ctx.response.is_done():
 88            await ctx.response.defer(ephemeral=True)
 89        if isinstance(ctx.command, app_commands.Command):
 90            # Splitting because, we don't want AttributeError
 91            if hasattr(ctx.command, "on_error"):
 92                # Same as above
 93                if ctx.command.on_error is not None:
 94                    return
 95
 96        emd = discord.Embed(
 97            title="Tickets+ Error: 500 - Internal Server Error",
 98            description=("An unexpected internal error occurred.\n"
 99                         "Please report this error to the bot "
100                         "developers. You can get the link to "
101                         "GitHub and support server by using "
102                         "the /version command."),
103            color=discord.Color.red(),
104            timestamp=utils.utcnow(),
105        )
106
107        if isinstance(error, app_commands.CommandNotFound):
108            emd.title = "Tickets+ Error: 404 - Command Not Found"
109            emd.description = "The command you tried to use does not exist."
110            emd.set_footer(text="If this error persists, please report it.")
111            await ctx.followup.send(embed=emd, ephemeral=True)
112            return
113
114        if isinstance(error, app_commands.CheckFailure):
115            if isinstance(error, app_commands.BotMissingPermissions):
116                emd.title = "Tickets+ Error: 503 - Bot Missing Permissions"
117                emd.description = ("The bot is missing permissions required"
118                                   " to run this command.\n"
119                                   "Please ask a server administrator to grant the bot the "
120                                   "following permissions:\n"
121                                   f"{chr(92).join(error.missing_permissions)}")
122                emd.set_footer(text="If you are sure the bot has the "
123                               "required permissions, please report this.")
124                await ctx.followup.send(embed=emd, ephemeral=True)
125                return
126
127            if isinstance(error, app_commands.NoPrivateMessage):
128                emd.title = "Tickets+ Error: 405 - DMs Not Allowed"
129                emd.description = "This command cannot be used in DMs."
130                emd.set_footer(text="Please use this command in a server.")
131                await ctx.followup.send(embed=emd, ephemeral=True)
132                return
133
134            if isinstance(error, app_commands.CommandOnCooldown):
135                emd.title = "Tickets+ Error: 429 - Command On Cooldown"
136                emd.description = ("This command is on cooldown.\n"
137                                   f"Please try again in {error.retry_after} seconds.")
138                emd.set_footer(text="Thank you for using Tickets+!")
139                await ctx.followup.send(embed=emd, ephemeral=True)
140                return
141
142            emd.title = "Tickets+ Error: 403 - Forbidden"
143            emd.description = "You do not have permission to use this command."
144            emd.set_footer(text=f"Error type: {type(error).__name__}")
145            await ctx.followup.send(embed=emd, ephemeral=True)
146            return
147
148        if isinstance(error, exceptions.TicketsPlusCommandError):
149            emd.title = "Tickets+ Error: 400 - Bad Request"
150            emd.description = str(error)
151            emd.set_footer(text=f"Error type: {type(error).__name__}")
152            await ctx.followup.send(embed=emd, ephemeral=True)
153            return  # We don't want to log this error.
154
155        if isinstance(error, app_commands.CommandInvokeError):
156            underlying_error = error.original
157
158            if isinstance(underlying_error, exc.SQLAlchemyError):
159                logging.error("An error occurred while accessing the database:", exc_info=underlying_error)
160                emd.title = "Tickets+ Error: 500 - Database Error"
161                emd.description = ("An error occurred while accessing the database.\n"
162                                   "Please try again later. If this error persists, "
163                                   "please report it.\n"
164                                   "You can get the link to GitHub and support server by "
165                                   "using the /version command.")
166                emd.set_footer(text=f"Error type: {type(underlying_error)}")
167                await ctx.followup.send(embed=emd, ephemeral=True)
168                return
169
170        logging.error("An unhandled error occurred while executing a command:", exc_info=error)
171        emd.set_footer(text=f"Error type: {type(error).__name__}")
172        await ctx.followup.send(embed=emd, ephemeral=True)
173
174
175async def setup(bot_instance: bot.TicketsPlusBot) -> None:
176    """Sets up the error handler.
177
178    This function is called when the cog is loaded.
179    It is used to add the cog to the bot.
180
181    Args:
182        bot_instance: The bot instance.
183    """
184    await bot_instance.add_cog(ErrorHandling(bot_instance))
class ErrorHandling(discord.ext.commands.cog.Cog):
 34class ErrorHandling(commands.Cog, name="AppCommandErrorHandler"):
 35    """Error handling for Tickets+.
 36
 37    This cog is used to handle errors from app commands globally.
 38    This is to actually handle and respond to errors.
 39
 40    Attributes:
 41        old_error_handler: The old error handler.
 42            This is used to restore the old error handler.
 43    """
 44
 45    def __init__(self, bot_instance: bot.TicketsPlusBot) -> None:
 46        """Initialises the cog instance.
 47
 48        We store some attributes here for later use.
 49
 50        Args:
 51            bot_instance: The bot instance.
 52        """
 53        self._bt = bot_instance
 54        logging.info("Loaded %s", self.__class__.__name__)
 55
 56    async def cog_load(self) -> None:
 57        """Adds the error handler to the bot.
 58
 59        Should not be called manually.
 60        """
 61        tree = self._bt.tree
 62        self.old_error_handler = tree.on_error
 63        tree.on_error = self.on_app_command_error
 64        logging.info("Error handling ready.")
 65
 66    async def cog_unload(self) -> None:
 67        """Removes the error handler from the bot.
 68
 69        Should not be called manually.
 70        """
 71        tree = self._bt.tree
 72        tree.on_error = self.old_error_handler
 73        logging.info("Error handling unloaded.")
 74
 75    @staticmethod
 76    async def on_app_command_error(ctx: discord.Interaction, error: app_commands.AppCommandError) -> None:
 77        """Handles errors from app commands globally.
 78
 79        This function is automatically called when an error is raised
 80        from an app command.
 81        This is used to handle and respond to errors.
 82        To not leave the user confused.
 83
 84        Args:
 85            ctx: The interaction that raised the error.
 86            error: The error that was raised.
 87        """
 88        if not ctx.response.is_done():
 89            await ctx.response.defer(ephemeral=True)
 90        if isinstance(ctx.command, app_commands.Command):
 91            # Splitting because, we don't want AttributeError
 92            if hasattr(ctx.command, "on_error"):
 93                # Same as above
 94                if ctx.command.on_error is not None:
 95                    return
 96
 97        emd = discord.Embed(
 98            title="Tickets+ Error: 500 - Internal Server Error",
 99            description=("An unexpected internal error occurred.\n"
100                         "Please report this error to the bot "
101                         "developers. You can get the link to "
102                         "GitHub and support server by using "
103                         "the /version command."),
104            color=discord.Color.red(),
105            timestamp=utils.utcnow(),
106        )
107
108        if isinstance(error, app_commands.CommandNotFound):
109            emd.title = "Tickets+ Error: 404 - Command Not Found"
110            emd.description = "The command you tried to use does not exist."
111            emd.set_footer(text="If this error persists, please report it.")
112            await ctx.followup.send(embed=emd, ephemeral=True)
113            return
114
115        if isinstance(error, app_commands.CheckFailure):
116            if isinstance(error, app_commands.BotMissingPermissions):
117                emd.title = "Tickets+ Error: 503 - Bot Missing Permissions"
118                emd.description = ("The bot is missing permissions required"
119                                   " to run this command.\n"
120                                   "Please ask a server administrator to grant the bot the "
121                                   "following permissions:\n"
122                                   f"{chr(92).join(error.missing_permissions)}")
123                emd.set_footer(text="If you are sure the bot has the "
124                               "required permissions, please report this.")
125                await ctx.followup.send(embed=emd, ephemeral=True)
126                return
127
128            if isinstance(error, app_commands.NoPrivateMessage):
129                emd.title = "Tickets+ Error: 405 - DMs Not Allowed"
130                emd.description = "This command cannot be used in DMs."
131                emd.set_footer(text="Please use this command in a server.")
132                await ctx.followup.send(embed=emd, ephemeral=True)
133                return
134
135            if isinstance(error, app_commands.CommandOnCooldown):
136                emd.title = "Tickets+ Error: 429 - Command On Cooldown"
137                emd.description = ("This command is on cooldown.\n"
138                                   f"Please try again in {error.retry_after} seconds.")
139                emd.set_footer(text="Thank you for using Tickets+!")
140                await ctx.followup.send(embed=emd, ephemeral=True)
141                return
142
143            emd.title = "Tickets+ Error: 403 - Forbidden"
144            emd.description = "You do not have permission to use this command."
145            emd.set_footer(text=f"Error type: {type(error).__name__}")
146            await ctx.followup.send(embed=emd, ephemeral=True)
147            return
148
149        if isinstance(error, exceptions.TicketsPlusCommandError):
150            emd.title = "Tickets+ Error: 400 - Bad Request"
151            emd.description = str(error)
152            emd.set_footer(text=f"Error type: {type(error).__name__}")
153            await ctx.followup.send(embed=emd, ephemeral=True)
154            return  # We don't want to log this error.
155
156        if isinstance(error, app_commands.CommandInvokeError):
157            underlying_error = error.original
158
159            if isinstance(underlying_error, exc.SQLAlchemyError):
160                logging.error("An error occurred while accessing the database:", exc_info=underlying_error)
161                emd.title = "Tickets+ Error: 500 - Database Error"
162                emd.description = ("An error occurred while accessing the database.\n"
163                                   "Please try again later. If this error persists, "
164                                   "please report it.\n"
165                                   "You can get the link to GitHub and support server by "
166                                   "using the /version command.")
167                emd.set_footer(text=f"Error type: {type(underlying_error)}")
168                await ctx.followup.send(embed=emd, ephemeral=True)
169                return
170
171        logging.error("An unhandled error occurred while executing a command:", exc_info=error)
172        emd.set_footer(text=f"Error type: {type(error).__name__}")
173        await ctx.followup.send(embed=emd, ephemeral=True)

Error handling for Tickets+.

This cog is used to handle errors from app commands globally. This is to actually handle and respond to errors.

Attributes:
  • old_error_handler: The old error handler. This is used to restore the old error handler.
ErrorHandling(bot_instance: tickets_plus.bot.TicketsPlusBot)
45    def __init__(self, bot_instance: bot.TicketsPlusBot) -> None:
46        """Initialises the cog instance.
47
48        We store some attributes here for later use.
49
50        Args:
51            bot_instance: The bot instance.
52        """
53        self._bt = bot_instance
54        logging.info("Loaded %s", self.__class__.__name__)

Initialises the cog instance.

We store some attributes here for later use.

Arguments:
  • bot_instance: The bot instance.
async def cog_load(self) -> None:
56    async def cog_load(self) -> None:
57        """Adds the error handler to the bot.
58
59        Should not be called manually.
60        """
61        tree = self._bt.tree
62        self.old_error_handler = tree.on_error
63        tree.on_error = self.on_app_command_error
64        logging.info("Error handling ready.")

Adds the error handler to the bot.

Should not be called manually.

async def cog_unload(self) -> None:
66    async def cog_unload(self) -> None:
67        """Removes the error handler from the bot.
68
69        Should not be called manually.
70        """
71        tree = self._bt.tree
72        tree.on_error = self.old_error_handler
73        logging.info("Error handling unloaded.")

Removes the error handler from the bot.

Should not be called manually.

@staticmethod
async def on_app_command_error( ctx: discord.interactions.Interaction, error: discord.app_commands.errors.AppCommandError) -> None:
 75    @staticmethod
 76    async def on_app_command_error(ctx: discord.Interaction, error: app_commands.AppCommandError) -> None:
 77        """Handles errors from app commands globally.
 78
 79        This function is automatically called when an error is raised
 80        from an app command.
 81        This is used to handle and respond to errors.
 82        To not leave the user confused.
 83
 84        Args:
 85            ctx: The interaction that raised the error.
 86            error: The error that was raised.
 87        """
 88        if not ctx.response.is_done():
 89            await ctx.response.defer(ephemeral=True)
 90        if isinstance(ctx.command, app_commands.Command):
 91            # Splitting because, we don't want AttributeError
 92            if hasattr(ctx.command, "on_error"):
 93                # Same as above
 94                if ctx.command.on_error is not None:
 95                    return
 96
 97        emd = discord.Embed(
 98            title="Tickets+ Error: 500 - Internal Server Error",
 99            description=("An unexpected internal error occurred.\n"
100                         "Please report this error to the bot "
101                         "developers. You can get the link to "
102                         "GitHub and support server by using "
103                         "the /version command."),
104            color=discord.Color.red(),
105            timestamp=utils.utcnow(),
106        )
107
108        if isinstance(error, app_commands.CommandNotFound):
109            emd.title = "Tickets+ Error: 404 - Command Not Found"
110            emd.description = "The command you tried to use does not exist."
111            emd.set_footer(text="If this error persists, please report it.")
112            await ctx.followup.send(embed=emd, ephemeral=True)
113            return
114
115        if isinstance(error, app_commands.CheckFailure):
116            if isinstance(error, app_commands.BotMissingPermissions):
117                emd.title = "Tickets+ Error: 503 - Bot Missing Permissions"
118                emd.description = ("The bot is missing permissions required"
119                                   " to run this command.\n"
120                                   "Please ask a server administrator to grant the bot the "
121                                   "following permissions:\n"
122                                   f"{chr(92).join(error.missing_permissions)}")
123                emd.set_footer(text="If you are sure the bot has the "
124                               "required permissions, please report this.")
125                await ctx.followup.send(embed=emd, ephemeral=True)
126                return
127
128            if isinstance(error, app_commands.NoPrivateMessage):
129                emd.title = "Tickets+ Error: 405 - DMs Not Allowed"
130                emd.description = "This command cannot be used in DMs."
131                emd.set_footer(text="Please use this command in a server.")
132                await ctx.followup.send(embed=emd, ephemeral=True)
133                return
134
135            if isinstance(error, app_commands.CommandOnCooldown):
136                emd.title = "Tickets+ Error: 429 - Command On Cooldown"
137                emd.description = ("This command is on cooldown.\n"
138                                   f"Please try again in {error.retry_after} seconds.")
139                emd.set_footer(text="Thank you for using Tickets+!")
140                await ctx.followup.send(embed=emd, ephemeral=True)
141                return
142
143            emd.title = "Tickets+ Error: 403 - Forbidden"
144            emd.description = "You do not have permission to use this command."
145            emd.set_footer(text=f"Error type: {type(error).__name__}")
146            await ctx.followup.send(embed=emd, ephemeral=True)
147            return
148
149        if isinstance(error, exceptions.TicketsPlusCommandError):
150            emd.title = "Tickets+ Error: 400 - Bad Request"
151            emd.description = str(error)
152            emd.set_footer(text=f"Error type: {type(error).__name__}")
153            await ctx.followup.send(embed=emd, ephemeral=True)
154            return  # We don't want to log this error.
155
156        if isinstance(error, app_commands.CommandInvokeError):
157            underlying_error = error.original
158
159            if isinstance(underlying_error, exc.SQLAlchemyError):
160                logging.error("An error occurred while accessing the database:", exc_info=underlying_error)
161                emd.title = "Tickets+ Error: 500 - Database Error"
162                emd.description = ("An error occurred while accessing the database.\n"
163                                   "Please try again later. If this error persists, "
164                                   "please report it.\n"
165                                   "You can get the link to GitHub and support server by "
166                                   "using the /version command.")
167                emd.set_footer(text=f"Error type: {type(underlying_error)}")
168                await ctx.followup.send(embed=emd, ephemeral=True)
169                return
170
171        logging.error("An unhandled error occurred while executing a command:", exc_info=error)
172        emd.set_footer(text=f"Error type: {type(error).__name__}")
173        await ctx.followup.send(embed=emd, ephemeral=True)

Handles errors from app commands globally.

This function is automatically called when an error is raised from an app command. This is used to handle and respond to errors. To not leave the user confused.

Arguments:
  • ctx: The interaction that raised the error.
  • error: The error that was raised.
async def setup(bot_instance: tickets_plus.bot.TicketsPlusBot) -> None:
176async def setup(bot_instance: bot.TicketsPlusBot) -> None:
177    """Sets up the error handler.
178
179    This function is called when the cog is loaded.
180    It is used to add the cog to the bot.
181
182    Args:
183        bot_instance: The bot instance.
184    """
185    await bot_instance.add_cog(ErrorHandling(bot_instance))

Sets up the error handler.

This function is called when the cog is loaded. It is used to add the cog to the bot.

Arguments:
  • bot_instance: The bot instance.