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 data import Database
from utils.lib import tabulate from utils.lib import tabulate
from gui.errors import RenderingException
from .config import Conf from .config import Conf
from .logger import logging_context, log_context, log_action_stack, log_wrap, set_logging_context from .logger import logging_context, log_context, log_action_stack, log_wrap, set_logging_context
@@ -204,13 +205,23 @@ class LionBot(Bot):
pass pass
except asyncio.TimeoutError: except asyncio.TimeoutError:
pass 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: except Exception as e:
logger.exception( logger.exception(
f"Caught an unknown CommandInvokeError while executing: {cmd_str}", f"Caught an unknown CommandInvokeError while executing: {cmd_str}",
extra={'action': 'BotError', 'with_ctx': True} 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 = ( error_embed.description = (
"An unexpected error occurred while processing your command!\n" "An unexpected error occurred while processing your command!\n"
"Our development team has been notified, and the issue will be addressed soon.\n" "Our development team has been notified, and the issue will be addressed soon.\n"
@@ -246,7 +257,7 @@ class LionBot(Bot):
try: try:
await ctx.error_reply(embed=error_embed) await ctx.error_reply(embed=error_embed)
except Exception: except discord.HTTPException:
pass pass
finally: finally:
exception.original = HandledException(exception.original) exception.original = HandledException(exception.original)

View File

@@ -8,9 +8,11 @@ from discord.enums import InteractionType
from discord.app_commands.namespace import Namespace from discord.app_commands.namespace import Namespace
from utils.lib import tabulate from utils.lib import tabulate
from gui.errors import RenderingException
from .logger import logging_context, set_logging_context, log_wrap, log_action_stack from .logger import logging_context, set_logging_context, log_wrap, log_action_stack
from .errors import SafeCancellation from .errors import SafeCancellation
from .config import conf
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -29,18 +31,37 @@ class LionTree(CommandTree):
except SafeCancellation: except SafeCancellation:
# Assume this has already been handled # Assume this has already been handled
pass 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: except Exception:
logger.exception(f"Unhandled exception in interaction: {interaction}", extra={'action': 'TreeError'}) logger.exception(f"Unhandled exception in interaction: {interaction}", extra={'action': 'TreeError'})
embed = self.bugsplat(interaction, error)
await self.error_reply(interaction, embed)
async def error_reply(self, interaction, embed):
if not interaction.is_expired(): if not interaction.is_expired():
splat = self.bugsplat(interaction, error)
try: try:
if interaction.response.is_done(): if interaction.response.is_done():
await interaction.followup.send(embed=splat, ephemeral=True) await interaction.followup.send(embed=embed, ephemeral=True)
else: else:
await interaction.response.send_message(embed=splat, ephemeral=True) await interaction.response.send_message(embed=embed, ephemeral=True)
except discord.HTTPException: except discord.HTTPException:
pass 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): def bugsplat(self, interaction, e):
error_embed = discord.Embed(title="Something went wrong!", colour=discord.Colour.red()) error_embed = discord.Embed(title="Something went wrong!", colour=discord.Colour.red())
error_embed.description = ( error_embed.description = (

View File

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

View File

@@ -299,42 +299,42 @@ class LeaderboardUI(StatsUI):
@button(label="This Season", style=ButtonStyle.grey) @button(label="This Season", style=ButtonStyle.grey)
async def season_button(self, press: discord.Interaction, pressed: Button): 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.current_period = LBPeriod.SEASON
self.focused = True self.focused = True
await self.refresh(thinking=press) await self.refresh(thinking=press)
@button(label="Today", style=ButtonStyle.grey) @button(label="Today", style=ButtonStyle.grey)
async def day_button(self, press: discord.Interaction, pressed: Button): 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.current_period = LBPeriod.DAY
self.focused = True self.focused = True
await self.refresh(thinking=press) await self.refresh(thinking=press)
@button(label="This Week", style=ButtonStyle.grey) @button(label="This Week", style=ButtonStyle.grey)
async def week_button(self, press: discord.Interaction, pressed: Button): 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.current_period = LBPeriod.WEEK
self.focused = True self.focused = True
await self.refresh(thinking=press) await self.refresh(thinking=press)
@button(label="This Month", style=ButtonStyle.grey) @button(label="This Month", style=ButtonStyle.grey)
async def month_button(self, press: discord.Interaction, pressed: Button): 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.current_period = LBPeriod.MONTH
self.focused = True self.focused = True
await self.refresh(thinking=press) await self.refresh(thinking=press)
@button(label="All Time", style=ButtonStyle.grey) @button(label="All Time", style=ButtonStyle.grey)
async def alltime_button(self, press: discord.Interaction, pressed: Button): 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.current_period = LBPeriod.ALLTIME
self.focused = True self.focused = True
await self.refresh(thinking=press) await self.refresh(thinking=press)
@button(emoji=conf.emojis.backward, style=ButtonStyle.grey) @button(emoji=conf.emojis.backward, style=ButtonStyle.grey)
async def prev_button(self, press: discord.Interaction, pressed: Button): 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.pagen -= 1
self.focused = False self.focused = False
await self.refresh(thinking=press) 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.logger import log_action_stack, logging_context
from meta.errors import SafeCancellation from meta.errors import SafeCancellation
from gui.errors import RenderingException
from . import logger from . import logger
from ..lib import MessageArgs, error_embed from ..lib import MessageArgs, error_embed
@@ -228,6 +230,12 @@ class LeoUI(View):
f"Caught a safe cancellation from LeoUI: {e.details}", f"Caught a safe cancellation from LeoUI: {e.details}",
extra={'action': 'Cancel'} 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: except Exception:
logger.exception( logger.exception(
f"Unhandled interaction exception occurred in item {item!r} of LeoUI {self!r} from interaction: " 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'} extra={'with_ctx': True, 'action': 'UIError'}
) )
# Explicitly handle the bugsplat ourselves # Explicitly handle the bugsplat ourselves
if not interaction.is_expired():
splat = interaction.client.tree.bugsplat(interaction, error) splat = interaction.client.tree.bugsplat(interaction, error)
try: await interaction.client.tree.error_reply(interaction, splat)
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
class MessageUI(LeoUI): class MessageUI(LeoUI):
@@ -475,21 +476,20 @@ class LeoModal(Modal):
""" """
try: try:
raise error 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: except Exception:
logger.exception( logger.exception(
f"Unhandled interaction exception occurred in {self!r}. Interaction: {interaction.data}", f"Unhandled interaction exception occurred in {self!r}. Interaction: {interaction.data}",
extra={'with_ctx': True, 'action': 'ModalError'} extra={'with_ctx': True, 'action': 'ModalError'}
) )
# Explicitly handle the bugsplat ourselves # Explicitly handle the bugsplat ourselves
if not interaction.is_expired():
splat = interaction.client.tree.bugsplat(interaction, error) splat = interaction.client.tree.bugsplat(interaction, error)
try: await interaction.client.tree.error_reply(interaction, splat)
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
def error_handler_for(exc): def error_handler_for(exc):