Enhancement: Allow releasing players to Free Agency via /ilmove Changes: - Added "Free Agency" as destination choice in command options - Updated destination_map to include "fa" -> RosterType.FREE_AGENCY - Added logic to fetch FA team from config (free_agent_team_id: 498) - Set to_team correctly: FA team when releasing, user team otherwise This allows users to immediately release players to Free Agency for current week, complementing the existing ML/MiL/IL destination options. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
256 lines
11 KiB
Python
256 lines
11 KiB
Python
"""
|
|
/ilmove Command - Real-time IL/Roster Moves
|
|
|
|
Interactive transaction builder for immediate roster changes (current week).
|
|
Unlike /dropadd which schedules moves for next week, /ilmove:
|
|
- Creates transactions for THIS week
|
|
- Immediately posts transactions to database
|
|
- Immediately updates player team assignments
|
|
"""
|
|
from typing import Optional
|
|
|
|
import discord
|
|
from discord.ext import commands
|
|
from discord import app_commands
|
|
|
|
from config import get_config
|
|
from utils.logging import get_contextual_logger
|
|
from utils.decorators import logged_command
|
|
from utils.autocomplete import player_autocomplete
|
|
from utils.team_utils import validate_user_has_team
|
|
|
|
from services.transaction_builder import (
|
|
TransactionBuilder,
|
|
RosterType,
|
|
TransactionMove,
|
|
get_transaction_builder,
|
|
clear_transaction_builder
|
|
)
|
|
from services.player_service import player_service
|
|
from services.team_service import team_service
|
|
from views.transaction_embed import TransactionEmbedView, create_transaction_embed
|
|
|
|
|
|
class ILMoveCommands(commands.Cog):
|
|
"""Real-time roster move commands (IL, activations, etc)."""
|
|
|
|
def __init__(self, bot: commands.Bot):
|
|
self.bot = bot
|
|
self.logger = get_contextual_logger(f'{__name__}.ILMoveCommands')
|
|
|
|
|
|
@app_commands.command(
|
|
name="ilmove",
|
|
description="Build a real-time roster move (executed immediately for this week)"
|
|
)
|
|
@app_commands.describe(
|
|
player="Player name; begin typing for autocomplete",
|
|
destination="Where to move the player: Major League, Minor League, Injured List, or Free Agency"
|
|
)
|
|
@app_commands.autocomplete(player=player_autocomplete)
|
|
@app_commands.choices(destination=[
|
|
app_commands.Choice(name="Major League", value="ml"),
|
|
app_commands.Choice(name="Minor League", value="mil"),
|
|
app_commands.Choice(name="Injured List", value="il"),
|
|
app_commands.Choice(name="Free Agency", value="fa")
|
|
])
|
|
@logged_command("/ilmove")
|
|
async def ilmove(
|
|
self,
|
|
interaction: discord.Interaction,
|
|
player: Optional[str] = None,
|
|
destination: Optional[str] = None
|
|
):
|
|
"""Interactive transaction builder for immediate roster moves."""
|
|
await interaction.response.defer(ephemeral=True)
|
|
|
|
# Get user's major league team
|
|
team = await validate_user_has_team(interaction)
|
|
if not team:
|
|
return
|
|
|
|
# Get or create transaction builder
|
|
builder = get_transaction_builder(interaction.user.id, team)
|
|
|
|
# Handle different scenarios based on builder state and parameters
|
|
if player and destination:
|
|
# User provided both parameters - try to add the move
|
|
success, error_message = await self._add_quick_move(builder, player, destination)
|
|
|
|
if success:
|
|
# Move added successfully - show updated transaction builder
|
|
embed = await create_transaction_embed(builder, command_name="/ilmove")
|
|
view = TransactionEmbedView(builder, interaction.user.id, submission_handler="immediate", command_name="/ilmove")
|
|
|
|
success_msg = f"✅ **Added {player} → {destination.upper()}**"
|
|
if builder.move_count > 1:
|
|
success_msg += f"\n📊 Transaction now has {builder.move_count} moves"
|
|
|
|
await interaction.followup.send(
|
|
content=success_msg,
|
|
embed=embed,
|
|
view=view,
|
|
ephemeral=True
|
|
)
|
|
self.logger.info(f"Move added for {team.abbrev}: {player} → {destination}")
|
|
|
|
else:
|
|
# Failed to add move - still show current transaction state
|
|
embed = await create_transaction_embed(builder, command_name="/ilmove")
|
|
view = TransactionEmbedView(builder, interaction.user.id, submission_handler="immediate", command_name="/ilmove")
|
|
|
|
await interaction.followup.send(
|
|
content=f"❌ **{error_message}**\n"
|
|
f"💡 Try using autocomplete for player names",
|
|
embed=embed,
|
|
view=view,
|
|
ephemeral=True
|
|
)
|
|
self.logger.warning(f"Failed to add move: {player} → {destination}: {error_message}")
|
|
else:
|
|
# No parameters or incomplete parameters - show current transaction state
|
|
embed = await create_transaction_embed(builder, command_name="/ilmove")
|
|
view = TransactionEmbedView(builder, interaction.user.id, submission_handler="immediate", command_name="/ilmove")
|
|
await interaction.followup.send(embed=embed, view=view, ephemeral=True)
|
|
|
|
async def _add_quick_move(
|
|
self,
|
|
builder: TransactionBuilder,
|
|
player_name: str,
|
|
destination_str: str
|
|
) -> tuple[bool, str]:
|
|
"""
|
|
Add a move quickly from command parameters by auto-determining the action.
|
|
|
|
Args:
|
|
builder: TransactionBuilder instance
|
|
player_name: Name of player to move
|
|
destination_str: Destination string (ml, mil, il, fa)
|
|
|
|
Returns:
|
|
Tuple of (success: bool, error_message: str)
|
|
"""
|
|
try:
|
|
# Find player using the new search endpoint
|
|
players = await player_service.search_players(player_name, limit=10, season=get_config().sba_current_season)
|
|
if not players:
|
|
self.logger.error(f"Player not found: {player_name}")
|
|
return False, f"Player '{player_name}' not found"
|
|
|
|
# Use exact match if available, otherwise first result
|
|
player = None
|
|
for p in players:
|
|
if p.name.lower() == player_name.lower():
|
|
player = p
|
|
break
|
|
|
|
if not player:
|
|
player = players[0] # Use first match
|
|
|
|
# Check if player belongs to another team (not user's team and not Free Agency)
|
|
if player.team and hasattr(player.team, 'abbrev'):
|
|
# Player belongs to another team if:
|
|
# 1. They have a team assigned AND
|
|
# 2. That team is not Free Agency (abbrev != 'FA') AND
|
|
# 3. That team is not in the same organization as the user's team
|
|
if (player.team.abbrev != 'FA' and
|
|
not builder.team.is_same_organization(player.team)):
|
|
self.logger.warning(f"Player {player.name} belongs to {player.team.abbrev}, cannot add to {builder.team.abbrev} transaction")
|
|
return False, f"{player.name} belongs to {player.team.abbrev} and cannot be added to your transaction"
|
|
|
|
# Parse destination
|
|
destination_map = {
|
|
"ml": RosterType.MAJOR_LEAGUE,
|
|
"mil": RosterType.MINOR_LEAGUE,
|
|
"il": RosterType.INJURED_LIST,
|
|
"fa": RosterType.FREE_AGENCY,
|
|
}
|
|
|
|
to_roster = destination_map.get(destination_str.lower())
|
|
if not to_roster:
|
|
self.logger.error(f"Invalid destination: {destination_str}")
|
|
return False, f"Invalid destination: {destination_str}"
|
|
|
|
# Determine player's current roster status by checking actual roster data
|
|
# Note: Minor League players have different team_id than Major League team
|
|
self.logger.debug(f"Player {player.name} team_id: {player.team_id}, Builder team_id: {builder.team.id}")
|
|
|
|
await builder.load_roster_data()
|
|
if builder._current_roster:
|
|
# Check which roster section the player is on (regardless of team_id)
|
|
player_on_active = any(p.id == player.id for p in builder._current_roster.active_players)
|
|
player_on_minor = any(p.id == player.id for p in builder._current_roster.minor_league_players)
|
|
player_on_il = any(p.id == player.id for p in builder._current_roster.il_players)
|
|
|
|
if player_on_active:
|
|
from_roster = RosterType.MAJOR_LEAGUE
|
|
self.logger.debug(f"Player {player.name} found on active roster (Major League)")
|
|
elif player_on_minor:
|
|
from_roster = RosterType.MINOR_LEAGUE
|
|
self.logger.debug(f"Player {player.name} found on minor league roster")
|
|
elif player_on_il:
|
|
from_roster = RosterType.INJURED_LIST
|
|
self.logger.debug(f"Player {player.name} found on injured list")
|
|
else:
|
|
# Player not found on user's roster - cannot move with /ilmove
|
|
from_roster = None
|
|
self.logger.warning(f"Player {player.name} not found on {builder.team.abbrev} roster")
|
|
return False, f"{player.name} is not on your roster (use /dropadd for FA signings)"
|
|
else:
|
|
# Couldn't load roster data
|
|
self.logger.error(f"Could not load roster data for {builder.team.abbrev}")
|
|
return False, "Could not load roster data. Please try again."
|
|
|
|
if from_roster is None:
|
|
return False, f"{player.name} is not on your roster"
|
|
|
|
# Determine destination team (Free Agency if releasing player)
|
|
if to_roster == RosterType.FREE_AGENCY:
|
|
config = get_config()
|
|
fa_team = await team_service.get_team(config.free_agent_team_id)
|
|
if not fa_team:
|
|
self.logger.error(f"Could not load Free Agency team (ID: {config.free_agent_team_id})")
|
|
return False, "Could not load Free Agency team. Please try again."
|
|
to_team = fa_team
|
|
else:
|
|
to_team = builder.team
|
|
|
|
# Create move
|
|
move = TransactionMove(
|
|
player=player,
|
|
from_roster=from_roster,
|
|
to_roster=to_roster,
|
|
from_team=builder.team,
|
|
to_team=to_team
|
|
)
|
|
|
|
success, error_message = builder.add_move(move)
|
|
if not success:
|
|
self.logger.warning(f"Failed to add quick move: {error_message}")
|
|
return False, error_message
|
|
return True, ""
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"Error adding quick move: {e}")
|
|
return False, f"Error adding move: {str(e)}"
|
|
|
|
@app_commands.command(
|
|
name="clearilmove",
|
|
description="Clear your current IL move transaction builder"
|
|
)
|
|
@logged_command("/clearilmove")
|
|
async def clear_ilmove(self, interaction: discord.Interaction):
|
|
"""Clear the user's current IL move transaction builder."""
|
|
clear_transaction_builder(interaction.user.id)
|
|
|
|
await interaction.response.send_message(
|
|
"✅ Your IL move transaction builder has been cleared.",
|
|
ephemeral=True
|
|
)
|
|
|
|
|
|
|
|
async def setup(bot):
|
|
"""Setup function for the cog."""
|
|
await bot.add_cog(ILMoveCommands(bot))
|