feat(tally): fix live VC status updates and add permission pre-flight check
- Move tally hook from process_session (per-guild, gated by Logs subs) to process_ws_replays (once per game, all guilds) via on_game_finished - Add set_voice_channel_status permission check at /tally-claim time so failures are immediate and visible rather than silent on every game - Remove entitlement gate from tally_claim and tally_transfer - Add VC tally permission section to /diagnose-perms when run in a VC - Add 5 new locale keys to en.json for the permission messages Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+13
-8
@@ -534,6 +534,19 @@ async def process_ws_replays(replays: list[dict]):
|
||||
)
|
||||
local_data["scoreboard_context"] = scoreboard_context
|
||||
|
||||
# Notify all active tallies immediately after the game is saved.
|
||||
# Done here rather than inside process_session so it fires for all
|
||||
# guilds regardless of their Logs channel subscriptions.
|
||||
try:
|
||||
await tally.on_game_finished(
|
||||
local_data.get("teams", []),
|
||||
local_data.get("winning_team_squadron") or None,
|
||||
bool(local_data.get("draw", False)),
|
||||
hex_id,
|
||||
)
|
||||
except Exception as e:
|
||||
logging.error(f"[TALLY] on_game_finished failed for {hex_id}: {e}")
|
||||
|
||||
forwarded_replays.append(local_data)
|
||||
validated_games.append({
|
||||
"sessionIdHex": hex_id,
|
||||
@@ -1132,14 +1145,6 @@ async def process_session(
|
||||
else:
|
||||
new_wl = get_standings(squadrons_clean)
|
||||
|
||||
# Update any live voice-channel tallies for this guild against this game.
|
||||
try:
|
||||
await tally.on_session_processed(
|
||||
guild_id, teams, winner, is_draw, session_id
|
||||
)
|
||||
except Exception as e:
|
||||
logging.error(f"[TALLY] hook failed for session {session_id}: {e}")
|
||||
|
||||
# Scoreboard Build
|
||||
lock = _scoreboard_locks.setdefault(session_id, asyncio.Lock())
|
||||
async with lock:
|
||||
|
||||
+13
-6
@@ -1233,6 +1233,16 @@ async def _diagnose_perms_logic(interaction: discord.Interaction, target_channel
|
||||
lines.append(t(lang, "diagnostics.premium_autolog_required"))
|
||||
lines.append("")
|
||||
|
||||
# ── 5. Voice channel tally permission ──
|
||||
if isinstance(channel, discord.VoiceChannel):
|
||||
vc_mention = f"<#{channel.id}>"
|
||||
lines.append(t(lang, "commands.tally.vc_perm_header", vc=vc_mention))
|
||||
if channel.permissions_for(bot_member).set_voice_channel_status:
|
||||
lines.append(f" ✅ {t(lang, 'commands.tally.vc_perm_ok', vc=vc_mention)}")
|
||||
else:
|
||||
lines.append(f" ❌ {t(lang, 'commands.tally.no_vc_perm_diagnose', vc=vc_mention)}")
|
||||
lines.append("")
|
||||
|
||||
# ── Send ──
|
||||
embed = discord.Embed(
|
||||
title=t(lang, "diagnostics.title"),
|
||||
@@ -4267,13 +4277,13 @@ def _invoker_voice_channel(interaction: discord.Interaction):
|
||||
@discord.app_commands.autocomplete(username=player_autocomplete, squadron=squadron_autocomplete)
|
||||
async def tally_claim(interaction: discord.Interaction, username: str = "", squadron: str = ""):
|
||||
lang = await guild_lang(interaction.guild.id) if interaction.guild else "en"
|
||||
if not interaction.guild_id or not await is_guild_entitled(interaction.guild_id):
|
||||
await interaction.response.send_message(t(lang, "commands.tally.premium_required"), ephemeral=True)
|
||||
return
|
||||
vc = _invoker_voice_channel(interaction)
|
||||
if vc is None:
|
||||
await interaction.response.send_message(t(lang, "commands.tally.not_in_vc"), ephemeral=True)
|
||||
return
|
||||
if not vc.permissions_for(vc.guild.me).set_voice_channel_status:
|
||||
await interaction.response.send_message(t(lang, "commands.tally.no_vc_perm"), ephemeral=True)
|
||||
return
|
||||
username = username.strip()
|
||||
squadron = squadron.strip()
|
||||
if bool(username) == bool(squadron): # neither or both
|
||||
@@ -4311,9 +4321,6 @@ async def tally_claim(interaction: discord.Interaction, username: str = "", squa
|
||||
@discord.app_commands.autocomplete(username=player_autocomplete)
|
||||
async def tally_transfer(interaction: discord.Interaction, username: str):
|
||||
lang = await guild_lang(interaction.guild.id) if interaction.guild else "en"
|
||||
if not interaction.guild_id or not await is_guild_entitled(interaction.guild_id):
|
||||
await interaction.response.send_message(t(lang, "commands.tally.premium_required"), ephemeral=True)
|
||||
return
|
||||
vc = _invoker_voice_channel(interaction)
|
||||
if vc is None:
|
||||
await interaction.response.send_message(t(lang, "commands.tally.not_in_vc"), ephemeral=True)
|
||||
|
||||
@@ -861,6 +861,11 @@
|
||||
"status_line": "{base}: {verb} against {opponent}",
|
||||
"not_in_vc": "You must be connected to a voice channel to use this.",
|
||||
"premium_required": "This is a premium feature. Use /unlock to enable it for this server.",
|
||||
"no_vc_perm": "The bot is missing the **Set Voice Channel Status** permission in this voice channel. Ask a server admin to grant it.",
|
||||
"no_vc_perm_diagnose": "Missing **Set Voice Channel Status** — `/tally-claim` will fail in {vc}. Grant the bot this permission.",
|
||||
"vc_perm_ok": "**Set Voice Channel Status** — `/tally-claim` can update {vc}.",
|
||||
"vc_perm_header": "Voice Channel Tally ({vc})",
|
||||
"vc_perm_not_in_vc": "Not in a voice channel — join one and re-run to check tally permissions.",
|
||||
"need_one_input": "Provide exactly one of `username` or `squadron`.",
|
||||
"already_active": "A tally is already active in **{channel}** tracking **{target}**. Use /tally-transfer or /tally-clear first.",
|
||||
"claimed": "Now tracking **{target}** in **{channel}**. Status set to `0W-0L`.",
|
||||
|
||||
@@ -266,6 +266,14 @@ async def on_session_processed(guild_id: int, teams: list[dict],
|
||||
logging.error(f"[TALLY] on_session_processed error (guild={guild_id}): {e}")
|
||||
|
||||
|
||||
async def on_game_finished(teams: list[dict], winner_short: Optional[str],
|
||||
is_draw: bool, session_id: str) -> None:
|
||||
"""Update all active tallies across every guild for one finished session."""
|
||||
guild_ids = {gid for (gid, _) in _REGISTRY}
|
||||
for guild_id in guild_ids:
|
||||
await on_session_processed(guild_id, teams, winner_short, is_draw, session_id)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Idle sweep
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user