Users were receiving "This interaction failed" when trying to open Team Choice packs that had neither pack_team nor pack_cardset assigned in the database. The previous fix only handled packs WITH cardset but WITHOUT team. This adds handling for completely unconfigured Team Choice packs. Now shows a helpful error message: "This Team Choice pack needs to be assigned a team and cardset. Please contact an admin to configure this pack." This prevents the KeyError exception that was being thrown at helpers.py:1692 when open_choice_pack() received a pack with pack_team=None. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
499 lines
20 KiB
Python
499 lines
20 KiB
Python
"""
|
|
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
|
|
|
|
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,
|
|
'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}')
|
|
raise ValueError(f'Unable to open packs')
|
|
|
|
# Open the packs
|
|
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)
|
|
|
|
|
|
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
|
|
|
|
if self.values[0] == 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', 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) |