diff --git a/BOT/game_api.py b/BOT/game_api.py index dc768b1..a4f07d1 100644 --- a/BOT/game_api.py +++ b/BOT/game_api.py @@ -655,8 +655,20 @@ async def obtain_clan_new_points( while isinstance(data, dict) and "member_ratings" not in data and len(data) == 1: data = next(iter(data.values())) - total_score = data["astat"]["dr_era5_hist"] - raw_ratings = data["member_ratings"] + # A real clan record always carries a `members` roster. Deleted + # clans come back as JSON (handled above), and truncated payloads + # raise inside bin_blk_to_json, so a parsed BLK without `members` + # is genuinely unavailable. + if not isinstance(data, dict) or "members" not in data: + raise KeyError("members") + + # After a season reset the game API can omit the aggregate `astat` + # block and/or the per-member `member_ratings` block for inactive + # squadrons even though the clan and its roster still exist. Default + # both to zero so the squadron renders at 0 points instead of being + # reported as deleted. + total_score = (data.get("astat") or {}).get("dr_era5_hist", 0) + raw_ratings = data.get("member_ratings") or {} except ClanInfoError as e: try: diff --git a/BOT/utils.py b/BOT/utils.py index eee2b69..8c1ba72 100644 --- a/BOT/utils.py +++ b/BOT/utils.py @@ -598,6 +598,15 @@ def gate_entitle(required_tier: str): async def permission_fail(interaction: discord.Interaction, error): """Handle permission-related errors with appropriate embeds.""" + # discord.py dispatches a command error to BOTH the command's local + # `@cmd.error` handler and the global `@tree.error` handler (see + # app_commands/tree.py). Since both route here, guard against sending the + # error embed twice for the same interaction — the second send would land + # as a separate (and, after a public defer, differently-scoped) message. + if interaction.extras.get("_permission_fail_handled"): + return + interaction.extras["_permission_fail_handled"] = True + lang = await guild_lang(interaction.guild_id) if interaction.guild_id else "en" if isinstance(error, BlacklistCheckFailure): reason = getattr(error, "reason", None)