""" 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(f'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: raise KeyError(f'Cannot identify pack details: {pack_vals}') # 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 an admin 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 an admin.', 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 an admin. 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=f'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=f'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(f'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)