fix: prevent crash when Check-In Player packs appear in open-packs menu
All checks were successful
Build Docker Image / build (pull_request) Successful in 3m17s

Check-In Player packs (auto-opened by daily check-in) could end up orphaned
in inventory if roll_for_cards failed. The open-packs command crashed because:
1. The hyphenated pack type name bypassed the pretty_name logic, producing an
   empty select menu that Discord rejected (400 Bad Request)
2. Even if displayed, selecting it would raise KeyError in the callback since
   "Check-In Player".split("-") doesn't match any known pack type token

Fixes:
- Filter auto-open pack types out of the manual open-packs menu
- Add fallback for hyphenated pack type names in pretty_name logic
- Replace KeyError with graceful user-facing message for unknown pack types
- Change "contact an admin" to "contact Cal" in all user-facing messages

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2026-03-26 08:45:51 -05:00
parent eb17b17dd4
commit 01f6fb50d5
2 changed files with 591 additions and 417 deletions

View File

@ -1,4 +1,4 @@
# Economy Packs Module # Economy Packs Module
# Contains pack opening, daily rewards, and donation commands from the original economy.py # Contains pack opening, daily rewards, and donation commands from the original economy.py
import logging import logging
@ -9,97 +9,135 @@ import datetime
# Import specific utilities needed by this module # Import specific utilities needed by this module
import random import random
from api_calls import db_get, db_post, db_patch from api_calls import db_get, db_post
from helpers.constants import PD_PLAYERS_ROLE_NAME, PD_PLAYERS, IMAGES from helpers.constants import PD_PLAYERS_ROLE_NAME, PD_PLAYERS, IMAGES
from helpers import ( from helpers import (
get_team_by_owner, display_cards, give_packs, legal_channel, get_channel, get_team_by_owner,
get_cal_user, refresh_sheet, roll_for_cards, int_timestamp, get_context_user display_cards,
give_packs,
legal_channel,
get_channel,
get_cal_user,
refresh_sheet,
roll_for_cards,
int_timestamp,
get_context_user,
) )
from helpers.discord_utils import get_team_embed, send_to_channel, get_emoji from helpers.discord_utils import get_team_embed, get_emoji
from discord_ui import SelectView, SelectOpenPack from discord_ui import SelectView, SelectOpenPack
logger = logging.getLogger('discord_app') logger = logging.getLogger("discord_app")
class Packs(commands.Cog): class Packs(commands.Cog):
"""Pack management, daily rewards, and donation system for Paper Dynasty.""" """Pack management, daily rewards, and donation system for Paper Dynasty."""
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
@commands.hybrid_group(name='donation', help='Mod: Give packs for PD donations') @commands.hybrid_group(name="donation", help="Mod: Give packs for PD donations")
@commands.has_any_role(PD_PLAYERS_ROLE_NAME) @commands.has_any_role(PD_PLAYERS_ROLE_NAME)
async def donation(self, ctx: commands.Context): async def donation(self, ctx: commands.Context):
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
await ctx.send('To buy packs, visit https://ko-fi.com/manticorum/shop and include your discord username!') await ctx.send(
"To buy packs, visit https://ko-fi.com/manticorum/shop and include your discord username!"
)
@donation.command(name='premium', help='Mod: Give premium packs', aliases=['p', 'prem']) @donation.command(
name="premium", help="Mod: Give premium packs", aliases=["p", "prem"]
)
async def donation_premium(self, ctx: commands.Context, num_packs: int, gm: Member): async def donation_premium(self, ctx: commands.Context, num_packs: int, gm: Member):
if ctx.author.id != self.bot.owner_id: if ctx.author.id != self.bot.owner_id:
await ctx.send('Wait a second. You\'re not in charge here!') await ctx.send("Wait a second. You're not in charge here!")
return return
team = await get_team_by_owner(gm.id) team = await get_team_by_owner(gm.id)
p_query = await db_get('packtypes', params=[('name', 'Premium')]) p_query = await db_get("packtypes", params=[("name", "Premium")])
if p_query['count'] == 0: if p_query["count"] == 0:
await ctx.send('Oof. I couldn\'t find a Premium Pack') await ctx.send("Oof. I couldn't find a Premium Pack")
return return
total_packs = await give_packs(team, num_packs, pack_type=p_query['packtypes'][0]) total_packs = await give_packs(
await ctx.send(f'The {team["lname"]} now have {total_packs["count"]} total packs!') team, num_packs, pack_type=p_query["packtypes"][0]
)
await ctx.send(
f"The {team['lname']} now have {total_packs['count']} total packs!"
)
@donation.command(name='standard', help='Mod: Give standard packs', aliases=['s', 'sta']) @donation.command(
async def donation_standard(self, ctx: commands.Context, num_packs: int, gm: Member): name="standard", help="Mod: Give standard packs", aliases=["s", "sta"]
)
async def donation_standard(
self, ctx: commands.Context, num_packs: int, gm: Member
):
if ctx.author.id != self.bot.owner_id: if ctx.author.id != self.bot.owner_id:
await ctx.send('Wait a second. You\'re not in charge here!') await ctx.send("Wait a second. You're not in charge here!")
return return
team = await get_team_by_owner(gm.id) team = await get_team_by_owner(gm.id)
p_query = await db_get('packtypes', params=[('name', 'Standard')]) p_query = await db_get("packtypes", params=[("name", "Standard")])
if p_query['count'] == 0: if p_query["count"] == 0:
await ctx.send('Oof. I couldn\'t find a Standard Pack') await ctx.send("Oof. I couldn't find a Standard Pack")
return return
total_packs = await give_packs(team, num_packs, pack_type=p_query['packtypes'][0]) total_packs = await give_packs(
await ctx.send(f'The {team["lname"]} now have {total_packs["count"]} total packs!') team, num_packs, pack_type=p_query["packtypes"][0]
)
await ctx.send(
f"The {team['lname']} now have {total_packs['count']} total packs!"
)
@commands.hybrid_command(name='lastpack', help='Replay your last pack') @commands.hybrid_command(name="lastpack", help="Replay your last pack")
@commands.check(legal_channel) @commands.check(legal_channel)
@commands.has_any_role(PD_PLAYERS_ROLE_NAME) @commands.has_any_role(PD_PLAYERS_ROLE_NAME)
async def last_pack_command(self, ctx: commands.Context): async def last_pack_command(self, ctx: commands.Context):
team = await get_team_by_owner(get_context_user(ctx).id) team = await get_team_by_owner(get_context_user(ctx).id)
if not team: if not team:
await ctx.send(f'I don\'t see a team for you, yet. You can sign up with the `/newteam` command!') await ctx.send(
"I don't see a team for you, yet. You can sign up with the `/newteam` command!"
)
return return
p_query = await db_get( p_query = await db_get(
'packs', "packs",
params=[('opened', True), ('team_id', team['id']), ('new_to_old', True), ('limit', 1)] params=[
("opened", True),
("team_id", team["id"]),
("new_to_old", True),
("limit", 1),
],
) )
if not p_query['count']: if not p_query["count"]:
await ctx.send(f'I do not see any packs for you, bub.') await ctx.send("I do not see any packs for you, bub.")
return return
pack_name = p_query['packs'][0]['pack_type']['name'] pack_name = p_query["packs"][0]["pack_type"]["name"]
if pack_name == 'Standard': if pack_name == "Standard":
pack_cover = IMAGES['pack-sta'] pack_cover = IMAGES["pack-sta"]
elif pack_name == 'Premium': elif pack_name == "Premium":
pack_cover = IMAGES['pack-pre'] pack_cover = IMAGES["pack-pre"]
else: else:
pack_cover = None pack_cover = None
c_query = await db_get( c_query = await db_get("cards", params=[("pack_id", p_query["packs"][0]["id"])])
'cards', if not c_query["count"]:
params=[('pack_id', p_query['packs'][0]['id'])] await ctx.send("Hmm...I didn't see any cards in that pack.")
)
if not c_query['count']:
await ctx.send(f'Hmm...I didn\'t see any cards in that pack.')
return return
await display_cards(c_query['cards'], team, ctx.channel, ctx.author, self.bot, pack_cover=pack_cover) await display_cards(
c_query["cards"],
team,
ctx.channel,
ctx.author,
self.bot,
pack_cover=pack_cover,
)
@app_commands.command(name='comeonmanineedthis', description='Daily check-in for cards, currency, and packs') @app_commands.command(
name="comeonmanineedthis",
description="Daily check-in for cards, currency, and packs",
)
@commands.has_any_role(PD_PLAYERS) @commands.has_any_role(PD_PLAYERS)
@commands.check(legal_channel) @commands.check(legal_channel)
async def daily_checkin(self, interaction: discord.Interaction): async def daily_checkin(self, interaction: discord.Interaction):
@ -107,97 +145,127 @@ class Packs(commands.Cog):
team = await get_team_by_owner(interaction.user.id) team = await get_team_by_owner(interaction.user.id)
if not team: if not team:
await interaction.edit_original_response( await interaction.edit_original_response(
content=f'I don\'t see a team for you, yet. You can sign up with the `/newteam` command!' content="I don't see a team for you, yet. You can sign up with the `/newteam` command!"
) )
return return
current = await db_get('current') current = await db_get("current")
now = datetime.datetime.now() now = datetime.datetime.now()
midnight = int_timestamp(datetime.datetime(now.year, now.month, now.day, 0, 0, 0)) midnight = int_timestamp(
daily = await db_get('rewards', params=[ datetime.datetime(now.year, now.month, now.day, 0, 0, 0)
('name', 'Daily Check-in'), ('team_id', team['id']), ('created_after', midnight) )
]) daily = await db_get(
logger.debug(f'midnight: {midnight} / now: {int_timestamp(now)}') "rewards",
logger.debug(f'daily_return: {daily}') params=[
("name", "Daily Check-in"),
("team_id", team["id"]),
("created_after", midnight),
],
)
logger.debug(f"midnight: {midnight} / now: {int_timestamp(now)}")
logger.debug(f"daily_return: {daily}")
if daily: if daily:
await interaction.edit_original_response( await interaction.edit_original_response(
content=f'Looks like you already checked in today - come back at midnight Central!' content="Looks like you already checked in today - come back at midnight Central!"
) )
return return
await db_post('rewards', payload={ await db_post(
'name': 'Daily Check-in', 'team_id': team['id'], 'season': current['season'], 'week': current['week'], "rewards",
'created': int_timestamp(now) payload={
}) "name": "Daily Check-in",
current = await db_get('current') "team_id": team["id"],
check_ins = await db_get('rewards', params=[ "season": current["season"],
('name', 'Daily Check-in'), ('team_id', team['id']), ('season', current['season']) "week": current["week"],
]) "created": int_timestamp(now),
},
)
current = await db_get("current")
check_ins = await db_get(
"rewards",
params=[
("name", "Daily Check-in"),
("team_id", team["id"]),
("season", current["season"]),
],
)
check_count = check_ins['count'] % 5 check_count = check_ins["count"] % 5
# 2nd, 4th, and 5th check-ins # 2nd, 4th, and 5th check-ins
if check_count == 0 or check_count % 2 == 0: if check_count == 0 or check_count % 2 == 0:
# Every fifth check-in # Every fifth check-in
if check_count == 0: if check_count == 0:
greeting = await interaction.edit_original_response( greeting = await interaction.edit_original_response(
content=f'Hey, you just earned a Standard pack of cards!' content="Hey, you just earned a Standard pack of cards!"
) )
pack_channel = get_channel(interaction, 'pack-openings') pack_channel = get_channel(interaction, "pack-openings")
p_query = await db_get('packtypes', params=[('name', 'Standard')]) p_query = await db_get("packtypes", params=[("name", "Standard")])
if not p_query: if not p_query:
await interaction.edit_original_response( await interaction.edit_original_response(
content=f'I was not able to pull this pack for you. ' content=f"I was not able to pull this pack for you. "
f'Maybe ping {get_cal_user(interaction).mention}?' f"Maybe ping {get_cal_user(interaction).mention}?"
) )
return return
# Every second and fourth check-in # Every second and fourth check-in
else: else:
greeting = await interaction.edit_original_response( greeting = await interaction.edit_original_response(
content=f'Hey, you just earned a player card!' content="Hey, you just earned a player card!"
) )
pack_channel = interaction.channel pack_channel = interaction.channel
p_query = await db_get('packtypes', params=[('name', 'Check-In Player')]) p_query = await db_get(
"packtypes", params=[("name", "Check-In Player")]
)
if not p_query: if not p_query:
await interaction.edit_original_response( await interaction.edit_original_response(
content=f'I was not able to pull this card for you. ' content=f"I was not able to pull this card for you. "
f'Maybe ping {get_cal_user(interaction).mention}?' f"Maybe ping {get_cal_user(interaction).mention}?"
) )
return return
await give_packs(team, 1, p_query['packtypes'][0]) await give_packs(team, 1, p_query["packtypes"][0])
p_query = await db_get( p_query = await db_get(
'packs', "packs",
params=[('opened', False), ('team_id', team['id']), ('new_to_old', True), ('limit', 1)] params=[
("opened", False),
("team_id", team["id"]),
("new_to_old", True),
("limit", 1),
],
) )
if not p_query['count']: if not p_query["count"]:
await interaction.edit_original_response( await interaction.edit_original_response(
content=f'I do not see any packs in here. {await get_emoji(interaction, "ConfusedPsyduck")}') content=f"I do not see any packs in here. {await get_emoji(interaction, 'ConfusedPsyduck')}"
)
return return
pack_ids = await roll_for_cards(p_query['packs'], extra_val=check_ins['count']) pack_ids = await roll_for_cards(
p_query["packs"], extra_val=check_ins["count"]
)
if not pack_ids: if not pack_ids:
await greeting.edit( await greeting.edit(
content=f'I was not able to create these cards {await get_emoji(interaction, "slight_frown")}' content=f"I was not able to create these cards {await get_emoji(interaction, 'slight_frown')}"
) )
return return
all_cards = [] all_cards = []
for p_id in pack_ids: for p_id in pack_ids:
new_cards = await db_get('cards', params=[('pack_id', p_id)]) new_cards = await db_get("cards", params=[("pack_id", p_id)])
all_cards.extend(new_cards['cards']) all_cards.extend(new_cards["cards"])
if not all_cards: if not all_cards:
await interaction.edit_original_response( await interaction.edit_original_response(
content=f'I was not able to pull these cards {await get_emoji(interaction, "slight_frown")}' content=f"I was not able to pull these cards {await get_emoji(interaction, 'slight_frown')}"
) )
return return
await display_cards(all_cards, team, pack_channel, interaction.user, self.bot) await display_cards(
all_cards, team, pack_channel, interaction.user, self.bot
)
await refresh_sheet(team, self.bot) await refresh_sheet(team, self.bot)
return return
@ -215,87 +283,102 @@ class Packs(commands.Cog):
else: else:
m_reward = 25 m_reward = 25
team = await db_post(f'teams/{team["id"]}/money/{m_reward}') team = await db_post(f"teams/{team['id']}/money/{m_reward}")
await interaction.edit_original_response( await interaction.edit_original_response(
content=f'You just earned {m_reward}₼! That brings your wallet to {team["wallet"]}₼!') content=f"You just earned {m_reward}₼! That brings your wallet to {team['wallet']}₼!"
)
@app_commands.command(name='open-packs', description='Open packs from your inventory') @app_commands.command(
name="open-packs", description="Open packs from your inventory"
)
@app_commands.checks.has_any_role(PD_PLAYERS) @app_commands.checks.has_any_role(PD_PLAYERS)
async def open_packs_slash(self, interaction: discord.Interaction): async def open_packs_slash(self, interaction: discord.Interaction):
if interaction.channel.name in ['paper-dynasty-chat', 'pd-news-ticker', 'pd-network-news']: if interaction.channel.name in [
"paper-dynasty-chat",
"pd-news-ticker",
"pd-network-news",
]:
await interaction.response.send_message( await interaction.response.send_message(
f'Please head to down to {get_channel(interaction, "pd-bot-hole")} to run this command.', f"Please head to down to {get_channel(interaction, 'pd-bot-hole')} to run this command.",
ephemeral=True ephemeral=True,
) )
return return
owner_team = await get_team_by_owner(interaction.user.id) owner_team = await get_team_by_owner(interaction.user.id)
if not owner_team: if not owner_team:
await interaction.response.send_message( await interaction.response.send_message(
f'I don\'t see a team for you, yet. You can sign up with the `/newteam` command!' "I don't see a team for you, yet. You can sign up with the `/newteam` command!"
) )
return return
p_query = await db_get('packs', params=[ p_query = await db_get(
('team_id', owner_team['id']), ('opened', False) "packs", params=[("team_id", owner_team["id"]), ("opened", False)]
]) )
if p_query['count'] == 0: if p_query["count"] == 0:
await interaction.response.send_message( await interaction.response.send_message(
f'Looks like you are clean out of packs, friendo. You can earn them by playing PD games or by ' "Looks like you are clean out of packs, friendo. You can earn them by playing PD games or by "
f'donating to the league.' "donating to the league."
) )
return return
# Pack types that are auto-opened and should not appear in the manual open menu
AUTO_OPEN_TYPES = {"Check-In Player"}
# Group packs by type and customization (e.g. Standard, Standard-Orioles, Standard-2012, Premium) # Group packs by type and customization (e.g. Standard, Standard-Orioles, Standard-2012, Premium)
p_count = 0 p_count = 0
p_data = { p_data = {
'Standard': [], "Standard": [],
'Premium': [], "Premium": [],
'Daily': [], "Daily": [],
'MVP': [], "MVP": [],
'All Star': [], "All Star": [],
'Mario': [], "Mario": [],
'Team Choice': [] "Team Choice": [],
} }
logger.debug(f'Parsing packs...') logger.debug("Parsing packs...")
for pack in p_query['packs']: for pack in p_query["packs"]:
p_group = None p_group = None
logger.debug(f'pack: {pack}') logger.debug(f"pack: {pack}")
logger.debug(f'pack cardset: {pack["pack_cardset"]}') logger.debug(f"pack cardset: {pack['pack_cardset']}")
if pack['pack_team'] is None and pack['pack_cardset'] is None: if pack["pack_type"]["name"] in AUTO_OPEN_TYPES:
p_group = pack['pack_type']['name'] logger.debug(
f"Skipping auto-open pack type: {pack['pack_type']['name']}"
)
continue
if pack["pack_team"] is None and pack["pack_cardset"] is None:
p_group = pack["pack_type"]["name"]
# Add to p_data if this is a new pack type # Add to p_data if this is a new pack type
if p_group not in p_data: if p_group not in p_data:
p_data[p_group] = [] p_data[p_group] = []
elif pack['pack_team'] is not None: elif pack["pack_team"] is not None:
if pack['pack_type']['name'] == 'Standard': if pack["pack_type"]["name"] == "Standard":
p_group = f'Standard-Team-{pack["pack_team"]["id"]}-{pack["pack_team"]["sname"]}' p_group = f"Standard-Team-{pack['pack_team']['id']}-{pack['pack_team']['sname']}"
elif pack['pack_type']['name'] == 'Premium': elif pack["pack_type"]["name"] == "Premium":
p_group = f'Premium-Team-{pack["pack_team"]["id"]}-{pack["pack_team"]["sname"]}' p_group = f"Premium-Team-{pack['pack_team']['id']}-{pack['pack_team']['sname']}"
elif pack['pack_type']['name'] == 'Team Choice': elif pack["pack_type"]["name"] == "Team Choice":
p_group = f'Team Choice-Team-{pack["pack_team"]["id"]}-{pack["pack_team"]["sname"]}' p_group = f"Team Choice-Team-{pack['pack_team']['id']}-{pack['pack_team']['sname']}"
elif pack['pack_type']['name'] == 'MVP': elif pack["pack_type"]["name"] == "MVP":
p_group = f'MVP-Team-{pack["pack_team"]["id"]}-{pack["pack_team"]["sname"]}' p_group = f"MVP-Team-{pack['pack_team']['id']}-{pack['pack_team']['sname']}"
if pack['pack_cardset'] is not None: if pack["pack_cardset"] is not None:
p_group += f'-Cardset-{pack["pack_cardset"]["id"]}' p_group += f"-Cardset-{pack['pack_cardset']['id']}"
elif pack['pack_cardset'] is not None: elif pack["pack_cardset"] is not None:
if pack['pack_type']['name'] == 'Standard': if pack["pack_type"]["name"] == "Standard":
p_group = f'Standard-Cardset-{pack["pack_cardset"]["id"]}-{pack["pack_cardset"]["name"]}' p_group = f"Standard-Cardset-{pack['pack_cardset']['id']}-{pack['pack_cardset']['name']}"
elif pack['pack_type']['name'] == 'Premium': elif pack["pack_type"]["name"] == "Premium":
p_group = f'Premium-Cardset-{pack["pack_cardset"]["id"]}-{pack["pack_cardset"]["name"]}' p_group = f"Premium-Cardset-{pack['pack_cardset']['id']}-{pack['pack_cardset']['name']}"
elif pack['pack_type']['name'] == 'Team Choice': elif pack["pack_type"]["name"] == "Team Choice":
p_group = f'Team Choice-Cardset-{pack["pack_cardset"]["id"]}-{pack["pack_cardset"]["name"]}' p_group = f"Team Choice-Cardset-{pack['pack_cardset']['id']}-{pack['pack_cardset']['name']}"
elif pack['pack_type']['name'] == 'All Star': elif pack["pack_type"]["name"] == "All Star":
p_group = f'All Star-Cardset-{pack["pack_cardset"]["id"]}-{pack["pack_cardset"]["name"]}' p_group = f"All Star-Cardset-{pack['pack_cardset']['id']}-{pack['pack_cardset']['name']}"
elif pack['pack_type']['name'] == 'MVP': elif pack["pack_type"]["name"] == "MVP":
p_group = f'MVP-Cardset-{pack["pack_cardset"]["id"]}-{pack["pack_cardset"]["name"]}' p_group = f"MVP-Cardset-{pack['pack_cardset']['id']}-{pack['pack_cardset']['name']}"
elif pack['pack_type']['name'] == 'Promo Choice': elif pack["pack_type"]["name"] == "Promo Choice":
p_group = f'Promo Choice-Cardset-{pack["pack_cardset"]["id"]}-{pack["pack_cardset"]["name"]}' p_group = f"Promo Choice-Cardset-{pack['pack_cardset']['id']}-{pack['pack_cardset']['name']}"
logger.info(f'p_group: {p_group}') logger.info(f"p_group: {p_group}")
if p_group is not None: if p_group is not None:
p_count += 1 p_count += 1
if p_group not in p_data: if p_group not in p_data:
@ -305,34 +388,41 @@ class Packs(commands.Cog):
if p_count == 0: if p_count == 0:
await interaction.response.send_message( await interaction.response.send_message(
f'Looks like you are clean out of packs, friendo. You can earn them by playing PD games or by ' "Looks like you are clean out of packs, friendo. You can earn them by playing PD games or by "
f'donating to the league.' "donating to the league."
) )
return return
# Display options and ask which group to open # Display options and ask which group to open
embed = get_team_embed(f'Unopened Packs', team=owner_team) embed = get_team_embed("Unopened Packs", team=owner_team)
embed.description = owner_team['lname'] embed.description = owner_team["lname"]
select_options = [] select_options = []
for key in p_data: for key in p_data:
if len(p_data[key]) > 0: if len(p_data[key]) > 0:
pretty_name = None pretty_name = None
# Not a specific pack # Not a specific pack
if '-' not in key: if "-" not in key:
pretty_name = key
elif "Team" in key:
pretty_name = f"{key.split('-')[0]} - {key.split('-')[3]}"
elif "Cardset" in key:
pretty_name = f"{key.split('-')[0]} - {key.split('-')[3]}"
else:
# Pack type name contains a hyphen (e.g. "Check-In Player")
pretty_name = key pretty_name = key
elif 'Team' in key:
pretty_name = f'{key.split("-")[0]} - {key.split("-")[3]}'
elif 'Cardset' in key:
pretty_name = f'{key.split("-")[0]} - {key.split("-")[3]}'
if pretty_name is not None: if pretty_name is not None:
embed.add_field(name=pretty_name, value=f'Qty: {len(p_data[key])}') embed.add_field(name=pretty_name, value=f"Qty: {len(p_data[key])}")
select_options.append(discord.SelectOption(label=pretty_name, value=key)) select_options.append(
discord.SelectOption(label=pretty_name, value=key)
)
view = SelectView(select_objects=[SelectOpenPack(select_options, owner_team)], timeout=15) view = SelectView(
select_objects=[SelectOpenPack(select_options, owner_team)], timeout=15
)
await interaction.response.send_message(embed=embed, view=view) await interaction.response.send_message(embed=embed, view=view)
async def setup(bot): async def setup(bot):
"""Setup function for the Packs cog.""" """Setup function for the Packs cog."""
await bot.add_cog(Packs(bot)) await bot.add_cog(Packs(bot))

View File

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