fix(core): Handle rendering errors.

This commit is contained in:
2023-09-19 22:59:01 +03:00
parent c63027f20e
commit 17683a7d96
7 changed files with 83 additions and 47 deletions

Submodule src/gui updated: b781f7f9f2...ba9ace6ced

View File

@@ -12,6 +12,7 @@ from aiohttp import ClientSession
from data import Database
from utils.lib import tabulate
from gui.errors import RenderingException
from .config import Conf
from .logger import logging_context, log_context, log_action_stack, log_wrap, set_logging_context
@@ -204,13 +205,23 @@ class LionBot(Bot):
pass
except asyncio.TimeoutError:
pass
except RenderingException as e:
logger.info(f"Command failed due to RenderingException: {repr(e)}")
embed = self.tree.rendersplat(e)
try:
await ctx.error_reply(embed=embed)
except discord.HTTPException:
pass
except Exception as e:
logger.exception(
f"Caught an unknown CommandInvokeError while executing: {cmd_str}",
extra={'action': 'BotError', 'with_ctx': True}
)
error_embed = discord.Embed(title="Something went wrong!")
error_embed = discord.Embed(
title="Something went wrong!",
colour=discord.Colour.dark_red()
)
error_embed.description = (
"An unexpected error occurred while processing your command!\n"
"Our development team has been notified, and the issue will be addressed soon.\n"
@@ -246,7 +257,7 @@ class LionBot(Bot):
try:
await ctx.error_reply(embed=error_embed)
except Exception:
except discord.HTTPException:
pass
finally:
exception.original = HandledException(exception.original)

View File

@@ -8,9 +8,11 @@ from discord.enums import InteractionType
from discord.app_commands.namespace import Namespace
from utils.lib import tabulate
from gui.errors import RenderingException
from .logger import logging_context, set_logging_context, log_wrap, log_action_stack
from .errors import SafeCancellation
from .config import conf
logger = logging.getLogger(__name__)
@@ -29,17 +31,36 @@ class LionTree(CommandTree):
except SafeCancellation:
# Assume this has already been handled
pass
except RenderingException as e:
logger.info(f"Tree interaction failed due to rendering exception: {repr(e)}")
embed = self.rendersplat(e)
await self.error_reply(interaction, embed)
except Exception:
logger.exception(f"Unhandled exception in interaction: {interaction}", extra={'action': 'TreeError'})
if not interaction.is_expired():
splat = self.bugsplat(interaction, error)
try:
if interaction.response.is_done():
await interaction.followup.send(embed=splat, ephemeral=True)
else:
await interaction.response.send_message(embed=splat, ephemeral=True)
except discord.HTTPException:
pass
embed = self.bugsplat(interaction, error)
await self.error_reply(interaction, embed)
async def error_reply(self, interaction, embed):
if not interaction.is_expired():
try:
if interaction.response.is_done():
await interaction.followup.send(embed=embed, ephemeral=True)
else:
await interaction.response.send_message(embed=embed, ephemeral=True)
except discord.HTTPException:
pass
def rendersplat(self, e: RenderingException):
embed = discord.Embed(
title="Resource Currently Unavailable!",
description=(
"Sorry, the graphics service is currently unavailable!\n"
"Please try again in a few minutes.\n"
"If the error persists, please contact our [support team]({link})"
).format(link=conf.bot.support_guild),
colour=discord.Colour.dark_red()
)
return embed
def bugsplat(self, interaction, e):
error_embed = discord.Embed(title="Something went wrong!", colour=discord.Colour.red())

View File

@@ -386,8 +386,9 @@ class TimerCog(LionCog):
)
else:
# Display the timer status ephemerally
await ctx.interaction.response.defer(thinking=True, ephemeral=True)
status = await timer.current_status(with_notify=False, with_warnings=False)
await ctx.reply(**status.send_args, ephemeral=True)
await ctx.interaction.edit_original_response(**status.edit_args)
if error is not None:
await ctx.reply(embed=error, ephemeral=True)

View File

@@ -12,6 +12,7 @@ from utils.lib import MessageArgs, utc_now, replace_multiple
from core.lion_guild import LionGuild
from core.data import CoreData
from babel.translator import ctx_locale
from gui.errors import RenderingException
from . import babel, logger
from .data import TimerData
@@ -83,6 +84,7 @@ class Timer:
self.destroyed = False
def __repr__(self):
# TODO: Add lock status and current state and stage
return (
"<Timer "
f"channelid={self.data.channelid} "
@@ -560,19 +562,20 @@ class Timer:
"Timer stopped! Press `Start` to restart the timer."
)).format(channel=f"<#{self.data.channelid}>")
card = await get_timer_card(self.bot, self, stage)
await card.render()
if (ui := self.status_view) is None:
ui = self.status_view = TimerStatusUI(self.bot, self, self.channel)
await ui.refresh()
return MessageArgs(
content=content,
file=card.as_file(f"pomodoro_{self.data.channelid}.png"),
view=ui
)
card = await get_timer_card(self.bot, self, stage)
try:
await card.render()
file = card.as_file(f"pomodoro_{self.data.channelid}.png")
args = MessageArgs(content=content, file=file, view=ui)
except RenderingException:
args = MessageArgs(content=content, view=ui)
return args
@log_wrap(action='Send Timer Status')
async def send_status(self, delete_last=True, **kwargs):
@@ -785,8 +788,8 @@ class Timer:
to_next_stage = (current.end - utc_now()).total_seconds()
# TODO: Consider request rate and load
if to_next_stage > 1 * 60 - drift:
time_to_sleep = 1 * 60
if to_next_stage > 5 * 60 - drift:
time_to_sleep = 5 * 60
else:
time_to_sleep = to_next_stage

