fix(core): Handle rendering errors.
This commit is contained in:
2
src/gui
2
src/gui
Submodule src/gui updated: b781f7f9f2...ba9ace6ced
@@ -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)
|
||||||
|
|||||||
@@ -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 = (
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user