major-domo-legacy/cogs/transactions.py
Cal Corum bbb4233b45 Replace hardcoded salary cap with dynamic Team.salary_cap
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>
2025-12-09 17:14:17 -06:00

2349 lines
109 KiB
Python

import re
import copy
import random
import os
from dataclasses import dataclass
from typing import List, Tuple, Optional, Dict, Set
from helpers import *
from api_calls.current import get_current
from db_calls import db_get, db_patch, get_team_by_owner, get_team_by_abbrev, get_player_by_name, put_player, db_post
from discord.ext import commands, tasks
OFFSEASON_FLAG = False
USE_NEW_TRANSACTION_LOGIC = os.getenv('USE_NEW_TRANSACTION_LOGIC', 'true').lower() == 'true'
logger = logging.getLogger('discord_app')
@dataclass
class TransactionPriority:
"""Data class to hold transaction priority calculation results"""
roster_priority: int # 1 = major league, 2 = minor league
win_percentage: float # Lower is better (worse teams get priority)
random_tiebreaker: int # Random number for final tie resolution
move_id: str
major_league_team_abbrev: str # The major league team (without MiL)
contested_players: List[str] # List of contested player names in this transaction
def get_sort_tuple(self) -> Tuple[int, float, int]:
"""Return tuple for sorting - lower values have higher priority"""
return (self.roster_priority, self.win_percentage, self.random_tiebreaker)
def get_major_league_team_abbrev(old_team_abbrev: str, new_team_abbrev: str) -> str:
"""
Extract the major league team abbreviation from a transaction.
Args:
old_team_abbrev: Source team abbreviation
new_team_abbrev: Destination team abbreviation
Returns:
Major league team abbreviation (the one that's not FA and doesn't end in MiL)
"""
# Find the team that is not FA and strip MiL if needed
if old_team_abbrev != 'FA':
return old_team_abbrev.replace('MiL', '') if old_team_abbrev.endswith('MiL') else old_team_abbrev
elif new_team_abbrev != 'FA':
return new_team_abbrev.replace('MiL', '') if new_team_abbrev.endswith('MiL') else new_team_abbrev
else:
raise ValueError("Both teams cannot be FA")
def determine_roster_priority(moves: List[dict]) -> int:
"""
Determine roster priority for a transaction based on all movements.
Args:
moves: List of all movements in the transaction
Returns:
1 for major league priority, 2 for minor league priority
"""
# If ANY move involves major league roster (no MiL suffix), entire transaction gets major league priority
for move in moves:
old_abbrev = move['oldteam']['abbrev']
new_abbrev = move['newteam']['abbrev']
# Check if either team is major league (not FA and doesn't end with MiL)
if (old_abbrev != 'FA' and not old_abbrev.endswith('MiL')) or \
(new_abbrev != 'FA' and not new_abbrev.endswith('MiL')):
return 1 # Major league priority
return 2 # Minor league priority (all moves involve MiL teams)
async def calculate_transaction_priority(move_id: str, moves: List[dict], current_season: int,
contested_players: Set[str]) -> TransactionPriority:
"""
Calculate priority for an entire transaction (all moves with same move_id).
Args:
move_id: The transaction ID
moves: List of all movements in this transaction
current_season: Current season number
contested_players: Set of all contested player names
Returns:
TransactionPriority object with calculated values
"""
if not moves:
raise ValueError("No moves provided for transaction")
# Get major league team abbreviation from any move in the transaction
first_move = moves[0]
major_league_team_abbrev = get_major_league_team_abbrev(
first_move['oldteam']['abbrev'],
first_move['newteam']['abbrev']
)
# Determine roster priority for entire transaction
roster_priority = determine_roster_priority(moves)
# Calculate win percentage using major league team's record
if major_league_team_abbrev == 'FA':
win_percentage = 0.0
else:
major_league_team = await get_team_by_abbrev(major_league_team_abbrev, current_season)
if major_league_team is None:
raise ValueError(f'Team `{major_league_team_abbrev}` not found')
standings_query = await db_get('standings', params=[
('season', current_season),
('team_id', major_league_team['id']),
]
)
if standings_query is None:
raise ValueError(f'Standings not found for `{major_league_team_abbrev}`')
standings = standings_query['standings'][0]
total_games = standings['wins'] + standings['losses']
win_percentage = standings['wins'] / total_games if total_games > 0 else 0.0
# Find which players in this transaction are contested
transaction_contested_players = [
move['player']['name'] for move in moves
if move['player']['name'] in contested_players
]
return TransactionPriority(
roster_priority=roster_priority,
win_percentage=win_percentage,
random_tiebreaker=random.randint(1, 100000),
move_id=move_id,
major_league_team_abbrev=major_league_team_abbrev,
contested_players=transaction_contested_players
)
async def resolve_contested_transactions(moves: List[dict], current_season: int) -> Tuple[List[str], List[str]]:
"""
Resolve disputes for contested players at the transaction level.
Args:
moves: List of all transaction moves from database
current_season: Current season number
Returns:
Tuple of (winning_move_ids, losing_move_ids)
"""
# Group moves by move_id (transaction)
transactions = {}
for move in moves:
move_id = move['moveid']
if move_id not in transactions:
transactions[move_id] = []
transactions[move_id].append(move)
# Group moves by player name to identify contested players
player_to_move_ids = {}
for move in moves:
player_name = move['player']['name']
move_id = move['moveid']
if player_name not in player_to_move_ids:
player_to_move_ids[player_name] = set()
player_to_move_ids[player_name].add(move_id)
# Find contested players (claimed by multiple transactions)
contested_players = {
player_name for player_name, move_ids in player_to_move_ids.items()
if len(move_ids) > 1
}
logger.info(f"Found {len(contested_players)} contested players: {list(contested_players)}")
# Find transactions that claim any contested player
contested_move_ids = set()
for player_name in contested_players:
contested_move_ids.update(player_to_move_ids[player_name])
logger.info(f"Found {len(contested_move_ids)} transactions claiming contested players")
# Calculate priorities for contested transactions
contested_priorities = []
for move_id in contested_move_ids:
priority = await calculate_transaction_priority(
move_id, transactions[move_id], current_season, contested_players
)
contested_priorities.append(priority)
# Group contested transactions by the players they're fighting over
player_disputes = {}
for priority in contested_priorities:
for player_name in priority.contested_players:
if player_name not in player_disputes:
player_disputes[player_name] = []
player_disputes[player_name].append(priority)
# Resolve each player dispute
winning_move_ids = set()
losing_move_ids = set()
for player_name, competing_priorities in player_disputes.items():
# Sort by priority tuple (lower values = higher priority)
sorted_priorities = sorted(competing_priorities, key=lambda p: p.get_sort_tuple())
winner = sorted_priorities[0]
losers = sorted_priorities[1:]
winning_move_ids.add(winner.move_id)
for loser in losers:
losing_move_ids.add(loser.move_id)
# Log the resolution decision
logger.info(f"Contested player '{player_name}' resolution:")
logger.info(f" Winner: {winner.major_league_team_abbrev} (move_id={winner.move_id}, "
f"roster_priority={winner.roster_priority}, win_pct={winner.win_percentage:.3f}, "
f"random={winner.random_tiebreaker})")
for i, loser in enumerate(losers):
logger.info(f" Loser {i+1}: {loser.major_league_team_abbrev} (move_id={loser.move_id}, "
f"roster_priority={loser.roster_priority}, win_pct={loser.win_percentage:.3f}, "
f"random={loser.random_tiebreaker})")
# Add non-contested transactions to winners
non_contested_move_ids = [
move_id for move_id in transactions.keys()
if move_id not in contested_move_ids
]
winning_move_ids.update(non_contested_move_ids)
return list(winning_move_ids), list(losing_move_ids)
class SBaTransaction:
def __init__(self, channel, current, move_type, this_week=False, first_team=None, team_role=None):
self.players = {} # Example: { <Player ID>: { "player": { Strat Player Dict }, "to": { Strat Team Dict } } }
self.teams = {} # Example: { <Team ID>: { "team": { Strat Team Dict }, "role": <Discord Role Object> } } }
self.picks = {} # Example: { <Pick ID>: { "pick": { Strat Pick Dict }, "to": { Strat Team Dict } } }
self.avoid_freeze = True
self.channel = channel
self.gms = []
self.current = current
self.effective_week = current.week + 1 if not this_week else current.week
self.move_type = move_type
if first_team and team_role:
self.add_team(first_team, team_role)
if (first_team and not team_role) or (team_role and not first_team):
logger.error(f'Trade creation failed:\nteam: {first_team}\nteam_role: {team_role}')
raise ValueError('Trade creation failed')
def add_team(self, new_team, role):
if new_team['id'] not in self.teams.keys():
self.teams[new_team['id']] = {
'team': new_team,
'role': role
}
self.gms.append(new_team['gmid'])
if new_team['gmid2']:
self.gms.append(new_team['gmid2'])
return True
async def add_player(self, player, to_team):
if player['id'] not in self.players.keys():
self.players[player['id']] = {
'player': player,
'to': to_team
}
await self.send(f'Got it - throwing {player["name"]} onto a plane.')
else:
await self.send(f'{player["name"]} is already part of this transaction. They\'re heading to '
f'{self.players[player["id"]]["to"]["sname"]}. Try to keep up.')
async def add_pick(self, pick, to_team):
if pick['id'] not in self.picks.keys():
self.picks[pick['id']] = {
'pick': pick,
'to': to_team
}
await self.send(f'Okay, {pick["origowner"]["abbrev"]}\'s {pick["round"]} is on the table.')
else:
await self.send(f'{pick["origowner"]["abbrev"]}\'s {pick["round"]} is already part of the deal. It\'s '
f'going to {self.picks[pick["id"]]["to"]["abbrev"]}. Please try to keep up.')
def included_team(self, team):
team_abbrev = team['abbrev']
for team in self.teams.values():
if team_abbrev in [team['team']['abbrev'], f'{team["team"]["abbrev"]}IL', f'{team["team"]["abbrev"]}MiL']:
return True
return False
async def not_available(self, player, this_week=False):
transactions = await self.get_player_moves(player, this_week)
logger.info(f'Is {player["name"]} not available? - {len(transactions)}')
if len(transactions) > 0:
return True
return False
async def get_gms(self, bot, team=None):
if team is None:
gms = []
for gm_id in self.gms:
try:
user = await bot.fetch_user(gm_id)
if user:
gms.append(user)
except discord.NotFound:
logger.warning(f'User {gm_id} not found')
except Exception as e:
logger.error(f'Error fetching user {gm_id}: {e}')
return gms
else:
these_gms = []
try:
user = await bot.fetch_user(self.teams[team]['team']['gmid'])
if user:
these_gms.append(user)
except discord.NotFound:
logger.warning(f'User {self.teams[team]["team"]["gmid"]} not found')
except Exception as e:
logger.error(f'Error fetching user {self.teams[team]["team"]["gmid"]}: {e}')
if self.teams[team]['team']['gmid2']:
try:
user = await bot.fetch_user(self.teams[team]['team']['gmid2'])
if user:
these_gms.append(user)
except discord.NotFound:
logger.warning(f'User {self.teams[team]["team"]["gmid2"]} not found')
except Exception as e:
logger.error(f'Error fetching user {self.teams[team]["team"]["gmid2"]}: {e}')
return these_gms
async def send(self, content=None, embed=None):
return await self.channel.send(content=content, embed=embed)
async def show_moves(self, here=True):
embed = get_team_embed(f'Week {self.effective_week} Transaction', team=[*self.teams.values()][0]['team'])
teams = [self.teams[x]['team']['sname'] for x in self.teams]
embed.description = f'{", ".join(teams)}'
# Get player string
player_string = ''
for x in self.players:
player_string += f'**{self.players[x]["player"]["name"]}** ({self.players[x]["player"]["wara"]}) from ' \
f'{self.players[x]["player"]["team"]["abbrev"]} to {self.players[x]["to"]["abbrev"]}\n'
if len(player_string) == 0:
player_string = 'None...yet'
embed.add_field(name='Player Moves', value=player_string, inline=False)
# Get draft pick string
pick_string = ''
for x in self.picks:
pick_string += f'**{self.picks[x]["pick"]["origowner"]["abbrev"]} {self.picks[x]["pick"]["round"]}** to ' \
f'{self.picks[x]["to"]["abbrev"]}\n'
if len(pick_string) > 0:
embed.add_field(name='Pick Trades', value=pick_string, inline=False)
if here:
await self.send(content=None, embed=embed)
else:
return embed
async def timed_delete(self):
await self.channel.send('I will delete this channel in 5 minutes.')
await asyncio.sleep(300)
await self.channel.delete()
async def get_player_moves(self, player, this_week):
if this_week:
t_query = await db_get('transactions', params=[
('season', self.current.season), ('week_start', self.current.week),
('week_end', self.current.season), ('player_id', player['id'])
])
return t_query['transactions']
else:
t_query = await db_get('transactions', params=[
('season', self.current.season), ('week_start', self.effective_week),
('week_end', self.effective_week), ('player_id', player['id'])
])
return t_query['transactions']
async def check_major_league_errors(self, team):
wara = 0
mil_wara = 0
this_team = self.teams[team]['team']
# team_roster = await get_players(self.current.season, this_team['abbrev'])
t_query = await db_get('players', params=[
('season', self.current.season), ('team_id', this_team['id'])
])
team_roster = t_query['players']
ml_query = await db_get('teams', params=[
('season', self.current.season), ('team_abbrev', f'{this_team["abbrev"]}MiL')
])
# mil_roster = await get_players(self.current.season, f'{this_team["abbrev"]}MiL')
mil_team = await get_team_by_abbrev(f'{this_team["abbrev"]}MiL', season=this_team['season'])
m_query = await db_get('players', params=[
('season', self.current.season), ('team_id', mil_team['id'])
])
mil_roster = m_query['players']
for player in team_roster:
wara += player['wara']
for player in mil_roster:
mil_wara += player['wara']
logger.info(f'checking future moves')
if self.effective_week > self.current.week:
# set_moves = await get_transactions(
# self.current.season, team_abbrev=this_team['abbrev'], week_start=self.effective_week,
# week_end=self.effective_week
# )
t_query = await db_get('transactions', params=[
('season', self.current.season), ('week_start', self.effective_week),
('week_end', self.effective_week), ('team_abbrev', this_team['abbrev'])
])
set_moves = t_query['transactions']
# freeze_moves = await get_transactions(
# self.current.season, team_abbrev=this_team['abbrev'], week_start=self.effective_week,
# week_end=self.effective_week, frozen=True
# )
t_query = await db_get('transactions', params=[
('season', self.current.season), ('week_start', self.effective_week),
('week_end', self.effective_week), ('team_abbrev', this_team['abbrev']), ('frozen', True)
])
freeze_moves = t_query['transactions']
moves = set_moves + freeze_moves
for x in moves:
# If player is joining this team, add to roster and add WARa
if x['newteam'] == this_team:
team_roster.append(x['player'])
wara += x['player']['wara']
# If player is joining MiL team, add to roster and add WARa
elif x['newteam']['abbrev'] == f'{this_team["abbrev"]}MiL':
mil_roster.append(x['player'])
mil_wara += x['player']['wara']
# If player is leaving this team, remove from roster and subtract WARa
if x['oldteam'] == this_team:
team_roster = [p for p in team_roster if p['id'] != x['player']['id']]
wara -= x['player']['wara']
# If player is leaving MiL team, remove from roster and subtract WARa
elif x['oldteam']['abbrev'] == f'{this_team["abbrev"]}MiL':
mil_roster = [p for p in mil_roster if p['id'] != x['player']['id']]
mil_wara -= x['player']['wara']
logger.info(f'updating rosters')
for x in self.players:
logger.info(f'x player: {x}')
# If player is joining this team, add to roster and add WARa
if self.players[x]['to'] == this_team:
team_roster.append(self.players[x]['player'])
wara += self.players[x]['player']['wara']
# If player is joining MiL team, add to roster and add WARa
elif self.players[x]['to']['abbrev'] == f'{this_team["abbrev"]}MiL':
mil_roster.append(self.players[x]['player'])
mil_wara += self.players[x]['player']['wara']
# If player is joining IL team, remove from roster and cut WARa
elif self.players[x]['to']['abbrev'] == f'{this_team["abbrev"]}IL':
wara -= self.players[x]['player']['wara']
# If player is leaving this team next week, remove from roster and subtract WARa
if self.players[x]['player']['team'] == this_team:
logger.info(f'major league player')
# logger.info(f'team roster: {team_roster}')
team_roster = [p for p in team_roster if p['id'] != self.players[x]['player']['id']]
# 06-13: COMMENTED OUT TO RESOLVE MID-WEEK IL REPLACEMENT BEING SENT BACK DOWN
# if self.effective_week != self.current.week:
wara -= self.players[x]['player']['wara']
# If player is leaving MiL team next week, remove from roster and subtract WARa
if self.players[x]['player']['team']['abbrev'] == f'{this_team["abbrev"]}MiL':
logger.info(f'minor league player')
mil_roster = [p for p in mil_roster if p['id'] != self.players[x]['player']['id']]
# logger.info(f'mil roster: {mil_roster}')
if self.effective_week != self.current.week:
mil_wara -= self.players[x]['player']['wara']
return {'roster': team_roster, 'wara': wara, 'mil_roster': mil_roster, 'mil_wara': mil_wara}
# async def check_minor_league_errors(self, ml_team_id):
# wara = 0
# this_team = await get_one_team(ml_team_id + 2)
# team_roster = await get_players(self.current.season, this_team['abbrev'])
#
# for player in team_roster:
# wara += team_roster[player]['wara']
#
# if self.effective_week > self.current.week:
# set_moves = await get_transactions(
# self.current.season, team_abbrev=this_team['abbrev'], week_start=self.effective_week,
# week_end=self.effective_week
# )
# freeze_moves = await get_transactions(
# self.current.season, team_abbrev=this_team['abbrev'], week_start=self.effective_week,
# week_end=self.effective_week, frozen=True
# )
# moves = {**set_moves, **freeze_moves}
#
# for x in moves:
# # If player is joining this team, add to roster and add WARa
# if moves[x]['newteam'] == this_team:
# team_roster[moves[x]['player']['name']] = moves[x]['player']
# wara += moves[x]['player']['wara']
# # If player is leaving this team, remove from roster and subtract WARa
# else:
# # del team_roster[moves[x]['player']['name']]
# team_roster.pop(moves[x]['player']['name'], None)
# wara -= moves[x]['player']['wara']
#
# for x in self.players:
# # If player is joining this team, add to roster and add WARa
# if self.players[x]['to'] == this_team:
# team_roster[self.players[x]['player']['name']] = self.players[x]['player']
# wara += self.players[x]['player']['wara']
# # If player is leaving this team next week, remove from roster and subtract WARa
# elif self.players[x]['player']['team'] == this_team:
# team_roster.pop(self.players[x]['player']['name'], None)
# if self.effective_week != self.current.week:
# wara -= self.players[x]['player']['wara']
#
# return {'roster': team_roster, 'wara': wara}
async def send_transaction(self):
team_id = list(self.teams.keys())[0]
moveid = f'Season-{self.current.season:03d}-Week-{self.effective_week:0>2}-{datetime.datetime.now().strftime("%d-%H:%M:%S")}'
moves = []
logger.warning(f'move_id: {moveid} / move_type: {self.move_type} / avoid_freeze: {self.avoid_freeze} / '
f'week: {self.current.week}')
if self.current.freeze and not self.avoid_freeze:
frozen = True
else:
frozen = False
for x in self.players:
moves.append({
'week': self.effective_week,
'player_id': self.players[x]['player']['id'],
'oldteam_id': self.players[x]['player']['team']['id'],
'newteam_id': self.players[x]['to']['id'],
'season': self.current.season,
'moveid': moveid,
'frozen': frozen
})
await db_post('transactions', payload={'count': len(moves), 'moves': moves})
# TO BE UPDATED IF PICK DRAFTING RETURNS
# for x in self.picks:
# await patch_draftpick(self.picks[x]['pick']['id'], owner_id=self.picks[x]['to']['id'])
return moveid
def __str__(self):
trans_string = 'Players:\n'
for x in self.players:
trans_string += f'{self.players[x]}\n'
trans_string += '\nTeams:\n'
for x in self.teams:
trans_string += f'{self.teams[x]}\n'
trans_string += f'\nChannel: {self.channel}\n\nGMs:\n'
for x in self.gms:
trans_string += f'{x}\n'
return trans_string
class Transactions(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.trade_season = False
self.weekly_warning_sent = False
self.weekly_loop.start()
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(minutes=1)
async def weekly_loop(self):
try:
logger.info(f'Inside weekly_loop')
if OFFSEASON_FLAG:
logger.info(f'Exiting weekly_loop; OFFSEASON_FLAG: {OFFSEASON_FLAG}')
return
current = await get_current()
now = datetime.datetime.now()
logger.info(f'Datetime: {now} / weekday: {now.weekday()} / current: {current}')
# Begin Freeze
# if now.weekday() == 0 and now.hour == 5 and not current.freeze: # Spring/Summer
if now.weekday() == 0 and now.hour == 0 and not current.freeze: # Fall/Winter
logger.info(f'weekly_loop - setting the freeze')
current.week += 1
await db_patch('current', object_id=current.id, params=[('week', current.week), ('freeze', True)])
await self.run_transactions(current)
logger.debug(f'Building freeze string')
week_num = f'Week {current.week}'
stars = f'{"":*<32}'
freeze_message = f'```\n' \
f'{stars}\n'\
f'{week_num: >9} Freeze Period Begins\n' \
f'{stars}\n```'
logger.debug(f'Freeze string:\n\n{freeze_message}')
await send_to_channel(self.bot, 'transaction-log', freeze_message)
if current.week > 0 and current.week <= 18:
await self.post_weekly_info(current)
# End Freeze
# elif now.weekday() == 5 and now.hour == 5 and current.freeze: # Spring/Summer
elif now.weekday() == 5 and now.hour == 0 and current.freeze: # Fall/Winter
logger.info(f'weekly_loop - running the un-freeze')
await db_patch('current', object_id=current.id, params=[('freeze', False)])
week_num = f'Week {current.week}'
stars = f'{"":*<30}'
freeze_message = f'```\n' \
f'{stars}\n'\
f'{week_num: >9} Freeze Period Ends\n' \
f'{stars}\n```'
await self.process_freeze_moves(current)
await send_to_channel(self.bot, 'transaction-log', freeze_message)
self.trade_season = False
else:
logger.info(f'weekly_loop - No freeze actions being taken')
except Exception as e:
logger.error(f'Unhandled exception in weekly_loop: {e}')
error_message = f"⚠️ **Weekly Freeze Task Failed**\n```\nError: {str(e)}\nTime: {datetime.datetime.now()}\nTask: weekly_loop in transactions.py\n```"
try:
if not self.weekly_warning_sent:
await send_owner_notification(error_message)
self.weekly_warning_sent = True
except Exception as e:
logger.error(f'Failed to send error message :( {e}')
@weekly_loop.before_loop
async def before_notif_check(self):
await self.bot.wait_until_ready()
async def run_transactions(self, current):
m_query = await db_get('transactions', params=[
('season', current.season), ('week_start', current.week), ('week_end', current.week)
])
if m_query['count'] == 0:
return
all_moves = m_query['transactions']
# for move in [*all_moves.values()]:
# try:
# if (move['newteam']['abbrev'][-3:] == 'MiL' and move['oldteam']['abbrev'] == 'FA') or \
# move['newteam']['abbrev'][-2:] == 'IL' or move['newteam']['abbrev'].lower() == 'fa':
# dem_week = current.week
# else:
# dem_week = current.week + 1
#
# await patch_player(
# move['player']['id'],
# team_id=move['newteam']['id'],
# demotion_week=dem_week
# )
# except Exception as e:
# await send_to_channel(self.bot, 'commissioners-office', f'Error running move:\n{move["moveid"]}')
for x in all_moves:
if (x['newteam']['abbrev'][-3:] == 'MiL' and x['oldteam']['abbrev'] == 'FA') or \
x['newteam']['abbrev'][-2:] == 'IL' or x['newteam']['abbrev'] == 'FA':
dem_week = current.week
else:
dem_week = current.week + 1
x['player']['team'] = x['newteam']
x['player']['demotion_week'] = dem_week
await put_player(x['player'])
async def notify_cancel(self, trans_data):
team = trans_data[1]
guild = self.bot.get_guild(int(os.environ.get('GUILD_ID')))
cancel_text = f'Your transaction containing {trans_data[0]["name"]} has been cancelled because some douche ' \
f'with a worse team wanted him, too.'
gm_one = guild.get_member(team['gmid'])
try:
await gm_one.send(cancel_text)
except Exception as e:
logger.error(f'Could not notify GM1 of {team["abbrev"]} ({team["gmid"]}) of move cancellation: {e}')
gm_two = None
if team['gmid2'] is not None:
gm_two = guild.get_member(team['gmid2'])
try:
await gm_two.send(cancel_text)
except Exception as e:
logger.error(f'Could not notifiy GM2 of {team["abbrev"]} ({team["gmid2"]})of move cancellation: {e}')
async def process_freeze_moves_backup(self, current):
"""Original implementation - kept for rollback purposes"""
# all_moves = await get_transactions(
# season=current.season,
# week_start=current.week,
# week_end=current.week + 1,
# frozen=True
# )
m_query = await db_get('transactions', params=[
('season', current.season), ('week_start', current.week), ('week_end', current.week + 1),
('frozen', True)
])
if m_query['count'] == 0:
logger.warning(f'No transactions to process for the freeze in week {current.week}')
return
moves = m_query['transactions']
logger.info(f'freeze / all_moves: {len(moves)}')
# {'player name': [[Player, TeamAdding, moveid], [Player, OtherTeamAdding, moveid]]}
added_players = {}
contested_players = {}
for move in moves:
if move['newteam']['abbrev'][-3:] == 'MiL':
new_team = await get_team_by_abbrev(move['newteam']['abbrev'][:-3], current.season)
else:
new_team = move['newteam']
# team_record = await get_team_record(new_team, week_num=current.week)
if new_team['abbrev'] == 'FA':
win_pct = 0
else:
r_query = await db_get('standings', params=[
('season', current.season), ('team_id', new_team['id'])
])
win_pct = r_query['standings'][0]['wins'] / (
r_query['standings'][0]['wins'] + r_query['standings'][0]['losses'])
tiebreaker = win_pct + (random.randint(10000, 99999) * .00000001)
if move["player"]["name"] not in added_players.keys():
added_players[move["player"]["name"]] = [[move["player"], move["newteam"], tiebreaker, move["moveid"]]]
else:
added_players[move["player"]["name"]].append(
[move["player"], move["newteam"], tiebreaker, move["moveid"]]
)
logger.info(f'freeze / added_players: {added_players.keys()}')
# Check added_players for keys (player names) with more than one move in their list
for name in added_players:
if len(added_players[name]) > 1:
contested_players[name] = added_players[name]
logger.info(f'freeze / contested_players: {contested_players.keys()}')
# Determine winner for contested players, mark moveid cancelled for loser
def tiebreaker(val):
logger.info(f'tiebreaker: {val}')
return val[2]
for guy in contested_players:
contested_players[guy].sort(key=tiebreaker)
first = True
logger.info(f'Contested Player: {contested_players[guy]}\n\n')
for x in contested_players[guy]:
logger.info(f'First: {first} / x: {x}\n\n')
if not first:
await db_patch('transactions', object_id=x[3], params=[('frozen', False), ('cancelled', True)])
# await patch_transaction(move_id=x[3], cancelled=True, frozen=False)
await self.notify_cancel(x)
else:
first = False
# Post transactions that are not cancelled
# final_moves = await get_transactions(
# season=current.season,
# week_start=current.week,
# week_end=current.week + 1,
# frozen=True
# )
m_query = await db_get('transactions', params=[
('season', current.season), ('week_start', current.week), ('week_end', current.week + 1),
('frozen', True)
])
final_moves = m_query['transactions']
these_ids = []
for x in final_moves:
if x["moveid"] not in these_ids:
these_ids.append(x["moveid"])
# TODO: not sure I like this method of running moves
for move_id in these_ids:
# await patch_transaction(move_id, frozen=False)
await db_patch('transactions', object_id=move_id, params=[('frozen', False)])
await self.post_move_to_transaction_log(move_id)
async def process_freeze_moves(self, current):
"""
Route to either new or backup implementation based on feature flag.
Set USE_NEW_TRANSACTION_LOGIC=false to use original implementation.
"""
if USE_NEW_TRANSACTION_LOGIC:
logger.info("Using new transaction priority logic")
await self.process_freeze_moves_new(current)
else:
logger.info("Using backup transaction priority logic")
await self.process_freeze_moves_backup(current)
async def process_freeze_moves_new(self, current):
"""
New implementation of freeze move processing with proper transaction-level priority resolution.
This function is designed to be easily testable by extracting business logic
into pure functions.
"""
# Get all frozen transactions for this week
moves_query = await db_get('transactions', params=[
('season', current.season),
('week_start', current.week),
('week_end', current.week + 1),
('frozen', True)
])
if moves_query['count'] == 0:
logger.warning(f'No transactions to process for the freeze in week {current.week}')
return
moves = moves_query['transactions']
logger.info(f'Processing {len(moves)} frozen moves across multiple transactions for week {current.week}')
try:
# Resolve contested transactions using extracted business logic
winning_move_ids, losing_move_ids = await resolve_contested_transactions(moves, current.season)
# Cancel losing transactions (entire transactions, not just individual moves)
for losing_move_id in losing_move_ids:
# Get all moves in this losing transaction for notification
losing_moves = [m for m in moves if m['moveid'] == losing_move_id]
# Cancel all moves in this transaction
await db_patch('transactions', object_id=losing_move_id,
params=[('frozen', False), ('cancelled', True)])
# Notify the losing team about the cancelled transaction
if losing_moves:
# Use first move to identify the team for notification
first_move = losing_moves[0]
# Find the non-FA team for notification
team_for_notification = (first_move['newteam']
if first_move['newteam']['abbrev'] != 'FA'
else first_move['oldteam'])
await self.notify_cancel([first_move['player'], team_for_notification])
contested_players_in_transaction = [
move['player']['name'] for move in losing_moves
]
logger.info(f"Cancelled entire transaction {losing_move_id} due to contested players: "
f"{contested_players_in_transaction}")
# Unfreeze winning transactions
for winning_move_id in winning_move_ids:
await db_patch('transactions', object_id=winning_move_id, params=[('frozen', False)])
await self.post_move_to_transaction_log(winning_move_id)
logger.info(f"Processed successful transaction {winning_move_id}")
logger.info(f"Freeze processing complete: {len(winning_move_ids)} successful transactions, "
f"{len(losing_move_ids)} cancelled transactions")
except Exception as e:
logger.error(f"Error during freeze processing: {e}", exc_info=True)
raise
async def post_move_to_transaction_log(self, move_id):
current = await get_current()
# all_moves = await get_transactions(
# season=current.season,
# move_id=move_id
# )
m_query = await db_get('transactions', params=[
('season', current.season), ('move_id', move_id)
])
all_moves = m_query['transactions']
this_team = None
week_num = None
move_string = ''
for move in all_moves:
if this_team is None:
if move['newteam']['abbrev'] != 'FA' and 'IL' not in move['newteam']['abbrev']:
this_team = move['newteam']
else:
this_team = move['oldteam']
if move['oldteam']['abbrev'] != 'FA' and 'IL' not in move['oldteam']['abbrev']:
this_team = move['oldteam']
if week_num is None:
week_num = move['week']
move_string += f'**{move["player"]["name"]}** ({move["player"]["wara"]}) from ' \
f'{move["oldteam"]["abbrev"]} to {move["newteam"]["abbrev"]}\n'
embed = get_team_embed(f'Week {week_num} Transaction', this_team)
embed.description = this_team['sname']
embed.add_field(name='Player Moves', value=move_string)
await send_to_channel(self.bot, 'transaction-log', embed=embed)
async def post_weekly_info(self, current):
guild = self.bot.get_guild(int(os.environ.get('GUILD_ID')))
info_channel = discord.utils.get(guild.text_channels, name='weekly-info')
async for message in info_channel.history(limit=25):
await message.delete()
night_str = '\U0001F319 Night'
day_str = '\U0001F31E Day'
season_str = f'\U0001F3D6 **Summer**'
if current.week <= 5:
season_str = f'\U0001F33C **Spring**'
elif current.week > 14:
season_str = f'\U0001F342 **Fall**'
is_div_week = current.week in [1, 3, 6, 14, 16, 18]
weekly_str = f'**Season**: {season_str}\n' \
f'**Time of Day**: {night_str} / {night_str if is_div_week else day_str} / ' \
f'{night_str} / {day_str}'
await info_channel.send(
content=f'Each team has manage permissions in their home ballpark. They may pin messages and rename the '
f'channel.\n\n**Make sure your ballpark starts with your team abbreviation.**'
)
await info_channel.send(weekly_str)
@staticmethod
def on_team_il(team, player):
player_team_abbrev = player['team']['abbrev']
sil_abbrev = f'{team["abbrev"]}IL'
mil_abbrev = f'{team["abbrev"]}MiL'
if player_team_abbrev in [sil_abbrev, mil_abbrev]:
return True
else:
return False
@commands.command(name='run_transactions')
@commands.is_owner()
async def run_transactions_helper_command(self, ctx):
current = await get_current()
await self.run_transactions(current)
await ctx.send(new_rand_conf_gif())
@commands.command(name='process_freeze')
@commands.is_owner()
async def process_freeze_helper_command(self, ctx):
current = await get_current()
await self.process_freeze_moves_new(current)
await ctx.send(random_conf_gif())
@commands.command(name='post-weekly')
@commands.is_owner()
async def post_weekly_info_command(self, ctx):
await self.post_weekly_info(await db_get('current'))
@commands.command(
name='trade', aliases=['tradeplz', 'pleasetrade', 'tradeplease', 'plztrade'], help='Trade players')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME)
async def trade_command(self, ctx):
current = await get_current()
if current.week > current.trade_deadline:
await ctx.send(await get_emoji(ctx, 'oof', False))
await ctx.send(f'The trade deadline is **week {current.trade_deadline}**. Since it is currently **week '
f'{current.week}** I am just going to stop you right there.')
return
if current.week < -2 or current.week == -1:
await ctx.send(await get_emoji(ctx, 'oof', False))
await ctx.send(f'Patience, grasshopper. Trades open soon.')
return
team = await get_team_by_owner(current.season, ctx.author.id)
if team is None:
await ctx.send(f'Who are you? Why are you talking to me, you don\'t have a team.')
return
team_role = get_team_role(ctx, team)
if team_role is None:
await ctx.send(f'I could not find a team role for the {team["lname"]}')
return
poke_role = get_role(ctx, 'Pokétwo')
if poke_role is None:
await ctx.send(f'I could not find the poke role. This is an emergency.')
return
player_cog = self.bot.get_cog('Players')
# Create trade channel
overwrites = {ctx.guild.default_role: discord.PermissionOverwrite(read_messages=False),
team_role: discord.PermissionOverwrite(read_messages=True),
ctx.guild.me: discord.PermissionOverwrite(read_messages=True),
poke_role: discord.PermissionOverwrite(read_messages=True, send_messages=False)}
try:
t_channel = await ctx.guild.create_text_channel(
f'{team["abbrev"]}-trade',
overwrites=overwrites,
category=discord.utils.get(ctx.guild.categories, name=f'Transactions')
)
# t_channel = await create_channel(
# ctx,
# channel_name=f'{team["abbrev"]}-trade',
# category_name=f'Transactions',
# everyone_read=False,
# read_send_roles=team_role
# )
except Exception as e:
await ctx.send(f'{e}\n\n'
f'Discord is having issues creating private channels right now. Please try again later.')
return
# Create trade and post to channel
trade = SBaTransaction(t_channel, current, 'trade', first_team=team, team_role=team_role)
await trade.send(f'Let\'s start here, {team_role.mention}.')
await ctx.send(f'Take my hand... {trade.channel.mention}')
intro = await trade.send(
'First, we will add each team involved in the trade. Then I will have you enter each of the players '
'involved with the trade and where they are going.\n\nOnce that is done, each of the teams involved '
'will need to confirm the trade.'
)
await intro.pin()
# Add teams loop
while True:
prompt = 'Are you adding a team to this deal? (Yes/No)'
this_q = Question(self.bot, trade.channel, prompt, 'yesno', 30)
resp = await this_q.ask(await trade.get_gms(self.bot))
if resp is None:
await trade.send('RIP this move. Maybe next time.')
await trade.timed_delete()
return
elif not resp:
break
else:
this_q.prompt = 'Please enter the team\'s abbreviation.'
this_q.qtype = 'text'
resp = await this_q.ask(await trade.get_gms(self.bot))
if not resp:
await trade.send('RIP this move. Maybe next time.')
await trade.timed_delete()
return
else:
next_team = await get_team_by_abbrev(resp, current.season)
if next_team is None:
await trade.send('Who the fuck even is that? Try again.')
else:
next_team_role = get_team_role(ctx, next_team)
overwrites[next_team_role] = discord.PermissionOverwrite(read_messages=True)
await trade.channel.edit(overwrites=overwrites)
trade.add_team(next_team, next_team_role)
await trade.send(f'Welcome to the trade, {next_team_role.mention}!')
# Get player trades
while True:
prompt = f'Are you trading a player between teams?'
this_q = Question(self.bot, trade.channel, prompt, 'yesno', 300)
resp = await this_q.ask(await trade.get_gms(self.bot))
if resp is None:
await trade.send('RIP this move. Maybe next time.')
await trade.timed_delete()
return
elif not resp:
break
else:
this_q.prompt = 'Which player is being traded?'
this_q.qtype = 'text'
resp = await this_q.ask(await trade.get_gms(self.bot))
if not resp:
pass
else:
try:
player = await get_player_by_name(
current.season,
await fuzzy_player_search(ctx, trade.channel, self.bot, resp, player_cog.player_list.keys())
)
except ValueError as e:
logger.error(f'Could not find player *{resp}*')
else:
if player is None:
await trade.send(f'{await get_emoji(ctx, "squint")}')
await trade.send('Who even is that? Try again.')
else:
# Check that player is on one of the teams
# Check that the player hasn't been dropped (IL is okay)
if not trade.included_team(player['team']):
await trade.send(f'You know that {player["name"]} is on {player["team"]["abbrev"]}, right? '
f'They\'re not part of this trade so...nah.')
elif await trade.not_available(player):
await trade.send(f'Ope. {player["name"]} is already one the move next week.')
else:
this_q.prompt = 'Where are they going? Please enter the destination team\'s abbreviation.'
resp = await this_q.ask(await trade.get_gms(self.bot))
if resp is None:
await trade.send('RIP this move. Maybe next time.')
await trade.timed_delete()
else:
dest_team = await get_team_by_abbrev(resp, current.season)
if dest_team is None:
await trade.send(f'{await get_emoji(ctx, "facepalm")} They aren\'t even part of '
f'this trade. Come on.')
else:
if player['team'] == dest_team:
await trade.send(f'{await get_emoji(ctx, "lolwhat")} {player["name"]} '
f'is already on {dest_team["abbrev"]}.')
elif not trade.included_team(dest_team):
await trade.send(f'{await get_emoji(ctx, "lolwhat")} {dest_team["abbrev"]} '
f'isn\'t even part of this trade.')
else:
await trade.add_player(player, dest_team)
await trade.show_moves()
# Get pick trades
# while True and current.pick_trade_end >= current.week >= current.pick_trade_start:
# prompt = f'Are you trading any draft picks?'
# this_q = Question(self.bot, trade.channel, prompt, 'yesno', 300)
# resp = await this_q.ask(await trade.get_gms(self.bot))
# effective_season = current.season if OFFSEASON_FLAG else current.season + 1
# team_season = current.season
#
# if resp is None:
# await trade.send('RIP this move. Maybe next time.')
# await trade.timed_delete()
# return
# elif not resp:
# break
# else:
# # Get first pick
# this_q.prompt = 'Enter the pick\'s original owner and round number like this: TIT 17'
# this_q.qtype = 'text'
# resp = await this_q.ask(await trade.get_gms(self.bot))
#
# if not resp:
# await trade.send('RIP this move. Maybe next time.')
# await trade.timed_delete()
# return
# else:
# team_abbrev = resp.split(' ')[0]
# round_num = resp.split(' ')[1]
# try:
# first_pick = await get_one_draftpick_search(
# effective_season,
# orig_owner_abbrev=team_abbrev,
# round_num=round_num,
# team_season=team_season
# )
# except Exception as e:
# await trade.send(f'Uh oh, I couldn\'t find **{team_abbrev} {round_num}**. Check the team and '
# f'round number, please.')
# else:
# this_q.prompt = 'Now enter the return pick\'s original owner and round number like this: TIT 17'
# resp = await this_q.ask(await trade.get_gms(self.bot))
#
# if not resp:
# await trade.send('RIP this move. Maybe next time.')
# await trade.timed_delete()
# return
# else:
# team_abbrev = resp.split(' ')[0]
# round_num = resp.split(' ')[1]
# try:
# second_pick = await get_one_draftpick_search(
# effective_season,
# orig_owner_abbrev=team_abbrev,
# round_num=round_num,
# team_season=team_season
# )
# except Exception as e:
# await trade.send(
# f'Uh oh, I couldn\'t find **{team_abbrev} {round_num}**. Check the team and '
# f'round number, please.')
# else:
# team_getting_first_pick = second_pick['owner']
# team_getting_second_pick = first_pick['owner']
#
# if not trade.included_team(first_pick['owner']):
# await trade.send(
# f'Imma let you finish, but **{first_pick["owner"]["abbrev"]}** currently holds '
# f'{first_pick["origowner"]["abbrev"]} {first_pick["round"]} and are not part '
# f'of this deal so let\'s try this again.'
# )
# else:
# if not trade.included_team(second_pick['owner']):
# await trade.send(
# f'Imma let you finish, but **{second_pick["owner"]["abbrev"]}** currently '
# f'holds {second_pick["origowner"]["abbrev"]} {second_pick["round"]} and '
# f'are not part of this deal so let\'s try this again.'
# )
# else:
# await trade.add_pick(first_pick, team_getting_first_pick)
# await trade.add_pick(second_pick, team_getting_second_pick)
#
# await trade.show_moves()
# FA drops per team
if current.week > 0:
for team in trade.teams:
while True:
this_q.prompt = f'{trade.teams[team]["role"].mention}\nAre you making an FA drop?'
this_q.qtype = 'yesno'
resp = await this_q.ask(await trade.get_gms(self.bot, team))
if resp is None:
await trade.send('RIP this move. Maybe next time.')
await trade.timed_delete()
return
elif not resp:
break
else:
this_q.prompt = 'Who are you dropping?'
this_q.qtype = 'text'
resp = await this_q.ask(await trade.get_gms(self.bot, team))
if resp is None:
await trade.send('RIP this move. Maybe next time.')
await trade.timed_delete()
return
else:
try:
player = await get_player_by_name(
current.season,
await fuzzy_player_search(ctx, trade.channel, self.bot, resp, player_cog.player_list.keys())
)
except ValueError as e:
logger.error(f'Could not find player *{resp}*')
else:
if player is None:
await trade.send(f'{await get_emoji(ctx, "squint")}')
await trade.send('Who even is that? Try again.')
else:
if not trade.included_team(player['team']):
await t_channel.send(f'It looks like {player.name} is on {player.team.abbrev} '
f'so I can\'t let you do that.')
# elif player['demotion_week'] > current.week:
# await trade.send(f'Oof. {player["name"]} cannot be dropped until week '
# f'{player["demotion_week"]}.')
else:
dest_team = await get_team_by_abbrev('FA', current.season)
await trade.add_player(player, dest_team)
await trade.show_moves()
# Check for empty move
if len(trade.players) + len(trade.picks) == 0:
await trade.send(f'This has been fun. Come again and maybe do something next time.')
await trade.timed_delete()
return
# Check legality for all teams
errors = []
roster_errors = []
for team in trade.teams:
data = await trade.check_major_league_errors(team)
team_obj = trade.teams[team]["team"]
team_cap = get_team_salary_cap(team_obj)
logger.warning(f'Done checking data - checking sWAR now ({data["wara"]}')
if exceeds_salary_cap(data['wara'], team_obj) and not OFFSEASON_FLAG:
errors.append(f'- {team_obj["abbrev"]} would have {data["wara"]:.2f} WARa (cap {team_cap:.1f})')
logger.warning(f'Now checking roster {len(data["roster"])}')
if len(data['roster']) > 26 and not OFFSEASON_FLAG:
errors.append(f'- {team_obj["abbrev"]} would have {len(data["roster"])} players')
logger.warning(f'Any errors? {errors}')
if (exceeds_salary_cap(data['wara'], team_obj) or len(data['roster']) > 26) and not OFFSEASON_FLAG:
roster_string = ''
for x in data['roster']:
roster_string += f'{x["wara"]: >5} - {x["name"]}\n'
roster_errors.append(f'- This is the roster I have for {trade.teams[team]["team"]["abbrev"]}:\n'
f'```\n{roster_string}```')
if len(errors) + len(roster_errors) > 0:
error_message = '\n'.join(errors)
await trade.send(f'Yikes. I\'m gonna put the kibosh on this trade. Below is why:\n\n{error_message}')
if len(roster_errors) > 0:
for x in roster_errors:
await trade.send(x)
await trade.timed_delete()
return
# Ask for each team's explict confirmation
for team in trade.teams:
this_q.prompt = f'{trade.teams[team]["role"].mention}\nDo you accept this trade?'
this_q.qtype = 'yesno'
resp = await this_q.ask(await trade.get_gms(self.bot, team))
if not resp:
await trade.send('RIP this move. Maybe next time.')
await trade.timed_delete()
return
else:
await trade.show_moves()
# Run moves
trans_id = await trade.send_transaction()
await send_to_channel(self.bot, 'transaction-log', embed=await trade.show_moves(here=False))
await trade.send(f'All done! Your transaction id is: {trans_id}')
try:
choas = get_role(ctx, 'CHOAS ALERT')
await send_to_channel(self.bot, f'season-{current.season}-chat', f'{choas.mention}')
except Exception as e:
logger.error(f'Couldn\'t ping chaos for a trade')
await trade.timed_delete()
# @commands.command(name='picktrade', help='Trade draft picks', hidden=True)
# @commands.is_owner()
# async def pick_trade_command(self, ctx):
# current = await get_current()
#
# if current.week < -2:
# await ctx.send(await get_emoji(ctx, 'oof', False))
# await ctx.send(f'Patience, grasshopper. Trades open up Monday.')
# return
# if not OFFSEASON_FLAG:
# await ctx.send(await get_emoji(ctx, 'oof', False))
# await ctx.send(f'You\'ll have to wait, hoss. No pick trades until the offseason.')
# return
#
# team = await get_team_by_owner(current.season, ctx.author.id)
# team_role = get_team_role(ctx, team)
# player_cog = self.bot.get_cog('Players')
#
# # Create trade channel
# overwrites = {ctx.guild.default_role: discord.PermissionOverwrite(read_messages=False),
# team_role: discord.PermissionOverwrite(read_messages=True),
# ctx.guild.me: discord.PermissionOverwrite(read_messages=True)}
#
# try:
# # t_channel = await ctx.guild.create_text_channel(
# # f'{team["abbrev"]}-trade',
# # overwrites=overwrites,
# # category=discord.utils.get(ctx.guild.categories, name=f'Transactions')
# # )
# t_channel = await create_channel(
# ctx,
# channel_name=f'{team["abbrev"]}-trade',
# category_name=f'Transactions',
# everyone_read=False,
# read_send_roles=team_role
# )
# except Exception as e:
# await ctx.send(f'{e}\n\n'
# f'Discord is having issues creating private channels right now. Please try again later.')
# return
#
# # Create trade and post to channel
# trade = SBaTransaction(t_channel, current, 'trade', first_team=team, team_role=team_role)
# await trade.send(f'Let\'s start here, {team_role.mention}.')
# await ctx.send(f'Take my hand... {trade.channel.mention}')
# intro = await trade.send(
# 'Alrighty, let\'s collect the pick trades. Once they are all entered, each of the teams involved '
# 'will need to confirm the trade.'
# )
# await intro.pin()
#
# # Add teams loop
# while True:
# prompt = 'Are you adding a team to this deal? (Yes/No)'
# this_q = Question(self.bot, trade.channel, prompt, 'yesno', 30)
# resp = await this_q.ask(await trade.get_gms(self.bot))
#
# if resp is None:
# await trade.send('RIP this move. Maybe next time.')
# await trade.timed_delete()
# return
# elif not resp:
# break
# else:
# this_q.prompt = 'Please enter the team\'s abbreviation.'
# this_q.qtype = 'text'
# resp = await this_q.ask(await trade.get_gms(self.bot))
#
# if not resp:
# await trade.send('RIP this move. Maybe next time.')
# await trade.timed_delete()
# return
# else:
# try:
# next_team = await get_team_by_abbrev(resp, current.season)
# except ValueError:
# await trade.send('Who the fuck even is that? Try again.')
# else:
# next_team_role = get_team_role(ctx, next_team)
# overwrites[next_team_role] = discord.PermissionOverwrite(read_messages=True)
# await trade.channel.edit(overwrites=overwrites)
# trade.add_team(next_team, next_team_role)
# await trade.send(f'Welcome to the trade, {next_team_role.mention}!')
#
# # Get pick trades
# while True:
# prompt = f'Are you trading any draft picks?'
# this_q = Question(self.bot, trade.channel, prompt, 'yesno', 300)
# resp = await this_q.ask(await trade.get_gms(self.bot))
# effective_season = current.season if OFFSEASON_FLAG else current.season + 1
# team_season = current.season
#
# if resp is None:
# await trade.send('RIP this move. Maybe next time.')
# await trade.timed_delete()
# return
# elif not resp:
# break
# else:
# # Get first pick
# this_q.prompt = 'Enter the overall pick number being traded.'
# this_q.qtype = 'int'
# resp = await this_q.ask(await trade.get_gms(self.bot))
#
# if not resp:
# await trade.send('RIP this move. Maybe next time.')
# await trade.timed_delete()
# return
# else:
# try:
# first_pick = await get_one_draftpick_byoverall(
# effective_season,
# overall=resp
# )
# except Exception as e:
# await trade.send(f'Frick, I couldn\'t find pick #{resp}.')
# else:
# this_q.prompt = 'Now enter the return pick\'s overall pick number.'
# resp = await this_q.ask(await trade.get_gms(self.bot))
#
# if not resp:
# await trade.send('RIP this move. Maybe next time.')
# await trade.timed_delete()
# return
# else:
# try:
# second_pick = await get_one_draftpick_byoverall(
# effective_season,
# overall=resp
# )
# except Exception as e:
# await trade.send(f'Frick, I couldn\'t find pick #{resp}.')
# else:
# team_getting_first_pick = second_pick['owner']
# team_getting_second_pick = first_pick['owner']
#
# if not trade.included_team(first_pick['owner']):
# await trade.send(
# f'Ope. **{first_pick["owner"]["abbrev"]}** currently holds '
# f'{first_pick["origowner"]["abbrev"]} {first_pick["round"]} and are not part '
# f'of this deal so let\'s try this again.'
# )
# else:
# if not trade.included_team(second_pick['owner']):
# await trade.send(
# f'Imma let you finish, but **{second_pick["owner"]["abbrev"]}** currently '
# f'holds {second_pick["origowner"]["abbrev"]} {second_pick["round"]} and '
# f'are not part of this deal so let\'s try this again.'
# )
# else:
# await trade.add_pick(first_pick, team_getting_first_pick)
# await trade.add_pick(second_pick, team_getting_second_pick)
#
# await trade.show_moves()
#
# # Check for empty move
# if len(trade.players) + len(trade.picks) == 0:
# await trade.send(f'This has been fun. Come again and maybe do something next time.')
# await trade.timed_delete()
# return
#
# # Check legality for all teams
# errors = []
# for team in trade.teams:
# data = await trade.check_major_league_errors(team)
# logger.warning(f'Done checking data - checking WARa now ({data["wara"]}')
#
# if data['wara'] > 32.001:
# errors.append(f'- {trade.teams[team]["team"]["abbrev"]} would have {data["wara"]:.2f} WARa')
#
# logger.warning(f'Now checking roster {len(data["roster"])}')
# if len(data['roster']) > 26:
# errors.append(f'- {trade.teams[team]["team"]["abbrev"]} would have {len(data["roster"])} players')
#
# logger.warning(f'Any errors? {errors}')
# if data['wara'] > 32.001 or len(data['roster']) > 26:
# roster_string = ''
# for x in data['roster']:
# roster_string += f'{data["roster"][x]["wara"]: >5} - {data["roster"][x]["name"]}\n'
# errors.append(f'- This is the roster I have for {trade.teams[team]["team"]["abbrev"]}:\n'
# f'```\n{roster_string}```')
#
# if len(errors) > 0:
# error_message = '\n'.join(errors)
# await trade.send(f'Yikes. I\'m gonna put the kibosh on this trade. Below is why:\n\n{error_message}')
# await trade.timed_delete()
# return
#
# # Ask for each team's explict confirmation
# for team in trade.teams:
# this_q.prompt = f'{trade.teams[team]["role"].mention}\nDo you accept this trade?'
# this_q.qtype = 'yesno'
# resp = await this_q.ask(await trade.get_gms(self.bot, team))
#
# if not resp:
# await trade.send('RIP this move. Maybe next time.')
# await trade.timed_delete()
# return
# else:
# await trade.show_moves()
#
# # Run moves
# trans_id = await trade.send_transaction()
#
# await send_to_channel(self.bot, 'sba-network-news', embed=await trade.show_moves(here=False))
# await self.send_move_to_sheets(trans_id)
#
# await trade.send(f'All done! Your transaction id is: {trans_id}')
# try:
# choas = get_role(ctx, 'CHOAS ALERT')
# await send_to_channel(self.bot, f'season-{current.season}-chat', f'{choas.mention}')
# except Exception as e:
# logger.error('I was not able to ping CHOAS ALERT')
# await trade.timed_delete()
@commands.command(name='dropadd', aliases=['drop', 'add', 'adddrop', 'longil'], help='FA/MiL moves')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME)
async def drop_add_command(self, ctx):
current = await get_current()
team = await get_team_by_owner(current.season, ctx.author.id)
# team_schedule = await get_schedule(
# current.season,
# team_abbrev1=team["abbrev"],
# week_start=current.week + 1,
# week_end=current.week + 1,
# )
s_query = await db_get('games', params=[
('season', current.season), ('team1_id', team['id']), ('week', current.week + 1)
])
team_role = get_team_role(ctx, team)
player_cog = self.bot.get_cog('Players')
poke_role = get_role(ctx, 'Pokétwo')
if s_query['count'] == 0 and not OFFSEASON_FLAG and current.week != 18:
await ctx.send('It looks like your season is over so transactions are locked.')
return
# Create transaction channel
overwrites = {ctx.guild.default_role: discord.PermissionOverwrite(read_messages=False),
team_role: discord.PermissionOverwrite(read_messages=True),
ctx.guild.me: discord.PermissionOverwrite(read_messages=True),
poke_role: discord.PermissionOverwrite(read_messages=True, send_messages=False)}
try:
t_channel = await ctx.guild.create_text_channel(
f'{team["abbrev"]}-transaction',
overwrites=overwrites,
category=discord.utils.get(ctx.guild.categories, name=f'Transactions')
)
except Exception as e:
await ctx.send(f'{e}\n\n'
f'Discord is having issues creating private channels right now. Please try again later.')
return
dropadd = SBaTransaction(t_channel, current, 'dropadd', first_team=team, team_role=team_role)
await dropadd.send(f'Let\'s start here, {team_role.mention}')
await ctx.send(f'Take my hand... {dropadd.channel.mention}')
await dropadd.send(f'This transaction is for __next week__. It will go into effect for '
f'week {current.week + 1}.')
# Get MiL moves
while True and not OFFSEASON_FLAG:
prompt = f'Are you adding someone to the Minor League roster?'
this_q = Question(self.bot, dropadd.channel, prompt, 'yesno', 300)
resp = await this_q.ask(await dropadd.get_gms(self.bot))
if resp is None:
await dropadd.send('RIP this move. Maybe next time.')
await dropadd.timed_delete()
return
elif not resp:
break
else:
this_q.prompt = 'Who are you sending to the MiL?'
this_q.qtype = 'text'
resp = await this_q.ask(await dropadd.get_gms(self.bot))
if resp is None:
await dropadd.send('RIP this move. Maybe next time.')
await dropadd.timed_delete()
return
else:
try:
player = await get_player_by_name(
current.season,
await fuzzy_player_search(ctx, dropadd.channel, self.bot, resp, player_cog.player_list.keys())
)
except ValueError as e:
logger.error(f'Could not find player *{resp}*')
else:
if player is None:
await dropadd.send(f'{await get_emoji(ctx, "squint")}')
await dropadd.send('Who even is that? Try again.')
else:
fa_team = await get_team_by_abbrev('FA', current.season)
dest_team = await get_team_by_abbrev(f'{team["abbrev"]}MiL', current.season)
if current.week >= FA_LOCK_WEEK and player['team'] == fa_team:
await dropadd.send(f'FA transactions are locked starting in Week {FA_LOCK_WEEK} so I gotta say no. For more information, search for Rule 34 Draft.')
elif not dropadd.included_team(player['team']) and player['team'] != fa_team:
await t_channel.send(f'It looks like {player["name"]} is on {player["team"]["abbrev"]} '
f'so I can\'t let you do that.')
elif await dropadd.not_available(player):
await dropadd.send(f'Uh oh, looks like {player["name"]} is already on the move next week.')
elif player['demotion_week'] > current.week:
await dropadd.send(f'Oof. {player["name"]} cannot be dropped until week '
f'{player["demotion_week"]}.')
else:
if player['team'] == fa_team:
dropadd.avoid_freeze = False
await dropadd.add_player(player, dest_team)
await dropadd.show_moves()
# Get Major League Adds
while True and not OFFSEASON_FLAG:
prompt = f'Are you adding any players to the Major League roster?'
this_q = Question(self.bot, dropadd.channel, prompt, 'yesno', 300)
resp = await this_q.ask(await dropadd.get_gms(self.bot))
if resp is None:
await dropadd.send('RIP this move. Maybe next time.')
await dropadd.timed_delete()
return
elif not resp:
break
else:
this_q.prompt = 'Who are you adding?'
this_q.qtype = 'text'
resp = await this_q.ask(await dropadd.get_gms(self.bot))
if resp is None:
await dropadd.send('RIP this move. Maybe next time.')
await dropadd.timed_delete()
return
else:
try:
player = await get_player_by_name(
current.season,
await fuzzy_player_search(ctx, dropadd.channel, self.bot, resp, player_cog.player_list.keys())
)
except ValueError as e:
logger.error(f'Could not find player *{resp}*')
else:
if player is None:
await dropadd.send(f'{await get_emoji(ctx, "squint")}')
await dropadd.send('Who even is that? Try again.')
else:
if OFFSEASON_FLAG:
if not self.on_team_il(team, player):
await t_channel.send(f'It looks like {player["name"]} is on {player["team"]["abbrev"]} '
f'so I can\'t let you do that.')
else:
await dropadd.add_player(player, team)
else:
fa_team = await get_team_by_abbrev('FA', current.season)
if current.week >= FA_LOCK_WEEK and player['team'] == fa_team:
await dropadd.send(f'FA transactions are locked starting in Week {FA_LOCK_WEEK} so I gotta say no. For more information, run !rule34 <player name>.')
elif player['team'] != fa_team and not self.on_team_il(team, player):
await t_channel.send(f'It looks like {player["name"]} is on {player["team"]["abbrev"]} '
f'so I can\'t let you do that.')
elif await dropadd.not_available(player):
await dropadd.send(f'Uh oh, looks like {player["name"]} is already on the move '
f'next week.')
elif player['demotion_week'] > current.week:
await dropadd.send(f'Oof. {player["name"]} cannot be dropped until week '
f'{player["demotion_week"]}.')
else:
if player['team'] == fa_team:
dropadd.avoid_freeze = False
await dropadd.add_player(player, team)
await dropadd.show_moves()
# Get FA Drops
while True:
prompt = f'Are you dropping anyone to FA?'
this_q = Question(self.bot, dropadd.channel, prompt, 'yesno', 300)
resp = await this_q.ask(await dropadd.get_gms(self.bot))
if resp is None:
await dropadd.send('RIP this move. Maybe next time.')
await dropadd.timed_delete()
return
elif not resp:
break
else:
this_q.prompt = 'Who are you dropping to FA?'
this_q.qtype = 'text'
resp = await this_q.ask(await dropadd.get_gms(self.bot))
if resp is None:
await dropadd.send('RIP this move. Maybe next time.')
await dropadd.timed_delete()
return
else:
try:
player = await get_player_by_name(
current.season,
await fuzzy_player_search(ctx, dropadd.channel, self.bot, resp, player_cog.player_list.keys())
)
except ValueError as e:
logger.error(f'Could not find player *{resp}*')
else:
if player is None:
await dropadd.send(f'{await get_emoji(ctx, "squint")}')
await dropadd.send('Who even is that? Try again.')
else:
if not dropadd.included_team(player['team']):
await t_channel.send(f'It looks like {player["name"]} is on {player["team"]["abbrev"]} '
f'so I can\'t let you do that.')
elif await dropadd.not_available(player):
await dropadd.send(f'Uh oh, looks like {player["name"]} is already on the move next week.')
elif player['demotion_week'] > current.week:
await dropadd.send(f'Oof. {player["name"]} cannot be dropped until week '
f'{player["demotion_week"]}.')
else:
dest_team = await get_team_by_abbrev('FA', current.season)
await dropadd.add_player(player, dest_team)
await dropadd.show_moves()
# Check for empty move
if len(dropadd.players) == 0:
await dropadd.send(f'This has been fun. Come again and maybe do something next time.')
await dropadd.timed_delete()
return
# Check legality
errors = []
roster_errors = []
for team in dropadd.teams:
data = await dropadd.check_major_league_errors(team)
team_obj = dropadd.teams[team]["team"]
team_cap = get_team_salary_cap(team_obj)
logger.warning(f'Done checking data - checking WARa now ({data["wara"]})')
if exceeds_salary_cap(data['wara'], team_obj) and not OFFSEASON_FLAG:
errors.append(f'- {team_obj["abbrev"]} would have {data["wara"]:.2f} sWAR (cap {team_cap:.1f})')
logger.warning(f'Now checking roster {len(data["roster"])}')
if len(data['roster']) > 26 and not OFFSEASON_FLAG:
errors.append(f'- {team_obj["abbrev"]} would have {len(data["roster"])} players')
logger.warning(f'Any errors? {errors}')
if (exceeds_salary_cap(data['wara'], team_obj) or len(data['roster']) > 26) and not OFFSEASON_FLAG:
roster_string = ''
for x in data['roster']:
roster_string += f'{x["wara"]: >5} - {x["name"]}\n'
errors.append(f'- This is the roster I have for {team_obj["abbrev"]}:\n'
f'```\n{roster_string}```')
mil_cap = 6 if current.week <= FA_LOCK_WEEK else 14
if len(data['mil_roster']) > mil_cap:
errors.append(
f'- {dropadd.teams[team]["team"]["abbrev"]}MiL would have {len(data["mil_roster"])} players'
)
if len(errors) + len(roster_errors) > 0:
error_message = '\n'.join(errors)
await dropadd.send(f'Yikes. I\'m gonna put the kibosh on this move. Below is why:\n\n{error_message}')
if len(roster_errors) > 0:
for x in roster_errors:
await dropadd.send(x)
await dropadd.timed_delete()
return
# Ask for confirmation
for team in dropadd.teams:
this_q.prompt = f'{dropadd.teams[team]["role"].mention}\nWould you like me to run this move?'
this_q.qtype = 'yesno'
resp = await this_q.ask(await dropadd.get_gms(self.bot, team))
if not resp:
await dropadd.send('RIP this move. Maybe next time.')
await dropadd.timed_delete()
return
else:
await dropadd.show_moves()
# Run moves
trans_id = await dropadd.send_transaction()
if not current.freeze or dropadd.avoid_freeze:
await send_to_channel(self.bot, 'transaction-log', embed=await dropadd.show_moves(here=False))
await dropadd.send(f'All done! Your transaction id is: {trans_id}')
await dropadd.timed_delete()
@commands.command(name='rule34', help='Take a 50/50')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME)
async def rule_34_command(self, ctx, *, player_name: str):
dest_url = ''
if random.randint(1, 2) == 1:
q_string = player_name.replace(' ', '%20')
dest_url = f'https://www.bing.com/images/search?q=rule%2034%20{q_string}&safesearch=off'
else:
dest_url = 'https://docs.google.com/spreadsheets/d/1nNkStOm1St1U2aQ0Jrdhyti5Qe0b-sI8xTUGCTOLuXE/edit?usp=sharing'
await ctx.send(
content=f'To play the 50/50, click the \'Trust Links\' button that pops up for [Bing](<https://www.bing.com/>) and for [Sheets](<https://docs.google.com/spreadsheets/u/0/>).\n\n[Rule 34 Draft](<{dest_url}>)'
)
@commands.command(name='ilmove', help='IL move')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME)
async def il_move_command(self, ctx):
current = await get_current()
if OFFSEASON_FLAG:
await ctx.send(await get_emoji(ctx, 'oof', False))
await ctx.send(f'I\'m not supposed to let anybody make IL moves during the offseason. You can still '
f'trade and drop players to FA, though!')
return
team = await get_team_by_owner(current.season, ctx.author.id)
# team = await get_team_by_abbrev('VA', current.season)
s_query = await db_get('games', params=[
('season', current.season), ('team1_id', team['id']), ('week', current.week)
])
if s_query['count'] == 0 and current.week != 18:
await ctx.send('It looks like your season is over so transactions are locked.')
return
team_role = get_team_role(ctx, team)
player_cog = self.bot.get_cog('Players')
poke_role = get_role(ctx, 'Pokétwo')
overwrites = {ctx.guild.default_role: discord.PermissionOverwrite(read_messages=False),
team_role: discord.PermissionOverwrite(read_messages=True),
ctx.guild.me: discord.PermissionOverwrite(read_messages=True),
poke_role: discord.PermissionOverwrite(read_messages=True, send_messages=False)}
try:
t_channel = await ctx.guild.create_text_channel(
f'{team["abbrev"]}-transaction',
overwrites=overwrites,
category=discord.utils.get(ctx.guild.categories, name=f'Transactions')
)
except Exception as e:
await ctx.send(f'{e}\n\n'
f'Discord is having issues creating private channels right now. Please try again later.')
return
dropadd = SBaTransaction(t_channel, current, 'dropadd', this_week=True, first_team=team, team_role=team_role)
await dropadd.send(f'Let\'s start here, {team_role.mention}')
await ctx.send(f'Take my hand... {dropadd.channel.mention}')
await dropadd.send(f'This transaction is for __this week__. It will go into effect for week {current.week}.')
# Get IL moves
while True:
prompt = f'Are you sending someone to the IL?'
this_q = Question(self.bot, dropadd.channel, prompt, 'yesno', 300)
resp = await this_q.ask(await dropadd.get_gms(self.bot))
if resp is None:
await dropadd.send('RIP this move. Maybe next time.')
await dropadd.timed_delete()
return
elif not resp:
break
else:
this_q.prompt = 'Who are you sending to the IL?'
this_q.qtype = 'text'
resp = await this_q.ask(await dropadd.get_gms(self.bot))
if resp is None:
await dropadd.send('RIP this move. Maybe next time.')
await dropadd.timed_delete()
return
else:
try:
player = await get_player_by_name(
current.season,
await fuzzy_player_search(ctx, dropadd.channel, self.bot, resp, player_cog.player_list.keys())
)
except ValueError as e:
logger.error(f'Could not find player *{resp}*')
else:
if player is None:
await dropadd.send(f'{await get_emoji(ctx, "squint")}')
await dropadd.send('Who even is that? Try again.')
else:
if not dropadd.included_team(player['team']):
await t_channel.send(f'It looks like {player["name"]} is on {player["team"]["abbrev"]} '
f'so I can\'t let you do that.')
else:
dest_team = await get_team_by_abbrev(f'{team["abbrev"]}IL', current.season)
await dropadd.add_player(player, dest_team)
await dropadd.show_moves()
# Get Major League Adds
while True and not OFFSEASON_FLAG:
prompt = f'Are you adding someone to the Major League team?'
this_q = Question(self.bot, dropadd.channel, prompt, 'yesno', 300)
resp = await this_q.ask(await dropadd.get_gms(self.bot))
if resp is None:
await dropadd.send('RIP this move. Maybe next time.')
await dropadd.timed_delete()
return
elif not resp:
break
else:
this_q.prompt = 'Who are you adding?'
this_q.qtype = 'text'
resp = await this_q.ask(await dropadd.get_gms(self.bot))
if resp is None:
await dropadd.send('RIP this move. Maybe next time.')
await dropadd.timed_delete()
return
else:
try:
player = await get_player_by_name(
current.season,
await fuzzy_player_search(ctx, dropadd.channel, self.bot, resp, player_cog.player_list.keys())
)
except ValueError as e:
logger.error(f'Could not find player *{resp}*')
else:
if player is None:
await dropadd.send(f'{await get_emoji(ctx, "squint")}')
await dropadd.send('Who even is that? Try again.')
else:
if not self.on_team_il(team, player):
await t_channel.send(f'It looks like {player["name"]} is on {player["team"]["abbrev"]} '
f'- you can only call up your MiL players mid-week.')
else:
await dropadd.add_player(player, team)
await dropadd.show_moves()
# Get MiL moves
while True and not OFFSEASON_FLAG:
prompt = f'Are you adding someone to the Minor League team?'
this_q = Question(self.bot, dropadd.channel, prompt, 'yesno', 300)
resp = await this_q.ask(await dropadd.get_gms(self.bot))
if resp is None:
await dropadd.send('RIP this move. Maybe next time.')
await dropadd.timed_delete()
return
elif not resp:
break
else:
this_q.prompt = 'Who are you sending to the MiL?'
this_q.qtype = 'text'
resp = await this_q.ask(await dropadd.get_gms(self.bot))
if resp is None:
await dropadd.send('RIP this move. Maybe next time.')
await dropadd.timed_delete()
return
else:
try:
player = await get_player_by_name(
current.season,
await fuzzy_player_search(ctx, dropadd.channel, self.bot, resp, player_cog.player_list.keys())
)
except ValueError as e:
logger.error(f'Could not find player *{resp}*')
else:
if player is None:
await dropadd.send(f'{await get_emoji(ctx, "squint")}')
await dropadd.send('Who even is that? Try again.')
else:
if not dropadd.included_team(player['team']):
await t_channel.send(f'It looks like {player["name"]} is on {player["team"]["abbrev"]} '
f'so I can\'t let you do that.')
# elif player['demotion_week'] > current.week:
# await dropadd.send(f'Oof. {player["name"]} cannot be dropped until week '
# f'{player["demotion_week"]}.')
else:
dest_team = await get_team_by_abbrev(f'{team["abbrev"]}MiL', current.season)
await dropadd.add_player(player, dest_team)
await dropadd.show_moves()
# Get FA Drops
while True:
prompt = f'Are you dropping anyone to FA?'
this_q = Question(self.bot, dropadd.channel, prompt, 'yesno', 300)
resp = await this_q.ask(await dropadd.get_gms(self.bot))
if resp is None:
await dropadd.send('RIP this move. Maybe next time.')
await dropadd.timed_delete()
return
elif not resp:
break
else:
this_q.prompt = 'Who are you dropping to FA?'
this_q.qtype = 'text'
resp = await this_q.ask(await dropadd.get_gms(self.bot))
if resp is None:
await dropadd.send('RIP this move. Maybe next time.')
await dropadd.timed_delete()
return
else:
try:
player = await get_player_by_name(
current.season,
await fuzzy_player_search(ctx, dropadd.channel, self.bot, resp, player_cog.player_list.keys())
)
except ValueError as e:
logger.error(f'Could not find player *{resp}*')
else:
if player is None:
await dropadd.send(f'{await get_emoji(ctx, "squint")}')
await dropadd.send('Who even is that? Try again.')
else:
if not dropadd.included_team(player['team']):
await t_channel.send(f'It looks like {player["name"]} is on {player["team"]["abbrev"]} '
f'so I can\'t let you do that.')
# elif player['demotion_week'] > current.week:
# await dropadd.send(f'Oof. {player["name"]} cannot be dropped until week '
# f'{player["demotion_week"]}.')
else:
dest_team = await get_team_by_abbrev('FA', current.season)
await dropadd.add_player(player, dest_team)
await dropadd.show_moves()
# Check for empty move
if len(dropadd.players) == 0:
await dropadd.send(f'This has been fun. Come again and maybe do something next time.')
await dropadd.timed_delete()
return
# Check legality
errors = []
roster_errors = []
for team in dropadd.teams:
data = await dropadd.check_major_league_errors(team)
team_obj = dropadd.teams[team]["team"]
team_cap = get_team_salary_cap(team_obj)
if exceeds_salary_cap(data['wara'], team_obj) and not OFFSEASON_FLAG:
errors.append(f'- {team_obj["abbrev"]} would have {data["wara"]:.2f} WARa (cap {team_cap:.1f})')
if len(data['roster']) > 26 and not OFFSEASON_FLAG:
errors.append(f'- {team_obj["abbrev"]} would have {len(data["roster"])} players')
if (exceeds_salary_cap(data['wara'], team_obj) or len(data['roster']) > 26) and not OFFSEASON_FLAG:
roster_string = ''
for x in data['roster']:
roster_string += f'{x["wara"]: >5} - {x["name"]}\n'
errors.append(f'- This is the roster I have for {team_obj["abbrev"]}:\n'
f'```\n{roster_string}```')
if len(errors) + len(roster_errors) > 0:
error_message = '\n'.join(errors)
await dropadd.send(f'Yikes. I\'m gonna put the kibosh on this move. Below is why:\n\n{error_message}')
if len(roster_errors) > 0:
for x in roster_errors:
await dropadd.send(x)
await dropadd.timed_delete()
return
# Ask for confirmation
for team in dropadd.teams:
this_q.prompt = f'{dropadd.teams[team]["role"].mention}\nWould you like me to run this move?'
this_q.qtype = 'yesno'
resp = await this_q.ask(await dropadd.get_gms(self.bot, team))
if not resp:
await dropadd.send('RIP this move. Maybe next time.')
await dropadd.timed_delete()
return
else:
await dropadd.show_moves()
# Post moves
trans_id = await dropadd.send_transaction()
if dropadd.avoid_freeze:
# Run moves
for player_move in [*dropadd.players.values()]:
this_guy = copy.deepcopy(player_move['player'])
this_guy['team'] = player_move['to']
this_guy['demotion_week'] = current.week
await put_player(this_guy)
await send_to_channel(self.bot, 'transaction-log', embed=await dropadd.show_moves(here=False))
await dropadd.send(f'All done! Your transaction id is: {trans_id}')
await dropadd.timed_delete()
@commands.command(name='mymoves', help='Show upcoming moves')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME)
async def my_moves_command(self, ctx):
current = await get_current()
team = await get_team_by_owner(current.season, ctx.author.id)
# set_moves = await get_transactions(
# current.season,
# team_abbrev=team['abbrev'],
# week_start=current.week+1,
# week_end=current.week+1
# )
t_query = await db_get('transactions', params=[
('season', current.season), ('week_start', current.week),
('week_end', current.season), ('team_abbrev', team['abbrev'])
])
set_moves = t_query['transactions']
# frozen_moves = await get_transactions(
# current.season,
# team_abbrev=team['abbrev'],
# week_start=current.week+1,
# week_end=current.week+1,
# frozen=True
# )
t_query = await db_get('transactions', params=[
('season', current.season), ('week_start', current.week),
('week_end', current.season), ('team_abbrev', team['abbrev']), ('frozen', True)
])
frozen_moves = t_query['transactions']
logger.info(f'Num Moves: {len(set_moves)}')
embed = get_team_embed(f'{team["lname"]} Guaranteed Transactions', team=team)
embed.description = f'Week {current.week + 1} Moves'
guaranteed = {}
frozen = {}
for x in set_moves:
if x["moveid"] not in guaranteed.keys():
guaranteed[x["moveid"]] = []
guaranteed[x["moveid"]].append(
f'**{x["player"]["name"]}** ({x["player"]["wara"]}) from '
f'{x["oldteam"]["abbrev"]} to {x["newteam"]["abbrev"]}\n'
)
for x in frozen_moves:
if x["moveid"] not in frozen.keys():
frozen[x["moveid"]] = []
frozen[x["moveid"]].append(
f'**{x["player"]["name"]}** ({x["player"]["wara"]}) from '
f'{x["oldteam"]["abbrev"]} to {x["newteam"]["abbrev"]}\n'
)
if len(guaranteed) > 0:
for move_id in guaranteed:
move_string = ''.join(guaranteed[move_id])
embed.add_field(name=f'Trans ID: {move_id}', value=move_string)
if len(frozen) > 0:
for move_id in frozen:
move_string = ''.join(frozen[move_id])
embed.add_field(name=f'Trans ID: {move_id}', value=move_string)
await ctx.author.send(content='Hey, boo. Here are your upcoming transactions:', embed=embed)
@commands.command(name='legal', help='Check roster legality')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME)
async def legal_command(self, ctx, team_abbrev: str):
current = await get_current()
if team_abbrev:
this_team = await get_team_by_abbrev(team_abbrev, current.season)
else:
this_team = await get_team_by_owner(current.season, ctx.author.id)
# this_week_team = await get_team_roster(this_team, 'current')
# next_week_team = await get_team_roster(this_team, 'next')
this_week_team = await db_get(f'teams/{this_team["id"]}/roster/current')
next_week_team = await db_get(f'teams/{this_team["id"]}/roster/next')
count = 0
all_players = []
for roster in [this_week_team, next_week_team]:
embed = get_team_embed(f'{this_team["lname"]} Roster Check', team=this_team)
if count == 0:
embed.description = f'Week {current.week}'
else:
embed.description = f'Week {current.week + 1}'
errors = []
team_cap = get_team_salary_cap(this_team)
sil_wara = roster['shortil']['WARa']
total_wara = roster['active']['WARa']
wara_string = f'{total_wara:.2f}'
if sil_wara > 0:
wara_string += f' ({sil_wara:.2f} IL)'
embed.add_field(name='sWAR', value=wara_string)
if exceeds_salary_cap(total_wara, this_team):
errors.append(f'- sWAR currently {total_wara:.2f} (cap {team_cap:.1f})')
player_count = len(roster["active"]["players"])
embed.add_field(name='Player Count', value=f'{player_count}')
if player_count != 26:
errors.append(f'- Currently have {player_count} players (need 26)')
pos_string = f'```\nC 1B 2B 3B SS\n' \
f'{roster["active"]["C"]} {roster["active"]["1B"]} {roster["active"]["2B"]} ' \
f'{roster["active"]["3B"]} {roster["active"]["SS"]}\n\n' \
f'LF CF RF SP RP\n' \
f'{roster["active"]["LF"]} {roster["active"]["CF"]} {roster["active"]["RF"]} ' \
f'{roster["active"]["SP"]} {roster["active"]["RP"]}\n```'
embed.add_field(name='Position Checks', value=pos_string, inline=False)
for pos in roster['active']:
if pos in ['C', '1B', '2B', '3B', 'SS', 'LF', 'CF', 'RF']:
logger.info(f'Pos: {pos} / {roster["active"][pos]}')
if roster["active"][pos] < 2:
errors.append(f'- Only have {roster["active"][pos]} {pos} (need 2)')
# elif pos in ['SP', 'RP']:
# logger.info(f'Pos: {pos} / {roster["active"][pos]}')
# if roster["active"][pos] < 5:
# errors.append(f'- Only have {roster["active"][pos]} {pos} (need 5)')
if len(errors) > 0:
embed.add_field(name='Legality: ILLEGAL ❌', value="\n".join(errors), inline=False)
else:
embed.add_field(name='Legality: LEGAL 👍️', value='All good!', inline=False)
await ctx.send(content=None, embed=embed)
count += 1
@commands.command(name='tomil', help='Post-draft demotions')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME)
async def to_mil_command(self, ctx, *player_list):
current = await get_current()
team = await get_team_by_owner(current.season, owner_id=ctx.author.id)
il_team = await get_team_by_abbrev(f'{team["abbrev"]}MiL', current.season)
if current.week != 0:
logger.info('entering the thot check')
await react_and_reply(ctx, '👀', 'https://c.tenor.com/FCAj8xDvEHwAAAAC/be-gone-thot.gif')
player_role = get_role(ctx, SBA_PLAYERS_ROLE_NAME)
bonked_role = get_role(ctx, 'BAINSHED')
logger.info('beginning sleep')
await asyncio.sleep(3)
# try:
# await ctx.author.add_roles(bonked_role)
# except Exception as e:
# logger.error(f'unable to add {bonked_role} role to {ctx.author}: {e}')
try:
await ctx.author.remove_roles(player_role)
except Exception as e:
logger.error(f'unable to remove {player_role} role from {ctx.author}: {e}')
return
player_list = ' '.join(player_list)
player_names = re.split(',', player_list)
output_string = ''
errors = []
moves = []
for x in player_names:
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())
except Exception as e:
logger.error(f'Could not demote {x} for {team["abbrev"]}: {e}')
errors.append(x)
else:
player = await get_player_by_name(current.season, player_name)
if player['team']['id'] != team['id']:
await ctx.send(f'Omg stop trying to make {player["name"]} happen. It\'s not going to happen.')
else:
output_string += f'**{player["name"]}** ({player["wara"]}) to MiL\n'
if player['team']['id'] == team['id']:
moves.append({
'week': 1,
'player_id': player['id'],
'oldteam_id': team['id'],
'newteam_id': il_team['id'],
'season': current.season,
'moveid': f's{current.season}-draft-tomil-{team["abbrev"]}'
})
if len(moves) == 0:
await react_and_reply(ctx, '🖕', 'Thanks for that. So much fun.')
return
await db_post('transactions', payload={'count': len(moves), 'moves': moves})
embed = get_team_embed(f'Pre-Season Demotions', team)
embed.add_field(name='Demotions', value=output_string, inline=False)
await send_to_channel(self.bot, 'transaction-log', content=None, embed=embed)
if len(moves) > 0:
await react_and_reply(ctx, '', random_conf_gif())
if len(errors) > 0:
await react_and_reply(ctx, '', f'I wasn\'t able to find {", ".join(errors)}')
@commands.command(name='calisamazing', help='Make up for your idiocy')
async def cal_is_amazing_command(self, ctx):
current = await get_current()
team = await get_team_by_owner(current.season, owner_id=ctx.author.id)
if not team:
await ctx.send(random_conf_gif())
player_role = get_role(ctx, SBA_PLAYERS_ROLE_NAME)
try:
await ctx.author.add_roles(player_role)
except Exception as e:
logger.error(f'unable to add {player_role} role to {ctx.author}: {e}')
await react_and_reply(
ctx, '😩',
f'{e}\n\nLooks like you didn\'t apologize hard enough. I can\'t role you back up.')
await react_and_reply(ctx, '💖', 'You are too kind.')
async def setup(bot):
await bot.add_cog(Transactions(bot))