""" Scouting Helper Functions Handles creation of scout opportunities after pack openings and embed formatting for the scouting feature. """ import datetime import logging import os import random import discord from api_calls import db_get, db_post from helpers.utils import int_timestamp, midnight_timestamp from helpers.discord_utils import get_team_embed from helpers.constants import IMAGES, PD_SEASON logger = logging.getLogger("discord_app") SCOUT_TOKENS_PER_DAY = 2 SCOUT_TOKEN_COST = 200 # Currency cost to buy an extra scout token SCOUT_WINDOW_SECONDS = 1800 # 30 minutes _scoutable_raw = os.environ.get("SCOUTABLE_PACK_TYPES", "Standard,Premium") SCOUTABLE_PACK_TYPES = {s.strip() for s in _scoutable_raw.split(",") if s.strip()} # Rarity value → display symbol RARITY_SYMBOLS = { 8: "\U0001f7e3", # HoF — purple (#751cea) 5: "\U0001f535", # MVP — cyan/blue (#56f1fa) 3: "\U0001f7e1", # All-Star — gold (#FFD700) 2: "\u26aa", # Starter — silver (#C0C0C0) 1: "\U0001f7e4", # Reserve — bronze (#CD7F32) 0: "\u26ab", # Replacement — dark gray (#454545) } async def get_scout_tokens_used(team_id: int) -> int: """Return how many scout tokens a team has used today.""" used_today = await db_get( "rewards", params=[ ("name", "Scout Token"), ("team_id", team_id), ("created_after", midnight_timestamp()), ], ) return used_today["count"] if used_today else 0 def _build_card_lines(cards: list[dict]) -> list[tuple[int, str]]: """Build a shuffled list of (player_id, display_line) tuples.""" lines = [] for card in cards: player = card["player"] rarity_val = player["rarity"]["value"] symbol = RARITY_SYMBOLS.get(rarity_val, "\u26ab") desc = player.get("description", "") image_url = player.get("image", "") name_display = ( f"[{desc} {player['p_name']}]({image_url})" if image_url else f"{desc} {player['p_name']}" ) lines.append( ( player["player_id"], f"{symbol} {player['rarity']['name']} — {name_display}", ) ) random.shuffle(lines) return lines def build_scout_embed( opener_team: dict, cards: list[dict] = None, card_lines: list[tuple[int, str]] = None, expires_unix: int = None, claims: dict[int, list[str]] = None, total_scouts: int = 0, closed: bool = False, ) -> tuple[discord.Embed, list[tuple[int, str]]]: """Build the embed shown above the scout buttons. Shows a shuffled list of cards (rarity + player name) so scouters know what's in the pack but not which button maps to which card. Returns (embed, card_lines) so the view can store the shuffled order. Parameters ---------- closed : if True, renders the "Scout Window Closed" variant claims : scouted card tracking dict for build_scouted_card_list total_scouts : number of scouts so far (for title display) """ if card_lines is None and cards is not None: card_lines = _build_card_lines(cards) if claims and card_lines: card_list = build_scouted_card_list(card_lines, claims) elif card_lines: card_list = "\n".join(line for _, line in card_lines) else: card_list = "" if closed: if total_scouts > 0: title = f"Scout Window Closed ({total_scouts} scouted)" else: title = "Scout Window Closed" elif total_scouts > 0: title = f"Scout Opportunity! ({total_scouts} scouted)" else: title = "Scout Opportunity!" embed = get_team_embed(title=title, team=opener_team) if closed: embed.description = f"**{opener_team['lname']}**'s pack\n\n" f"{card_list}" embed.set_footer( text=f"Paper Dynasty Season {PD_SEASON}", icon_url=IMAGES["logo"], ) else: if expires_unix: time_line = f"Scout window closes ." else: time_line = "Scout window closes in **30 minutes**." embed.description = ( f"**{opener_team['lname']}**'s pack\n\n" f"{card_list}\n\n" f"Pick a card — but which is which?\n" f"Costs 1 Scout Token (2 per day, resets at midnight Central).\n" f"{time_line}" ) embed.set_footer( text=f"Paper Dynasty Season {PD_SEASON} \u2022 One scout per player", icon_url=IMAGES["logo"], ) return embed, card_lines def build_scouted_card_list( card_lines: list[tuple[int, str]], scouted_cards: dict[int, list[str]], ) -> str: """Rebuild the card list marking scouted cards with scouter team names. Parameters ---------- card_lines : shuffled list of (player_id, display_line) tuples scouted_cards : {player_id: [team_name, ...]} for each claimed card """ result = [] for player_id, line in card_lines: teams = scouted_cards.get(player_id) if teams: count = len(teams) names = ", ".join(f"*{t}*" for t in teams) if count == 1: result.append(f"{line} \u2014 \u2714\ufe0f {names}") else: result.append(f"{line} \u2014 \u2714\ufe0f x{count} ({names})") else: result.append(line) return "\n".join(result) async def create_scout_opportunity( pack_cards: list[dict], opener_team: dict, channel: discord.TextChannel, opener_user, context, ) -> None: """Create a scout opportunity and post the ScoutView to the channel. Called after display_cards() completes in open_st_pr_packs(). Wrapped in try/except so scouting failures never crash pack opening. Parameters ---------- pack_cards : list of card dicts from a single pack opener_team : team dict for the pack opener channel : the #pack-openings channel opener_user : discord.Member or discord.User who opened the pack context : the command context (Context or Interaction), used to get bot """ from discord_ui.scout_view import ScoutView # Only create scout opportunities in the pack-openings channel if not channel or channel.name != "pack-openings": return if not pack_cards: return now = datetime.datetime.now() expires_dt = now + datetime.timedelta(seconds=SCOUT_WINDOW_SECONDS) expires_at = int_timestamp(expires_dt) expires_unix = int(expires_dt.timestamp()) created = int_timestamp(now) card_ids = [c["id"] for c in pack_cards] try: scout_opp = await db_post( "scout_opportunities", payload={ "pack_id": pack_cards[0].get("pack_id"), "opener_team_id": opener_team["id"], "card_ids": card_ids, "expires_at": expires_at, "created": created, }, ) except Exception as e: logger.error(f"Failed to create scout opportunity: {e}") return embed, card_lines = build_scout_embed( opener_team, pack_cards, expires_unix=expires_unix ) # Get bot reference from context bot = getattr(context, "bot", None) or getattr(context, "client", None) view = ScoutView( scout_opp_id=scout_opp["id"], cards=pack_cards, opener_team=opener_team, opener_user_id=opener_user.id, bot=bot, expires_unix=expires_unix, ) view.card_lines = card_lines try: msg = await channel.send(embed=embed, view=view) view.message = msg except Exception as e: logger.error(f"Failed to post scout opportunity message: {e}")