P2 Tasks completed: - SWAR-002: Update draft.py cap check to use exceeds_salary_cap() - SWAR-003: Update trade validation in transactions.py - SWAR-004: Update first drop/add validation - SWAR-005: Update second drop/add validation - SWAR-006: Update legal command roster validation Changes: - Enhanced helper functions to support both dict and Pydantic models - All error messages now show actual team cap value - Added 4 additional tests for Pydantic model support (21 total) - All salary cap checks now use centralized exceeds_salary_cap() 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1015 lines
46 KiB
Python
1015 lines
46 KiB
Python
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_delete, 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 <command_name> 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'<t:{deadline.timestamp()}>'
|
|
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']
|
|
|
|
team_cap = get_team_salary_cap(draft_pick.owner)
|
|
if exceeds_salary_cap(total_swar, draft_pick.owner):
|
|
return {
|
|
'success': False,
|
|
'error': f'Drafting {player["name"]} would put you at {total_swar:.2f} '
|
|
f'sWAR (cap {team_cap:.1f}), friendo.'
|
|
}
|
|
logger.info(
|
|
f'{draft_pick.owner.lname} selects {player["name"]} with the #{draft_pick.overall} overall pick'
|
|
)
|
|
draft_pick.player = Player(**player)
|
|
logger.info(f'draft_pick test: {draft_pick}')
|
|
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)
|
|
|
|
t_query = await db_get(
|
|
'transactions',
|
|
params=[('season', 12), ('move_id', f'draft-overall-{this_pick.overall}')]
|
|
)
|
|
if not t_query or t_query.get('count', 0) == 0:
|
|
await interaction.edit_original_response(
|
|
content='I couldn\'t find the transaction record of this move so that\'s Cal\'s problem now. The pick is wiped, though.',
|
|
embed=await get_player_embed(this_player, current)
|
|
)
|
|
else:
|
|
try:
|
|
del_query = await db_delete(
|
|
'transactions',
|
|
object_id=t_query['transactions'][0]['moveid']
|
|
)
|
|
except ValueError as e:
|
|
logger.error(f'Could not delete draft transaction: {e}')
|
|
await interaction.edit_original_response(
|
|
content='I couldn\'t delete the transaction record of this move so that\'s Cal\'s problem now. The pick is wiped, though.',
|
|
embed=await get_player_embed(this_player, current)
|
|
)
|
|
return
|
|
await interaction.edit_original_response(
|
|
content=f'[I gotchu]({random_gif("blows kiss")}) boo boo, like it never even happened.',
|
|
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))
|