View File

@@ -299,42 +299,42 @@ class LeaderboardUI(StatsUI):
@button(label="This Season", style=ButtonStyle.grey)
async def season_button(self, press: discord.Interaction, pressed: Button):
await press.response.defer(thinking=True)
await press.response.defer(thinking=True, ephemeral=True)
self.current_period = LBPeriod.SEASON
self.focused = True
await self.refresh(thinking=press)
@button(label="Today", style=ButtonStyle.grey)
async def day_button(self, press: discord.Interaction, pressed: Button):
await press.response.defer(thinking=True)
await press.response.defer(thinking=True, ephemeral=True)
self.current_period = LBPeriod.DAY
self.focused = True
await self.refresh(thinking=press)
@button(label="This Week", style=ButtonStyle.grey)
async def week_button(self, press: discord.Interaction, pressed: Button):
await press.response.defer(thinking=True)
await press.response.defer(thinking=True, ephemeral=True)
self.current_period = LBPeriod.WEEK
self.focused = True
await self.refresh(thinking=press)
@button(label="This Month", style=ButtonStyle.grey)
async def month_button(self, press: discord.Interaction, pressed: Button):
await press.response.defer(thinking=True)
await press.response.defer(thinking=True, ephemeral=True)
self.current_period = LBPeriod.MONTH
self.focused = True
await self.refresh(thinking=press)
@button(label="All Time", style=ButtonStyle.grey)
async def alltime_button(self, press: discord.Interaction, pressed: Button):
await press.response.defer(thinking=True)
await press.response.defer(thinking=True, ephemeral=True)
self.current_period = LBPeriod.ALLTIME
self.focused = True
await self.refresh(thinking=press)
@button(emoji=conf.emojis.backward, style=ButtonStyle.grey)
async def prev_button(self, press: discord.Interaction, pressed: Button):
await press.response.defer(thinking=True)
await press.response.defer(thinking=True, ephemeral=True)
self.pagen -= 1
self.focused = False
await self.refresh(thinking=press)

View File

@@ -10,6 +10,8 @@ from discord.ui import Modal, View, Item
from meta.logger import log_action_stack, logging_context
from meta.errors import SafeCancellation
from gui.errors import RenderingException
from . import logger
from ..lib import MessageArgs, error_embed
@@ -228,6 +230,12 @@ class LeoUI(View):
f"Caught a safe cancellation from LeoUI: {e.details}",
extra={'action': 'Cancel'}
)
except RenderingException as e:
logger.info(
f"UI interaction failed due to rendering exception: {repr(e)}"
)
embed = interaction.client.tree.rendersplat(e)
await interaction.client.tree.error_reply(interaction, embed)
except Exception:
logger.exception(
f"Unhandled interaction exception occurred in item {item!r} of LeoUI {self!r} from interaction: "
@@ -235,15 +243,8 @@ class LeoUI(View):
extra={'with_ctx': True, 'action': 'UIError'}
)
# Explicitly handle the bugsplat ourselves
if not interaction.is_expired():
splat = interaction.client.tree.bugsplat(interaction, error)
try:
if interaction.response.is_done():
await interaction.followup.send(embed=splat, ephemeral=True)
else:
await interaction.response.send_message(embed=splat, ephemeral=True)
except discord.HTTPException:
pass
splat = interaction.client.tree.bugsplat(interaction, error)
await interaction.client.tree.error_reply(interaction, splat)
class MessageUI(LeoUI):
@@ -475,21 +476,20 @@ class LeoModal(Modal):
"""
try:
raise error
except RenderingException as e:
logger.info(
f"Modal submit failed due to rendering exception: {repr(e)}"
)
embed = interaction.client.tree.rendersplat(e)
await interaction.client.tree.error_reply(interaction, embed)
except Exception:
logger.exception(
f"Unhandled interaction exception occurred in {self!r}. Interaction: {interaction.data}",
extra={'with_ctx': True, 'action': 'ModalError'}
)
# Explicitly handle the bugsplat ourselves
if not interaction.is_expired():
splat = interaction.client.tree.bugsplat(interaction, error)
try:
if interaction.response.is_done():
await interaction.followup.send(embed=splat, ephemeral=True)
else:
await interaction.response.send_message(embed=splat, ephemeral=True)
except discord.HTTPException:
pass
splat = interaction.client.tree.bugsplat(interaction, error)
await interaction.client.tree.error_reply(interaction, splat)
def error_handler_for(exc):