import asyncio import datetime import logging import math import os import random import traceback import discord import pygsheets import requests from discord.ext import commands from db_calls import * from bs4 import BeautifulSoup from difflib import get_close_matches from dataclasses import dataclass from typing import Optional, Literal SBA_SEASON = 9 PD_SEASON = 7 LIVE_CARDSET_ID = 17 LIVE_PROMO_CARDSET_ID = 18 SBA_COLOR = 'a6ce39' PD_PLAYERS = 'Paper Dynasty Players' SBA_PLAYERS_ROLE_NAME = f'Season {SBA_SEASON} Players' PD_PLAYERS_ROLE_NAME = f'Paper Dynasty Players' PD_CARD_URL = 'https://sombaseball.ddns.net/cards/pd' RATINGS_BATTER_FORMULA = '=IMPORTRANGE("1zDmlOw94gTzOAjqOpNdDZsg0O6rxNWkL4-XT6-iL2IE","guide_Batters!A1:CD")' RATINGS_PITCHER_FORMULA = '=IMPORTRANGE("1zDmlOw94gTzOAjqOpNdDZsg0O6rxNWkL4-XT6-iL2IE","guide_Pitchers!A1:BQ")' RATINGS_SHEET_KEY = '1zDmlOw94gTzOAjqOpNdDZsg0O6rxNWkL4-XT6-iL2IE' ALL_MLB_TEAMS = { 'Arizona Diamondbacks': ['ARI', 'Diamondbacks'], 'Atlanta Braves': ['ATL', 'MLN', 'Braves'], 'Baltimore Orioles': ['BAL', 'Orioles'], 'Boston Red Sox': ['BOS', 'Red Sox'], 'Chicago Cubs': ['CHC', 'Cubs'], 'Chicago White Sox': ['CHW', 'White Sox'], 'Cincinnati Reds': ['CIN', 'Reds'], 'Cleveland Guardians': ['CLE', 'Guardians'], 'Colorado Rockies': ['COL', 'Rockies'], 'Detroit Tigers': ['DET', 'Tigers'], 'Houston Astros': ['HOU', 'Astros'], 'Kansas City Royals': ['KCR', 'Royals'], 'Los Angeles Angels': ['LAA', 'CAL', 'Angels'], 'Los Angeles Dodgers': ['LAD', 'Dodgers'], 'Miami Marlins': ['MIA', 'Marlins'], 'Milwaukee Brewers': ['MIL', 'MKE', 'Brewers'], 'Minnesota Twins': ['MIN', 'Twins'], 'New York Mets': ['NYM', 'Mets'], 'New York Yankees': ['NYY', 'Yankees'], 'Oakland Athletics': ['OAK', 'Athletics'], 'Philadelphia Phillies': ['PHI', 'Phillies'], 'Pittsburgh Pirates': ['PIT', 'Pirates'], 'San Diego Padres': ['SDP', 'Padres'], 'Seattle Mariners': ['SEA', 'Mariners'], 'San Francisco Giants': ['SFG', 'Giants'], 'St Louis Cardinals': ['STL', 'Cardinals'], 'Tampa Bay Rays': ['TBR', 'Rays'], 'Texas Rangers': ['TEX', 'Senators', 'Rangers'], 'Toronto Blue Jays': ['TOR', 'Jays'], 'Washington Nationals': ['WSN', 'WAS', 'Nationals'], } IMAGES = { 'logo': 'https://sombaseball.ddns.net/static/images/sba-logo.png', 'mvp-hype': f'{PD_CARD_URL}/mvp.png', 'pack-sta': f'{PD_CARD_URL}/pack-standard.png', 'pack-pre': f'{PD_CARD_URL}/pack-premium.png', 'pack-mar': f'{PD_CARD_URL}/sluggers/mario-gauntlet.png', 'mvp': { 'Arizona Diamondbacks': f'{PD_CARD_URL}/mvp/arizona-diamondbacks.gif', 'Atlanta Braves': f'{PD_CARD_URL}/mvp/atlanta-braves.gif', 'Baltimore Orioles': f'{PD_CARD_URL}/mvp/baltimore-orioles.gif', 'Boston Red Sox': f'{PD_CARD_URL}/mvp/boston-red-sox.gif', 'Chicago Cubs': f'{PD_CARD_URL}/mvp/chicago-cubs.gif', 'Chicago White Sox': f'{PD_CARD_URL}/mvp/chicago-white-sox.gif', 'Cincinnati Reds': f'{PD_CARD_URL}/mvp/cincinnati-reds.gif', 'Cleveland Indians': f'{PD_CARD_URL}/mvp/cleveland-guardians.gif', 'Cleveland Guardians': f'{PD_CARD_URL}/mvp/cleveland-guardians.gif', 'Colorado Rockies': f'{PD_CARD_URL}/mvp/colorado-rockies.gif', 'Detroit Tigers': f'{PD_CARD_URL}/mvp/detroit-tigers.gif', 'Houston Astros': f'{PD_CARD_URL}/mvp/houston-astros.gif', 'Kansas City Royals': f'{PD_CARD_URL}/mvp/kansas-city-royals.gif', 'Los Angeles Angels': f'{PD_CARD_URL}/mvp/los-angeles-angels.gif', 'Los Angeles Dodgers': f'{PD_CARD_URL}/mvp/los-angeles-dodgers.gif', 'Miami Marlins': f'{PD_CARD_URL}/mvp/miami-marlins.gif', 'Milwaukee Brewers': f'{PD_CARD_URL}/mvp/milwaukee-brewers.gif', 'Minnesota Twins': f'{PD_CARD_URL}/mvp/minnesota-twins.gif', 'New York Mets': f'{PD_CARD_URL}/mvp/new-york-mets.gif', 'New York Yankees': f'{PD_CARD_URL}/mvp/new-york-yankees.gif', 'Oakland Athletics': f'{PD_CARD_URL}/mvp/oakland-athletics.gif', 'Philadelphia Phillies': f'{PD_CARD_URL}/mvp/philadelphia-phillies.gif', 'Pittsburgh Pirates': f'{PD_CARD_URL}/mvp/pittsburgh-pirates.gif', 'San Diego Padres': f'{PD_CARD_URL}/mvp/san-diego-padres.gif', 'Seattle Mariners': f'{PD_CARD_URL}/mvp/seattle-mariners.gif', 'San Francisco Giants': f'{PD_CARD_URL}/mvp/san-francisco-giants.gif', 'St Louis Cardinals': f'{PD_CARD_URL}/mvp/st-louis-cardinals.gif', 'St. Louis Cardinals': f'{PD_CARD_URL}/mvp/st-louis-cardinals.gif', 'Tampa Bay Rays': f'{PD_CARD_URL}/mvp/tampa-bay-rays.gif', 'Texas Rangers': f'{PD_CARD_URL}/mvp/texas-rangers.gif', 'Toronto Blue Jays': f'{PD_CARD_URL}/mvp/toronto-blue-jays.gif', 'Washington Nationals': f'{PD_CARD_URL}/mvp/washington-nationals.gif', 'Junior All Stars': f'{PD_CARD_URL}/mvp.png', 'Mario Super Sluggers': f'{PD_CARD_URL}/mvp.png' }, 'gauntlets': f'{PD_CARD_URL}/gauntlets.png' } INFIELD_X_CHART = { 'si1': { 'rp': 'No runner on first: Batter is safe at first and no one covers second. Batter to second, runners only ' 'advance 1 base.\nRunner on first: batter singles, runners advance 1 base.', 'e1': 'Single and Error, batter to second, runners advance 2 bases.', 'e2': 'Single and Error, batter to third, all runners score.', 'no': 'Single, runners advance 1 base.' }, 'po': { 'rp': 'The batters hits a popup. None of the fielders take charge on the play and the ball drops in the ' 'infield for a SI1! All runners advance 1 base.', 'e1': 'The catcher drops a popup for an error. All runners advance 1 base.', 'e2': 'The catcher grabs a squib in front of the plate and throws it into right field. The batter goes to ' 'second and all runners score.', 'no': 'The batter pops out to the catcher.' }, 'fo': { 'rp': 'Batter swings and misses, but is awarded first base on a catcher interference call! One base error, ' 'baserunners advance only if forced.', 'e1': 'The catcher drops a foul popup for an error. Batter rolls AB again.', 'e2': 'The catcher drops a foul popup for an error. Batter rolls AB again.', 'no': 'Runner(s) on base: make a passed ball check. If no passed ball, batter pops out to the catcher. If a ' 'passed ball occurs, batter roll his AB again.\nNo runners: batter pops out to the catcher' }, 'g1': { 'rp': 'Runner on first, <2 outs: runner on first breaks up the double play, gbB\n' 'Else: gbA', 'e1': 'Error, batter to first, runners advance 1 base.', 'e2': 'Error, batter to second, runners advance 2 bases.', 'no': 'Consult Groundball Chart: `!gbA`' }, 'g2': { 'rp': 'Runner(s) on base: fielder makes bad throw for lead runner but batter is out at first for a gbC\n' 'No runners: gbB', 'e1': 'Error, batter to first, runners advance 1 base.', 'e2': 'Error, batter to second, runners advance 2 bases.', 'no': 'Consult Groundball Chart: `!gbB`' }, 'g3': { 'rp': 'Runner(s) on base: fielder checks the runner before throwing to first and allows a SI*\n' 'No runners: gbC', 'e1': 'Error, batter to first, runners advance 1 base.', 'e2': 'Error, batter to second, runners advance 2 bases.', 'no': 'Consult Groundball Chart: `!gbC`' }, 'spd': { 'rp': 'Catcher throws to first and hits the batter-runner in the back, SI1', 'e1': 'Error, batter to first, runners advance 1 base.', 'e2': 'Error, batter to second, runners advance 2 bases.', 'no': 'Speed check, Batter\'s safe range = Running; if safe, SI*; if out, gbC' }, } OUTFIELD_X_CHART = { 'si2': { 'rp': 'Batter singles, baserunners advance 2 bases. As the batter rounds first, the fielder throws behind him ' 'and catches him off the bag for an out!', 'e1': 'Single and error, batter to second, runners advance 2 bases.', 'e2': 'Single and error, batter to third, all runners score.', 'e3': 'Single and error, batter to third, all runners score', 'no': 'Single, all runners advance 2 bases.' }, 'do2': { 'rp': 'Batter doubles and runners advance three bases, but batter-runner is caught between second and third! ' 'He is tagged out in the rundown.', 'e1': 'Double and error, batter to third, all runners score.', 'e2': 'Double and error, batter to third, all runners score.', 'e3': 'Double and error, batter and all runners score. Little league home run!', 'no': 'Double, all runners advance 2 bases.' }, 'do3': { 'rp': 'Batter doubles and runners advance three bases, but batter-runner is caught between second and third! ' 'He is tagged out in the rundown.', 'e1': 'Double and error, batter to third, all runners score.', 'e2': 'Double and error, batter and all runners score. Little league home run!', 'e3': 'Double and error, batter and all runners score. Little league home run!', 'no': 'Double, all runners score.' }, 'tr3': { 'rp': 'Batter hits a ball into the gap and the outfielders collide trying to make the play! The ball rolls to ' 'the wall and the batter trots home with an inside-the-park home run!', 'e1': 'Triple and error, batter and all runners score. Little league home run!', 'e2': 'Triple and error, batter and all runners score. Little league home run!', 'e3': 'Triple and error, batter and all runners score. Little league home run!', 'no': 'Triple, all runners score.' }, 'f1': { 'rp': 'The outfielder races back and makes a diving catch and collides with the wall! In the time he takes to ' 'recuperate, all baserunners tag-up and advance 2 bases.', 'e1': '1 base error, runners advance 1 base.', 'e2': '2 base error, runners advance 2 bases.', 'e3': '3 base error, batter to third, all runners score.', 'no': 'Flyball A' }, 'f2': { 'rp': 'The outfielder catches the flyball for an out. If there is a runner on third, he tags-up and scores. ' 'The play is appealed and the umps rule that the runner left early and is out on the appeal!', 'e1': '1 base error, runners advance 1 base.', 'e2': '2 base error, runners advance 2 bases.', 'e3': '3 base error, batter to third, all runners score.', 'no': 'Flyball B' }, 'f3': { 'rp': 'The outfielder makes a running catch in the gap! The lead runner lost track of the ball and was ' 'advancing - he cannot return in time and is doubled off by the outfielder.', 'e1': '1 base error, runners advance 1 base.', 'e2': '2 base error, runners advance 2 bases.', 'e3': '3 base error, batter to third, all runners score.', 'no': 'Flyball C' } } RARITY = { 'HoF': 8, 'MVP': 5, 'All-Star': 3, 'Starter': 2, 'Reserve': 1, 'Replacement': 0 } SELECT_CARDSET_OPTIONS = [ discord.SelectOption(label='2024 Live', value='17'), discord.SelectOption(label='2024 Promos', value='18'), discord.SelectOption(label='2023 Season', value='9'), discord.SelectOption(label='2023 Promos', value='10'), discord.SelectOption(label='2022 Season', value='3'), discord.SelectOption(label='2022 Promos', value='4'), discord.SelectOption(label='2021 Season', value='1'), discord.SelectOption(label='2019 Season', value='5'), discord.SelectOption(label='2018 Season', value='13'), discord.SelectOption(label='2018 Promos', value='14'), discord.SelectOption(label='2016 Season', value='11'), discord.SelectOption(label='2013 Season', value='6'), discord.SelectOption(label='2012 Season', value='7') ] class Question: def __init__(self, bot, channel, prompt, qtype, timeout, embed=None): """ Version 0.4 :param bot, discord bot object :param channel, discord Channel object :param prompt, string, prompt message to post :param qtype, string, 'yesno', 'int', 'float', 'text', or 'url' :param timeout, float, time to wait for a response """ if not prompt and not embed: raise TypeError('prompt and embed may not both be None') self.bot = bot self.channel = channel self.prompt = prompt self.qtype = qtype self.timeout = timeout self.embed = embed async def ask(self, responders: list): """ Params: responder, list of discord User objects Returns: True/False if confirm question; full response otherwise """ yes = ['yes', 'y', 'ye', 'yee', 'yerp', 'yep', 'yeet', 'yip', 'yup', 'yarp', 'si'] no = ['no', 'n', 'nope', 'nah', 'nyet', 'nein'] if type(responders) is not list: raise TypeError('Responders must be a list of Members') def yesno(mes): return mes.channel == self.channel and mes.author in responders and mes.content.lower() in yes + no def text(mes): return mes.channel == self.channel and mes.author in responders await self.channel.send(content=self.prompt, embed=self.embed) try: resp = await self.bot.wait_for( 'message', timeout=self.timeout, check=yesno if self.qtype == 'yesno' else text ) except asyncio.TimeoutError: return None except Exception as e: await self.channel.send(f'Yuck, do you know what this means?\n\n{e}') return None if self.qtype == 'yesno': if resp.content.lower() in yes: return True else: return False elif self.qtype == 'int': return int(resp.content) elif self.qtype == 'float': return float(resp.content) elif self.qtype == 'url': if resp.content[:7] == 'http://' or resp.content[:8] == 'https://': return resp.content else: return False else: return resp.content class Confirm(discord.ui.View): def __init__(self, responders: list, timeout: float = 300.0, label_type: Literal['yes', 'confirm'] = None): super().__init__(timeout=timeout) if not isinstance(responders, list): raise TypeError('responders must be a list') self.value = None self.responders = responders if label_type == 'yes': self.confirm.label = 'Yes' self.cancel.label = 'No' # When the confirm button is pressed, set the inner value to `True` and # stop the View from listening to more input. # We also send the user an ephemeral message that we're confirming their choice. @discord.ui.button(label='Confirm', style=discord.ButtonStyle.green) async def confirm(self, interaction: discord.Interaction, button: discord.ui.Button): if interaction.user not in self.responders: return self.value = True self.clear_items() self.stop() # This one is similar to the confirmation button except sets the inner value to `False` @discord.ui.button(label='Cancel', style=discord.ButtonStyle.grey) async def cancel(self, interaction: discord.Interaction, button: discord.ui.Button): if interaction.user not in self.responders: return self.value = False self.clear_items() self.stop() class ButtonOptions(discord.ui.View): def __init__(self, responders: list, timeout: float = 300.0, labels=None): super().__init__(timeout=timeout) if not isinstance(responders, list): raise TypeError('responders must be a list') self.value = None self.responders = responders self.options = labels for count, x in enumerate(labels): if count == 0: self.option1.label = x if x is None or x.lower() == 'na' or x == 'N/A': self.remove_item(self.option1) if count == 1: self.option2.label = x if x is None or x.lower() == 'na' or x == 'N/A': self.remove_item(self.option2) if count == 2: self.option3.label = x if x is None or x.lower() == 'na' or x == 'N/A': self.remove_item(self.option3) if count == 3: self.option4.label = x if x is None or x.lower() == 'na' or x == 'N/A': self.remove_item(self.option4) if count == 4: self.option5.label = x if x is None or x.lower() == 'na' or x == 'N/A': self.remove_item(self.option5) @discord.ui.button(label='Option 1', style=discord.ButtonStyle.primary) async def option1(self, interaction: discord.Interaction, button: discord.ui.Button): if interaction.user not in self.responders: return self.value = self.options[0] self.clear_items() self.stop() @discord.ui.button(label='Option 2', style=discord.ButtonStyle.primary) async def option2(self, interaction: discord.Interaction, button: discord.ui.Button): if interaction.user not in self.responders: return self.value = self.options[1] self.clear_items() self.stop() @discord.ui.button(label='Option 3', style=discord.ButtonStyle.primary) async def option3(self, interaction: discord.Interaction, button: discord.ui.Button): if interaction.user not in self.responders: return self.value = self.options[2] self.clear_items() self.stop() @discord.ui.button(label='Option 4', style=discord.ButtonStyle.primary) async def option4(self, interaction: discord.Interaction, button: discord.ui.Button): if interaction.user not in self.responders: return self.value = self.options[3] self.clear_items() self.stop() @discord.ui.button(label='Option 5', style=discord.ButtonStyle.primary) async def option5(self, interaction: discord.Interaction, button: discord.ui.Button): if interaction.user not in self.responders: return self.value = self.options[4] self.clear_items() self.stop() class Pagination(discord.ui.View): def __init__(self, responders: list, timeout: float = 300.0): super().__init__(timeout=timeout) if not isinstance(responders, list): raise TypeError('responders must be a list') self.value = None self.responders = responders @discord.ui.button(label='⏮️', style=discord.ButtonStyle.blurple) async def left_button(self, interaction: discord.Interaction, button: discord.ui.Button): if interaction.user not in self.responders: logging.info(f'{interaction.user} is not in {self.responders}') return self.value = 'left' await interaction.response.defer() self.stop() @discord.ui.button(label='❌️', style=discord.ButtonStyle.secondary) async def cancel_button(self, interaction: discord.Interaction, button: discord.ui.Button): if interaction.user not in self.responders: logging.info(f'{interaction.user} is not in {self.responders}') return self.value = 'cancel' await interaction.response.defer() self.stop() @discord.ui.button(label='⏭️', style=discord.ButtonStyle.blurple) async def right_button(self, interaction: discord.Interaction, button: discord.ui.Button): if interaction.user not in self.responders: logging.info(f'{interaction.user} is not in {self.responders}') return self.value = 'right' await interaction.response.defer() self.stop() 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='Baltimore Orioles'), discord.SelectOption(label='Boston Red Sox'), discord.SelectOption(label='Chicago White Sox'), discord.SelectOption(label='Cleveland Guardians'), discord.SelectOption(label='Detroit Tigers'), discord.SelectOption(label='Houston Astros'), discord.SelectOption(label='Kansas City Royals'), discord.SelectOption(label='Los Angeles Angels'), discord.SelectOption(label='Minnesota Twins'), discord.SelectOption(label='New York Yankees'), discord.SelectOption(label='Oakland Athletics'), discord.SelectOption(label='Seattle Mariners'), discord.SelectOption(label='Tampa Bay Rays'), discord.SelectOption(label='Texas Rangers'), discord.SelectOption(label='Toronto Blue Jays') ] else: options = [ discord.SelectOption(label='Arizona Diamondbacks'), discord.SelectOption(label='Atlanta Braves'), discord.SelectOption(label='Chicago Cubs'), discord.SelectOption(label='Cincinnati Reds'), discord.SelectOption(label='Colorado Rockies'), discord.SelectOption(label='Los Angeles Dodgers'), discord.SelectOption(label='Miami Marlins'), discord.SelectOption(label='Milwaukee Brewers'), discord.SelectOption(label='New York Mets'), discord.SelectOption(label='Philadelphia Phillies'), discord.SelectOption(label='Pittsburgh Pirates'), discord.SelectOption(label='San Diego Padres'), discord.SelectOption(label='San Francisco Giants'), discord.SelectOption(label='St. Louis Cardinals'), discord.SelectOption(label='Washington Nationals') ] super().__init__(placeholder=f'Select an {which} team', options=options) async def callback(self, interaction: discord.Interaction): team_id = None if self.which == 'AL': if self.values[0] == 'Baltimore Orioles': team_id = 3 elif self.values[0] == 'Boston Red Sox': team_id = 4 elif self.values[0] == 'Chicago White Sox': team_id = 6 elif self.values[0] == 'Cleveland Guardians': team_id = 8 elif self.values[0] == 'Detroit Tigers': team_id = 10 elif self.values[0] == 'Houston Astros': team_id = 11 elif self.values[0] == 'Kansas City Royals': team_id = 12 elif self.values[0] == 'Los Angeles Angels': team_id = 13 elif self.values[0] == 'Minnesota Twins': team_id = 17 elif self.values[0] == 'New York Yankees': team_id = 19 elif self.values[0] == 'Oakland Athletics': team_id = 20 elif self.values[0] == 'Seattle Mariners': team_id = 24 elif self.values[0] == 'Tampa Bay Rays': team_id = 27 elif self.values[0] == 'Texas Rangers': team_id = 28 elif self.values[0] == 'Toronto Blue Jays': team_id = 29 else: if self.values[0] == 'Arizona Diamondbacks': team_id = 1 elif self.values[0] == 'Atlanta Braves': team_id = 2 elif self.values[0] == 'Chicago Cubs': team_id = 5 elif self.values[0] == 'Cincinnati Reds': team_id = 7 elif self.values[0] == 'Colorado Rockies': team_id = 9 elif self.values[0] == 'Los Angeles Dodgers': team_id = 14 elif self.values[0] == 'Miami Marlins': team_id = 15 elif self.values[0] == 'Milwaukee Brewers': team_id = 16 elif self.values[0] == 'New York Mets': team_id = 18 elif self.values[0] == 'Philadelphia Phillies': team_id = 21 elif self.values[0] == 'Pittsburgh Pirates': team_id = 22 elif self.values[0] == 'San Diego Padres': team_id = 23 elif self.values[0] == 'San Francisco Giants': team_id = 25 elif self.values[0] == 'St. Louis Cardinals': team_id = 26 elif self.values[0] == 'Washington Nationals': team_id = 30 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: logging.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): logging.info(f'SelectPackChoice - selection: {self.values[0]}') pack_vals = self.values[0].split('-') logging.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 if 'Team Choice' in pack_vals and 'Cardset' in pack_vals: cardset_id = pack_vals[2] 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.channel.send( content=None, view=view ) return elif 'Team' in pack_vals: params.append(('pack_team_id', pack_vals[2])) elif 'Cardset' in pack_vals: params.append(('pack_cardset_id', pack_vals[2])) cardset_id = pack_vals[2] p_query = await db_get('packs', params=params) if p_query['count'] == 0: logging.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='2024 Live'), 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): logging.info(f'SelectPaperdexCardset - selection: {self.values[0]}') cardset_id = None if self.values[0] == '2022 Season': cardset_id = 3 elif self.values[0] == '2022 Promos': cardset_id = 4 elif self.values[0] == '2021 Season': cardset_id = 1 elif self.values[0] == '2019 Season': cardset_id = 5 elif self.values[0] == '2013 Season': cardset_id = 6 elif self.values[0] == '2012 Season': cardset_id = 7 elif self.values[0] == 'Mario Super Sluggers': cardset_id = 8 elif self.values[0] == '2023 Season': cardset_id = 9 elif self.values[0] == '2016 Season': cardset_id = 11 elif self.values[0] == '2008 Season': cardset_id = 12 elif self.values[0] == '2018 Season': cardset_id = 13 elif self.values[0] == '2024 Live': cardset_id = 17 elif self.values[0] == '2024 Promos': cardset_id = 18 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='Baltimore Orioles'), discord.SelectOption(label='Boston Red Sox'), discord.SelectOption(label='Chicago White Sox'), discord.SelectOption(label='Cleveland Guardians'), discord.SelectOption(label='Detroit Tigers'), discord.SelectOption(label='Houston Astros'), discord.SelectOption(label='Kansas City Royals'), discord.SelectOption(label='Los Angeles Angels'), discord.SelectOption(label='Minnesota Twins'), discord.SelectOption(label='New York Yankees'), discord.SelectOption(label='Oakland Athletics'), discord.SelectOption(label='Seattle Mariners'), discord.SelectOption(label='Tampa Bay Rays'), discord.SelectOption(label='Texas Rangers'), discord.SelectOption(label='Toronto Blue Jays') ] else: options = [ discord.SelectOption(label='Arizona Diamondbacks'), discord.SelectOption(label='Atlanta Braves'), discord.SelectOption(label='Chicago Cubs'), discord.SelectOption(label='Cincinnati Reds'), discord.SelectOption(label='Colorado Rockies'), discord.SelectOption(label='Los Angeles Dodgers'), discord.SelectOption(label='Miami Marlins'), discord.SelectOption(label='Milwaukee Brewers'), discord.SelectOption(label='New York Mets'), discord.SelectOption(label='Philadelphia Phillies'), discord.SelectOption(label='Pittsburgh Pirates'), discord.SelectOption(label='San Diego Padres'), discord.SelectOption(label='San Francisco Giants'), discord.SelectOption(label='St. Louis Cardinals'), discord.SelectOption(label='Washington Nationals') ] super().__init__(placeholder=f'Select an {which} team', options=options) async def callback(self, interaction: discord.Interaction): team_id = None if self.which == 'AL': if self.values[0] == 'Baltimore Orioles': team_id = 3 elif self.values[0] == 'Boston Red Sox': team_id = 4 elif self.values[0] == 'Chicago White Sox': team_id = 6 elif self.values[0] == 'Cleveland Guardians': team_id = 8 elif self.values[0] == 'Detroit Tigers': team_id = 10 elif self.values[0] == 'Houston Astros': team_id = 11 elif self.values[0] == 'Kansas City Royals': team_id = 12 elif self.values[0] == 'Los Angeles Angels': team_id = 13 elif self.values[0] == 'Minnesota Twins': team_id = 17 elif self.values[0] == 'New York Yankees': team_id = 19 elif self.values[0] == 'Oakland Athletics': team_id = 20 elif self.values[0] == 'Seattle Mariners': team_id = 24 elif self.values[0] == 'Tampa Bay Rays': team_id = 27 elif self.values[0] == 'Texas Rangers': team_id = 28 elif self.values[0] == 'Toronto Blue Jays': team_id = 29 else: if self.values[0] == 'Arizona Diamondbacks': team_id = 1 elif self.values[0] == 'Atlanta Braves': team_id = 2 elif self.values[0] == 'Chicago Cubs': team_id = 5 elif self.values[0] == 'Cincinnati Reds': team_id = 7 elif self.values[0] == 'Colorado Rockies': team_id = 9 elif self.values[0] == 'Los Angeles Dodgers': team_id = 14 elif self.values[0] == 'Miami Marlins': team_id = 15 elif self.values[0] == 'Milwaukee Brewers': team_id = 16 elif self.values[0] == 'New York Mets': team_id = 18 elif self.values[0] == 'Philadelphia Phillies': team_id = 21 elif self.values[0] == 'Pittsburgh Pirates': team_id = 22 elif self.values[0] == 'San Diego Padres': team_id = 23 elif self.values[0] == 'San Francisco Giants': team_id = 25 elif self.values[0] == 'St. Louis Cardinals': team_id = 26 elif self.values[0] == 'Washington Nationals': team_id = 30 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='2024 Live'), 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): logging.info(f'SelectBuyPacksCardset - selection: {self.values[0]}') cardset_id = None if self.values[0] == '2022 Season': cardset_id = 3 elif self.values[0] == '2021 Season': cardset_id = 1 elif self.values[0] == '2019 Season': cardset_id = 5 elif self.values[0] == '2013 Season': cardset_id = 6 elif self.values[0] == '2012 Season': cardset_id = 7 elif self.values[0] == '2023 Season': cardset_id = 9 elif self.values[0] == '2016 Season': cardset_id = 11 elif self.values[0] == '2008 Season': cardset_id = 12 elif self.values[0] == '2018 Season': cardset_id = 13 elif self.values[0] == '2024 Live': cardset_id = 17 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='Baltimore Orioles'), discord.SelectOption(label='Boston Red Sox'), discord.SelectOption(label='Chicago White Sox'), discord.SelectOption(label='Cleveland Guardians'), discord.SelectOption(label='Detroit Tigers'), discord.SelectOption(label='Houston Astros'), discord.SelectOption(label='Kansas City Royals'), discord.SelectOption(label='Los Angeles Angels'), discord.SelectOption(label='Minnesota Twins'), discord.SelectOption(label='New York Yankees'), discord.SelectOption(label='Oakland Athletics'), discord.SelectOption(label='Seattle Mariners'), discord.SelectOption(label='Tampa Bay Rays'), discord.SelectOption(label='Texas Rangers'), discord.SelectOption(label='Toronto Blue Jays') ] else: options = [ discord.SelectOption(label='Arizona Diamondbacks'), discord.SelectOption(label='Atlanta Braves'), discord.SelectOption(label='Chicago Cubs'), discord.SelectOption(label='Cincinnati Reds'), discord.SelectOption(label='Colorado Rockies'), discord.SelectOption(label='Los Angeles Dodgers'), discord.SelectOption(label='Miami Marlins'), discord.SelectOption(label='Milwaukee Brewers'), discord.SelectOption(label='New York Mets'), discord.SelectOption(label='Philadelphia Phillies'), discord.SelectOption(label='Pittsburgh Pirates'), discord.SelectOption(label='San Diego Padres'), discord.SelectOption(label='San Francisco Giants'), discord.SelectOption(label='St. Louis Cardinals'), discord.SelectOption(label='Washington Nationals') ] super().__init__(placeholder=f'Select an {which} team', options=options) async def callback(self, interaction: discord.Interaction): team_id = None if self.which == 'AL': if self.values[0] == 'Baltimore Orioles': team_id = 3 elif self.values[0] == 'Boston Red Sox': team_id = 4 elif self.values[0] == 'Chicago White Sox': team_id = 6 elif self.values[0] == 'Cleveland Guardians': team_id = 8 elif self.values[0] == 'Detroit Tigers': team_id = 10 elif self.values[0] == 'Houston Astros': team_id = 11 elif self.values[0] == 'Kansas City Royals': team_id = 12 elif self.values[0] == 'Los Angeles Angels': team_id = 13 elif self.values[0] == 'Minnesota Twins': team_id = 17 elif self.values[0] == 'New York Yankees': team_id = 19 elif self.values[0] == 'Oakland Athletics': team_id = 20 elif self.values[0] == 'Seattle Mariners': team_id = 24 elif self.values[0] == 'Tampa Bay Rays': team_id = 27 elif self.values[0] == 'Texas Rangers': team_id = 28 elif self.values[0] == 'Toronto Blue Jays': team_id = 29 else: if self.values[0] == 'Arizona Diamondbacks': team_id = 1 elif self.values[0] == 'Atlanta Braves': team_id = 2 elif self.values[0] == 'Chicago Cubs': team_id = 5 elif self.values[0] == 'Cincinnati Reds': team_id = 7 elif self.values[0] == 'Colorado Rockies': team_id = 9 elif self.values[0] == 'Los Angeles Dodgers': team_id = 14 elif self.values[0] == 'Miami Marlins': team_id = 15 elif self.values[0] == 'Milwaukee Brewers': team_id = 16 elif self.values[0] == 'New York Mets': team_id = 18 elif self.values[0] == 'Philadelphia Phillies': team_id = 21 elif self.values[0] == 'Pittsburgh Pirates': team_id = 22 elif self.values[0] == 'San Diego Padres': team_id = 23 elif self.values[0] == 'San Francisco Giants': team_id = 25 elif self.values[0] == 'St. Louis Cardinals': team_id = 26 elif self.values[0] == 'Washington Nationals': team_id = 30 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='Baltimore Orioles'), discord.SelectOption(label='Boston Red Sox'), discord.SelectOption(label='Chicago White Sox'), discord.SelectOption(label='Cleveland Guardians'), discord.SelectOption(label='Detroit Tigers'), discord.SelectOption(label='Houston Astros'), discord.SelectOption(label='Kansas City Royals'), discord.SelectOption(label='Los Angeles Angels'), discord.SelectOption(label='Minnesota Twins'), discord.SelectOption(label='New York Yankees'), discord.SelectOption(label='Oakland Athletics'), discord.SelectOption(label='Seattle Mariners'), discord.SelectOption(label='Tampa Bay Rays'), discord.SelectOption(label='Texas Rangers'), discord.SelectOption(label='Toronto Blue Jays') ] else: options = [ discord.SelectOption(label='Arizona Diamondbacks'), discord.SelectOption(label='Atlanta Braves'), discord.SelectOption(label='Chicago Cubs'), discord.SelectOption(label='Cincinnati Reds'), discord.SelectOption(label='Colorado Rockies'), discord.SelectOption(label='Los Angeles Dodgers'), discord.SelectOption(label='Miami Marlins'), discord.SelectOption(label='Milwaukee Brewers'), discord.SelectOption(label='New York Mets'), discord.SelectOption(label='Philadelphia Phillies'), discord.SelectOption(label='Pittsburgh Pirates'), discord.SelectOption(label='San Diego Padres'), discord.SelectOption(label='San Francisco Giants'), discord.SelectOption(label='St. Louis Cardinals'), discord.SelectOption(label='Washington Nationals') ] super().__init__(placeholder=f'Select an {which} team', options=options) async def callback(self, interaction: discord.Interaction): 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: [discord.ui.Select], timeout: float = 300.0): super().__init__(timeout=timeout) for x in select_objects: self.add_item(x) class Dropdown(discord.ui.Select): def __init__(self, option_list: list, placeholder: str = 'Make your selection', min_values: int = 1, max_values: int = 1, callback=None): # Set the options that will be presented inside the dropdown # options = [ # discord.SelectOption(label='Red', description='Your favourite colour is red', emoji='🟥'), # discord.SelectOption(label='Green', description='Your favourite colour is green', emoji='🟩'), # discord.SelectOption(label='Blue', description='Your favourite colour is blue', emoji='🟦'), # ] # The placeholder is what will be shown when no option is chosen # The min and max values indicate we can only pick one of the three options # The options parameter defines the dropdown options. We defined this above # If a default option is set on any SelectOption, the View will not process if only the default is # selected by the user self.custom_callback = callback super().__init__( placeholder=placeholder, min_values=min_values, max_values=max_values, options=option_list ) async def callback(self, interaction: discord.Interaction): # Use the interaction object to send a response message containing # the user's favourite colour or choice. The self object refers to the # Select object, and the values attribute gets a list of the user's # selected options. We only want the first one. # await interaction.response.send_message(f'Your favourite colour is {self.values[0]}') logging.info(f'Dropdown callback: {self.custom_callback}') await self.custom_callback(interaction, self.values) class DropdownView(discord.ui.View): def __init__(self, dropdown_objects: [Dropdown], timeout: float = 300.0): super().__init__(timeout=timeout) # self.add_item(Dropdown()) for x in dropdown_objects: self.add_item(x) def random_conf_gif(): conf_gifs = [ 'https://tenor.com/view/boom-annakendrick-pitchperfect-pitchperfect2-micdrop-gif-5143507', 'https://tenor.com/view/boom-annakendrick-pitchperfect-pitchperfect2-micdrop-gif-5143507', 'https://tenor.com/view/boom-annakendrick-pitchperfect-pitchperfect2-micdrop-gif-5143507', 'https://tenor.com/view/explosion-boom-iron-man-gif-14282225', 'https://tenor.com/view/betty-white-dab-consider-it-done-gif-11972415', 'https://tenor.com/view/done-and-done-spongebob-finished-just-did-it-gif-10843280', 'https://tenor.com/view/thumbs-up-okay-ok-well-done-gif-13840394', 'https://tenor.com/view/tinkerbell-peter-pan-all-done-gif-15003723', 'https://tenor.com/view/done-and-done-ron-swanson-gotchu-gif-10843254', 'https://tenor.com/view/sponge-bob-thumbs-up-ok-smile-gif-12038157', 'https://tenor.com/view/thumbs-up-cool-okay-bye-gif-8633196', 'https://i0.wp.com/media1.giphy.com/media/iwvuPyfi7z14I/giphy.gif', 'https://media1.tenor.com/images/859a2d3b201fbacec13904242976b9e0/tenor.gif', 'https://tenor.com/bc1OJ.gif', 'https://tenor.com/1EmF.gif', 'https://tenor.com/ZYCh.gif', 'https://tenor.com/patd.gif', 'https://tenor.com/u6mU.gif', 'https://tenor.com/x2sa.gif', 'https://tenor.com/bAVeS.gif', 'https://tenor.com/bxOcj.gif', 'https://tenor.com/ETJ7.gif', 'https://tenor.com/bpH3g.gif', 'https://tenor.com/biF9q.gif', 'https://tenor.com/OySS.gif', 'https://tenor.com/bvVFv.gif', 'https://tenor.com/bFeqA.gif' ] return conf_gifs[random.randint(0, len(conf_gifs) - 1)] def random_no_gif(): no_gifs = [ 'https://tenor.com/view/youre-not-my-dad-dean-jensen-ackles-supernatural-you-arent-my-dad-gif-19503399', 'https://tenor.com/view/youre-not-my-dad-kid-gif-8300190', 'https://tenor.com/view/youre-not-my-supervisor-youre-not-my-boss-gif-12971403', 'https://tenor.com/view/dont-tell-me-what-to-do-gif-4951202' ] return no_gifs[random.randint(0, len(no_gifs) - 1)] def random_salute_gif(): salute_gifs = [ 'https://media.giphy.com/media/fSAyceY3BCgtiQGnJs/giphy.gif', 'https://media.giphy.com/media/bsWDUSFUmJCOk/giphy.gif', 'https://media.giphy.com/media/hStvd5LiWCFzYNyxR4/giphy.gif', 'https://media.giphy.com/media/RhSR5xXDsXJ7jbnrRW/giphy.gif', 'https://media.giphy.com/media/lNQvrlPdbmZUU2wlh9/giphy.gif', 'https://gfycat.com/skeletaldependableandeancat', 'https://i.gifer.com/5EJk.gif', 'https://tenor.com/baJUV.gif', 'https://tenor.com/bdnQH.gif', 'https://tenor.com/bikQU.gif', 'https://i.pinimg.com/originals/04/36/bf/0436bfc9861b4b57ffffda82d3adad6e.gif', 'https://media.giphy.com/media/6RtOG4Q7v34kw/giphy.gif', 'https://keyassets-p2.timeincuk.net/wp/prod/wp-content/uploads/sites/42/2017/04/anigif_' 'enhanced-946-1433453114-7.gif', 'https://keyassets-p2.timeincuk.net/wp/prod/wp-content/uploads/sites/42/2017/04/100c5d677cc28ea3f15' '4c70d641f655b_meme-crying-gif-crying-gif-meme_620-340.gif', 'https://media.giphy.com/media/fnKd6rCHaZoGdzLjjA/giphy.gif', 'https://media.giphy.com/media/47D5jmVc4f7ylygXYD/giphy.gif', 'https://media.giphy.com/media/I4wGMXoi2kMDe/giphy.gif', ] return salute_gifs[random.randint(0, len(salute_gifs) - 1)] def random_conf_word(): conf_words = [ 'dope', 'cool', 'got it', 'noice', 'ok', 'lit', ] return conf_words[random.randint(0, len(conf_words) - 1)] def random_codename(): all_names = [ 'Shong', 'DerekSux', 'JoeSux', 'CalSux', 'Friend', 'Andrea', 'Ent', 'Lindved', 'Camp', 'Idyll', 'Elaphus', 'Turki', 'Shrimp', 'Primary', 'Anglica', 'Shail', 'Blanket', 'Baffled', 'Deer', 'Thisted', 'Brisk', 'Shy', 'Table', 'Jorts', 'Renati', 'Gisky', 'Prig', 'Bathtub', 'Gallery', 'Mavas', 'Chird', 'Oxyura', 'Mydal', 'Brown', 'Vasen', 'Worthy', 'Bivver', 'Cirlus', 'Self', 'Len', 'Sharp', 'Dart', 'Crepis', 'Ferina', 'Curl', 'Lancome', 'Stuff', 'Glove', 'Consist', 'Smig', 'Egg', 'Pleat', 'Picture', 'Spin', 'Ridgty', 'Ickled', 'Abashed', 'Haul', 'Cordage', 'Chivery', 'Stointy', 'Baa', 'Here', 'Ulmi', 'Tour', 'Tribe', 'Crunch', 'Used', 'Pigface', 'Audit', 'Written', 'Once', 'Fickle', 'Drugged', 'Swarm', 'Blimber', 'Torso', 'Retusa', 'Hockey', 'Pusty', 'Sallow', 'Next', 'Mansion', 'Glass', 'Screen', 'Josiah', 'Bonkey', 'Stuff', 'Sane', 'Blooded', 'Gnat', 'Liparis', 'Ocean', 'Sway', 'Roband', 'Still', 'Ribba', 'Biryani', 'Halibut', 'Flyn', 'Until', 'Depend', 'Intel', 'Affinis', 'Chef', 'Trounce', 'Crawl', 'Grab', 'Eggs', 'Malfroy', 'Sitta', 'Cretin', 'May', 'Smithii', 'Saffron', 'Crummy', 'Powered', 'Rail', 'Trait', 'Koiled', 'Bronze', 'Quickly', 'Vikis', 'Trift', 'Jubilar', 'Deft', 'Juncus', 'Sodding', 'Distant', 'Poecile', 'Pipe', 'Sell', 'Inops', 'Peusi', 'Sparrow', 'Yams', 'Kidneys', 'Artery', 'Vuffin', 'Boink', 'Bos', 'Notable', 'Alba', 'Spurge', 'Ruby', 'Cilia', 'Pellow', 'Nox', 'Woozy', 'Semvik', 'Tyda', 'Season', 'Lychnis', 'Ibestad', 'Bagge', 'Marked', 'Browdie', 'Fisher', 'Tilly', 'Troll', 'Gypsy', 'Thisted', 'Flirt', 'Stop', 'Radiate', 'Poop', 'Plenty', 'Jeff', 'Magpie', 'Roof', 'Ent', 'Dumbo', 'Pride', 'Weights', 'Winted', 'Dolden', 'Meotica', 'Yikes', 'Teeny', 'Fizz', 'Eide', 'Foetida', 'Crash', 'Mann', 'Salong', 'Cetti', 'Balloon', 'Petite', 'Find', 'Sputter', 'Patula', 'Upstage', 'Aurora', 'Dadson', 'Drate', 'Heidal', 'Robin', 'Auditor', 'Ithil', 'Warmen', 'Pat', 'Muppet', '007', 'Advantage', 'Alert', 'Backhander', 'Badass', 'Blade', 'Blaze', 'Blockade', 'Blockbuster', 'Boxer', 'Brimstone', 'Broadway', 'Buccaneer', 'Champion', 'Cliffhanger', 'Coachman', 'Comet', 'Commander', 'Courier', 'Cowboy', 'Crawler', 'Crossroads', 'DeepSpace', 'Desperado', 'Double-Decker', 'Echelon', 'Edge', 'Encore', 'EnRoute', 'Escape', 'Eureka', 'Evangelist', 'Excursion', 'Explorer', 'Fantastic', 'Firefight', 'Foray', 'Forge', 'Freeway', 'Frontier', 'FunMachine', 'Galaxy', 'GameOver', 'Genesis', 'Hacker', 'Hawkeye', 'Haybailer', 'Haystack', 'Hexagon', 'Hitman', 'Hustler', 'Iceberg', 'Impossible', 'Impulse', 'Invader', 'Inventor', 'IronWolf', 'Jackrabbit', 'Juniper', 'Keyhole', 'Lancelot', 'Liftoff', 'MadHatter', 'Magnum', 'Majestic', 'Merlin', 'Multiplier', 'Netiquette', 'Nomad', 'Octagon', 'Offense', 'OliveBranch', 'OlympicTorch', 'Omega', 'Onyx', 'Orbit', 'OuterSpace', 'Outlaw', 'Patron', 'Patriot', 'Pegasus', 'Pentagon', 'Pilgrim', 'Pinball', 'Pinnacle', 'Pipeline', 'Pirate', 'Portal', 'Predator', 'Prism', 'RagingBull', 'Ragtime', 'Reunion', 'Ricochet', 'Roadrunner', 'Rockstar', 'RobinHood', 'Rover', 'Runabout', 'Sapphire', 'Scrappy', 'Seige', 'Shadow', 'Shakedown', 'Shockwave', 'Shooter', 'Showdown', 'SixPack', 'SlamDunk', 'Slasher', 'Sledgehammer', 'Spirit', 'Spotlight', 'Starlight', 'Steamroller', 'Stride', 'Sunrise', 'Superhuman', 'Supernova', 'SuperBowl', 'Sunset', 'Sweetheart', 'TopHand', 'Touchdown', 'Tour', 'Trailblazer', 'Transit', 'Trekker', 'Trio', 'TriplePlay', 'TripleThreat', 'Universe', 'Unstoppable', 'Utopia', 'Vicinity', 'Vector', 'Vigilance', 'Vigilante', 'Vista', 'Visage', 'Vis-à-vis', 'VIP', 'Volcano', 'Volley', 'Whizzler', 'Wingman', 'Badger', 'BlackCat', 'Bobcat', 'Caracal', 'Cheetah', 'Cougar', 'Jaguar', 'Leopard', 'Lion', 'Lynx', 'MountainLion', 'Ocelot', 'Panther', 'Puma', 'Siamese', 'Serval', 'Tiger', 'Wolverine', 'Abispa', 'Andrena', 'BlackWidow', 'Cataglyphis', 'Centipede', 'Cephalotes', 'Formica', 'Hornet', 'Jellyfish', 'Scorpion', 'Tarantula', 'Yellowjacket', 'Wasp', 'Apollo', 'Ares', 'Artemis', 'Athena', 'Hercules', 'Hermes', 'Iris', 'Medusa', 'Nemesis', 'Neptune', 'Perseus', 'Poseidon', 'Triton', 'Zeus', 'Aquarius', 'Aries', 'Cancer', 'Capricorn', 'Gemini', 'Libra', 'Leo', 'Pisces', 'Sagittarius', 'Scorpio', 'Taurus', 'Virgo', 'Andromeda', 'Aquila', 'Cassiopeia', 'Cepheus', 'Cygnus', 'Delphinus', 'Drako', 'Lyra', 'Orion', 'Perseus', 'Serpens', 'Triangulum', 'Anaconda', 'Boa', 'Cobra', 'Copperhead', 'Cottonmouth', 'Garter', 'Kingsnake', 'Mamba', 'Python', 'Rattler', 'Sidewinder', 'Taipan', 'Viper', 'Alligator', 'Barracuda', 'Crocodile', 'Gator', 'GreatWhite', 'Hammerhead', 'Jaws', 'Lionfish', 'Mako', 'Moray', 'Orca', 'Piranha', 'Shark', 'Stingray', 'Axe', 'BattleAxe', 'Bayonet', 'Blade', 'Crossbowe', 'Dagger', 'Excalibur', 'Halberd', 'Hatchet', 'Machete', 'Saber', 'Samurai', 'Scimitar', 'Scythe', 'Stiletto', 'Spear', 'Sword', 'Aurora', 'Avalanche', 'Blizzard', 'Cyclone', 'Dewdrop', 'Downpour', 'Duststorm', 'Fogbank', 'Freeze', 'Frost', 'GullyWasher', 'Gust', 'Hurricane', 'IceStorm', 'JetStream', 'Lightning', 'Mist', 'Monsoon', 'Rainbow', 'Raindrop', 'SandStorm', 'Seabreeze', 'Snowflake', 'Stratosphere', 'Storm', 'Sunrise', 'Sunset', 'Tornado', 'Thunder', 'Thunderbolt', 'Thunderstorm', 'TropicalStorm', 'Twister', 'Typhoon', 'Updraft', 'Vortex', 'Waterspout', 'Whirlwind', 'WindChill', 'Archimedes', 'Aristotle', 'Confucius', 'Copernicus', 'Curie', 'daVinci', 'Darwin', 'Descartes', 'Edison', 'Einstein', 'Epicurus', 'Freud', 'Galileo', 'Hawking', 'Machiavelli', 'Marx', 'Newton', 'Pascal', 'Pasteur', 'Plato', 'Sagan', 'Socrates', 'Tesla', 'Voltaire', 'Baccarat', 'Backgammon', 'Blackjack', 'Chess', 'Jenga', 'Jeopardy', 'Keno', 'Monopoly', 'Pictionary', 'Poker', 'Scrabble', 'TrivialPursuit', 'Twister', 'Roulette', 'Stratego', 'Yahtzee', 'Aquaman', 'Batman', 'BlackPanther', 'BlackWidow', 'CaptainAmerica', 'Catwoman', 'Daredevil', 'Dr.Strange', 'Flash', 'GreenArrow', 'GreenLantern', 'Hulk', 'IronMan', 'Phantom', 'Thor', 'SilverSurfer', 'SpiderMan', 'Supergirl', 'Superman', 'WonderWoman', 'Wolverine', 'Hypersonic', 'Lightspeed', 'Mach1,2,3,4,etc', 'Supersonic', 'WarpSpeed', 'Amiatina', 'Andalusian', 'Appaloosa', 'Clydesdale', 'Colt', 'Falabella', 'Knabstrupper', 'Lipizzan', 'Lucitano', 'Maverick', 'Mustang', 'Palomino', 'Pony', 'QuarterHorse', 'Stallion', 'Thoroughbred', 'Zebra', 'Antigua', 'Aruba', 'Azores', 'Baja', 'Bali', 'Barbados', 'Bermuda', 'BoraBora', 'Borneo', 'Capri', 'Cayman', 'Corfu', 'Cozumel', 'Curacao', 'Fiji', 'Galapagos', 'Hawaii', 'Ibiza', 'Jamaica', 'Kauai', 'Lanai', 'Majorca', 'Maldives', 'Maui', 'Mykonos', 'Nantucket', 'Oahu', 'Tahiti', 'Tortuga', 'Roatan', 'Santorini', 'Seychelles', 'St.Johns', 'St.Lucia', 'Albatross', 'BaldEagle', 'Blackhawk', 'BlueJay', 'Chukar', 'Condor', 'Crane', 'Dove', 'Eagle', 'Falcon', 'Goose(GoldenGoose)', 'Grouse', 'Hawk', 'Heron', 'Hornbill', 'Hummingbird', 'Lark', 'Mallard', 'Oriole', 'Osprey', 'Owl', 'Parrot', 'Penguin', 'Peregrine', 'Pelican', 'Pheasant', 'Quail', 'Raptor', 'Raven', 'Robin', 'Sandpiper', 'Seagull', 'Sparrow', 'Stork', 'Thunderbird', 'Toucan', 'Vulture', 'Waterfowl', 'Woodpecker', 'Wren', 'C-3PO', 'Chewbacca', 'Dagobah', 'DarthVader', 'DeathStar', 'Devaron', 'Droid', 'Endor', 'Ewok', 'Hoth', 'Jakku', 'Jedi', 'Leia', 'Lightsaber', 'Lothal', 'Naboo', 'Padawan', 'R2-D2', 'Scarif', 'Sith', 'Skywalker', 'Stormtrooper', 'Tatooine', 'Wookie', 'Yoda', 'Zanbar', 'Canoe', 'Catamaran', 'Cruiser', 'Cutter', 'Ferry', 'Galleon', 'Gondola', 'Hovercraft', 'Hydrofoil', 'Jetski', 'Kayak', 'Longboat', 'Motorboat', 'Outrigger', 'PirateShip', 'Riverboat', 'Sailboat', 'Skipjack', 'Schooner', 'Skiff', 'Sloop', 'Steamboat', 'Tanker', 'Trimaran', 'Trawler', 'Tugboat', 'U-boat', 'Yacht', 'Yawl', 'Lancer', 'Volunteer', 'Searchlight', 'Passkey', 'Deacon', 'Rawhide', 'Timberwolf', 'Eagle', 'Tumbler', 'Renegade', 'Mogul' ] this_name = all_names[random.randint(0, len(all_names) - 1)] return this_name def random_no_phrase(): phrases = [ 'uhh...no', 'lol no', 'nope', ] async def send_to_bothole(ctx, content, embed): await discord.utils.get(ctx.guild.text_channels, name='pd-bot-hole') \ .send(content=content, embed=embed) async def send_to_news(ctx, content, embed): await discord.utils.get(ctx.guild.text_channels, name='pd-news-ticker') \ .send(content=content, embed=embed) async def typing_pause(ctx, seconds=1): async with ctx.typing(): await asyncio.sleep(seconds) async def pause_then_type(ctx, message): async with ctx.typing(): await asyncio.sleep(len(message) / 100) await ctx.send(message) async def check_if_pdhole(ctx): if ctx.message.channel.name != 'pd-bot-hole': await ctx.send('Slide on down to my bot-hole for running commands.') await ctx.message.add_reaction('❌') return False return True def get_roster_sheet_legacy(team): return f'https://docs.google.com/spreadsheets/d/{team.gsheet}/edit' def get_special_embed(special): embed = discord.Embed(title=f'{special.name} - Special #{special.get_id()}', color=discord.Color.random(), description=f'{special.short_desc}') embed.add_field(name='Description', value=f'{special.long_desc}', inline=False) # embed.add_field(name='To Redeem', value=f'Run .redeem {special.get_id()}', inline=False) if special.thumbnail.lower() != 'none': embed.set_thumbnail(url=f'{special.thumbnail}') if special.url.lower() != 'none': embed.set_image(url=f'{special.url}') return embed def get_random_embed(title, thumb=None): embed = discord.Embed(title=title, color=discord.Color.random()) if thumb: embed.set_thumbnail(url=thumb) return embed async def get_player_photo(player): search_term = player['bbref_id'] if player['bbref_id'] else player['p_name'] req_url = f'https://www.thesportsdb.com/api/v1/json/1/searchplayers.php?p={search_term}' try: resp = requests.get(req_url, timeout=.5) except Exception as e: return None if resp.status_code == 200 and resp.json()['player']: if resp.json()['player'][0]['strSport'] == 'Baseball': await db_patch('players', object_id=player['player_id'], params=[('headshot', resp.json()['player'][0]['strThumb'])]) return resp.json()['player'][0]['strThumb'] return None async def get_player_headshot(player): search_term = player['bbref_id'] if player['bbref_id'] else player['p_name'] req_url = f'https://www.baseball-reference.com/search/search.fcgi?search={search_term}' try: resp = requests.get(req_url, timeout=2).text soup = BeautifulSoup(resp, 'html.parser') for item in soup.find_all('img'): if 'headshot' in item['src']: await db_patch('players', object_id=player['player_id'], params=[('headshot', item['src'])]) return item['src'] except: pass return await get_player_photo(player) def fuzzy_search(name, master_list): if name.lower() in master_list: return name.lower() great_matches = get_close_matches(name, master_list, cutoff=0.8) if len(great_matches) == 1: return great_matches[0] elif len(great_matches) > 0: matches = great_matches else: matches = get_close_matches(name, master_list, n=6) if len(matches) == 1: return matches[0] if not matches: raise ValueError(f'{name.title()} was not found') return matches[0] async def fuzzy_player_search(ctx, channel, bot, name, master_list): """ Takes a name to search and returns the name of the best match :param ctx: discord context :param channel: discord channel :param bot: discord.py bot object :param name: string :param master_list: list of names :return: """ matches = fuzzy_search(name, master_list) embed = discord.Embed( title="Did You Mean...", description='Enter the number of the card you would like to see.', color=0x7FC600 ) count = 1 for x in matches: embed.add_field(name=f'{count}', value=x, inline=False) count += 1 embed.set_footer(text='These are the closest matches. Spell better if they\'re not who you want.') this_q = Question(bot, channel, None, 'int', 45, embed=embed) resp = await this_q.ask([ctx.author]) if not resp: return None if resp < count: return matches[resp - 1] else: raise ValueError(f'{resp} is not a valid response.') async def create_channel_old( ctx, channel_name: str, category_name: str, everyone_send=False, everyone_read=True, allowed_members=None, allowed_roles=None): this_category = discord.utils.get(ctx.guild.categories, name=category_name) if not this_category: raise ValueError(f'I couldn\'t find a category named **{category_name}**') overwrites = { ctx.guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True), ctx.guild.default_role: discord.PermissionOverwrite(read_messages=everyone_read, send_messages=everyone_send) } if allowed_members: if isinstance(allowed_members, list): for member in allowed_members: overwrites[member] = discord.PermissionOverwrite(read_messages=True, send_messages=True) if allowed_roles: if isinstance(allowed_roles, list): for role in allowed_roles: overwrites[role] = discord.PermissionOverwrite(read_messages=True, send_messages=True) this_channel = await ctx.guild.create_text_channel( channel_name, overwrites=overwrites, category=this_category ) logging.info(f'Creating channel ({channel_name}) in ({category_name})') return this_channel async def react_and_reply(ctx, reaction, message): await ctx.message.add_reaction(reaction) await ctx.send(message) async def send_to_channel(bot, channel_name, content=None, embed=None): guild = bot.get_guild(int(os.environ.get('GUILD_ID'))) if not guild: logging.error('Cannot send to channel - bot not logged in') return this_channel = discord.utils.get(guild.text_channels, name=channel_name) if not this_channel: this_channel = discord.utils.get(guild.text_channels, id=channel_name) if not this_channel: raise NameError(f'**{channel_name}** channel not found') return await this_channel.send(content=content, embed=embed) async def get_or_create_role(ctx, role_name, mentionable=True): this_role = discord.utils.get(ctx.guild.roles, name=role_name) # logging.info(f'this_role: {this_role} / role_name: {role_name} (POST SEARCH)') if not this_role: this_role = await ctx.guild.create_role(name=role_name, mentionable=mentionable) # logging.info(f'this_role: {this_role} / role_name: {role_name} (PRE RETURN)') return this_role """ NEW FOR SEASON 4 """ def get_team_embed(title, team=None, thumbnail: bool = True): if team: embed = discord.Embed( title=title, color=int(team["color"], 16) if team["color"] else int(SBA_COLOR, 16) ) embed.set_footer(text=f'Paper Dynasty Season {team["season"]}', icon_url=IMAGES['logo']) if thumbnail: embed.set_thumbnail(url=team["logo"] if team["logo"] else IMAGES['logo']) else: embed = discord.Embed( title=title, color=int(SBA_COLOR, 16) ) embed.set_footer(text=f'Paper Dynasty Season {PD_SEASON}', icon_url=IMAGES['logo']) if thumbnail: embed.set_thumbnail(url=IMAGES['logo']) return embed async def get_team_by_owner(owner_id: int): team = await db_get('teams', params=[('gm_id', owner_id)]) if not team['count']: return None return team['teams'][0] async def team_role(ctx, team): return await get_or_create_role(ctx, f'{team["abbrev"]} - {team["lname"]}') def get_cal_user(ctx): return ctx.guild.get_member(258104532423147520) async def get_emoji(ctx, name, return_empty=True): try: emoji = await commands.converter.EmojiConverter().convert(ctx, name) except: if return_empty: emoji = '' else: return name return emoji def get_all_pos(player): all_pos = [] for x in range(1, 8): if player[f'pos_{x}']: all_pos.append(player[f'pos_{x}']) return all_pos async def create_channel( ctx, channel_name: str, category_name: str, everyone_send=False, everyone_read=True, read_send_members: list = None, read_send_roles: list = None, read_only_roles: list = None): this_category = discord.utils.get(ctx.guild.categories, name=category_name) if not this_category: raise ValueError(f'I couldn\'t find a category named **{category_name}**') overwrites = { ctx.guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True), ctx.guild.default_role: discord.PermissionOverwrite(read_messages=everyone_read, send_messages=everyone_send) } if read_send_members: for member in read_send_members: overwrites[member] = discord.PermissionOverwrite(read_messages=True, send_messages=True) if read_send_roles: for role in read_send_roles: overwrites[role] = discord.PermissionOverwrite(read_messages=True, send_messages=True) if read_only_roles: for role in read_only_roles: overwrites[role] = discord.PermissionOverwrite(read_messages=True, send_messages=False) this_channel = await ctx.guild.create_text_channel( channel_name, overwrites=overwrites, category=this_category ) logging.info(f'Creating channel ({channel_name}) in ({category_name})') return this_channel async def share_channel(channel, user, read_only=False): await channel.set_permissions(user, read_messages=True, send_messages=not read_only) async def get_card_embeds(card, include_stats=False) -> list: embed = discord.Embed( title=f'{card["player"]["p_name"]}', color=int(card['player']['rarity']['color'], 16) ) # embed.description = card['team']['lname'] embed.description = f'{card["player"]["cardset"]["name"]} / {card["player"]["mlbclub"]}' embed.set_author(name=card['team']['lname'], url=IMAGES['logo'], icon_url=card['team']['logo']) embed.set_footer(text=f'Paper Dynasty Season {card["team"]["season"]}', icon_url=IMAGES['logo']) if include_stats: b_query = await db_get( 'plays/batting', params=[('player_id', card['player']['player_id']), ('season', PD_SEASON)]) p_query = await db_get( 'plays/pitching', params=[('player_id', card['player']['player_id']), ('season', PD_SEASON)]) embed.add_field(name='Player ID', value=f'{card["player"]["player_id"]}') embed.add_field(name='Rarity', value=f'{card["player"]["rarity"]["name"]}') embed.add_field(name='Cost', value=f'{card["player"]["cost"]}₼') pos_string = ", ".join(get_all_pos(card['player'])) embed.add_field(name='Positions', value=pos_string) # all_dex = card['player']['paperdex'] all_dex = await db_get('paperdex', params=[("player_id", card["player"]["player_id"]), ('flat', True)]) count = all_dex['count'] if card['team']['lname'] != 'Paper Dynasty': bool_list = [True for elem in all_dex['paperdex'] if elem['team'] == card['team']['id']] if any(bool_list): if count == 1: coll_string = f'Only you' else: coll_string = f'You and {count - 1} other{"s" if count - 1 != 1 else ""}' elif count: coll_string = f'{count} other team{"s" if count != 1 else ""}' else: coll_string = f'0 teams' embed.add_field(name='Collected By', value=coll_string) else: embed.add_field(name='Collected By', value=f'{count} team{"s" if count != 1 else ""}') # TODO: check for dupes with the included paperdex data # if card['team']['lname'] != 'Paper Dynasty': # team_dex = await db_get('cards', params=[("player_id", card["player"]["player_id"]), ('team_id', card['team']['id'])]) # count = 1 if not team_dex['count'] else team_dex['count'] # embed.add_field(name='# Dupes', value=f'{count - 1} dupe{"s" if count - 1 != 1 else ""}') # embed.add_field(name='Team', value=f'{card["player"]["mlbclub"]}') player_pages = f'[BBRef]({get_player_url(card["player"], "bbref")})' embed.add_field(name='Player Page', value=f'{player_pages}') embed.set_image(url=card["player"]["image"]) headshot = card['player']['headshot'] if card['player']['headshot'] else await get_player_headshot(card['player']) if headshot: embed.set_thumbnail(url=headshot) else: embed.set_thumbnail(url=IMAGES['logo']) if include_stats: if b_query['count'] > 0: b = b_query['stats'][0] re24 = f'{b["re24"]:.2f}' batting_string = f'```\n' \ f' AVG OBP SLG\n' \ f' {b["avg"]:.3f} {b["obp"]:.3f} {b["slg"]:.3f}\n``````\n' \ f' OPS wOBA RE24\n' \ f' {b["ops"]:.3f} {b["woba"]:.3f} {re24: ^5}\n``````\n' \ f' PA H RBI 2B 3B HR SB\n' \ f'{b["pa"]: >3} {b["hit"]: ^3} {b["rbi"]: ^3} {b["double"]: >2} {b["triple"]: >2} ' \ f'{b["hr"]: >2} {b["sb"]: >2}```\n' embed.add_field(name='Batting Stats', value=batting_string, inline=False) if p_query['count'] > 0: p = p_query['stats'][0] ip_whole = math.floor(p['outs'] / 3) ip_denom = p['outs'] % 3 ips = ip_whole + (ip_denom * 0.1) kpbb = f'{p["k/bb"]:.1f}' era = f'{p["era"]:.2f}' whip = f'{p["whip"]:.2f}' re24 = f'{p["re24"]:.2f}' pitching_string = f'```\n' \ f' W-L SV ERA WHIP\n' \ f'{p["win"]: >2}-{p["loss"]: <2} {p["save"]: >2} {era: >5} {whip: >4}\n``````\n' \ f' IP SO K/BB RE24\n' \ f'{ips: >5} {p["so"]: ^3} {kpbb: ^4} {re24: ^5}\n```' embed.add_field(name='Pitching Stats', value=pitching_string, inline=False) if not card['player']['image2']: return [embed] card_two = discord.Embed(color=int(card['player']['rarity']['color'], 16)) card_two.set_footer(text=f'Paper Dynasty Season {card["team"]["season"]}', icon_url=IMAGES['logo']) card_two.set_image(url=card['player']['image2']) return [embed, card_two] def image_embed(image_url: str, title: str = None, color: str = None, desc: str = None, author_name: str = None, author_icon: str = None): embed_color = int(SBA_COLOR, 16) if color is not None: embed_color = int(color, 16) embed = discord.Embed(color=embed_color) if title is not None: embed.title = title if desc is not None: embed.description = desc if author_name is not None: icon = author_icon if author_icon is not None else IMAGES['logo'] embed.set_author(name=author_name, icon_url=icon) embed.set_footer(text=f'Paper Dynasty Season {PD_SEASON}', icon_url=IMAGES['logo']) embed.set_image(url=image_url) return embed def is_shiny(card): if card['player']['rarity']['value'] >= 5: return True return False async def display_cards( cards: list, team: dict, channel, user, bot=None, pack_cover: str = None, cust_message: str = None, add_roster: bool = True, pack_name: str = None) -> bool: cards.sort(key=lambda x: x['player']['rarity']['value']) card_embeds = [await get_card_embeds(x) for x in cards] page_num = 0 if pack_cover is None else -1 seen_shiny = False view = Pagination([user], timeout=10) l_emoji = await get_emoji(channel.guild, 'arrow_left') r_emoji = await get_emoji(channel.guild, 'arrow_right') view.left_button.disabled = True view.left_button.label = f'{l_emoji}Prev: -/{len(card_embeds)}' view.cancel_button.label = f'Close Pack' view.right_button.label = f'Next: {page_num + 2}/{len(card_embeds)}{r_emoji}' if len(cards) == 1: view.right_button.disabled = True if pack_cover: msg = await channel.send( content=None, embed=image_embed(pack_cover, title=f'{team["lname"]}', desc=pack_name), view=view ) else: msg = await channel.send(content=None, embeds=card_embeds[page_num], view=view) if cust_message: follow_up = await channel.send(cust_message) else: follow_up = await channel.send(f'{user.mention} you\'ve got {len(cards)} cards here') while True: await view.wait() if view.value: if view.value == 'cancel': await msg.edit(view=None) if add_roster: await follow_up.edit(content=f'Refresh your cards here: {get_roster_sheet(team)}') return True if view.value == 'left': page_num -= 1 if page_num > 0 else 0 if view.value == 'right': page_num += 1 if page_num <= len(card_embeds) else len(card_embeds) else: if page_num == len(card_embeds) - 1: await msg.edit(view=None) if add_roster: await follow_up.edit(content=f'Refresh your cards here: {get_roster_sheet(team)}') return True else: page_num += 1 view.value = None if is_shiny(cards[page_num]) and not seen_shiny: seen_shiny = True view = Pagination([user], timeout=300) view.cancel_button.style = discord.ButtonStyle.success view.cancel_button.label = 'Flip!' view.left_button.label = '-' view.right_button.label = '-' view.left_button.disabled = True view.right_button.disabled = True await msg.edit( embed=image_embed( IMAGES['mvp'][cards[page_num]["player"]["franchise"]], color='56f1fa', author_name=team['lname'], author_icon=team['logo'] ), view=view) tmp_msg = await channel.send(content=f'<@&1163537676885033010> we\'ve got an MVP!') await follow_up.edit(content=f'<@&1163537676885033010> we\'ve got an MVP!') await tmp_msg.delete() await view.wait() view = Pagination([user], timeout=10) view.right_button.label = f'Next: {page_num + 2}/{len(card_embeds)}{r_emoji}' view.cancel_button.label = f'Close Pack' view.left_button.label = f'{l_emoji}Prev: {page_num}/{len(card_embeds)}' if page_num == 0: view.left_button.label = f'{l_emoji}Prev: -/{len(card_embeds)}' view.left_button.disabled = True elif page_num == len(card_embeds) - 1: view.timeout = 600.0 view.right_button.label = f'Next: -/{len(card_embeds)}{r_emoji}' view.right_button.disabled = True await msg.edit(content=None, embeds=card_embeds[page_num], view=view) async def embed_pagination( all_embeds: list, channel, user: discord.Member, custom_message: str = None, timeout: int = 10, start_page: int = 0): if start_page > len(all_embeds) - 1 or start_page < 0: page_num = 0 else: page_num = start_page view = Pagination([user], timeout=timeout) l_emoji = '' r_emoji = '' view.right_button.label = f'Next: {page_num + 2}/{len(all_embeds)}{r_emoji}' view.cancel_button.label = f'Cancel' view.left_button.label = f'{l_emoji}Prev: {page_num}/{len(all_embeds)}' if page_num == 0: view.left_button.label = f'{l_emoji}Prev: -/{len(all_embeds)}' view.left_button.disabled = True elif page_num == len(all_embeds) - 1: view.right_button.label = f'Next: -/{len(all_embeds)}{r_emoji}' view.right_button.disabled = True msg = await channel.send(content=custom_message, embed=all_embeds[page_num], view=view) while True: await view.wait() if view.value: if view.value == 'cancel': await msg.edit(view=None) return True if view.value == 'left': page_num -= 1 if page_num > 0 else 0 if view.value == 'right': page_num += 1 if page_num <= len(all_embeds) else len(all_embeds) else: if page_num == len(all_embeds) - 1: await msg.edit(view=None) return True else: page_num += 1 view.value = None view = Pagination([user], timeout=timeout) view.right_button.label = f'Next: {page_num + 2}/{len(all_embeds)}{r_emoji}' view.cancel_button.label = f'Cancel' view.left_button.label = f'{l_emoji}Prev: {page_num}/{len(all_embeds)}' if page_num == 0: view.left_button.label = f'{l_emoji}Prev: -/{len(all_embeds)}' view.left_button.disabled = True elif page_num == len(all_embeds) - 1: view.timeout = 600.0 view.right_button.label = f'Next: -/{len(all_embeds)}{r_emoji}' view.right_button.disabled = True await msg.edit(content=None, embed=all_embeds[page_num], view=view) def get_roster_sheet(team, allow_embed: bool = False): return f'{"" if allow_embed else "<"}' \ f'https://docs.google.com/spreadsheets/d/{team["gsheet"]}/edit' \ f'{"" if allow_embed else ">"}' def get_player_url(player, which="sba"): if which == 'bbref': return f'https://www.baseball-reference.com/search/search.fcgi?search={player["bbref_id"]}' else: stub_name = player["p_name"].replace(" ", "%20") return f'https://sombaseball.ddns.net/players?name={stub_name}' async def bad_channel(ctx): bad_channels = ['paper-dynasty-chat', 'pd-news-ticker'] if ctx.message.channel.name in bad_channels: await ctx.message.add_reaction('❌') bot_hole = discord.utils.get( ctx.guild.text_channels, name=f'pd-bot-hole' ) await ctx.send(f'Slide on down to the {bot_hole.mention} ;)') return True else: return False def get_channel(ctx, name) -> Optional[discord.TextChannel]: channel = discord.utils.get( ctx.guild.text_channels, name=name ) if channel: return channel return None async def get_test_pack(ctx, team): pull_notifs = [] this_pack = await db_post('packs/one', payload={ 'team_id': team['id'], 'pack_type_id': 1, 'open_time': int(datetime.datetime.timestamp(datetime.datetime.now())*1000) }) ft_query = await db_get('players/random', params=[('max_rarity', 1), ('limit', 3)]) four_query = await db_get('players/random', params=[('min_rarity', 1), ('max_rarity', 3), ('limit', 1)]) five_query = await db_get('players/random', params=[('min_rarity', 5), ('max_rarity', 5), ('limit', 1)]) first_three = ft_query['players'] fourth = four_query['players'] fifth = five_query['players'] all_cards = [*first_three, *fourth, *fifth] success = await db_post('cards', timeout=10, payload={'cards': [{ 'player_id': x['player_id'], 'team_id': team['id'], 'pack_id': this_pack['id']} for x in all_cards] }) if not success: await ctx.send(f'I was not able to create these cards {get_emoji(ctx, "slight_frown")}') return for x in all_cards: if x['rarity']['value'] >= 3: pull_notifs.append(x) for pull in pull_notifs: await db_post('notifs', payload={ 'created': int(datetime.datetime.timestamp(datetime.datetime.now())*1000), 'title': 'Rare Pull', 'field_name': f'{player_desc(pull)} ({pull["rarity"]["name"]})', 'message': f'Pulled by {team["abbrev"]}', 'about': f'Player-{pull["player_id"]}' }) return [{'player': x, 'team': team} for x in all_cards] async def roll_for_cards(all_packs: list, extra_val=None) -> list: """ Pack odds are calculated based on the pack type Parameters ---------- extra_val all_packs Returns ------- """ all_players = [] team = all_packs[0]['team'] pack_ids = [] for pack in all_packs: counts = { 'Rep': { 'count': 0, 'rarity': 0 }, 'Res': { 'count': 0, 'rarity': 1 }, 'Sta': { 'count': 0, 'rarity': 2 }, 'All': { 'count': 0, 'rarity': 3 }, 'MVP': { 'count': 0, 'rarity': 5 }, 'HoF': { 'count': 0, 'rarity': 8 }, } this_pack_players = [] if pack['pack_type']['name'] == 'Standard': # Cards 1 - 2 for x in range(2): d_1000 = random.randint(1, 1000) if d_1000 <= 450: counts['Rep']['count'] += 1 elif d_1000 <= 900: counts['Res']['count'] += 1 else: counts['Sta']['count'] += 1 # Card 3 d_1000 = random.randint(1, 1000) if d_1000 <= 350: counts['Rep']['count'] += 1 elif d_1000 <= 700: counts['Res']['count'] += 1 elif d_1000 <= 950: counts['Sta']['count'] += 1 else: counts['All']['count'] += 1 # Card 4 d_1000 = random.randint(1, 1000) if d_1000 <= 310: counts['Rep']['count'] += 1 elif d_1000 <= 620: counts['Res']['count'] += 1 elif d_1000 <= 940: counts['Sta']['count'] += 1 elif d_1000 <= 990: counts['All']['count'] += 1 else: counts['MVP']['count'] += 1 # Card 5 d_1000 = random.randint(1, 1000) if d_1000 <= 215: counts['Rep']['count'] += 1 elif d_1000 <= 430: counts['Res']['count'] += 1 elif d_1000 <= 930: counts['Sta']['count'] += 1 elif d_1000 <= 980: counts['All']['count'] += 1 elif d_1000 <= 990: counts['MVP']['count'] += 1 else: counts['HoF']['count'] += 1 elif pack['pack_type']['name'] == 'Premium': # Card 1 d_1000 = random.randint(1, 1000) if d_1000 <= 400: counts['Rep']['count'] += 1 elif d_1000 <= 870: counts['Res']['count'] += 1 elif d_1000 <= 970: counts['Sta']['count'] += 1 elif d_1000 <= 990: counts['All']['count'] += 1 else: counts['MVP']['count'] += 1 # Card 2 d_1000 = random.randint(1, 1000) if d_1000 <= 300: counts['Rep']['count'] += 1 elif d_1000 <= 770: counts['Res']['count'] += 1 elif d_1000 <= 970: counts['Sta']['count'] += 1 elif d_1000 <= 990: counts['All']['count'] += 1 else: counts['MVP']['count'] += 1 # Card 3 d_1000 = random.randint(1, 1000) if d_1000 <= 200: counts['Rep']['count'] += 1 elif d_1000 <= 640: counts['Res']['count'] += 1 elif d_1000 <= 940: counts['Sta']['count'] += 1 elif d_1000 <= 990: counts['All']['count'] += 1 else: counts['MVP']['count'] += 1 # Card 4 d_1000 = random.randint(1, 1000) if d_1000 <= 100: counts['Rep']['count'] += 1 if d_1000 <= 530: counts['Res']['count'] += 1 elif d_1000 <= 930: counts['Sta']['count'] += 1 elif d_1000 <= 980: counts['All']['count'] += 1 elif d_1000 <= 990: counts['MVP']['count'] += 1 else: counts['HoF']['count'] += 1 # Card 5 d_1000 = random.randint(1, 1000) if d_1000 <= 380: counts['Res']['count'] += 1 elif d_1000 <= 880: counts['Sta']['count'] += 1 elif d_1000 <= 980: counts['All']['count'] += 1 elif d_1000 <= 990: counts['MVP']['count'] += 1 else: counts['HoF']['count'] += 1 elif pack['pack_type']['name'] == 'Check-In Player': logging.info(f'Building Check-In Pack // extra_val (type): {extra_val} {type(extra_val)}') # Single Card mod = 0 if isinstance(extra_val, int): mod = extra_val d_1000 = random.randint(1, 1000 + mod) if d_1000 >= 1100: counts['All']['count'] += 1 elif d_1000 >= 1000: counts['Sta']['count'] += 1 elif d_1000 >= 500: counts['Res']['count'] += 1 else: counts['Rep']['count'] += 1 else: raise TypeError(f'Pack type not recognized: {pack["pack_type"]["name"]}') pull_notifs = [] for key in counts: mvp_flag = None if counts[key]['count'] > 0: params = [ ('min_rarity', counts[key]['rarity']), ('max_rarity', counts[key]['rarity']), ('limit', counts[key]['count']), ('in_packs', True) ] if all_packs[0]['pack_team'] is not None: params.append(('franchise', all_packs[0]['pack_team']['lname'])) elif all_packs[0]['pack_cardset'] is not None: params.append(('cardset_id', all_packs[0]['pack_cardset']['id'])) pl = await db_get('players/random', params=params) if pl['count'] != counts[key]['count']: mvp_flag = counts[key]['count'] - pl['count'] for x in pl['players']: this_pack_players.append(x) all_players.append(x) if x['rarity']['value'] >= 3: pull_notifs.append(x) if mvp_flag: pl = await db_get('players/random', params=[('min_rarity', 5), ('limit', mvp_flag)]) for x in pl['players']: this_pack_players.append(x) all_players.append(x) success = await db_post( 'cards', payload={'cards': [{ 'player_id': x['player_id'], 'team_id': pack['team']['id'], 'pack_id': pack['id']} for x in this_pack_players] }, timeout=10 ) if not success: raise ConnectionError(f'Failed to create this pack of cards.') await db_patch('packs', object_id=pack['id'], params=[ ('open_time', int(datetime.datetime.timestamp(datetime.datetime.now())*1000)) ]) pack_ids.append(pack['id']) for pull in pull_notifs: logging.info(f'good pull: {pull}') await db_post('notifs', payload={ 'created': int(datetime.datetime.timestamp(datetime.datetime.now())*1000), 'title': 'Rare Pull', 'field_name': f'{player_desc(pull)} ({pull["rarity"]["name"]})', 'message': f'Pulled by {team["abbrev"]}', 'about': f'Player-{pull["player_id"]}' }) return pack_ids async def give_packs(team: dict, num_packs: int, pack_type: dict = None) -> dict: """ Parameters ---------- pack_type team num_packs Returns ------- { 'count': int, 'packs': [ all team packs ] } """ pt_id = pack_type['id'] if pack_type is not None else 1 await db_post( 'packs', payload={'packs': [{'team_id': team['id'], 'pack_type_id': pt_id} for x in range(num_packs)]} ) total_packs = await db_get('packs', params=[ ('team_id', team['id']), ('opened', False) ]) return total_packs def get_sheets(bot): try: return bot.get_cog('Players').sheets except Exception as e: logging.error(f'Could not grab sheets auth: {e}') raise ConnectionError(f'Bot has not authenticated with discord; please try again in 1 minute.') def create_team_sheet(team, email: str, current, bot): sheets = get_sheets(bot) new_sheet = sheets.drive.copy_file( f'{current["gsheet_template"]}', f'{team["lname"]} Roster Sheet v{current["gsheet_version"]}', '1539D0imTMjlUx2VF3NPMt7Sv85sb2XAJ' ) logging.info(f'new_sheet: {new_sheet}') this_sheet = sheets.open_by_key(new_sheet['id']) this_sheet.share(email, role='writer') team_data = this_sheet.worksheet_by_title('Team Data') team_data.update_values( crange='B1:B2', values=[[f'{team["id"]}'], [f'\'{team_hash(team)}']] ) logging.debug(f'this_sheet: {this_sheet}') return this_sheet async def refresh_sheet(team, bot, sheets=None) -> None: return if not sheets: sheets = get_sheets(bot) this_sheet = sheets.open_by_key(team['gsheet']) my_cards = this_sheet.worksheet_by_title('My Cards') all_cards = this_sheet.worksheet_by_title('All Cards') my_cards.update_value('A2', 'FALSE') all_cards.update_value('A2', 'FALSE') await asyncio.sleep(1) my_cards.update_value('A2', 'TRUE') await asyncio.sleep(0.5) all_cards.update_value('A2', 'TRUE') def delete_sheet(team, bot): sheets = get_sheets(bot) this_sheet = sheets.open_by_key(team['gsheet']) this_sheet.delete() def share_sheet(team, email, bot) -> None: sheets = get_sheets(bot) this_sheet = sheets.open_by_key(team['gsheet']) this_sheet.share(email, role='writer') def int_timestamp(datetime_obj: datetime.datetime) -> int: return int(datetime.datetime.timestamp(datetime_obj) * 1000) def get_pos_abbrev(pos_name): if pos_name == 'Catcher': return 'C' elif pos_name == 'First Base': return '1B' elif pos_name == 'Second Base': return '2B' elif pos_name == 'Third Base': return '3B' elif pos_name == 'Shortstop': return 'SS' elif pos_name == 'Left Field': return 'LF' elif pos_name == 'Center Field': return 'CF' elif pos_name == 'Right Field': return 'RF' elif pos_name == 'Pitcher': return 'P' elif pos_name == 'Designated Hitter': return 'DH' elif pos_name == 'Pinch Hitter': return 'PH' else: raise KeyError(f'{pos_name} is not a recognized position name') async def cardset_search(cardset: str, cardset_list: list) -> Optional[dict]: cardset_name = fuzzy_search(cardset, cardset_list) if not cardset_name: return None c_query = await db_get('cardsets', params=[('name', cardset_name)]) if c_query['count'] == 0: return None return c_query['cardsets'][0] def get_blank_team_card(player): return {'player': player, 'team': {'lname': 'Paper Dynasty', 'logo': IMAGES['logo'], 'season': PD_SEASON}} def get_rosters(team, bot, roster_num: Optional[int] = None) -> list: sheets = get_sheets(bot) this_sheet = sheets.open_by_key(team['gsheet']) r_sheet = this_sheet.worksheet_by_title(f'My Rosters') logging.debug(f'this_sheet: {this_sheet} / r_sheet = {r_sheet}') all_rosters = [None, None, None] # Pull roster 1 if not roster_num or roster_num == 1: roster_1 = r_sheet.range('B3:B28') roster_name = r_sheet.cell('F30').value logging.info(f'roster_1: {roster_1}') if not roster_1[0][0].value == '': all_rosters[0] = {'name': roster_name, 'roster_num': 1, 'team_id': team['id'], 'cards': None} all_rosters[0]['cards'] = [int(x[0].value) for x in roster_1] # Pull roster 2 if not roster_num or roster_num == 2: roster_2 = r_sheet.range('B29:B54') roster_name = r_sheet.cell('F31').value logging.info(f'roster_2: {roster_2}') if not roster_2[0][0].value == '': all_rosters[1] = {'name': roster_name, 'roster_num': 2, 'team_id': team['id'], 'cards': None} all_rosters[1]['cards'] = [int(x[0].value) for x in roster_2] # Pull roster 3 if not roster_num or roster_num == 3: roster_3 = r_sheet.range('B55:B80') roster_name = r_sheet.cell('F32').value logging.info(f'roster_3: {roster_3}') if not roster_3[0][0].value == '': all_rosters[2] = {'name': roster_name, 'roster_num': 3, 'team_id': team['id'], 'cards': None} all_rosters[2]['cards'] = [int(x[0].value) for x in roster_3] return all_rosters def get_roster_lineups(team, bot, roster_num, lineup_num) -> list: sheets = get_sheets(bot) logging.debug(f'sheets: {sheets}') this_sheet = sheets.open_by_key(team['gsheet']) logging.debug(f'this_sheet: {this_sheet}') r_sheet = this_sheet.worksheet_by_title('My Rosters') logging.debug(f'r_sheet: {r_sheet}') if lineup_num == 1: row_start = 9 row_end = 17 else: row_start = 18 row_end = 26 if roster_num == 1: l_range = f'H{row_start}:I{row_end}' elif roster_num == 2: l_range = f'J{row_start}:K{row_end}' else: l_range = f'L{row_start}:M{row_end}' logging.debug(f'l_range: {l_range}') raw_cells = r_sheet.range(l_range) logging.debug(f'raw_cells: {raw_cells}') try: lineup_cells = [(row[0].value, int(row[1].value)) for row in raw_cells] except ValueError as e: logging.error(f'Could not pull roster for {team["abbrev"]} due to a ValueError') raise ValueError(f'Uh oh. Looks like your roster might not be saved. I am reading blanks when I try to ' f'get the card IDs') logging.debug(f'lineup_cells: {lineup_cells}') return lineup_cells def post_ratings_guide(team, bot, this_sheet=None): if not this_sheet: sheets = get_sheets(bot) this_sheet = sheets.open_by_key(team['gsheet']) p_guide = this_sheet.worksheet_by_title('Full Guide - Pitchers') b_guide = this_sheet.worksheet_by_title('Full Guide - Batters') p_guide.update_value('A1', RATINGS_PITCHER_FORMULA) b_guide.update_value('A1', RATINGS_BATTER_FORMULA) async def legal_channel(ctx): bad_channels = ['paper-dynasty-chat', 'pd-news-ticker', 'pd-network-news'] if isinstance(ctx, commands.Context): if ctx.channel.name in bad_channels: raise commands.CheckFailure(f'Slide on down to the {get_channel(ctx, "pd-bot-hole").mention} ;)') else: return True elif ctx.channel.name in bad_channels: # await ctx.message.add_reaction('❌') # await ctx.send(f'Slide on down to the {get_channel(ctx, "pd-bot-hole").mention} ;)') # logging.warning(f'{ctx.author.name} posted in illegal channel.') # return False raise discord.app_commands.AppCommandError(f'Slide on down to the {get_channel(ctx, "pd-bot-hole").mention} ;)') else: return True def owner_only(interaction: discord.Interaction): if interaction.user.id == 258104532423147520: return True else: return False def get_role(ctx, role_name): return discord.utils.get(ctx.guild.roles, name=role_name) async def team_summary_embed(team, ctx, include_roster: bool = True): embed = get_team_embed(f'{team["lname"]} Overview', team) embed.add_field(name='General Manager', value=team['gmname'], inline=False) embed.add_field(name='Wallet', value=f'{team["wallet"]}₼') # embed.add_field(name='Collection Value', value=team['collection_value']) p_query = await db_get('packs', params=[('team_id', team['id']), ('opened', False)]) if p_query['count'] > 0: all_packs = {} for x in p_query['packs']: if x['pack_type']['name'] not in all_packs: all_packs[x['pack_type']['name']] = 1 else: all_packs[x['pack_type']['name']] += 1 pack_string = '' for pack_type in all_packs: pack_string += f'{pack_type.title()}: {all_packs[pack_type]}\n' else: pack_string = 'None' embed.add_field(name='Unopened Packs', value=pack_string) embed.add_field(name='Team Rating', value=f'{team["ranking"]}') r_query = await db_get(f'results/team/{team["id"]}?season={PD_SEASON}') if r_query: embed.add_field( name='Record', value=f'Ranked: {r_query["ranked_wins"]}-{r_query["ranked_losses"]}\n' f'Unlimited: {r_query["casual_wins"]}-{r_query["casual_losses"]}' ) # try: # r_query = await db_get('rosters', params=[('team_id', team['id'])]) # if r_query['count']: # embed.add_field(name=f'Rosters', value=f'** **', inline=False) # for roster in r_query['rosters']: # roster_string = '' # for i in range(1, 27): # card = roster[f'card_{i}'] # roster_string += f'{card["player"]["description"]} ({card["player"]["pos_1"]})\n' # embed.add_field( # name=f'{roster["name"]} Roster', # value=roster_string if len(roster_string) else "Unknown" # ) # else: # embed.add_field( # name='Rosters', # value='You can set up to three rosters for quick switching from your team sheet.', # inline=False # ) # except Exception as e: # logging.error(f'Could not pull rosters for {team["abbrev"]}') # embed.add_field( # name='Rosters', # value='Unable to pull current rosters. `/pullroster` to sync.', # inline=False # ) if include_roster: embed.add_field(name='Team Sheet', value=get_roster_sheet(team, allow_embed=True), inline=False) embed.add_field( name='For Help', value=f'`/help-pd` has FAQs; feel free to post questions in ' f'{get_channel(ctx, "paper-dynasty-chat").mention}.', inline=False ) return embed async def give_cards_to_team(team, players: list = None, player_ids: list = None, pack_id=None): if not pack_id: p_query = await db_post( 'packs/one', payload={ 'team_id': team['id'], 'pack_type_id': 4, 'open_time': datetime.datetime.timestamp(datetime.datetime.now()) * 1000} ) pack_id = p_query['id'] if not players and not player_ids: raise ValueError('One of players or player_ids must be provided to distribute cards') if players: await db_post('cards', payload={'cards': [ {'player_id': x['player_id'], 'team_id': team['id'], 'pack_id': pack_id} for x in players ]}, timeout=10) elif player_ids: await db_post('cards', payload={'cards': [ {'player_id': x, 'team_id': team['id'], 'pack_id': pack_id} for x in player_ids ]}, timeout=10) def get_ratings_guide(sheets): this_sheet = sheets.open_by_key(RATINGS_SHEET_KEY) b_sheet = this_sheet.worksheet_by_title('ratings_Batters') p_sheet = this_sheet.worksheet_by_title('ratings_Pitchers') b_data = b_sheet.range('A2:N') p_data = p_sheet.range('A2:N') try: batters = [ { 'player_id': int(x[0].value), 'p_name': x[1].value, 'rating': int(x[2].value), 'contact-r': int(x[3].value), 'contact-l': int(x[4].value), 'power-r': int(x[5].value), 'power-l': int(x[6].value), 'vision': int(x[7].value), 'speed': int(x[8].value), 'stealing': int(x[9].value), 'reaction': int(x[10].value), 'arm': int(x[11].value), 'fielding': int(x[12].value), 'hand': int(x[13].value), } for x in b_data ] pitchers = [ { 'player_id': int(x[0].value), 'p_name': x[1].value, 'rating': int(x[2].value), 'control-r': int(x[3].value), 'control-l': int(x[4].value), 'stuff-r': int(x[5].value), 'stuff-l': int(x[6].value), 'stamina': int(x[7].value), 'fielding': int(x[8].value), 'hit-9': int(x[9].value), 'k-9': int(x[10].value), 'bb-9': int(x[11].value), 'hr-9': int(x[12].value), 'hand': int(x[13].value), } for x in p_data ] except Exception as e: return {'valid': False} return { 'valid': True, 'batter_ratings': batters, 'pitcher_ratings': pitchers } async def paperdex_cardset_embed(team: dict, this_cardset: dict) -> [discord.Embed]: all_dex = await db_get( 'paperdex', params=[('team_id', team['id']), ('cardset_id', this_cardset['id']), ('flat', True)] ) dex_player_list = [x['player'] for x in all_dex['paperdex']] hof_embed = get_team_embed(f'{team["lname"]} Collection', team=team) mvp_embed = get_team_embed(f'{team["lname"]} Collection', team=team) as_embed = get_team_embed(f'{team["lname"]} Collection', team=team) sta_embed = get_team_embed(f'{team["lname"]} Collection', team=team) res_embed = get_team_embed(f'{team["lname"]} Collection', team=team) rep_embed = get_team_embed(f'{team["lname"]} Collection', team=team) coll_data = { 99: { 'name': 'Hall of Fame', 'owned': 0, 'players': [], 'embeds': [hof_embed] }, 1: { 'name': 'MVP', 'owned': 0, 'players': [], 'embeds': [mvp_embed] }, 2: { 'name': 'All-Star', 'owned': 0, 'players': [], 'embeds': [as_embed] }, 3: { 'name': 'Starter', 'owned': 0, 'players': [], 'embeds': [sta_embed] }, 4: { 'name': 'Reserve', 'owned': 0, 'players': [], 'embeds': [res_embed] }, 5: { 'name': 'Replacement', 'owned': 0, 'players': [], 'embeds': [rep_embed] }, 'total_owned': 0 } set_players = await db_get( 'players', params=[('cardset_id', this_cardset['id']), ('flat', True), ('inc_dex', False)], timeout=5 ) for player in set_players['players']: if player['player_id'] in dex_player_list: coll_data[player['rarity']]['owned'] += 1 coll_data['total_owned'] += 1 player['owned'] = True else: player['owned'] = False logging.debug(f'player: {player} / type: {type(player)}') coll_data[player['rarity']]['players'].append(player) cover_embed = get_team_embed(f'{team["lname"]} Collection', team=team) cover_embed.description = this_cardset['name'] cover_embed.add_field(name='# Total Cards', value=f'{set_players["count"]}') cover_embed.add_field(name='# Collected', value=f'{coll_data["total_owned"]}') display_embeds = [cover_embed] for rarity_id in coll_data: if rarity_id != 'total_owned': if coll_data[rarity_id]['players']: coll_data[rarity_id]['embeds'][0].description = f'Rarity: {coll_data[rarity_id]["name"]}' coll_data[rarity_id]['embeds'][0].add_field( name='# Collected / # Total Cards', value=f'{coll_data[rarity_id]["owned"]} / {len(coll_data[rarity_id]["players"])}', inline=False ) chunk_string = '' for index, this_player in enumerate(coll_data[rarity_id]['players']): logging.debug(f'this_player: {this_player}') chunk_string += '☑ ' if this_player['owned'] else '⬜ ' chunk_string += f'{this_player["p_name"]}\n' if (index + 1) == len(coll_data[rarity_id]["players"]): coll_data[rarity_id]['embeds'][0].add_field( name=f'Group {math.ceil((index + 1) / 20)} / ' f'{math.ceil(len(coll_data[rarity_id]["players"]) / 20)}', value=chunk_string ) elif (index + 1) % 20 == 0: coll_data[rarity_id]['embeds'][0].add_field( name=f'Group {math.floor((index + 1) / 20)} / ' f'{math.ceil(len(coll_data[rarity_id]["players"]) / 20)}', value=chunk_string ) chunk_string = '' display_embeds.append(coll_data[rarity_id]['embeds'][0]) return display_embeds async def paperdex_team_embed(team: dict, mlb_team: dict) -> [discord.Embed]: all_dex = await db_get( 'paperdex', params=[('team_id', team['id']), ('franchise', mlb_team['lname']), ('flat', True)] ) dex_player_list = [x['player'] for x in all_dex['paperdex']] c_query = await db_get('cardsets') coll_data = {'total_owned': 0} total_players = 0 for x in c_query['cardsets']: set_players = await db_get( 'players', params=[('cardset_id', x['id']), ('franchise', mlb_team['lname']), ('flat', True), ('inc_dex', False)] ) if set_players is not None: coll_data[x['id']] = { 'name': x['name'], 'owned': 0, 'players': [], 'embeds': [get_team_embed(f'{team["lname"]} Collection', team=team)] } total_players += set_players['count'] for player in set_players['players']: if player['player_id'] in dex_player_list: coll_data[x['id']]['owned'] += 1 coll_data['total_owned'] += 1 player['owned'] = True else: player['owned'] = False logging.debug(f'player: {player} / type: {type(player)}') coll_data[x['id']]['players'].append(player) cover_embed = get_team_embed(f'{team["lname"]} Collection', team=team) cover_embed.description = mlb_team['lname'] cover_embed.add_field(name='# Total Cards', value=f'{total_players}') cover_embed.add_field(name='# Collected', value=f'{coll_data["total_owned"]}') display_embeds = [cover_embed] for cardset_id in coll_data: if cardset_id != 'total_owned': if coll_data[cardset_id]['players']: coll_data[cardset_id]['embeds'][0].description = f'{mlb_team["lname"]} / ' \ f'{coll_data[cardset_id]["name"]}' coll_data[cardset_id]['embeds'][0].add_field( name='# Collected / # Total Cards', value=f'{coll_data[cardset_id]["owned"]} / {len(coll_data[cardset_id]["players"])}', inline=False ) chunk_string = '' for index, this_player in enumerate(coll_data[cardset_id]['players']): logging.debug(f'this_player: {this_player}') chunk_string += '☑ ' if this_player['owned'] else '⬜ ' chunk_string += f'{this_player["p_name"]}\n' if (index + 1) == len(coll_data[cardset_id]["players"]): coll_data[cardset_id]['embeds'][0].add_field( name=f'Group {math.ceil((index + 1) / 20)} / ' f'{math.ceil(len(coll_data[cardset_id]["players"]) / 20)}', value=chunk_string ) elif (index + 1) % 20 == 0: coll_data[cardset_id]['embeds'][0].add_field( name=f'Group {math.floor((index + 1) / 20)} / ' f'{math.ceil(len(coll_data[cardset_id]["players"]) / 20)}', value=chunk_string ) chunk_string = '' display_embeds.append(coll_data[cardset_id]['embeds'][0]) return display_embeds def get_pack_cover(pack): if pack['pack_type']['name'] in ['Premium', 'MVP']: return IMAGES['pack-pre'] elif pack['pack_type']['name'] == 'Standard': return IMAGES['pack-sta'] elif pack['pack_type']['name'] == 'Mario': return IMAGES['pack-mar'] else: return None async def open_st_pr_packs(all_packs: list, team: dict, context): pack_channel = get_channel(context, 'pack-openings') pack_cover = get_pack_cover(all_packs[0]) if pack_cover is None: pack_channel = context.channel if not pack_channel: raise ValueError(f'I cannot find the pack-openings channel. {get_cal_user(context).mention} - halp?') pack_ids = await roll_for_cards(all_packs) if not pack_ids: logging.error(f'open_packs - unable to roll_for_cards for packs: {all_packs}') raise ValueError(f'I was not able to unpack these cards') all_cards = [] for p_id in pack_ids: new_cards = await db_get('cards', params=[('pack_id', p_id)]) all_cards.extend(new_cards['cards']) if not all_cards: logging.error(f'open_packs - unable to get cards for packs: {pack_ids}') raise ValueError(f'I was not able to display these cards') # Present cards to opening channel if type(context) == commands.Context: author = context.author else: author = context.user await context.channel.send(content=f'Let\'s head down to {pack_channel.mention}!') await display_cards(all_cards, team, pack_channel, author, pack_cover=pack_cover) async def get_choice_from_cards( interaction: discord.Interaction, all_players: list = None, cover_title: str = None, cover_desc: str = None, cover_image_url: str = None, callback=None, temp_message: str = None, conf_message: str = None, delete_message: bool = False): # Display them with pagination, prev/next/select card_embeds = [ await get_card_embeds( {'player': x, 'team': {'lname': 'Paper Dynasty', 'season': PD_SEASON, 'logo': IMAGES['logo']}} ) for x in all_players ] logging.debug(f'card embeds: {card_embeds}') if cover_title is not None and cover_image_url is not None: page_num = 0 view = Pagination([interaction.user], timeout=30) view.left_button.disabled = True view.left_button.label = f'Prev: -/{len(card_embeds)}' view.cancel_button.label = f'Take This Card' view.cancel_button.style = discord.ButtonStyle.success view.cancel_button.disabled = True view.right_button.label = f'Next: 1/{len(card_embeds)}' msg = await interaction.channel.send( content=None, embed=image_embed( image_url=cover_image_url, title=cover_title, desc=cover_desc ), view=view ) else: page_num = 1 view = Pagination([interaction.user], timeout=30) view.left_button.label = f'Prev: -/{len(card_embeds)}' view.left_button.disabled = True view.cancel_button.label = f'Take This Card' view.cancel_button.style = discord.ButtonStyle.success view.right_button.label = f'Next: {page_num + 1}/{len(card_embeds)}' msg = await interaction.channel.send(content=None, embeds=card_embeds[page_num - 1], view=view) if temp_message is not None: temp_msg = await interaction.channel.send(content=temp_message) else: temp_msg = None while True: await view.wait() if view.value: if view.value == 'cancel': await msg.edit(view=None) if callback is not None: callback(all_players[page_num - 1]) if conf_message is not None: if temp_msg is not None: await temp_msg.edit(content=conf_message) else: await interaction.channel.send(content=conf_message) break if view.value == 'left': page_num -= 1 if page_num > 1 else len(card_embeds) if view.value == 'right': page_num += 1 if page_num < len(card_embeds) else 1 else: if page_num == len(card_embeds): page_num = 1 else: page_num += 1 view.value = None view = Pagination([interaction.user], timeout=30) view.left_button.label = f'Prev: {page_num - 1}/{len(card_embeds)}' view.cancel_button.label = f'Take This Card' view.cancel_button.style = discord.ButtonStyle.success view.right_button.label = f'Next: {page_num + 1}/{len(card_embeds)}' if page_num == 1: view.left_button.label = f'Prev: -/{len(card_embeds)}' view.left_button.disabled = True elif page_num == len(card_embeds): view.right_button.label = f'Next: -/{len(card_embeds)}' view.right_button.disabled = True await msg.edit(content=None, embeds=card_embeds[page_num - 1], view=view) if delete_message: await msg.delete() return all_players[page_num - 1] async def open_choice_pack(this_pack, team: dict, context, cardset_id: Optional[int] = None): pack_channel = get_channel(context, 'pack-openings') pack_cover = get_pack_cover(this_pack) pack_type = this_pack['pack_type']['name'] players = [] if pack_type == 'Mario': d1000 = random.randint(1, 1000) if d1000 > 800: rarity_id = 5 elif d1000 > 550: rarity_id = 3 else: rarity_id = 2 pl = await db_get( 'players/random', params=[ ('cardset_id', 8), ('min_rarity', rarity_id), ('max_rarity', rarity_id), ('limit', 4) ] ) players = pl['players'] elif pack_type == 'Team Choice': if this_pack['pack_team'] is None: raise KeyError(f'Team not listed for Team Choice pack') d1000 = random.randint(1, 1000) pack_cover = this_pack['pack_team']['logo'] if d1000 > 800: rarity_id = 5 pack_cover = IMAGES['mvp'][this_pack['pack_team']['lname']] elif d1000 > 550: rarity_id = 3 else: rarity_id = 2 # HAX FOR SOCC TO GET HIS MVP PACK if (team['abbrev'] in ['KSK', 'NJY']) and (datetime.datetime.today().day == 24): rarity_id = 5 min_rarity = rarity_id while len(players) < 4 and rarity_id < 10: params = [ ('min_rarity', min_rarity), ('max_rarity', rarity_id), ('limit', 4 - len(players)), ('franchise', this_pack['pack_team']['lname']) ] if this_pack['pack_team']['abbrev'] not in ['MSS']: params.append(('in_packs', True)) if cardset_id is not None: params.append(('cardset_id', cardset_id)) pl = await db_get( 'players/random', params=params ) if pl['count'] >= 0: for x in pl['players']: if x not in players: players.append(x) if len(players) < 4: min_rarity += 1 rarity_id += 1 elif pack_type == 'Promo Choice': if this_pack['pack_cardset'] is None: raise KeyError(f'Cardset not listed for Promo Choice pack') d1000 = random.randint(1, 1000) pack_cover = IMAGES['mvp-hype'] cardset_id = this_pack['pack_cardset']['id'] rarity_id = 5 if d1000 > 800: rarity_id = 8 while len(players) < 4 and rarity_id < 10: pl = await db_get( 'players/random', params=[('cardset_id', cardset_id), ('min_rarity', rarity_id), ('max_rarity', rarity_id), ('limit', 8)] ) if pl['count'] >= 0: for x in pl['players']: if len(players) >= 4: break if x not in players: players.append(x) if len(players) < 4: cardset_id = 17 else: # Get 4 MVP cards rarity_id = 5 if pack_type == 'HoF': rarity_id = 8 elif pack_type == 'All Star': rarity_id = 3 min_rarity = rarity_id while len(players) < 4 and rarity_id < 10: params = [ ('min_rarity', min_rarity), ('max_rarity', rarity_id), ('limit', 4), ('in_packs', True) ] if cardset_id is not None: params.append(('cardset_id', cardset_id)) pl = await db_get('players/random', params=params) if pl['count'] > 0: players.extend(pl['players']) if len(players) < 4: rarity_id += 3 if len(players) == 0: logging.error(f'Could not create choice pack') raise ConnectionError(f'Could not create choice pack') if type(context) == commands.Context: author = context.author else: author = context.user logging.info(f'helpers - open_choice_pack - players: {players}') # Display them with pagination, prev/next/select card_embeds = [ await get_card_embeds( # {'player': x, 'team': {'lname': 'Paper Dynasty', 'season': PD_SEASON, 'logo': IMAGES['logo']}} {'player': x, 'team': team} # Show team and dupe info ) for x in players ] logging.debug(f'card embeds: {card_embeds}') page_num = 0 view = Pagination([author], timeout=30) view.left_button.disabled = True view.left_button.label = f'Prev: -/{len(card_embeds)}' view.cancel_button.label = f'Take This Card' view.cancel_button.style = discord.ButtonStyle.success view.cancel_button.disabled = True view.right_button.label = f'Next: 1/{len(card_embeds)}' # React to selection await context.channel.send(f'Let\'s head down to {pack_channel.mention}!') msg = await pack_channel.send( content=None, embed=image_embed(pack_cover, title=f'{team["lname"]}', desc=f'{pack_type} Pack - Choose 1 of 4 {pack_type}s!'), view=view ) if rarity_id >= 5: tmp_msg = await pack_channel.send(content=f'<@&1163537676885033010> we\'ve got an MVP!') else: tmp_msg = await pack_channel.send(content=f'We\'ve got a choice pack here!') while True: await view.wait() if view.value: if view.value == 'cancel': await msg.edit(view=None) try: await give_cards_to_team(team, players=[players[page_num - 1]], pack_id=this_pack['id']) except Exception as e: logging.error(f'failed to create cards: {e}') raise ConnectionError(f'Failed to distribute these cards.') await db_patch('packs', object_id=this_pack['id'], params=[ ('open_time', int(datetime.datetime.timestamp(datetime.datetime.now()) * 1000)) ]) await tmp_msg.edit( content=f'{players[page_num - 1]["p_name"]} has been added to the ' f'**{team["sname"]}** binder!' ) break if view.value == 'left': page_num -= 1 if page_num > 1 else len(card_embeds) if view.value == 'right': page_num += 1 if page_num < len(card_embeds) else 1 else: if page_num == len(card_embeds): page_num = 1 else: page_num += 1 view.value = None view = Pagination([author], timeout=30) view.left_button.label = f'Prev: {page_num - 1}/{len(card_embeds)}' view.cancel_button.label = f'Take This Card' view.cancel_button.style = discord.ButtonStyle.success view.right_button.label = f'Next: {page_num + 1}/{len(card_embeds)}' if page_num == 1: view.left_button.label = f'Prev: -/{len(card_embeds)}' view.left_button.disabled = True elif page_num == len(card_embeds): view.right_button.label = f'Next: -/{len(card_embeds)}' view.right_button.disabled = True await msg.edit(content=None, embeds=card_embeds[page_num - 1], view=view) async def confirm_pack_purchase(interaction, owner_team, num_packs, total_cost, pack_embed): view = Confirm(responders=[interaction.user], timeout=30) await interaction.channel.send( content=None, embed=pack_embed ) question = await interaction.channel.send( content=f'Your Wallet: {owner_team["wallet"]}₼\n' f'Pack{"s" if num_packs > 1 else ""} Price: {total_cost}₼\n' f'After Purchase: {owner_team["wallet"] - total_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 None else: return question def player_desc(this_player) -> str: if this_player['p_name'] in this_player['description']: return this_player['description'] return f'{this_player["description"]} {this_player["p_name"]}' def player_pcard(this_player): if this_player['image'] is not None and 'pitching' in this_player['image']: return this_player['image'] elif this_player['image2'] is not None and 'pitching' in this_player['image2']: return this_player['image2'] else: return this_player['image'] def player_bcard(this_player): if this_player['image'] is not None and 'batting' in this_player['image']: return this_player['image'] elif this_player['image2'] is not None and 'batting' in this_player['image2']: return this_player['image2'] # elif this_player['image'] is not None and 'pitching' in this_player['image']: # return PITCHER_BATTING_CARD else: return this_player['image'] def random_gif(search_term: str): req_url = f'https://api.giphy.com/v1/gifs/translate?s={search_term}&api_key=H86xibttEuUcslgmMM6uu74IgLEZ7UOD' resp = requests.get(req_url, timeout=3) if resp.status_code == 200: data = resp.json() if 'trump' in data['data']['title']: return random_conf_gif() else: return data['data']['url'] else: logging.warning(resp.text) raise ValueError(f'DB: {resp.text}') def random_from_list(data_list: list): item = data_list[random.randint(0, len(data_list) - 1)] logging.info(f'random_from_list: {item}') return item