Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/error handler #77

Merged
merged 14 commits into from
Sep 29, 2021
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 61 additions & 50 deletions modmail/extensions/utils/error_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ def __init__(self, bot: ModmailBot):
self.bot = bot

@staticmethod
def error_embed(message: str, title: str = None) -> discord.Embed:
def error_embed(title: str, message: str) -> discord.Embed:
"""Create an error embed with an error colour and reason and return it."""
return discord.Embed(message, colour=ERROR_COLOUR, title=title or "Error Occurred")
return discord.Embed(title=title, description=message, colour=ERROR_COLOUR)

@staticmethod
def get_title_from_name(error: typing.Union[Exception, str]) -> str:
Expand Down Expand Up @@ -60,54 +60,64 @@ async def handle_user_input_error(
"""Handling deferred from main error handler to handle UserInputErrors."""
if reset_cooldown:
self._reset_command_cooldown(ctx)
embed = None
msg = None
title = "User Input Error"
if isinstance(error, commands.BadUnionArgument):
msg = self.get_title_from_name(str(error))
title = self.get_title_from_name(error)
else:
title = self.get_title_from_name(error)
return embed or self.error_embed(msg or str(error), title=title)
title = self.get_title_from_name(error)
return self.error_embed(title, msg or str(error))

async def handle_bot_missing_perms(
self, ctx: commands.Context, error: commands.BotMissingPermissions
) -> bool:
"""Handles bot missing permissing by dming the user if they have a permission which may be able to fix this.""" # noqa: E501
embed = self.error_embed(str(error))
try:
embed = self.error_embed("Permissions Failure", str(error))
bot_perms = ctx.channel.permissions_for(ctx.me)
responded = None
if bot_perms >= discord.Permissions(send_messages=True, embed_links=True):
await ctx.send(embeds=[embed])
except discord.Forbidden:
logger.error(f"Unable to send an error message to {ctx.channel!s}")
if ANY_DEV_MODE:
# non-general permissions
perms = discord.Permissions(
administrator=True,
manage_threads=True,
manage_roles=True,
manage_channels=True,
responded = True
elif bot_perms >= discord.Permissions(send_messages=True):
# make a message as similar to the embed, using as few permissions as possible
# this is the only place we send a standard message instead of an embed, so no helper methods
onerandomusername marked this conversation as resolved.
Show resolved Hide resolved
await ctx.send(
"**Permissions Failure**\n\n"
"I am missing the permissions required to properly execute your command."
)
# intentionally not setting responded to True, since we want to attempt to dm the user
logger.warning(
f"Missing partial required permissions for {ctx.channel}. "
"I am able to send messages, but not embeds."
)
else:
logger.error(f"Unable to send an error message to channel {ctx.channel}")

if responded is not True and ANY_DEV_MODE:
onerandomusername marked this conversation as resolved.
Show resolved Hide resolved
# non-general permissions
perms = discord.Permissions(
administrator=True,
manage_threads=True,
manage_roles=True,
manage_channels=True,
)
if perms.value & ctx.channel.permissions_for(ctx.author).value:
logger.info(
f"Attempting to dm {ctx.author} since they have a permission which may be able "
"to give the bot send message permissions."
)
if perms.value & ctx.channel.permissions_for(ctx.author).value:
logger.info(
f"Attempting to dm {ctx.author} since they have a permission which may be able "
"to give the bot send message permissions."
)
try:
await ctx.author.send(embeds=[embed])
except discord.Forbidden:
logger.notice("Also encountered an error when trying to reply in dms.")
return False
try:
await ctx.author.send(embeds=[embed])
except discord.Forbidden:
logger.notice("Also encountered an error when trying to reply in dms.")
return False
return True

async def handle_check_failure(
self, ctx: commands.Context, error: commands.CheckFailure
) -> typing.Optional[discord.Embed]:
"""Handle CheckFailures seperately given that there are many of them."""
title = "Check Failure"
msg = None
if isinstance(error, commands.CheckAnyFailure):
title = self.get_title_from_name(error.checks[-1])
msg = str(error)
elif isinstance(error, commands.PrivateMessageOnly):
title = "DMs Only"
elif isinstance(error, commands.NoPrivateMessage):
Expand All @@ -118,14 +128,14 @@ async def handle_check_failure(
return None
else:
title = self.get_title_from_name(error)
embed = self.error_embed(msg or str(error), title=title)
embed = self.error_embed(title, str(error))
return embed

@ModmailCog.listener()
async def on_command_error(self, ctx: commands.Context, error: commands.CommandError) -> None:
"""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.")
logging.debug(f"Command {ctx.command} had its error already handled locally, ignoring.")
return

if isinstance(error, commands.CommandNotFound):
Expand All @@ -137,8 +147,6 @@ async def on_command_error(self, ctx: commands.Context, error: commands.CommandE

logger.trace(error)

title = None
msg = None
embed: typing.Optional[discord.Embed] = None
should_respond = True

Expand All @@ -150,10 +158,7 @@ async def on_command_error(self, ctx: commands.Context, error: commands.CommandE
if embed is None:
should_respond = False
elif isinstance(error, commands.ConversionError):
# s = object()
# error.converter.convert.__annotations__.get("return", s)
# embed = error
...
pass
elif isinstance(error, commands.DisabledCommand):
logger.debug("")
if ctx.command.hidden:
Expand All @@ -162,30 +167,36 @@ async def on_command_error(self, ctx: commands.Context, error: commands.CommandE
msg = f"Command `{ctx.invoked_with}` is disabled."
if reason := ctx.command.extras.get("disabled_reason", None):
msg += f"\nReason: {reason}"
embed = self.error_embed(msg, title="Command Disabled")
embed = self.error_embed("Command Disabled", msg or str(error))
onerandomusername marked this conversation as resolved.
Show resolved Hide resolved

elif isinstance(error, commands.CommandInvokeError):
# generic error
logger.error(f'Error occurred in command "{ctx.command}".', exc_info=error.original)
# todo: this should log somewhere else since this is a bot bug.
embed = self.error_embed(
"Oops! Something went wrong internally in the command you were trying to execute. "
"Please report this error and what you were trying to do to the developers."
)
if isinstance(error.original, discord.Forbidden):
logger.warn(f"Permissions error occurred in {ctx.command}.")
await self.handle_bot_missing_perms(ctx, error.original)
return
should_respond = False
else:
# generic error
logger.error(f'Error occurred in command "{ctx.command}".', exc_info=error.original)
# todo: this should log somewhere else since this is a bot bug.
embed = self.error_embed(
"Error Occurred",
"Oops! Something went wrong internally in the command you were trying to execute. "
"Please report this error and what you were trying to do to the developers.",
)
bast0006 marked this conversation as resolved.
Show resolved Hide resolved

# TODO: this has a fundamental problem with any BotMissingPermissions error
# if the issue is the bot does not have permissions to send embeds or send messages...
# yeah, problematic.

if not should_respond:
logger.debug("Not responding to error since should_respond is falsey.")
logger.debug(
"Not responding to error since should_respond is falsey because either "
"the embed has already been sent or belongs to a hidden command and thus should be hidden."
)
return

if embed is None:
embed = self.error_embed(msg or str(error), title=title or self.get_title_from_name(error))
embed = self.error_embed(self.get_title_from_name(error), str(error))

await ctx.send(embeds=[embed])

Expand Down