import copy import math import re from typing import Optional from api_calls.current import get_current from api_calls.draft_data import DraftData, get_draft_data from api_calls.draft_pick import DraftPick, get_one_draftpick, patch_draftpick from api_calls.player import Player from exceptions import ApiException, log_exception from helpers import * from db_calls import db_get, db_patch, put_player, db_post, get_player_by_name from discord.ext import commands, tasks from discord import TextChannel, app_commands logger = logging.getLogger('discord_app') class Draft(commands.Cog): def __init__(self, bot): self.bot = bot self.warnings = 0 self.pick_lock = True self.guild: Optional[discord.Guild] = None self.fa_team_id = 498 # self.sheets = pygsheets.authorize(service_file='storage/major-domo-service-creds.json') self.draft_loop.start() # type: ignore async def cog_command_error(self, ctx, error): logger.error(msg=error, stack_info=True, exc_info=True) await ctx.send(f'{error}\n\nRun !help to see the command requirements') async def slash_error(self, ctx, error): logger.error(msg=error, stack_info=True, exc_info=True) await ctx.send(f'{error[:1600]}') @tasks.loop(seconds=10) async def draft_loop(self): logger.info(f'JUST ENTERED THE DRAFTD LOOP') guild_id = os.environ.get('GUILD_ID') if guild_id is None: log_exception(AttributeError(f'Guild ID was not found')) logger.info(f'GUILD ID: {guild_id}') guild = self.bot.get_guild(int(guild_id)) if guild is None: log_exception(LookupError(f'Guild not found with ID {guild_id}')) self.guild = guild logger.info(f'GUILD: {guild}') draft_data = await get_draft_data() now = datetime.datetime.now() # logger.info('Entering draft loop') deadline = draft_data.pick_deadline # logger.info(f'Timer: {draft_data.timer} / Deadline: {deadline} / Warnings: {self.warnings}') # logger.info(f'10 Hrs?: {deadline - datetime.timedelta(hours=10) <= now}') # Timer is active and pick is due if draft_data.timer and deadline: current = await get_current() draft_pick = await get_one_draftpick(season=current.season, overall=draft_data.currentpick) if not draft_pick.owner: log_exception(ValueError(f'Pick #{draft_pick.overall} has no owner associated')) return logger.info(f'getting team role') team_role = get_team_role(None, draft_pick.owner.model_dump(), self.bot) logger.info(f'team role: {team_role}') if deadline <= now: logger.info(f'deadline has past') # Auto-draft # team_list = await get_draft_list(draft_pick['owner']) l_query = await db_get('draftlist', params=[ ('season', current.season), ('team_id', draft_pick.owner.id) ]) if l_query and l_query.get('count', 0) > 0: for x in l_query['picks']: this_pick = await self.draft_player(current, draft_data, draft_pick, x['player']) if this_pick['success']: break await self.advance_pick() else: logger.info(f'deadline has not yet past') # # Slow Draft # if (deadline - datetime.timedelta(hours=10) <= now) and self.warnings == 0: # current = await get_current() # draft_pick = await get_one_draftpick_byoverall(current.season, draft_data.currentpick) # team_role = get_team_role(None, draft_pick['owner'], self.bot) # await send_to_channel( # self.bot, # draft_data.ping_channel, # f'{team_role.mention if team_role else draft_pick.owner.lname}\n' # f'You are two hours in on pick #{draft_pick.overall}.' # ) # self.warnings = 1 # elif (deadline - datetime.timedelta(hours=6) <= now) and self.warnings == 1: # current = await get_current() # draft_pick = await get_one_draftpick_byoverall(current.season, draft_data.currentpick) # team_role = get_team_role(None, draft_pick['owner'], self.bot) # await send_to_channel( # self.bot, # draft_data.ping_channel, # f'{team_role.mention if team_role else draft_pick.owner.lname}\n' # f'You are halfway into the 12-hour timer for pick #{draft_pick.overall}.' # ) # self.warnings = 2 # elif (deadline - datetime.timedelta(hours=2) <= now) and self.warnings == 2: # current = await get_current() # draft_pick = await get_one_draftpick_byoverall(current.season, draft_data.currentpick) # team_role = get_team_role(None, draft_pick['owner'], self.bot) # await send_to_channel( # self.bot, # draft_data.ping_channel, # f'{team_role.mention if team_role else draft_pick.owner.lname}\n' # f'You have two hours remaining to make pick #{draft_pick.overall}.' # ) # self.warnings = 3 # elif (deadline - datetime.timedelta(hours=1) <= now) and self.warnings == 3: # current = await get_current() # draft_pick = await get_one_draftpick_byoverall(current.season, draft_data.currentpick) # team_role = get_team_role(None, draft_pick['owner'], self.bot) # await send_to_channel( # self.bot, # draft_data.ping_channel, # f'{team_role.mention if team_role else draft_pick.owner.lname}\n' # f'You have one hour remaining to make pick #{draft_pick.overall}.' # ) # self.warnings = 4 # elif (deadline - datetime.timedelta(minutes=5) <= now) and self.warnings == 4: # current = await get_current() # draft_pick = await get_one_draftpick_byoverall(current.season, draft_data.currentpick) # team_role = get_team_role(None, draft_pick['owner'], self.bot) # await send_to_channel( # self.bot, # draft_data.ping_channel, # f'{team_role.mention if team_role else draft_pick.owner.lname}\n' # f'You have five minutes remaining to make pick #{draft_pick.overall}. Is Neil Walker available?' # ) # self.warnings = 5 # 2-minute draft logger.info(f'pulling p_channel') p_channel = discord.utils.get(self.guild.text_channels, id=int(draft_data.ping_channel)) # type: ignore if not p_channel: log_exception(LookupError(f'Ping channel with ID {draft_data.ping_channel} not found')) logger.info(f'p_channel: {p_channel}') if (deadline - datetime.timedelta(seconds=59) <= now) and self.warnings < 1: await p_channel.send( content=f'{team_role.mention if team_role else draft_pick.owner.lname}\nLess than a minute remaining to make pick #{draft_pick.overall}!' ) self.warnings += 1 elif (deadline - datetime.timedelta(seconds=29) <= now) and self.warnings < 2: await p_channel.send( content=f'{team_role.mention if team_role else draft_pick.owner.lname}\nLess than 30 seconds remaining to make pick #{draft_pick.overall}!' ) self.warnings += 1 @draft_loop.before_loop async def before_weekly_check(self): await self.bot.wait_until_ready() async def update_timer(self, draft_data): deadline = datetime.datetime.now() + datetime.timedelta(minutes=draft_data.pick_minutes) # await patch_draftdata(pick_deadline=deadline) await db_patch('draftdata', object_id=draft_data.id, params=[('pick_deadline', deadline)]) async def send_draft_ping(self, ping=True): current = await get_current() draft_data = await get_draft_data() logger.info(f'current: {current}\nd_data: {draft_data}') this_pick = await get_one_draftpick(season=current.season, overall=draft_data.currentpick) if not this_pick.owner: log_exception(ValueError(f'Pick #{this_pick.overall} has no owner associated')) return logger.info(f'pick: {this_pick}') this_team = await db_get('teams', object_id=this_pick.owner.id) logger.info(f'team: {this_team}') if not this_team: raise ApiException(f'Team for {this_pick.owner.abbrev} not found') team_role = get_team_role(None, this_team, self.bot) logger.info(f'role: {team_role}') team_ping = None if ping and team_role: team_ping = team_role.mention pick_num = this_pick.overall % 16 if pick_num == 0: pick_num = 16 logger.info(f'current: {current}\ndata: {draft_data}\npick: {this_pick}\nteam: {this_team}\n' f'role: {team_role}\nping: {team_ping}') embed = get_team_embed(f'{this_team["lname"]} On The Clock', team=this_team) embed.description = f'Round {this_pick.round} / Pick {pick_num} / Overall {this_pick.overall}' # players = await get_players(current.season, team_abbrev=this_team['abbrev'], sort='wara-desc') p_query = await db_get('players', params=[ ('season', current.season), ('team_id', this_team['id']), ('sort', 'cost-desc') ]) if p_query and p_query.get('count') > 0: count = 0 core_players_string = '' for x in p_query['players']: if count < 5: core_players_string += f'{x["pos_1"]} {x["name"]} ({x["wara"]})\n' else: break count += 1 embed.add_field(name='Core Players', value=core_players_string) # roster = await get_team_roster(this_team, 'current') r_query = await db_get(f'teams/{this_team["id"]}/roster/current') if r_query and r_query.get('active'): embed.add_field(name=f'{this_team["sname"]} sWAR', value=f'{r_query["active"]["WARa"]:.2f}') # embed.add_field( # name=f'{this_team["abbrev"]} Roster Page', # value=f'https://sombaseball.ddns.net/teams?abbrev={this_team["abbrev"]}', # inline=False # ) embed.add_field( name=f'Draft Sheet', value=f'https://docs.google.com/spreadsheets/d/{DRAFT_KEY.get(current.season, f'help-i-dont-know-the-sheet-id-for-season-{current.season}')}', inline=False ) # Last 5 Loop # all_picks = await get_draftpicks( # current.season, overall_start=this_pick['overall'] - 15, overall_end=this_pick['overall'] - 1 # ) # last_five = dict(sorted(all_picks.items(), key=lambda item: item[1]["overall"], reverse=True)) l_query = await db_get('draftpicks', params=[ ('season', current.season), ('sort', 'order-desc'), ('limit', 5), ('short_output', False), ('overall_end', this_pick.overall - 1), ('player_taken', True) ]) if not l_query or not l_query.get('picks'): all_picks = [] else: all_picks = l_query['picks'] last_string = '' count = 0 for x in all_picks: if x['player'] is not None: last_string += f'Pick #{x["overall"]}: {x["player"]["name"]}\n' count += 1 if count >= 5: break if len(last_string) == 0: last_string = 'None, yet' embed.add_field(name='Last 5', value=last_string) # Next 5 Loop # all_picks = await get_draftpicks( # current.season, overall_start=this_pick['overall'] + 1, overall_end=this_pick['overall'] + 15 # ) # next_five = dict(sorted(all_picks.items(), key=lambda item: item[1]["overall"])) n_query = await db_get('draftpicks', params=[ ('season', current.season), ('sort', 'order-asc'), ('limit', 5), ('short_output', False), ('overall_start', this_pick.overall + 1) ]) if not n_query or not n_query.get('picks'): all_picks = [] else: all_picks = n_query['picks'] next_string = '' for x in all_picks: next_string += f'Pick #{x["overall"]}: {x["owner"]["sname"]}\n' if len(next_string) == 0: next_string = 'None, yet' embed.add_field(name='Next 5', value=next_string) # Pick Deadline if draft_data.pick_deadline: deadline = draft_data.pick_deadline # deadline = deadline - datetime.timedelta(hours=6) dead_string = deadline.strftime("%b %d @ %H:%M Central") # dead_string = f'' else: dead_string = 'None' embed.add_field(name='Pick Deadline', value=dead_string, inline=False) p_channel = discord.utils.get(self.guild.text_channels, id=int(draft_data.ping_channel)) # type: ignore if not p_channel: log_exception(LookupError(f'Ping channel with ID {draft_data.ping_channel} not found')) await p_channel.send(content=team_ping, embed=embed) # await send_to_channel(self.bot, draft_data.ping_channel, content=team_ping, embed=embed) async def advance_pick(self): self.pick_lock = True current = await get_current() draft_data = await get_draft_data() starting_round = math.ceil(draft_data.currentpick / 16) draft_pick = await get_one_draftpick(season=current.season, overall=draft_data.currentpick) if not draft_pick.owner: log_exception(ValueError(f'Pick #{draft_pick.overall} has no owner associated')) return p_channel = discord.utils.get(self.guild.text_channels, id=int(draft_data.ping_channel)) # type: ignore if not p_channel: log_exception(LookupError(f'Ping channel with ID {draft_data.ping_channel} not found')) r_channel = discord.utils.get(self.guild.text_channels, id=int(draft_data.result_channel)) # type: ignore if not r_channel: log_exception(LookupError(f'Ping channel with ID {draft_data.ping_channel} not found')) if draft_pick.player is None: await p_channel.send( content=f'It\'s time to to ***SKIP***. Can I get an F in chat?' ) self.warnings = 0 # await patch_draftdata(currentpick=draft_data.currentpick + 1) d_query = await db_patch('draftdata', object_id=draft_data.id, params=[ ('currentpick', draft_data.currentpick + 1) ]) draft_data = DraftData(**d_query) # Advance the current pick until a selection is possible while True: # draft_data = await get_draft_data() # draft_pick = await get_one_draftpick_byoverall(current.season, draft_data.currentpick) if draft_data.currentpick > 512: await p_channel.send( content='Looks like that is the end of the draft!' ) await db_patch('draftdata', object_id=draft_data.id, params=[('timer', False)]) self.pick_lock = False return draft_pick = await get_one_draftpick(season=current.season, overall=draft_data.currentpick) if not draft_pick.owner: log_exception(ValueError(f'Pick #{draft_pick.overall} has no owner associated')) return # Check that selection has been made for current pick, advance if so if draft_pick.player is not None: # await patch_draftdata(currentpick=draft_data.currentpick + 1) draft_data = await db_patch('draftdata', object_id=draft_data.id, params=[ ('currentpick', draft_data.currentpick + 1) ]) else: round_num = math.ceil(draft_data.currentpick / 16) if round_num > starting_round: await r_channel.send( content=f'# Round {round_num}' ) break # If timer is true, set new deadline # draft_data = await get_draft_data() # No longer needed if draft_data.timer: await self.update_timer(draft_data) else: deadline = datetime.datetime.now() + datetime.timedelta(days=690) # await patch_draftdata(pick_deadline=deadline) await db_patch('draftdata', object_id=draft_data.id, params=[('pick_deadline', deadline)]) # Post splash screen to ping_channel await self.send_draft_ping() self.pick_lock = False async def send_pick_to_sheets(self, draft_pick: DraftPick, player, pick_num): if not draft_pick.origowner or not draft_pick.owner: log_exception(AttributeError(f'Draft Pick ID #{draft_pick.id} is missing ownership data')) sheets = pygsheets.authorize(service_file='storage/major-domo-service-creds.json') # this_pick = [[ # draft_pick['round'], pick_num, draft_pick['owner']['abbrev'], draft_pick['owner']['sname'], # player['name'], player['wara'], draft_pick['overall'] # ]] this_pick = [[ draft_pick.origowner.abbrev, draft_pick.owner.abbrev, player['name'], player['wara'] ]] sheets_key = DRAFT_KEY.get(draft_pick.season) if sheets_key: logger.info(f'sending pick to sheets') sheets.open_by_key(sheets_key).worksheet_by_title('Ordered List').update_values( crange=f'D{draft_pick.overall + 1}', values=this_pick ) logger.info(f'pick has been sent to sheets') else: logger.error(f'Cannot find draft key for season {draft_pick.season}') async def draft_player(self, current, draft_data: DraftData, draft_pick: DraftPick, player): # Is this player available to be drafted? if draft_pick.owner is None: log_exception(ValueError(f'Draft pick S{draft_pick.season} / overall {draft_pick.overall} has no owner')) return if player['team']['id'] == draft_pick.owner.id: return {'success': False, 'error': f'{player["name"]} is already on your team, dingus.'} elif player['team']['abbrev'] != 'FA': return { 'success': False, 'error': f'Hey, uh, {player["team"]["sname"]}, you think {draft_pick.owner.abbrev} can have ' f'{player["name"]} for free? No? Sorry, I tried.' } # team_roster = await get_team_roster(draft_pick['owner'], 'current') team_roster = await db_get(f'teams/{draft_pick.owner.id}/roster/current') if not team_roster or not team_roster.get('active'): raise ApiException(f'Team roster not found for {draft_pick.owner.abbrev}') # Does this team have the cap space to afford this player? max_zeroes = 32 - len(team_roster['active']['players']) max_counted = 26 - max_zeroes total_swar = 0 count = 0 for x in team_roster['active']['players']: count += 1 if count > max_counted: break total_swar += x['wara'] if total_swar > 32.00001: return { 'success': False, 'error': f'Drafting {player["name"]} would put you at {total_swar:.2f} ' f'sWAR, friendo.' } logger.info( f'{draft_pick.owner.lname} selects {player["name"]} with the #{draft_pick.overall} overall pick' ) draft_pick.player = Player(**player) await patch_draftpick(draft_pick) # TODO: uncomment for live draft player['team']['id'] = draft_pick.owner.id player['demotion_week'] = 1 # await patch_player(player['id'], team_id=draft_pick['owner']['id'], demotion_week=2) await put_player(player) # TODO: uncomment for live draft # await post_transactions([{ # 'week': -1, # 'player_id': player['id'], # 'oldteam_id': 201, # FA team ID # 'newteam_id': draft_pick['owner']['id'], # 'season': current.season, # 'moveid': f'draft-overall-{draft_pick.overall}' # }]) await db_post('transactions', payload={'count': 1, 'moves': [{ 'week': 0, 'player_id': player['id'], 'oldteam_id': self.fa_team_id, 'newteam_id': draft_pick.owner.id, 'season': current.season, 'moveid': f'draft-overall-{draft_pick.overall}' }]}) p_channel = discord.utils.get(self.guild.text_channels, id=int(draft_data.ping_channel)) # type: ignore if not p_channel: log_exception(LookupError(f'Ping channel with ID {draft_data.ping_channel} not found')) await p_channel.send( content=None, embed=await get_player_embed(player, current) ) if player['image2']: embed = get_team_embed(f'{player["name"]}', player["team"], thumbnail=False) embed.set_image(url=player['image2']) await p_channel.send( content=None, embed=embed ) pick_num = draft_pick.overall % 16 if pick_num == 0: pick_num = 16 await self.send_pick_to_sheets(draft_pick, player, draft_pick.overall) await p_channel.send( content=f'Round {draft_pick.round} / Pick {pick_num}: {draft_pick.owner.abbrev} selects **{player["name"]}**' ) return {'success': True} async def draftdata_to_string(self, current, draft_data: DraftData): draft_pick = await get_one_draftpick(season=current.season, overall=draft_data.currentpick) if not draft_pick.owner: log_exception(ValueError(f'Pick #{draft_pick.overall} has no owner associated')) return if draft_data.pick_deadline: deadline = draft_data.pick_deadline deadline = deadline - datetime.timedelta(hours=6) dead_string = deadline.strftime("%b %d @ %H:%M Central") else: dead_string = 'None' p_channel = discord.utils.get(self.guild.text_channels, id=int(draft_data.ping_channel)) if not p_channel: log_exception(LookupError(f'Ping channel with ID {draft_data.ping_channel} not found')) r_channel = discord.utils.get(self.guild.text_channels, id=int(draft_data.result_channel)) if not r_channel: log_exception(LookupError(f'Result channel with ID {draft_data.result_channel} not found')) draft_string = f'Current Pick: {draft_data.currentpick}\n' \ f'Pick Owner: {draft_pick.owner.sname if draft_pick else "N/A"}\n' \ f'Timer: {"Active" if draft_data.timer else "Inactive"} ' \ f'({draft_data.pick_minutes} min total)\n' \ f'Pick Deadline: {dead_string}\n' \ f'Ping Channel: {p_channel.mention}\n' \ f'Result Channel: {r_channel.mention}\n' \ f'Pick Lock: {self.pick_lock}' logger.info(f'draft_string: {draft_string}') return draft_string @commands.command(name='select', aliases=['pick', 'draft', 'gib', 'gimme', 'deliveruntomewhatismine', 'itsmyplayerandineedhimnow'], help='Draft a player') @commands.cooldown(1, 5, commands.BucketType.user) @commands.has_any_role(SBA_PLAYERS_ROLE_NAME) async def draft_command(self, ctx, *, name): cal_can_pick_for_all = True if self.pick_lock: await react_and_reply(ctx, '❌', 'Picks are locked right now') return else: self.pick_lock = True current = await get_current() team = await get_team_by_owner(current.season, ctx.author.id) if not team: # if ctx.author.id == 403294362550796299: # team = await get_team_by_abbrev('HAM', current.season) # else: await ctx.message.add_reaction('❌') await ctx.send('I don\'t know youuuuuuuuu') self.pick_lock = False return draft_data = await get_draft_data() if draft_data is None: logger.error(f'Cannot find draft_data') await send_to_channel(self.bot, 'commissioners-office', f'Failed to GET draftdata') self.pick_lock = False raise LookupError('Cannot find draftdata, send help') draft_pick = await get_one_draftpick(season=current.season, overall=draft_data.currentpick) if not draft_pick.owner: self.pick_lock = False log_exception(ValueError(f'Pick #{draft_pick.overall} has no owner associated')) alt_pick_flag = False # Does the current pick belong to this team? if draft_pick.owner.id != team['id']: alt_pick_flag = True # Does this team have any skipped picks? # raw_picks = await get_draftpicks( # current.season, owner_team=team, round_end=math.ceil(draft_pick['overall'] / 16), round_start=1 # ) # TODO: add get_draftpicks to api_calls.draft_picks p_query = await db_get('draftpicks', params=[ ('season', current.season), ('owner_team_id', team['id']), ('round_start', 1), ('round_end', math.ceil(draft_pick.overall / 16)), ('sort', 'overall-asc'), ('short_output', False) ]) if p_query['count'] == 0: raise LookupError(f'Draft picks for {team["abbrev"]} not found') new_pick = None for x in p_query['picks']: if x["player"] is None and x['overall'] < draft_pick.overall: # new_pick = await get_one_draftpick_byoverall(current.season, team_picks[x]['overall']) # p_query = await db_get('draftpicks', params=[ # ('season', current.season), ('overall', x['overall']), ('short_output', False) # ]) # if p_query['count'] == 0: # raise ValueError(f'No pick found for overall #{x["overall"]}') new_pick = x break if new_pick is not None: draft_pick = new_pick else: # mil_team = await get_team_by_abbrev(f'{team["abbrev"]}MiL', current.season) # if mil_team == draft_pick['owner']: # team = mil_team if ctx.author.id == self.bot.owner_id and cal_can_pick_for_all: alt_pick_flag = False else: await ctx.message.add_reaction('❌') await ctx.send(f'You are not on the clock {ctx.author.mention}. **{draft_pick.owner.abbrev}** is up right now.') self.pick_lock = False return player_cog = self.bot.get_cog('Players') try: player_name = await fuzzy_player_search(ctx, ctx.channel, self.bot, name, player_cog.player_list.keys()) except ValueError as e: logger.error(e) await ctx.send(f'{name} not found') self.pick_lock = False return player = await get_player_by_name(current.season, player_name) if player is None: e_msg = f'Could not find {player_name} after matching the name' logger.error(e_msg) await ctx.send(e_msg) self.pick_lock = False return the_pick = await self.draft_player(current, draft_data, draft_pick, player) if the_pick is None: log_exception(ValueError('Did not receive confirmation for this pick.')) self.pick_lock = False return self.pick_lock = False if the_pick['success']: await ctx.message.add_reaction('✅') if not alt_pick_flag: await self.advance_pick() else: await react_and_reply(ctx, '❌', the_pick['error']) @commands.command(name='list', aliases=['draftlist', 'mylist'], help='Set your draft list') # @commands.has_any_role(SBA_PLAYERS_ROLE_NAME) async def draft_list_command(self, ctx, *player_list): current = await get_current() team = await get_team_by_owner(current.season, ctx.author.id) if not team: await react_and_reply(ctx, '❌', 'I don\'t know youuuuuuuuu') return if not player_list: # team_list = await get_draft_list(team) l_query = await db_get('draftlist', params=[ ('season', current.season), ('team_id', team['id']) ]) if not l_query: raise ApiException('There was a database error trying to pull your list') if l_query['count'] > 0: list_string = 'Current list:\n' for x in l_query['picks']: list_string += f'{x["rank"]}. {x["player"]["name"]}\n' await ctx.send(list_string) else: await ctx.send('I do not have a list for you.') return else: player_list = ' '.join(player_list) player_names = re.split(',', player_list) draft_list = [] spelling = [] errors = [] rank = 0 for x in player_names: rank += 1 player_cog = self.bot.get_cog('Players') try: player_name = await fuzzy_player_search(ctx, ctx.channel, self.bot, x, player_cog.player_list.keys()) player = await get_player_by_name(current.season, player_name) except Exception as e: logger.error(f'Could not draft {x} for {team["abbrev"]}: {e}') errors.append(x) rank -= 1 else: if not player: log_exception(LookupError(f'{player_name} not found')) if player.get('team', {'abbrev': None}).get('abbrev') != 'FA': errors.append(player['name']) rank -= 1 else: draft_list.append({ 'season': current.season, 'team_id': team['id'], 'rank': rank, 'player_id': player['id'] }) if len(errors) > 0: error_string = 'The following players have already been drafted: ' error_string += ', '.join(errors) await react_and_reply(ctx, '😬', error_string) if len(spelling) > 0: error_string = 'Who the fuck is this: ' error_string += ', '.join(spelling) await react_and_reply(ctx, '❓', error_string) # await post_draft_list(draft_list) await db_post('draftlist', payload={'count': len(draft_list), 'draft_list': draft_list}) await ctx.message.add_reaction('✅') @commands.command(name='restart-loop', help='Mod: Restart the draft tick loop') async def restart_loop_command(self, ctx): if ctx.author.id not in [258104532423147520, 139926308644847616]: await ctx.send(random_gif('you\'re not my dad')) return self.draft_loop.start() await ctx.send(random_gif('we are so back')) @commands.command(name='whomst', aliases=['draftstatus'], help='Get draft status') async def draft_status_command(self, ctx): current = await get_current() draft_data = await get_draft_data() logger.info(f'building draft string') async with ctx.typing(): await ctx.send(await self.draftdata_to_string(current, draft_data)) async with ctx.typing(): if draft_data.ping_channel is not None: await self.send_draft_ping(ping=False) @app_commands.command(name='draft-mod', description='Mod commands for draft administration') @app_commands.describe( result_channel='Text Channel: where Major Domo posts draft results', ping_channel='Text Channel: where Major Domo pings for draft picks', current_overall='Integer: override the current pick number', timer_master='Integer: number of minutes for all draft picks', timer_this_pick='Integer: number of minutes for the current draft pick', timer_active='Boolean: enable/disable the pick timer', wipe_pick='Integer: overall pick number to delete; cannot be combined with other parameters', pick_lock='Boolean: enable/disable the !select command' ) async def draftmod_slash_command( self, interaction: discord.Interaction, result_channel: Optional[TextChannel] = None, ping_channel: Optional[TextChannel] = None, current_overall: Optional[int] = None, timer_master: Optional[int] = None, timer_this_pick: Optional[int] = None, timer_active: Optional[bool] = None, wipe_pick: Optional[int] = None, pick_lock: Optional[bool] = None): if interaction.user.id not in [258104532423147520, 139926308644847616]: await interaction.response.send_message(random_gif('you\'re not my dad')) return await interaction.response.defer() current = await get_current() draft_data = await get_draft_data() pick_deadline = None res_channel_id = None ping_channel_id = None if wipe_pick is not None: this_pick = await get_one_draftpick(season=current.season, overall=wipe_pick) # if not this_pick.owner: # log_exception(ValueError(f'Pick #{this_pick.overall} has no owner associated')) # return if not this_pick.player: await interaction.edit_original_response(content=f'I don\'t see a player taken {wipe_pick} overall.') else: fa_team = await get_team_by_abbrev('FA', current.season) temp_player = this_pick.player.model_dump() this_pick.player = None await patch_draftpick(this_pick) temp_player['team'] = fa_team this_player = await put_player(temp_player) # this_player = await get_player_by_name(current.season, this_pick['player']['id']) # this_player = await db_get('players', object_id=this_pick['player']['id']) await interaction.edit_original_response( content='Don\'t forget to delete the transaction from the database.', embed=await get_player_embed(this_player, current) ) return if pick_lock is not None: if pick_lock: self.pick_lock = True else: self.pick_lock = False params = [] if timer_this_pick is not None: params.append(('pick_deadline', datetime.datetime.now() + datetime.timedelta(minutes=timer_this_pick))) if timer_active is not None: params.append(('timer', timer_active)) if timer_active is True: if timer_this_pick is not None: timer = timer_this_pick elif timer_master is not None: timer = timer_master params.append(('pick_minutes', timer_master)) else: timer = draft_data.pick_minutes deadline = datetime.datetime.now() + datetime.timedelta(minutes=timer) else: deadline = datetime.datetime.now() + datetime.timedelta(weeks=1) params.append(('pick_deadline', deadline)) if result_channel is not None: params.append(('result_channel', result_channel.id)) if ping_channel is not None: params.append(('ping_channel', ping_channel.id)) if current_overall is not None and draft_data.timer and timer_active is None: params.append( ('pick_deadline', datetime.datetime.now() + datetime.timedelta(minutes=draft_data.pick_minutes)) ) if current_overall is not None: params.append(('currentpick', current_overall)) if timer_master is not None: params.append(('pick_minutes', timer_master)) # await patch_draftdata( # currentpick=current_overall, # timer=timer_active, # pick_deadline=pick_deadline, # result_channel=res_channel_id, # ping_channel=ping_channel_id, # pick_minutes=timer_master # ) await db_patch('draftdata', object_id=draft_data.id, params=params) draft_data = await get_draft_data() await interaction.edit_original_response(content=await self.draftdata_to_string(current, draft_data)) if timer_active is True or draft_data.timer: self.warnings = 0 # draft_pick = await get_one_draftpick_byoverall(current.season, draft_data.currentpick) p_query = await db_get('draftpicks', params=[ ('season', current.season), ('overall', draft_data.currentpick), ('short_output', False) ]) if not p_query: raise ApiException(f'No pick found for Overall #{draft_data.currentpick}') if p_query['count'] == 0: raise ValueError(f'No pick found for overall #{wipe_pick}') draft_pick = p_query['picks'][0] if draft_pick['player']: await self.advance_pick() else: await self.send_draft_ping() @commands.command(name='keepers', help='Set keepers', hidden=True) async def keepers_command(self, ctx, team_abbrev, *, player_names): if ctx.author.id not in [258104532423147520, 139926308644847616]: return current = await get_current() player_list = [] this_team = await get_team_by_abbrev(team_abbrev, current.season) if not this_team: raise ApiException(f'{team_abbrev} not found') fa_team = await get_team_by_abbrev('FA', current.season) if not fa_team: raise ApiException(f'FA team not found') player_names = re.split(',', player_names) keepers = [] keeper_count = 1 for x in player_names: player_cog = self.bot.get_cog('Players') player_name = await fuzzy_player_search(ctx, ctx.channel, self.bot, x, player_cog.player_list.keys()) player = await get_player_by_name(current.season, player_name) if not player: raise ApiException(f'{player_name} not found') keepers.append(player["name"]) all_players = [] # active_roster = await get_players(season=current.season, team_abbrev=team_abbrev) # il_players = await get_players(season=current.season, team_abbrev=f'{team_abbrev}IL') # mil_players = await get_players(season=current.season, team_abbrev=f'{team_abbrev}MiL') active_roster = await db_get('players', params=[('season', current.season), ('team_id', this_team['id'])]) if not active_roster: raise ApiException(f'Active roster not found for {this_team["id"]}') il_players = await db_get('players', params=[('season', current.season), ('team_id', this_team['id'] + 1)]) if not il_players: raise ApiException(f'Active roster not found for {this_team["id"]}') mil_players = await db_get('players', params=[('season', current.season), ('team_id', this_team['id'] + 2)]) if not mil_players: raise ApiException(f'Active roster not found for {this_team["id"]}') for guy in active_roster['players']: all_players.append(guy) for guy in il_players['players']: all_players.append(guy) for guy in mil_players['players']: all_players.append(guy) # all_picks = await get_draftpicks(season=current.season, owner_team=this_team, round_end=7) if current.season % 2 == 1: start_round = 1 end_round = 7 else: start_round = 2 end_round = 8 p_query = await db_get( 'draftpicks', params=[('season', current.season), ('owner_team_id', this_team['id']), ('pick_round_start', start_round), ('pick_round_end', end_round), ('sort', 'order-asc')] ) if not p_query: raise ApiException(f'No picks found for {this_team["abbrev"]} in rounds {start_round} to {end_round}') # logger.info(f'\n{all_picks}\n') # sorted_picks = sorted(all_picks.items(), key=lambda item: item[1]["overall"]) sorted_picks = p_query['picks'] # for p in all_picks: # sorted_picks.append(all_picks[p]) # logger.info(f'{sorted_picks}') all_moves = [] all_keepers = [] for x in all_players: logger.info(f'Checking {x["name"]} for keeper status') this_player = copy.deepcopy(x) if x["name"] in keepers: logger.info(f'{x["name"]} is a keeper') draft_pick = sorted_picks[keeper_count - 1] this_player['demotion_week'] = 1 draft_pick['player'] = this_player logger.info(f'Setting {x["name"]} as {this_team["abbrev"]}\'s #{keeper_count} keeper with overall ' f'pick #{draft_pick.overall}') await patch_draftpick(draft_pick) await put_player(this_player) # await post_transactions([{ # 'week': -1, # 'player_id': x['id'], # 'oldteam_id': 201, # FA team ID # 'newteam_id': this_team['id'], # 'season': current.season, # 'moveid': f'keeper-{this_team["abbrev"]}-{keeper_count}' # }]) all_moves.append({ 'week': -1, 'player_id': x['id'], 'oldteam_id': fa_team['id'], # FA team ID 'newteam_id': this_team['id'], 'season': current.season, 'moveid': f'keeper-{this_team["abbrev"]}-{keeper_count}' }) all_keepers.append({ 'season': current.season, 'team_id': this_team['id'], 'player_id': this_player['id'] }) pick_num = draft_pick['overall'] % 16 if pick_num == 0: pick_num = 16 try: await self.send_pick_to_sheets(draft_pick, this_player, draft_pick['overall']) except Exception as e: logger.error(f'{e}') keeper_count += 1 else: this_player['team'] = {'id': fa_team['id']} await put_player(this_player) await db_post('transactions', payload={'count': len(all_moves), 'moves': all_moves}) await db_post('keepers', payload={'count': len(all_keepers), 'keepers': all_keepers}) await ctx.send(f'Posted {len(all_moves)} transactions with {len(all_keepers)} keepers for {this_team["lname"]}') async def setup(bot): await bot.add_cog(Draft(bot))