forked from sass/tipibot
refactor(teams): move team-role sync to the economy/community bot
The team-role feature was aimed at the wrong bot. Tournament participants live in the economy/community guild, but team-role sync had been bolted onto the dev bot's roster sync (sync_member), which bails out for anyone missing from the internal member sheet - so it could never reach its actual audience. Decouple it: keep the (roster-independent) team-sheet parsing, pull the team wiring off the dev/member-sync path, and re-home it on the economy bot. - core/member_sync: revert sync_member to add-only (drop team block + SyncResult.roles_removed); add roster-independent sync_team_role and a whole-guild sync_all_team_roles returning a reporting summary. Still only ever touches role NAMES present in the team sheet. - commands/economy_team_commands: new admin-only /teamsync command. - bot.py: hourly team_sync_hourly task (economy-only, no-op unless TEAM_SHEET_ID is set; first tick at boot covers startup load); register /teamsync under the economy profile; drop the dev-side startup load. - commands/dev_member_commands: /check no longer refreshes teams or reports removed roles. - strings + .env.example: TEAMSYNC_UI, CMD[teamsync], document TEAM_SHEET_ID. - tests: retarget sync tests to sync_team_role; add sync_all_team_roles case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R6JZkyszyDFuFtk25WBbcR
This commit is contained in:
87
commands/economy_team_commands.py
Normal file
87
commands/economy_team_commands.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""Tournament team-role sync for the economy/community guild.
|
||||
|
||||
The tournament participants live in the *economy* (community) guild, not the
|
||||
internal dev guild, so team-role assignment runs here rather than as part of the
|
||||
member-roster sync in :mod:`commands.dev_member_commands`. This is deliberately
|
||||
roster-INDEPENDENT: it matches Discord usernames straight against the separate
|
||||
registration spreadsheet (``TEAM_SHEET_ID``) and never touches the member sheet.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
import discord
|
||||
from discord import app_commands
|
||||
|
||||
from core import sheets
|
||||
from core.admin import bot_admin_check
|
||||
from core.member_sync import sync_all_team_roles
|
||||
import strings as S
|
||||
|
||||
|
||||
def register_economy_team_commands(
|
||||
tree: app_commands.CommandTree,
|
||||
bot: discord.Client,
|
||||
log: logging.Logger,
|
||||
) -> None:
|
||||
@tree.command(name="teamsync", description=S.CMD["teamsync"])
|
||||
@app_commands.guild_only()
|
||||
@bot_admin_check()
|
||||
async def cmd_teamsync(interaction: discord.Interaction):
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
|
||||
guild = interaction.guild
|
||||
if guild is None:
|
||||
await interaction.followup.send(S.ERR["guild_only"], ephemeral=True)
|
||||
return
|
||||
|
||||
try:
|
||||
rosters = await sheets.refresh_teams()
|
||||
except Exception as e:
|
||||
await interaction.followup.send(
|
||||
S.TEAMSYNC_UI["refresh_error"].format(error=e), ephemeral=True
|
||||
)
|
||||
return
|
||||
if not rosters:
|
||||
await interaction.followup.send(S.TEAMSYNC_UI["disabled"], ephemeral=True)
|
||||
return
|
||||
|
||||
summary = await sync_all_team_roles(guild, log)
|
||||
await interaction.followup.send(_format_summary(summary), ephemeral=True)
|
||||
log.info(
|
||||
"/teamsync - scanned=%d, assigned=%d, removed=%d, created=%d, errors=%d",
|
||||
summary.scanned,
|
||||
summary.assigned,
|
||||
summary.removed,
|
||||
len(summary.created),
|
||||
len(summary.errors),
|
||||
)
|
||||
|
||||
|
||||
def _format_summary(summary) -> str:
|
||||
lines = [
|
||||
S.TEAMSYNC_UI["done"],
|
||||
S.TEAMSYNC_UI["scanned"].format(count=summary.scanned),
|
||||
S.TEAMSYNC_UI["assigned"].format(count=summary.assigned),
|
||||
S.TEAMSYNC_UI["removed"].format(count=summary.removed),
|
||||
]
|
||||
if summary.created:
|
||||
# A team can be created only once, but the same role could surface for
|
||||
# several members in one run - de-dupe for the report.
|
||||
unique = list(dict.fromkeys(summary.created))
|
||||
lines.append(S.TEAMSYNC_UI["created"].format(roles=", ".join(unique)))
|
||||
if summary.errors:
|
||||
lines.append(S.TEAMSYNC_UI["errors"].format(count=len(summary.errors)))
|
||||
|
||||
text = "\n".join(lines)
|
||||
|
||||
if summary.changes:
|
||||
shown = summary.changes[:20]
|
||||
text += "\n\n" + S.TEAMSYNC_UI["changes_header"] + "\n" + "\n".join(shown)
|
||||
if len(summary.changes) > 20:
|
||||
text += "\n" + S.TEAMSYNC_UI["changes_more"].format(count=len(summary.changes) - 20)
|
||||
else:
|
||||
text += "\n\n" + S.TEAMSYNC_UI["no_changes"]
|
||||
|
||||
return text
|
||||
Reference in New Issue
Block a user