From 9faacbc5195885fb72964024700bbc6560f0bad0 Mon Sep 17 00:00:00 2001 From: esefxdz Date: Sat, 11 Jul 2026 18:16:26 +0300 Subject: [PATCH] add error handling to render pipeline, graceful portrait fallback --- blue_archive/gacha.py | 31 ++++++++++++++++++++++--------- blue_archive/gacha_renderer.py | 10 ++++++++-- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/blue_archive/gacha.py b/blue_archive/gacha.py index d647fdc..6578231 100644 --- a/blue_archive/gacha.py +++ b/blue_archive/gacha.py @@ -267,16 +267,29 @@ class BlueArchiveGacha(commands.Cog): # Render result image spark = user_state["spark"] - async with ctx.typing(): - img_bytes = await render_pull(pulls, banner_name, spark) - - # Send image only — the picture says it all - file = discord.File(img_bytes, filename="gacha_result.png") try: - await teaser.delete() - except (discord.NotFound, discord.Forbidden): - pass - await ctx.reply(file=file) + async with ctx.typing(): + img_bytes = await render_pull(pulls, banner_name, spark) + except Exception as e: + log.exception("Render failed") + try: + await teaser.delete() + except (discord.NotFound, discord.Forbidden): + pass + await ctx.reply(f"Render failed: {e}") + return + + # Send image only + try: + file = discord.File(img_bytes, filename="gacha_result.png") + try: + await teaser.delete() + except (discord.NotFound, discord.Forbidden): + pass + await ctx.reply(file=file) + except Exception as e: + log.exception("Failed to send result image") + await ctx.reply(f"Failed to send result: {e}") @commands.command(name="spark") async def spark(self, ctx: commands.Context, *, character_name: str = "") -> None: diff --git a/blue_archive/gacha_renderer.py b/blue_archive/gacha_renderer.py index ae42fe0..987ed95 100644 --- a/blue_archive/gacha_renderer.py +++ b/blue_archive/gacha_renderer.py @@ -201,9 +201,15 @@ async def render_pull( _draw_footer(draw, spark_count) # ── Fetch all portraits ───────────────────────────────────────────── - async with aiohttp.ClientSession() as session: + timeout = aiohttp.ClientTimeout(total=60, connect=10) + async with aiohttp.ClientSession(timeout=timeout) as session: portrait_tasks = [_fetch_portrait(session, p) for p in pulls] - portraits = await asyncio.gather(*portrait_tasks) + portraits = await asyncio.gather(*portrait_tasks, return_exceptions=True) + # Unwrap exceptions: failed fetches become None + for i, result in enumerate(portraits): + if isinstance(result, Exception): + log.warning("Portrait %d (%s) failed: %s", pulls[i]["Id"], pulls[i]["Name"], result) + portraits[i] = None # ── Draw cards in grid ────────────────────────────────────────────── for idx, (student, portrait) in enumerate(zip(pulls, portraits)):