""" Discord Select UI components. Contains all Select classes for various team, cardset, and pack selections. """ import logging import discord from typing import Literal, Optional from helpers.constants import ALL_MLB_TEAMS, IMAGES, normalize_franchise logger = logging.getLogger("discord_app") # Team name to ID mappings AL_TEAM_IDS = { "Baltimore Orioles": 3, "Boston Red Sox": 4, "Chicago White Sox": 6, "Cleveland Guardians": 8, "Detroit Tigers": 10, "Houston Astros": 11, "Kansas City Royals": 12, "Los Angeles Angels": 13, "Minnesota Twins": 17, "New York Yankees": 19, "Oakland Athletics": 20, "Athletics": 20, # Alias for post-Oakland move "Seattle Mariners": 24, "Tampa Bay Rays": 27, "Texas Rangers": 28, "Toronto Blue Jays": 29, } NL_TEAM_IDS = { "Arizona Diamondbacks": 1, "Atlanta Braves": 2, "Chicago Cubs": 5, "Cincinnati Reds": 7, "Colorado Rockies": 9, "Los Angeles Dodgers": 14, "Miami Marlins": 15, "Milwaukee Brewers": 16, "New York Mets": 18, "Philadelphia Phillies": 21, "Pittsburgh Pirates": 22, "San Diego Padres": 23, "San Francisco Giants": 25, "St Louis Cardinals": 26, # Note: constants has 'St Louis Cardinals' not 'St. Louis Cardinals' "Washington Nationals": 30, } # Get AL teams from constants AL_TEAMS = [team for team in ALL_MLB_TEAMS.keys() if team in AL_TEAM_IDS] NL_TEAMS = [ team for team in ALL_MLB_TEAMS.keys() if team in NL_TEAM_IDS or team == "St Louis Cardinals" ] # Cardset mappings CARDSET_LABELS_TO_IDS = { "2022 Season": 3, "2022 Promos": 4, "2021 Season": 1, "2019 Season": 5, "2013 Season": 6, "2012 Season": 7, "Mario Super Sluggers": 8, "2023 Season": 9, "2016 Season": 11, "2008 Season": 12, "2018 Season": 13, "2024 Season": 17, "2024 Promos": 18, "1998 Season": 20, "2025 Season": 24, "2005 Live": 27, "Pokemon - Brilliant Stars": 23, } def _get_team_id(team_name: str, league: Literal["AL", "NL"]) -> int: """Get team ID from team name and league.""" if league == "AL": return AL_TEAM_IDS.get(team_name) else: # Handle the St. Louis Cardinals special case if team_name == "St. Louis Cardinals": return NL_TEAM_IDS.get("St Louis Cardinals") return NL_TEAM_IDS.get(team_name) class SelectChoicePackTeam(discord.ui.Select): def __init__( self, which: Literal["AL", "NL"], team, cardset_id: Optional[int] = None ): self.which = which self.owner_team = team self.cardset_id = cardset_id if which == "AL": options = [discord.SelectOption(label=team) for team in AL_TEAMS] else: # Handle St. Louis Cardinals display name options = [ discord.SelectOption( label="St. Louis Cardinals" if team == "St Louis Cardinals" else team ) for team in NL_TEAMS ] super().__init__(placeholder=f"Select an {which} team", options=options) async def callback(self, interaction: discord.Interaction): # Import here to avoid circular imports from api_calls import db_get, db_patch from helpers import open_choice_pack team_id = _get_team_id(self.values[0], self.which) if team_id is None: raise ValueError(f"Unknown team: {self.values[0]}") await interaction.response.edit_message( content=f"You selected the **{self.values[0]}**", view=None ) # Get the selected packs params = [ ("pack_type_id", 8), ("team_id", self.owner_team["id"]), ("opened", False), ("limit", 1), ("exact_match", True), ] if self.cardset_id is not None: params.append(("pack_cardset_id", self.cardset_id)) p_query = await db_get("packs", params=params) if p_query["count"] == 0: logger.error(f"open-packs - no packs found with params: {params}") raise ValueError("Unable to open packs") this_pack = await db_patch( "packs", object_id=p_query["packs"][0]["id"], params=[("pack_team_id", team_id)], ) await open_choice_pack(this_pack, self.owner_team, interaction, self.cardset_id) class SelectOpenPack(discord.ui.Select): def __init__(self, options: list, team: dict): self.owner_team = team super().__init__(placeholder="Select a Pack Type", options=options) async def callback(self, interaction: discord.Interaction): # Import here to avoid circular imports from api_calls import db_get from helpers import open_st_pr_packs, open_choice_pack logger.info(f"SelectPackChoice - selection: {self.values[0]}") pack_vals = self.values[0].split("-") logger.info(f"pack_vals: {pack_vals}") # Get the selected packs params = [ ("team_id", self.owner_team["id"]), ("opened", False), ("limit", 5), ("exact_match", True), ] open_type = "standard" if "Standard" in pack_vals: open_type = "standard" params.append(("pack_type_id", 1)) elif "Premium" in pack_vals: open_type = "standard" params.append(("pack_type_id", 3)) elif "Daily" in pack_vals: params.append(("pack_type_id", 4)) elif "Promo Choice" in pack_vals: open_type = "choice" params.append(("pack_type_id", 9)) elif "MVP" in pack_vals: open_type = "choice" params.append(("pack_type_id", 5)) elif "All Star" in pack_vals: open_type = "choice" params.append(("pack_type_id", 6)) elif "Mario" in pack_vals: open_type = "choice" params.append(("pack_type_id", 7)) elif "Team Choice" in pack_vals: open_type = "choice" params.append(("pack_type_id", 8)) else: logger.error( f"Unrecognized pack type in selector: {self.values[0]} (split: {pack_vals})" ) await interaction.response.edit_message(view=None) await interaction.followup.send( content="This pack type cannot be opened manually. Please contact Cal.", ephemeral=True, ) return # If team isn't already set on team choice pack, make team pack selection now await interaction.response.edit_message(view=None) cardset_id = None # Handle Team Choice packs with no team/cardset assigned if ( "Team Choice" in pack_vals and "Team" not in pack_vals and "Cardset" not in pack_vals ): await interaction.followup.send( content="This Team Choice pack needs to be assigned a team and cardset. " "Please contact Cal to configure this pack.", ephemeral=True, ) return elif "Team Choice" in pack_vals and "Cardset" in pack_vals: # cardset_id = pack_vals[2] cardset_index = pack_vals.index("Cardset") cardset_id = pack_vals[cardset_index + 1] params.append(("pack_cardset_id", cardset_id)) if "Team" not in pack_vals: view = SelectView( [ SelectChoicePackTeam("AL", self.owner_team, cardset_id), SelectChoicePackTeam("NL", self.owner_team, cardset_id), ], timeout=30, ) await interaction.followup.send( content="Please select a team for your Team Choice pack:", view=view ) return params.append(("pack_team_id", pack_vals[pack_vals.index("Team") + 1])) else: if "Team" in pack_vals: params.append(("pack_team_id", pack_vals[pack_vals.index("Team") + 1])) if "Cardset" in pack_vals: cardset_id = pack_vals[pack_vals.index("Cardset") + 1] params.append(("pack_cardset_id", cardset_id)) p_query = await db_get("packs", params=params) if p_query["count"] == 0: logger.error(f"open-packs - no packs found with params: {params}") await interaction.followup.send( content="Unable to find the selected pack. Please contact Cal.", ephemeral=True, ) return # Open the packs try: if open_type == "standard": await open_st_pr_packs(p_query["packs"], self.owner_team, interaction) elif open_type == "choice": await open_choice_pack( p_query["packs"][0], self.owner_team, interaction, cardset_id ) except Exception as e: logger.error(f"Failed to open pack: {e}") await interaction.followup.send( content=f"Failed to open pack. Please contact Cal. Error: {str(e)}", ephemeral=True, ) return class SelectPaperdexCardset(discord.ui.Select): def __init__(self): options = [ discord.SelectOption(label="2005 Live"), discord.SelectOption(label="2025 Season"), discord.SelectOption(label="1998 Season"), discord.SelectOption(label="2024 Season"), discord.SelectOption(label="2023 Season"), discord.SelectOption(label="2022 Season"), discord.SelectOption(label="2022 Promos"), discord.SelectOption(label="2021 Season"), discord.SelectOption(label="2019 Season"), discord.SelectOption(label="2018 Season"), discord.SelectOption(label="2016 Season"), discord.SelectOption(label="2013 Season"), discord.SelectOption(label="2012 Season"), discord.SelectOption(label="2008 Season"), discord.SelectOption(label="Mario Super Sluggers"), ] super().__init__(placeholder="Select a Cardset", options=options) async def callback(self, interaction: discord.Interaction): # Import here to avoid circular imports from api_calls import db_get from helpers import get_team_by_owner, paperdex_cardset_embed, embed_pagination logger.info(f"SelectPaperdexCardset - selection: {self.values[0]}") cardset_id = CARDSET_LABELS_TO_IDS.get(self.values[0]) if cardset_id is None: raise ValueError(f"Unknown cardset: {self.values[0]}") c_query = await db_get("cardsets", object_id=cardset_id, none_okay=False) await interaction.response.edit_message( content="Okay, sifting through your cards...", view=None ) cardset_embeds = await paperdex_cardset_embed( team=await get_team_by_owner(interaction.user.id), this_cardset=c_query ) await embed_pagination(cardset_embeds, interaction.channel, interaction.user) class SelectPaperdexTeam(discord.ui.Select): def __init__(self, which: Literal["AL", "NL"]): self.which = which if which == "AL": options = [discord.SelectOption(label=team) for team in AL_TEAMS] else: # Handle St. Louis Cardinals display name options = [ discord.SelectOption( label="St. Louis Cardinals" if team == "St Louis Cardinals" else team ) for team in NL_TEAMS ] super().__init__(placeholder=f"Select an {which} team", options=options) async def callback(self, interaction: discord.Interaction): # Import here to avoid circular imports from api_calls import db_get from helpers import get_team_by_owner, paperdex_team_embed, embed_pagination team_id = _get_team_id(self.values[0], self.which) if team_id is None: raise ValueError(f"Unknown team: {self.values[0]}") t_query = await db_get("teams", object_id=team_id, none_okay=False) await interaction.response.edit_message( content="Okay, sifting through your cards...", view=None ) team_embeds = await paperdex_team_embed( team=await get_team_by_owner(interaction.user.id), mlb_team=t_query ) await embed_pagination(team_embeds, interaction.channel, interaction.user) class SelectBuyPacksCardset(discord.ui.Select): def __init__( self, team: dict, quantity: int, pack_type_id: int, pack_embed: discord.Embed, cost: int, ): options = [ discord.SelectOption(label="2005 Live"), discord.SelectOption(label="2025 Season"), discord.SelectOption(label="1998 Season"), discord.SelectOption(label="Pokemon - Brilliant Stars"), discord.SelectOption(label="2024 Season"), discord.SelectOption(label="2023 Season"), discord.SelectOption(label="2022 Season"), discord.SelectOption(label="2021 Season"), discord.SelectOption(label="2019 Season"), discord.SelectOption(label="2018 Season"), discord.SelectOption(label="2016 Season"), discord.SelectOption(label="2013 Season"), discord.SelectOption(label="2012 Season"), discord.SelectOption(label="2008 Season"), ] self.team = team self.quantity = quantity self.pack_type_id = pack_type_id self.pack_embed = pack_embed self.cost = cost super().__init__(placeholder="Select a Cardset", options=options) async def callback(self, interaction: discord.Interaction): # Import here to avoid circular imports from api_calls import db_post from discord_ui.confirmations import Confirm logger.info(f"SelectBuyPacksCardset - selection: {self.values[0]}") cardset_id = CARDSET_LABELS_TO_IDS.get(self.values[0]) if cardset_id is None: raise ValueError(f"Unknown cardset: {self.values[0]}") if self.values[0] == "Pokemon - Brilliant Stars": self.pack_embed.set_image(url=IMAGES["pack-pkmnbs"]) self.pack_embed.description = ( f"{self.pack_embed.description} - {self.values[0]}" ) view = Confirm(responders=[interaction.user], timeout=30) await interaction.response.edit_message( content=None, embed=self.pack_embed, view=None ) question = await interaction.channel.send( content=f"Your Wallet: {self.team['wallet']}₼\n" f"Pack{'s' if self.quantity > 1 else ''} Price: {self.cost}₼\n" f"After Purchase: {self.team['wallet'] - self.cost}₼\n\n" f"Would you like to make this purchase?", view=view, ) await view.wait() if not view.value: await question.edit(content="Saving that money. Smart.", view=None) return p_model = { "team_id": self.team["id"], "pack_type_id": self.pack_type_id, "pack_cardset_id": cardset_id, } await db_post( "packs", payload={"packs": [p_model for x in range(self.quantity)]} ) await db_post(f"teams/{self.team['id']}/money/-{self.cost}") await question.edit( content=f"{'They are' if self.quantity > 1 else 'It is'} all yours! Go rip 'em with `/open-packs`", view=None, ) class SelectBuyPacksTeam(discord.ui.Select): def __init__( self, which: Literal["AL", "NL"], team: dict, quantity: int, pack_type_id: int, pack_embed: discord.Embed, cost: int, ): self.which = which self.team = team self.quantity = quantity self.pack_type_id = pack_type_id self.pack_embed = pack_embed self.cost = cost if which == "AL": options = [discord.SelectOption(label=team) for team in AL_TEAMS] else: # Handle St. Louis Cardinals display name options = [ discord.SelectOption( label="St. Louis Cardinals" if team == "St Louis Cardinals" else team ) for team in NL_TEAMS ] super().__init__(placeholder=f"Select an {which} team", options=options) async def callback(self, interaction: discord.Interaction): # Import here to avoid circular imports from api_calls import db_post from discord_ui.confirmations import Confirm team_id = _get_team_id(self.values[0], self.which) if team_id is None: raise ValueError(f"Unknown team: {self.values[0]}") self.pack_embed.description = ( f"{self.pack_embed.description} - {self.values[0]}" ) view = Confirm(responders=[interaction.user], timeout=30) await interaction.response.edit_message( content=None, embed=self.pack_embed, view=None ) question = await interaction.channel.send( content=f"Your Wallet: {self.team['wallet']}₼\n" f"Pack{'s' if self.quantity > 1 else ''} Price: {self.cost}₼\n" f"After Purchase: {self.team['wallet'] - self.cost}₼\n\n" f"Would you like to make this purchase?", view=view, ) await view.wait() if not view.value: await question.edit(content="Saving that money. Smart.", view=None) return p_model = { "team_id": self.team["id"], "pack_type_id": self.pack_type_id, "pack_team_id": team_id, } await db_post( "packs", payload={"packs": [p_model for x in range(self.quantity)]} ) await db_post(f"teams/{self.team['id']}/money/-{self.cost}") await question.edit( content=f"{'They are' if self.quantity > 1 else 'It is'} all yours! Go rip 'em with `/open-packs`", view=None, ) class SelectUpdatePlayerTeam(discord.ui.Select): def __init__( self, which: Literal["AL", "NL"], player: dict, reporting_team: dict, bot ): self.bot = bot self.which = which self.player = player self.reporting_team = reporting_team if which == "AL": options = [discord.SelectOption(label=team) for team in AL_TEAMS] else: # Handle St. Louis Cardinals display name options = [ discord.SelectOption( label="St. Louis Cardinals" if team == "St Louis Cardinals" else team ) for team in NL_TEAMS ] super().__init__(placeholder=f"Select an {which} team", options=options) async def callback(self, interaction: discord.Interaction): # Import here to avoid circular imports from api_calls import db_patch, db_post from discord_ui.confirmations import Confirm from helpers import player_desc, send_to_channel # Check if already assigned - compare against both normalized franchise and full mlbclub normalized_selection = normalize_franchise(self.values[0]) if ( normalized_selection == self.player["franchise"] or self.values[0] == self.player["mlbclub"] ): await interaction.response.send_message( content=f"Thank you for the help, but it looks like somebody beat you to it! " f"**{player_desc(self.player)}** is already assigned to the **{self.player['mlbclub']}**." ) return view = Confirm(responders=[interaction.user], timeout=15) await interaction.response.edit_message( content=f"Should I update **{player_desc(self.player)}**'s team to the **{self.values[0]}**?", view=None, ) question = await interaction.channel.send(content=None, view=view) await view.wait() if not view.value: await question.edit( content="That didnt't sound right to me, either. Let's not touch that.", view=None, ) return else: await question.delete() await db_patch( "players", object_id=self.player["player_id"], params=[ ("mlbclub", self.values[0]), ("franchise", normalize_franchise(self.values[0])), ], ) await db_post(f"teams/{self.reporting_team['id']}/money/25") await send_to_channel( self.bot, "pd-news-ticker", content=f"{interaction.user.name} just updated **{player_desc(self.player)}**'s team to the " f"**{self.values[0]}**", ) await interaction.channel.send("All done!") class SelectView(discord.ui.View): def __init__(self, select_objects: list[discord.ui.Select], timeout: float = 300.0): super().__init__(timeout=timeout) for x in select_objects: self.add_item(x)