Skip to content

Commit 858ba22

Browse files
authored
Merge pull request #13 from TheCodeVerseHub/yc45
Hardened the send helper so if an interaction still expires
2 parents c139f83 + c948d13 commit 858ba22

1 file changed

Lines changed: 61 additions & 7 deletions

File tree

src/cogs/leveling.py

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,49 @@ async def cog_unload(self):
4545
logger.exception("Failed to close RankCardGenerator")
4646

4747
async def _send(self, ctx: commands.Context, *args, **kwargs):
48-
"""Send helper that avoids passing interaction-only kwargs in prefix mode."""
49-
if getattr(ctx, "interaction", None) is None:
48+
"""Send helper that works for both prefix and interaction invocations.
49+
50+
For interactions, responses must be sent within ~3s unless deferred.
51+
If an interaction has expired (error 10062), we fall back to a normal
52+
channel message as a last resort.
53+
"""
54+
interaction = getattr(ctx, "interaction", None)
55+
56+
if interaction is None:
5057
kwargs.pop("ephemeral", None)
51-
return await ctx.send(*args, **kwargs)
5258

53-
async def _maybe_defer(self, ctx: commands.Context):
54-
"""Defer only when invoked as a slash command."""
55-
if getattr(ctx, "interaction", None) is not None:
56-
await ctx.defer()
59+
try:
60+
return await ctx.send(*args, **kwargs)
61+
except discord.NotFound as e:
62+
# Slash interaction expired/invalid (common if not deferred quickly).
63+
# Fall back to a plain channel send (cannot be ephemeral).
64+
if interaction is not None and getattr(e, "code", None) == 10062 and getattr(ctx, "channel", None) is not None:
65+
kwargs.pop("ephemeral", None)
66+
return await ctx.channel.send(*args, **kwargs) # type: ignore[union-attr]
67+
raise
68+
except discord.InteractionResponded:
69+
# If something already responded via interaction, use follow-up.
70+
if interaction is not None:
71+
return await interaction.followup.send(*args, **kwargs)
72+
raise
73+
74+
async def _maybe_defer(self, ctx: commands.Context, *, ephemeral: bool = False):
75+
"""Defer only when invoked as a slash command and not already acknowledged."""
76+
interaction = getattr(ctx, "interaction", None)
77+
if interaction is None:
78+
return
79+
80+
# Avoid double-acknowledging the interaction.
81+
if interaction.response.is_done():
82+
return
83+
84+
try:
85+
await ctx.defer(ephemeral=ephemeral)
86+
except discord.NotFound:
87+
# Interaction already expired; caller should rely on _send() fallback.
88+
return
89+
except discord.HTTPException:
90+
return
5791

5892
# ========================================================================
5993
# Leveling Formula
@@ -307,6 +341,8 @@ async def leaderboard(self, ctx: commands.Context, page: int = 1):
307341
if ctx.guild is None:
308342
return
309343

344+
await self._maybe_defer(ctx)
345+
310346
if page < 1:
311347
page = 1
312348

@@ -364,6 +400,8 @@ async def xp(self, ctx: commands.Context, user: Optional[discord.Member] = None)
364400
if target.bot:
365401
await self._send(ctx, "Bots don't have XP!", ephemeral=True)
366402
return
403+
404+
await self._maybe_defer(ctx)
367405

368406
user_data = await db.get_user_data(target.id, ctx.guild.id)
369407

@@ -414,6 +452,8 @@ async def setlevel(self, ctx: commands.Context, user: discord.Member, level: int
414452
if ctx.guild is None:
415453
return
416454

455+
await self._maybe_defer(ctx)
456+
417457
if level < 0:
418458
await self._send(ctx, " Level must be 0 or higher!", ephemeral=True)
419459
return
@@ -445,6 +485,8 @@ async def addxp(self, ctx: commands.Context, user: discord.Member, amount: int):
445485
if ctx.guild is None:
446486
return
447487

488+
await self._maybe_defer(ctx)
489+
448490
user_data = await db.get_user_data(user.id, ctx.guild.id)
449491

450492
if user_data:
@@ -476,6 +518,8 @@ async def resetlevel(self, ctx: commands.Context, user: discord.Member):
476518
if ctx.guild is None:
477519
return
478520

521+
await self._maybe_defer(ctx)
522+
479523
await db.reset_user_data(user.id, ctx.guild.id)
480524

481525
embed = discord.Embed(
@@ -497,6 +541,8 @@ async def resetalllevels(self, ctx: commands.Context, confirm: Optional[str] = N
497541
if ctx.guild is None:
498542
return
499543

544+
await self._maybe_defer(ctx)
545+
500546
if confirm != "CONFIRM":
501547
embed = discord.Embed(
502548
title=" Warning",
@@ -532,6 +578,8 @@ async def setlevelchannel(self, ctx: commands.Context, channel: Optional[discord
532578
if ctx.guild is None:
533579
return
534580

581+
await self._maybe_defer(ctx)
582+
535583
if channel:
536584
await db.set_levelup_channel(ctx.guild.id, channel.id)
537585
embed = discord.Embed(
@@ -561,6 +609,8 @@ async def addrole(self, ctx: commands.Context, level: int, role: discord.Role):
561609
if ctx.guild is None:
562610
return
563611

612+
await self._maybe_defer(ctx)
613+
564614
if level < 1:
565615
await self._send(ctx, " Level must be 1 or higher!", ephemeral=True)
566616
return
@@ -596,6 +646,8 @@ async def removerole(self, ctx: commands.Context, level: int):
596646
if ctx.guild is None:
597647
return
598648

649+
await self._maybe_defer(ctx)
650+
599651
result = await db.remove_role_reward(ctx.guild.id, level)
600652

601653
if result:
@@ -624,6 +676,8 @@ async def rolerewards(self, ctx: commands.Context):
624676
if ctx.guild is None:
625677
return
626678

679+
await self._maybe_defer(ctx)
680+
627681
role_rewards = await db.get_role_rewards(ctx.guild.id)
628682

629683
if not role_rewards:

0 commit comments

Comments
 (0)