mirror of
https://github.com/allthingslinux/tux.git
synced 2024-10-02 16:43:12 +00:00
feat(ctx_error_handler.py): add new error handler for command context
refactor(error_handler.py): update error handling logic to improve error messages and logging fix(error_handler.py): handle app command errors separately to avoid conflicts style(error_handler.py): improve code readability and maintainability by simplifying error handling logic feat(test_error_handler.py): add new file for testing error handling in discord.py This new file contains a Cog class with commands to raise every type of discord.py error for testing purposes. feat: add error handling commands to ErrorTests cog in discord bot This commit adds a series of commands to the ErrorTests cog in the discord bot. Each command raises a specific exception when invoked, allowing for testing and debugging of error handling mechanisms. The exceptions covered include DisabledCommand, CommandInvokeError, CommandOnCooldown, MaxConcurrencyReached, various ExtensionErrors, ClientException, and CommandRegistrationError. refactor(error_handler.py): rename ErrorHandler to UnifiedErrorHandler for clarity feat(error_handler.py): add support for traditional command errors and app command errors fix(error_handler.py): improve error message for better user experience style(error_handler.py): refactor code for better readability and maintainability
This commit is contained in:
parent
aa11ece7bc
commit
5e67bc6306
4 changed files with 521 additions and 142 deletions
63
.archive/ctx_error_handler.py
Normal file
63
.archive/ctx_error_handler.py
Normal file
|
@ -0,0 +1,63 @@
|
|||
import contextlib
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
|
||||
class ContextCommandErrorHandler(commands.Cog):
|
||||
def __init__(self, bot: commands.Bot):
|
||||
self.bot = bot
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_command_error(self, ctx: commands.Context[commands.Bot], error: Exception) -> None:
|
||||
"""
|
||||
Handles errors that occur during command execution.
|
||||
|
||||
Args:
|
||||
ctx: The context in which the error occurred.
|
||||
error: The exception that was raised.
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
Raises:
|
||||
None
|
||||
"""
|
||||
|
||||
# If the command has its own error handler, or the cog has its own error handler, return
|
||||
if hasattr(ctx.command, "on_error") or (
|
||||
ctx.cog and ctx.cog._get_overridden_method(ctx.cog.cog_command_error) is not None
|
||||
):
|
||||
return
|
||||
|
||||
# Ignore these errors
|
||||
ignored = (commands.CommandNotFound,)
|
||||
|
||||
# Get the original exception if it exists
|
||||
error = getattr(error, "original", error)
|
||||
|
||||
# If the error is in the ignored tuple, return
|
||||
if isinstance(error, ignored):
|
||||
return
|
||||
|
||||
# If the command has been disabled, send a reply to the user
|
||||
if isinstance(error, commands.DisabledCommand):
|
||||
await ctx.send(f"{ctx.command} has been disabled.")
|
||||
|
||||
# Private message error
|
||||
elif isinstance(error, commands.NoPrivateMessage):
|
||||
with contextlib.suppress(discord.HTTPException):
|
||||
await ctx.author.send(f"{ctx.command} can not be used in Private Messages.")
|
||||
|
||||
# elif isinstance(error, commands.BadArgument):
|
||||
# if ctx.command and ctx.command.qualified_name == "tag list":
|
||||
# await ctx.send("I could not find that member. Please try again.")
|
||||
|
||||
else:
|
||||
traceback.print_exception(type(error), error, error.__traceback__, file=sys.stderr)
|
||||
|
||||
|
||||
async def setup(bot: commands.Bot):
|
||||
await bot.add_cog(ContextCommandErrorHandler(bot))
|
|
@ -1,132 +1,98 @@
|
|||
# utils/error_handler.py
|
||||
|
||||
from collections.abc import Callable, Coroutine
|
||||
import traceback
|
||||
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import (
|
||||
BotMissingPermissions,
|
||||
CommandNotFound,
|
||||
CommandOnCooldown,
|
||||
Context,
|
||||
MissingPermissions,
|
||||
MissingRequiredArgument,
|
||||
NotOwner,
|
||||
)
|
||||
from utils.tux_logger import TuxLogger
|
||||
from loguru import logger
|
||||
|
||||
logger = TuxLogger(__name__)
|
||||
|
||||
ErrorHandlerFunc = Callable[[Context, Exception], Coroutine[None, None, None]]
|
||||
# Custom error handling mappings and messages.
|
||||
error_map = {
|
||||
# app_commands
|
||||
app_commands.AppCommandError: "An error occurred: {error}",
|
||||
app_commands.CommandInvokeError: "A command invoke error occurred: {error}",
|
||||
app_commands.TransformerError: "A transformer error occurred: {error}",
|
||||
app_commands.MissingRole: "You are missing the role required to use this command.",
|
||||
app_commands.MissingAnyRole: "You are missing some roles required to use this command.",
|
||||
app_commands.MissingPermissions: "You are missing the required permissions to use this command.",
|
||||
app_commands.CheckFailure: "You are not allowed to use this command.",
|
||||
app_commands.CommandNotFound: "This command was not found.",
|
||||
app_commands.CommandOnCooldown: "This command is on cooldown. Try again in {error.retry_after:.2f} seconds.",
|
||||
app_commands.BotMissingPermissions: "The bot is missing the required permissions to use this command.",
|
||||
app_commands.CommandSignatureMismatch: "The command signature does not match: {error}",
|
||||
# commands
|
||||
commands.CommandError: "An error occurred: {error}",
|
||||
commands.CommandInvokeError: "A command invoke error occurred: {error}",
|
||||
commands.ConversionError: "An error occurred during conversion: {error}",
|
||||
commands.MissingRole: "You are missing the role required to use this command.",
|
||||
commands.MissingAnyRole: "You are missing some roles required to use this command.",
|
||||
commands.MissingPermissions: "You are missing the required permissions to use this command.",
|
||||
commands.CheckFailure: "You are not allowed to use this command.",
|
||||
commands.CommandNotFound: "This command was not found.",
|
||||
commands.CommandOnCooldown: "This command is on cooldown. Try again in {error.retry_after:.2f} seconds.",
|
||||
commands.BadArgument: "Invalid argument passed. Correct usage:\n```{ctx.command.usage}```",
|
||||
commands.MissingRequiredArgument: "Missing required argument. Correct usage:\n```{ctx.command.usage}```",
|
||||
commands.MissingRequiredAttachment: "Missing required attachment.",
|
||||
commands.NotOwner: "You are not the owner of this bot.",
|
||||
commands.BotMissingPermissions: "The bot is missing the required permissions to use this command.",
|
||||
}
|
||||
|
||||
|
||||
class ErrorHandler(commands.Cog):
|
||||
def __init__(self, bot):
|
||||
def __init__(self, bot: commands.Bot) -> None:
|
||||
self.bot = bot
|
||||
self.error_message = "An error occurred. Please try again later."
|
||||
bot.tree.error(self.dispatch_to_app_command_handler)
|
||||
|
||||
async def send_message_log_error(self, ctx, msg, error, error_type):
|
||||
"""
|
||||
Send a message to the context and log the error.
|
||||
"""
|
||||
await ctx.send(msg)
|
||||
logger.error(f"{error_type}: {error}")
|
||||
async def dispatch_to_app_command_handler(
|
||||
self, interaction: discord.Interaction, error: app_commands.AppCommandError
|
||||
):
|
||||
"""Dispatch command error to app_command_error event."""
|
||||
await self.on_app_command_error(interaction, error)
|
||||
|
||||
async def handle_command_not_found(self, ctx: Context, error):
|
||||
"""
|
||||
Handles the case when an invalid command is used.
|
||||
"""
|
||||
await self.send_message_log_error(
|
||||
ctx,
|
||||
f"I'm sorry, but I couldn't find the command: {ctx.message.content}. Please check your command and try again.",
|
||||
error,
|
||||
"CommandNotFound",
|
||||
)
|
||||
async def on_app_command_error(
|
||||
self, interaction: discord.Interaction, error: app_commands.AppCommandError
|
||||
):
|
||||
"""Handle app command errors."""
|
||||
error_message = error_map.get(type(error), self.error_message).format(error=error)
|
||||
|
||||
async def handle_missing_permissions(self, ctx: Context, error):
|
||||
"""
|
||||
Handles the case when a user does not have the necessary permissions
|
||||
to use a command.
|
||||
"""
|
||||
await self.send_message_log_error(
|
||||
ctx,
|
||||
"It seems you're missing the necessary permissions to perform this command.",
|
||||
f"User '{ctx.author.name}' lacks permission to use this command.",
|
||||
"MissingPermissions",
|
||||
)
|
||||
if interaction.response.is_done():
|
||||
await interaction.followup.send(error_message, ephemeral=True)
|
||||
else:
|
||||
await interaction.response.send_message(error_message, ephemeral=True)
|
||||
|
||||
async def handle_bot_missing_permissions(self, ctx: Context, error):
|
||||
"""
|
||||
Handles the case when the bot does not have the necessary permissions
|
||||
to execute a command.
|
||||
"""
|
||||
await self.send_message_log_error(
|
||||
ctx,
|
||||
"I'm sorry, but I don't have enough permissions to perform this command.",
|
||||
error,
|
||||
"BotMissingPermissions",
|
||||
)
|
||||
|
||||
async def handle_command_on_cooldown(self, ctx: Context, error):
|
||||
"""
|
||||
Handles the case when a user is on cooldown.
|
||||
"""
|
||||
await self.send_message_log_error(
|
||||
ctx,
|
||||
"You're on cooldown. Please wait a bit before using this command again.",
|
||||
error,
|
||||
"Cooldown",
|
||||
)
|
||||
|
||||
async def handle_missing_required_argument(self, ctx: Context, error):
|
||||
"""
|
||||
Handles the case when a user is missing a required argument.
|
||||
"""
|
||||
await self.send_message_log_error(
|
||||
ctx,
|
||||
"You're missing a required argument. Please check your command and try again.",
|
||||
error,
|
||||
"MissingRequiredArgument",
|
||||
)
|
||||
|
||||
async def handle_not_owner(self, ctx: Context, error):
|
||||
"""
|
||||
Handles the case when a user is not the owner of the bot.
|
||||
"""
|
||||
await self.send_message_log_error(
|
||||
ctx, "You're not the owner of this bot.", error, "NotOwner"
|
||||
)
|
||||
|
||||
async def handle_other_errors(self, ctx: Context, error):
|
||||
"""
|
||||
Handles all other types of errors.
|
||||
"""
|
||||
logger.exception(f"Unhandled exception in command {ctx.command}: {error}")
|
||||
if type(error) not in error_map:
|
||||
self.log_error_traceback(error)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_command_error(self, ctx: Context, error):
|
||||
"""Called when an error is raised while invoking a command.
|
||||
async def on_command_error(
|
||||
self, ctx: commands.Context[commands.Bot], error: commands.CommandError
|
||||
):
|
||||
"""Handle traditional command errors."""
|
||||
if isinstance(
|
||||
error,
|
||||
commands.CommandNotFound
|
||||
| commands.UnexpectedQuoteError
|
||||
| commands.InvalidEndOfQuotedStringError
|
||||
| commands.CheckFailure,
|
||||
):
|
||||
return # Ignore these specific errors.
|
||||
|
||||
Args:
|
||||
ctx (Context): The invocation context.
|
||||
error (Exception): The error that was raised.
|
||||
error_message = error_map.get(type(error), self.error_message).format(error=error, ctx=ctx)
|
||||
|
||||
Note:
|
||||
This function is called when an error is raised while invoking a command. If the error is not
|
||||
handled, it will be propagated to the global error handler.
|
||||
await ctx.send(
|
||||
content=error_message,
|
||||
ephemeral=False,
|
||||
)
|
||||
|
||||
https://discordpy.readthedocs.io/en/stable/ext/commands/api.html#discord.on_command_error
|
||||
"""
|
||||
error_handlers: dict[type[commands.CommandError], ErrorHandlerFunc] = {
|
||||
CommandNotFound: self.handle_command_not_found,
|
||||
MissingPermissions: self.handle_missing_permissions,
|
||||
BotMissingPermissions: self.handle_bot_missing_permissions,
|
||||
CommandOnCooldown: self.handle_command_on_cooldown,
|
||||
MissingRequiredArgument: self.handle_missing_required_argument,
|
||||
NotOwner: self.handle_not_owner,
|
||||
}
|
||||
if type(error) not in error_map:
|
||||
self.log_error_traceback(error)
|
||||
|
||||
handler = error_handlers.get(type(error), self.handle_other_errors)
|
||||
await handler(ctx, error)
|
||||
def log_error_traceback(self, error: Exception):
|
||||
"""Helper method to log error traceback."""
|
||||
trace = traceback.format_exception(None, error, error.__traceback__)
|
||||
formatted_trace = "".join(trace)
|
||||
logger.error(f"Error: {error}\nTraceback:\n{formatted_trace}")
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
async def setup(bot: commands.Bot) -> None:
|
||||
await bot.add_cog(ErrorHandler(bot))
|
||||
|
|
348
.archive/test_error_handler.py
Normal file
348
.archive/test_error_handler.py
Normal file
|
@ -0,0 +1,348 @@
|
|||
|
||||
"""A Cog dedicated to raise every discord.py error on command with the purpose of testing error handling."""
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context, Bot, Cog
|
||||
|
||||
""" Command Errors:
|
||||
https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#exception-hierarchy
|
||||
|
||||
ConversionError
|
||||
UserInputError
|
||||
MissingRequiredArgument
|
||||
TooManyArguments
|
||||
BadArgument
|
||||
MessageNotFound
|
||||
MemberNotFound
|
||||
UserNotFound
|
||||
ChannelNotFound
|
||||
ChannelNotReadable
|
||||
BadColourArgument
|
||||
RoleNotFound
|
||||
BadInviteArgument
|
||||
EmojiNotFound
|
||||
PartialEmojiConversionFailure
|
||||
BadBoolArgument
|
||||
BadUnionArgument
|
||||
ArgumentParsingError
|
||||
CommandNotFound
|
||||
CheckFailure
|
||||
BotMissingPermissions
|
||||
BotMissingRole
|
||||
BotMissingAnyRole
|
||||
MissingPermission
|
||||
MissingRole
|
||||
MissingAnyRole
|
||||
CheckAnyFailure
|
||||
NotOwner
|
||||
NoPrivateMessage
|
||||
PrivateMessageOnly
|
||||
NSFWChannelRequired
|
||||
DisabledCommand
|
||||
CommandInvokeError
|
||||
CommandOnCooldown
|
||||
MaxConcurrencyReached
|
||||
"""
|
||||
|
||||
|
||||
class ErrorTests(Cog):
|
||||
"""For Testing Error Handling"""
|
||||
|
||||
def __init__(self, bot: Bot):
|
||||
self.bot = bot
|
||||
|
||||
@discord.ext.commands.group(name="error", aliases=["err"])
|
||||
async def test_error(self, ctx: Context):
|
||||
"""Group of commands to raise errors for every type of discord.py error"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
# Send the help command for this group
|
||||
await ctx.send_help(ctx.command)
|
||||
|
||||
@test_error.command(name="DiscordException")
|
||||
async def discord_exception(self, ctx: Context):
|
||||
"""Base exception class for discord.py"""
|
||||
# https://discordpy.readthedocs.io/en/latest/api.html#discord.DiscordException
|
||||
raise discord.DiscordException()
|
||||
|
||||
@test_error.command(name="CommandError")
|
||||
async def CommandError(self, ctx, message=None, *args: object):
|
||||
"""The base exception type for all command related errors."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.CommandError
|
||||
raise commands.CommandError(message, *args)
|
||||
|
||||
@test_error.command(name="ConversionError")
|
||||
async def ConversionError(self, ctx, converter, original):
|
||||
"""Exception raised when a Converter class raises non-CommandError."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.ConversionError
|
||||
raise commands.ConversionError(converter, original)
|
||||
|
||||
@test_error.command(name="UserInputError")
|
||||
async def user_input_error(self, ctx: Context):
|
||||
"""The base exception type for errors that involve errors regarding user input."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.UserInputError
|
||||
raise commands.UserInputError()
|
||||
|
||||
@test_error.command(name="MissingRequiredArgument")
|
||||
async def missing_required_argument(self, ctx: Context):
|
||||
"""Exception raised when parsing a command and a parameter that is required is not encountered."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.MissingRequiredArgument
|
||||
raise commands.MissingRequiredArgument()
|
||||
|
||||
@test_error.command(name="TooManyArguments")
|
||||
async def too_many_arguments(self, ctx: Context):
|
||||
"""Exception raised when the command was passed too many arguments and its Command.ignore_extra attribute was not set to True."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.TooManyArguments
|
||||
raise commands.TooManyArguments()
|
||||
|
||||
@test_error.command(name="BadArgument")
|
||||
async def bad_argument(self, ctx: Context):
|
||||
"""Exception raised when a parsing or conversion failure is encountered on an argument to pass into a command."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.BadArgument
|
||||
raise commands.BadArgument()
|
||||
|
||||
@test_error.command(name="MessageNotFound")
|
||||
async def message_not_found(self, ctx: Context):
|
||||
"""Exception raised when the message provided was not found in the channel."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.MessageNotFound
|
||||
raise commands.MessageNotFound()
|
||||
|
||||
@test_error.command(name="MemberNotFound")
|
||||
async def member_not_found(self, ctx: Context):
|
||||
"""Exception raised when the member provided was not found in the bot’s cache."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.MemberNotFound
|
||||
raise commands.MemberNotFound()
|
||||
|
||||
@test_error.command(name="UserNotFound")
|
||||
async def user_not_found(self, ctx: Context):
|
||||
"""Exception raised when the user provided was not found in the bot’s cache."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.UserNotFound
|
||||
raise commands.UserNotFound()
|
||||
|
||||
@test_error.command(name="ChannelNotFound")
|
||||
async def channel_not_found(self, ctx: Context):
|
||||
"""Exception raised when the bot can not find the channel."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.ChannelNotFound
|
||||
raise commands.ChannelNotFound()
|
||||
|
||||
@test_error.command(name="ChannelNotReadable")
|
||||
async def channel_not_readable(self, ctx: Context):
|
||||
"""Exception raised when the bot does not have permission to read messages in the channel."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.ChannelNotReadable
|
||||
raise commands.ChannelNotReadable()
|
||||
|
||||
@test_error.command(name="BadColourArgument")
|
||||
async def bad_colour_argument(self, ctx: Context):
|
||||
"""Exception raised when the colour is not valid."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.BadColourArgument
|
||||
raise commands.BadColourArgument()
|
||||
|
||||
@test_error.command(name="RoleNotFound")
|
||||
async def role_not_found(self, ctx: Context):
|
||||
"""Exception raised when the bot can not find the role."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.RoleNotFound
|
||||
raise commands.RoleNotFound()
|
||||
|
||||
@test_error.command(name="BadInviteArgument")
|
||||
async def bad_invite_argument(self, ctx: Context):
|
||||
"""Exception raised when the invite is invalid or expired."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.BadInviteArgument
|
||||
raise commands.BadInviteArgument()
|
||||
|
||||
@test_error.command(name="EmojiNotFound")
|
||||
async def emoji_not_found(self, ctx: Context):
|
||||
"""Exception raised when the bot can not find the emoji."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.EmojiNotFound
|
||||
raise commands.EmojiNotFound()
|
||||
|
||||
@test_error.command(name="PartialEmojiConversionFailure")
|
||||
async def partial_emoji_conversion_failure(self, ctx: Context):
|
||||
"""Exception raised when the emoji provided does not match the correct format."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.PartialEmojiConversionFailure
|
||||
raise commands.PartialEmojiConversionFailure()
|
||||
|
||||
@test_error.command(name="BadUnionArgument")
|
||||
async def bad_union_argument(self, ctx: Context):
|
||||
"""Exception raised when a typing.Union converter fails for all its associated types."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.BadUnionArgument
|
||||
raise commands.BadUnionArgument()
|
||||
|
||||
@test_error.command(name="ArgumentParsingError")
|
||||
async def argument_parsing_error(self, ctx: Context):
|
||||
"""An exception raised when the parser fails to parse a user’s input."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.ArgumentParsingError
|
||||
raise commands.ArgumentParsingError()
|
||||
|
||||
@test_error.command(name="UnexpectedQuoteError")
|
||||
async def unexpected_quote_error(self, ctx: Context):
|
||||
"""An exception raised when the parser encounters a quote mark inside a non-quoted string."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.UnexpectedQuoteError
|
||||
raise commands.UnexpectedQuoteError()
|
||||
|
||||
@test_error.command(name="InvalidEndOfQuotedStringError")
|
||||
async def invalid_end_of_quoted_string_error(self, ctx: Context):
|
||||
"""An exception raised when a space is expected after the closing quote in a string but a different character is found."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.InvalidEndOfQuotedStringError
|
||||
raise commands.InvalidEndOfQuotedStringError()
|
||||
|
||||
@test_error.command(name="ExpectedClosingQuoteError")
|
||||
async def expected_closing_quote_error(self, ctx: Context):
|
||||
"""An exception raised when a quote character is expected but not found."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.ExpectedClosingQuoteError
|
||||
raise commands.ExpectedClosingQuoteError()
|
||||
|
||||
@test_error.command(name="CommandNotFound")
|
||||
async def command_not_found(self, ctx: Context):
|
||||
"""Exception raised when a command is attempted to be invoked but no command under that name is found."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.CommandNotFound
|
||||
raise commands.CommandNotFound()
|
||||
|
||||
@test_error.command(name="CheckFailure")
|
||||
async def check_failure(self, ctx: Context):
|
||||
"""Exception raised when the predicates in Command.checks have failed."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.CheckFailure
|
||||
raise commands.CheckFailure()
|
||||
|
||||
@test_error.command(name="CheckAnyFailure")
|
||||
async def check_any_failure(self, ctx: Context):
|
||||
"""Exception raised when all predicates in check_any() fail."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.CheckAnyFailure
|
||||
raise commands.CheckAnyFailure()
|
||||
|
||||
@test_error.command(name="PrivateMessageOnly")
|
||||
async def private_message_only(self, ctx: Context):
|
||||
"""Exception raised when an operation does not work outside of private message contexts."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.PrivateMessageOnly
|
||||
raise commands.PrivateMessageOnly()
|
||||
|
||||
@test_error.command(name="NoPrivateMessage")
|
||||
async def no_private_message(self, ctx: Context):
|
||||
"""Exception raised when an operation does not work in private message contexts."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.NoPrivateMessage
|
||||
raise commands.NoPrivateMessage()
|
||||
|
||||
@test_error.command(name="NotOwner")
|
||||
async def not_owner(self, ctx: Context):
|
||||
"""Exception raised when the message author is not the owner of the bot."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.NotOwner
|
||||
raise commands.NotOwner()
|
||||
|
||||
@test_error.command(name="MissingPermissions")
|
||||
async def missing_permissions(self, ctx: Context):
|
||||
"""Exception raised when the command invoker lacks permissions to run a command."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.MissingPermissions
|
||||
raise commands.MissingPermissions()
|
||||
|
||||
@test_error.command(name="BotMissingPermissions")
|
||||
async def _bot_missing_permissions(self, ctx: Context):
|
||||
"""Exception raised when the bot’s member lacks permissions to run a command."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.BotMissingPermissions
|
||||
raise commands.BotMissingPermissions()
|
||||
|
||||
@test_error.command(name="MissingRole")
|
||||
async def missing_role(self, ctx: Context):
|
||||
"""Exception raised when the command invoker lacks a role to run a command."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.MissingRole
|
||||
raise commands.MissingRole()
|
||||
|
||||
@test_error.command(name="BotMissingRole")
|
||||
async def _bot_missing_role(self, ctx: Context):
|
||||
"""Exception raised when the bot’s member lacks a role to run a command."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.BotMissingRole
|
||||
raise commands.BotMissingRole()
|
||||
|
||||
@test_error.command(name="MissingAnyRole")
|
||||
async def missing_any_role(self, ctx: Context):
|
||||
"""Exception raised when the command invoker lacks any of the roles specified to run a command."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.MissingAnyRole
|
||||
raise commands.MissingAnyRole()
|
||||
|
||||
@test_error.command(name="BotMissingAnyRole")
|
||||
async def _bot_missing_any_role(self, ctx: Context):
|
||||
"""Exception raised when the bot’s member lacks any of the roles specified to run a command."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.BotMissingAnyRole
|
||||
raise commands.BotMissingAnyRole()
|
||||
|
||||
@test_error.command(name="NSFWChannelRequired")
|
||||
async def nsfw_hannel_required(self, ctx: Context):
|
||||
"""Exception raised when a channel does not have the required NSFW setting."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.NSFWChannelRequired
|
||||
raise commands.NSFWChannelRequired()
|
||||
|
||||
@test_error.command(name="DisabledCommand")
|
||||
async def disabled_command(self, ctx: Context):
|
||||
"""Exception raised when the command being invoked is disabled."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.DisabledCommand
|
||||
raise commands.DisabledCommand()
|
||||
|
||||
@test_error.command(name="CommandInvokeError")
|
||||
async def command_invoke_error(self, ctx: Context):
|
||||
"""Exception raised when the command being invoked raised an exception."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.CommandInvokeError
|
||||
raise commands.CommandInvokeError()
|
||||
|
||||
@test_error.command(name="CommandOnCooldown")
|
||||
async def command_on_cooldown(self, ctx: Context):
|
||||
"""Exception raised when the command being invoked is on cooldown."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.CommandOnCooldown
|
||||
raise commands.CommandOnCooldown()
|
||||
|
||||
@test_error.command(name="MaxConcurrencyReached")
|
||||
async def max_concurrency_reached(self, ctx: Context):
|
||||
"""Exception raised when the command being invoked has reached its maximum concurrency."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.MaxConcurrencyReached
|
||||
raise commands.MaxConcurrencyReached()
|
||||
|
||||
@test_error.command(name="ExtensionError")
|
||||
async def extension_error(self, ctx: Context):
|
||||
"""Base exception for extension related errors."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.ExtensionError
|
||||
raise commands.ExtensionError()
|
||||
|
||||
@test_error.command(name="ExtensionAlreadyLoaded")
|
||||
async def extension_already_loaded(self, ctx: Context):
|
||||
"""An exception raised when an extension has already been loaded."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.ExtensionAlreadyLoaded
|
||||
raise commands.ExtensionAlreadyLoaded()
|
||||
|
||||
@test_error.command(name="ExtensionNotLoaded")
|
||||
async def extension_not_loaded(self, ctx: Context):
|
||||
"""An exception raised when an extension was not loaded."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.ExtensionNotLoaded
|
||||
raise commands.ExtensionNotLoaded()
|
||||
|
||||
@test_error.command(name="NoEntryPointError")
|
||||
async def no_entry_point_error(self, ctx: Context):
|
||||
"""An exception raised when an extension does not have a setup entry point function."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.NoEntryPointError
|
||||
raise commands.NoEntryPointError()
|
||||
|
||||
@test_error.command(name="ExtensionFailed")
|
||||
async def extension_failed(self, ctx: Context):
|
||||
"""An exception raised when an extension failed to load during execution of the module or setup entry point."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.ExtensionFailed
|
||||
raise commands.ExtensionFailed()
|
||||
|
||||
@test_error.command(name="ExtensionNotFound")
|
||||
async def extension_not_found(self, ctx: Context):
|
||||
"""An exception raised when an extension is not found."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.ExtensionNotFound
|
||||
raise commands.ExtensionNotFound()
|
||||
|
||||
@test_error.command(name="ClientException")
|
||||
async def client_exception(self, ctx: Context):
|
||||
"""Exception that’s thrown when an operation in the Client fails."""
|
||||
# https://discordpy.readthedocs.io/en/latest/api.html#discord.ClientException
|
||||
raise discord.ClientException()
|
||||
|
||||
@test_error.command(name="CommandRegistrationError")
|
||||
async def command_registration_error(self, ctx: Context):
|
||||
"""An exception raised when the command can’t be added because the name is already taken by a different command."""
|
||||
# https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.CommandRegistrationError
|
||||
raise commands.CommandRegistrationError()
|
||||
|
||||
|
||||
async def setup(bot: Bot) -> None:
|
||||
"""Load the ErrorTests cog."""
|
||||
await bot.add_cog(ErrorTests(bot))
|
||||
print("Cog loaded: ErrorTests")
|
|
@ -5,8 +5,7 @@ from discord import app_commands
|
|||
from discord.ext import commands
|
||||
from loguru import logger
|
||||
|
||||
# Custom error handling mappings and messages.
|
||||
error_map = {
|
||||
error_map: dict[type[Exception], str] = {
|
||||
# app_commands
|
||||
app_commands.AppCommandError: "An error occurred: {error}",
|
||||
app_commands.CommandInvokeError: "A command invoke error occurred: {error}",
|
||||
|
@ -37,29 +36,29 @@ error_map = {
|
|||
}
|
||||
|
||||
|
||||
class ErrorHandler(commands.Cog):
|
||||
def __init__(self, bot: commands.Bot) -> None:
|
||||
class UnifiedErrorHandler(commands.Cog):
|
||||
def __init__(self, bot: commands.Bot):
|
||||
self.bot = bot
|
||||
self.error_message = "An error occurred. Please try again later."
|
||||
bot.tree.error(self.dispatch_to_app_command_handler)
|
||||
self.error_message = "An error occurred. Please try again later or contact support."
|
||||
# Attach the error handler if using app commands
|
||||
if hasattr(bot, "tree"):
|
||||
bot.tree.error(self.dispatch_to_app_command_handler)
|
||||
|
||||
async def dispatch_to_app_command_handler(
|
||||
self, interaction: discord.Interaction, error: app_commands.AppCommandError
|
||||
):
|
||||
"""Dispatch command error to app_command_error event."""
|
||||
await self.on_app_command_error(interaction, error)
|
||||
"""Dispatch command error to appropriate handler."""
|
||||
await self.handle_app_command_error(interaction, error)
|
||||
|
||||
async def on_app_command_error(
|
||||
async def handle_app_command_error(
|
||||
self, interaction: discord.Interaction, error: app_commands.AppCommandError
|
||||
):
|
||||
"""Handle app command errors."""
|
||||
"""Handle errors for app commands."""
|
||||
error_message = error_map.get(type(error), self.error_message).format(error=error)
|
||||
|
||||
if interaction.response.is_done():
|
||||
await interaction.followup.send(error_message, ephemeral=True)
|
||||
else:
|
||||
await interaction.response.send_message(error_message, ephemeral=True)
|
||||
|
||||
if type(error) not in error_map:
|
||||
self.log_error_traceback(error)
|
||||
|
||||
|
@ -67,26 +66,29 @@ class ErrorHandler(commands.Cog):
|
|||
async def on_command_error(
|
||||
self, ctx: commands.Context[commands.Bot], error: commands.CommandError
|
||||
):
|
||||
"""Handle traditional command errors."""
|
||||
if isinstance(
|
||||
error,
|
||||
commands.CommandNotFound
|
||||
| commands.UnexpectedQuoteError
|
||||
| commands.InvalidEndOfQuotedStringError
|
||||
| commands.CheckFailure,
|
||||
"""Handle errors for traditional commands."""
|
||||
if (
|
||||
hasattr(ctx.command, "on_error")
|
||||
or ctx.cog
|
||||
and ctx.cog._get_overridden_method(ctx.cog.cog_command_error) is not None
|
||||
):
|
||||
return # Ignore these specific errors.
|
||||
|
||||
error_message = error_map.get(type(error), self.error_message).format(error=error, ctx=ctx)
|
||||
|
||||
await ctx.send(
|
||||
content=error_message,
|
||||
ephemeral=False,
|
||||
)
|
||||
|
||||
return
|
||||
if isinstance(error, commands.CommandNotFound):
|
||||
return # Optionally, provide feedback for unknown commands.
|
||||
error = getattr(error, "original", error)
|
||||
message: str = self.get_error_message(error, ctx)
|
||||
await ctx.send(content=message, ephemeral=False)
|
||||
if type(error) not in error_map:
|
||||
self.log_error_traceback(error)
|
||||
|
||||
def get_error_message(
|
||||
self, error: Exception, ctx: commands.Context[commands.Bot] | None = None
|
||||
) -> str:
|
||||
"""Generate an error message from the error map."""
|
||||
if ctx:
|
||||
return error_map.get(type(error), self.error_message).format(error=error, ctx=ctx)
|
||||
return error_map.get(type(error), self.error_message).format(error=error)
|
||||
|
||||
def log_error_traceback(self, error: Exception):
|
||||
"""Helper method to log error traceback."""
|
||||
trace = traceback.format_exception(None, error, error.__traceback__)
|
||||
|
@ -94,5 +96,5 @@ class ErrorHandler(commands.Cog):
|
|||
logger.error(f"Error: {error}\nTraceback:\n{formatted_trace}")
|
||||
|
||||
|
||||
async def setup(bot: commands.Bot) -> None:
|
||||
await bot.add_cog(ErrorHandler(bot))
|
||||
async def setup(bot: commands.Bot):
|
||||
await bot.add_cog(UnifiedErrorHandler(bot))
|
||||
|
|
Loading…
Reference in a new issue