major-domo-v2/views/draft_views.py
Cal Corum ff62529ee3 Add draft pause/resume functionality
- Add paused field to DraftData model
- Add pause_draft() and resume_draft() methods to DraftService
- Add /draft-admin pause and /draft-admin resume commands
- Block picks in /draft command when draft is paused
- Skip auto-draft in draft_monitor when draft is paused
- Update status embeds to show paused state
- Add comprehensive tests for pause/resume

When paused:
- Timer is stopped (set to False)
- Deadline is set far in future
- All /draft picks are blocked
- Auto-draft monitor skips processing

When resumed:
- Timer is restarted with fresh deadline
- Picks are allowed again

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-11 19:58:37 -06:00

638 lines
17 KiB
Python

"""
Draft Views for Discord Bot v2.0
Provides embeds and UI components for draft system.
"""
from typing import Optional, List
from datetime import datetime
import discord
from models.draft_pick import DraftPick
from models.draft_data import DraftData
from models.team import Team
from models.player import Player
from models.draft_list import DraftList
from views.embeds import EmbedTemplate, EmbedColors
from utils.draft_helpers import format_pick_display, get_round_name
from config import get_config
async def create_on_the_clock_embed(
current_pick: DraftPick,
draft_data: DraftData,
recent_picks: List[DraftPick],
upcoming_picks: List[DraftPick],
team_roster_swar: Optional[float] = None
) -> discord.Embed:
"""
Create "on the clock" embed showing current pick info.
Args:
current_pick: Current DraftPick being made
draft_data: Current draft configuration
recent_picks: List of recent draft picks
upcoming_picks: List of upcoming draft picks
team_roster_swar: Current team sWAR (optional)
Returns:
Discord embed with pick information
"""
if not current_pick.owner:
raise ValueError("Pick must have owner")
# Create base embed with team colors
embed = EmbedTemplate.create_base_embed(
title=f"{current_pick.owner.lname} On The Clock",
description=format_pick_display(current_pick.overall),
color=EmbedColors.PRIMARY
)
# Add team info
if current_pick.owner.sname:
embed.add_field(
name="Team",
value=f"{current_pick.owner.abbrev} {current_pick.owner.sname}",
inline=True
)
# Add timer info
if draft_data.pick_deadline:
deadline_timestamp = int(draft_data.pick_deadline.timestamp())
embed.add_field(
name="Deadline",
value=f"<t:{deadline_timestamp}:R>",
inline=True
)
# Add team sWAR if provided
if team_roster_swar is not None:
from utils.helpers import get_team_salary_cap
cap_limit = get_team_salary_cap(current_pick.owner)
embed.add_field(
name="Current sWAR",
value=f"{team_roster_swar:.2f} / {cap_limit:.2f}",
inline=True
)
# Add recent picks
if recent_picks:
recent_str = ""
for pick in recent_picks[:5]:
if pick.player:
recent_str += f"**#{pick.overall}** - {pick.player.name}\n"
if recent_str:
embed.add_field(
name="📋 Last 5 Picks",
value=recent_str or "None",
inline=False
)
# Add upcoming picks
if upcoming_picks:
upcoming_str = ""
for pick in upcoming_picks[:5]:
upcoming_str += f"**#{pick.overall}** - {pick.owner.sname if pick.owner else 'Unknown'}\n"
if upcoming_str:
embed.add_field(
name="🔜 Next 5 Picks",
value=upcoming_str,
inline=False
)
# Add footer
if current_pick.is_traded:
embed.set_footer(text="📝 This pick was traded")
return embed
async def create_draft_status_embed(
draft_data: DraftData,
current_pick: DraftPick,
lock_status: str = "🔓 No pick in progress",
sheet_url: Optional[str] = None
) -> discord.Embed:
"""
Create draft status embed showing current state.
Args:
draft_data: Current draft configuration
current_pick: Current DraftPick
lock_status: Lock status message
sheet_url: Optional Google Sheets URL for draft tracking
Returns:
Discord embed with draft status
"""
# Use warning color if paused
if draft_data.paused:
embed = EmbedTemplate.warning(
title="Draft Status - PAUSED",
description=f"Currently on {format_pick_display(draft_data.currentpick)}"
)
else:
embed = EmbedTemplate.info(
title="Draft Status",
description=f"Currently on {format_pick_display(draft_data.currentpick)}"
)
# On the clock
if current_pick.owner:
embed.add_field(
name="On the Clock",
value=f"{current_pick.owner.abbrev} {current_pick.owner.sname}",
inline=True
)
# Timer status (show paused state prominently)
if draft_data.paused:
timer_status = "⏸️ PAUSED"
elif draft_data.timer:
timer_status = "✅ Active"
else:
timer_status = "⏹️ Inactive"
embed.add_field(
name="Timer",
value=f"{timer_status} ({draft_data.pick_minutes} min)",
inline=True
)
# Deadline
if draft_data.pick_deadline:
deadline_timestamp = int(draft_data.pick_deadline.timestamp())
embed.add_field(
name="Deadline",
value=f"<t:{deadline_timestamp}:R>",
inline=True
)
else:
embed.add_field(
name="Deadline",
value="None",
inline=True
)
# Pause status (if paused, show prominent warning)
if draft_data.paused:
embed.add_field(
name="Pause Status",
value="🚫 **Draft is paused** - No picks allowed until admin resumes",
inline=False
)
# Lock status
embed.add_field(
name="Lock Status",
value=lock_status,
inline=False
)
# Draft Sheet link
if sheet_url:
embed.add_field(
name="Draft Sheet",
value=f"[View Sheet]({sheet_url})",
inline=False
)
return embed
async def create_player_draft_card(
player: Player,
draft_pick: DraftPick
) -> discord.Embed:
"""
Create player draft card embed.
Args:
player: Player being drafted
draft_pick: DraftPick information
Returns:
Discord embed with player info
"""
if not draft_pick.owner:
raise ValueError("Pick must have owner")
embed = EmbedTemplate.success(
title=f"{player.name} Drafted!",
description=format_pick_display(draft_pick.overall)
)
# Team info
embed.add_field(
name="Selected By",
value=f"{draft_pick.owner.abbrev} {draft_pick.owner.sname}",
inline=True
)
# Player info
if hasattr(player, 'pos_1') and player.pos_1:
embed.add_field(
name="Position",
value=player.pos_1,
inline=True
)
if hasattr(player, 'wara') and player.wara is not None:
embed.add_field(
name="sWAR",
value=f"{player.wara:.2f}",
inline=True
)
# Add player image if available
if hasattr(player, 'image') and player.image:
embed.set_thumbnail(url=player.image)
return embed
async def create_draft_list_embed(
team: Team,
draft_list: List[DraftList]
) -> discord.Embed:
"""
Create draft list embed showing team's auto-draft queue.
Args:
team: Team owning the list
draft_list: List of DraftList entries
Returns:
Discord embed with draft list
"""
embed = EmbedTemplate.info(
title=f"{team.sname} Draft List",
description=f"Auto-draft queue for {team.abbrev}"
)
if not draft_list:
embed.add_field(
name="Queue Empty",
value="No players in auto-draft queue",
inline=False
)
else:
# Group players by rank
list_str = ""
for entry in draft_list[:25]: # Limit to 25 for embed size
player_name = entry.player.name if entry.player else f"Player {entry.player_id}"
player_swar = f" ({entry.player.wara:.2f})" if entry.player and hasattr(entry.player, 'wara') else ""
list_str += f"**{entry.rank}.** {player_name}{player_swar}\n"
embed.add_field(
name=f"Queue ({len(draft_list)} players)",
value=list_str,
inline=False
)
embed.set_footer(text="Commands: /draft-list-add, /draft-list-remove, /draft-list-clear")
return embed
async def create_draft_board_embed(
round_num: int,
picks: List[DraftPick]
) -> discord.Embed:
"""
Create draft board embed showing all picks in a round.
Args:
round_num: Round number
picks: List of DraftPick for this round
Returns:
Discord embed with draft board
"""
embed = EmbedTemplate.create_base_embed(
title=f"📋 {get_round_name(round_num)}",
description=f"Draft board for round {round_num}",
color=EmbedColors.PRIMARY
)
if not picks:
embed.add_field(
name="No Picks",
value="No picks found for this round",
inline=False
)
else:
# Create picks display
picks_str = ""
for pick in picks:
if pick.player:
player_display = pick.player.name
else:
player_display = "TBD"
team_display = pick.owner.abbrev if pick.owner else "???"
round_pick = pick.overall % 16 or 16
# Format: `RR.PP (#OOO)` - padded for alignment (rounds 1-99, picks 1-16, overall 1-999)
pick_info = f"{round_num:>2}.{round_pick:<2} (#{pick.overall:>3})"
picks_str += f"`{pick_info}` {team_display} - {player_display}\n"
embed.add_field(
name="Picks",
value=picks_str,
inline=False
)
embed.set_footer(text="Use /draft-board [round] to view different rounds")
return embed
async def create_pick_illegal_embed(
reason: str,
details: Optional[str] = None
) -> discord.Embed:
"""
Create embed for illegal pick attempt.
Args:
reason: Main reason pick is illegal
details: Additional details (optional)
Returns:
Discord error embed
"""
embed = EmbedTemplate.error(
title="Invalid Pick",
description=reason
)
if details:
embed.add_field(
name="Details",
value=details,
inline=False
)
return embed
async def create_pick_success_embed(
player: Player,
team: Team,
pick_overall: int,
projected_swar: float,
cap_limit: float | None = None
) -> discord.Embed:
"""
Create embed for successful pick.
Args:
player: Player drafted
team: Team that drafted player
pick_overall: Overall pick number
projected_swar: Projected team sWAR after pick
cap_limit: Team's salary cap limit (optional, uses helper if not provided)
Returns:
Discord success embed
"""
from utils.helpers import get_team_salary_cap
embed = EmbedTemplate.success(
title=f"{team.sname} select **{player.name}**",
description=format_pick_display(pick_overall)
)
if team.thumbnail is not None:
embed.set_thumbnail(url=team.thumbnail)
embed.set_image(url=player.image)
embed.add_field(
name="Player ID",
value=f"{player.id}",
inline=True
)
if hasattr(player, 'wara') and player.wara is not None:
embed.add_field(
name="sWAR",
value=f"{player.wara:.2f}",
inline=True
)
# Use provided cap_limit or get from team
if cap_limit is None:
cap_limit = get_team_salary_cap(team)
embed.add_field(
name="Projected Team sWAR",
value=f"{projected_swar:.2f} / {cap_limit:.2f}",
inline=False
)
return embed
async def create_admin_draft_info_embed(
draft_data: DraftData,
current_pick: Optional[DraftPick] = None
) -> discord.Embed:
"""
Create detailed admin view of draft status.
Args:
draft_data: Current draft configuration
current_pick: Current DraftPick (optional)
Returns:
Discord embed with admin information
"""
# Use warning color if paused
if draft_data.paused:
embed = EmbedTemplate.create_base_embed(
title="⚙️ Draft Administration - PAUSED",
description="Current draft configuration and state",
color=EmbedColors.WARNING
)
else:
embed = EmbedTemplate.create_base_embed(
title="⚙️ Draft Administration",
description="Current draft configuration and state",
color=EmbedColors.INFO
)
# Current pick
embed.add_field(
name="Current Pick",
value=str(draft_data.currentpick),
inline=True
)
# Timer status (show paused prominently)
if draft_data.paused:
timer_emoji = "⏸️"
timer_text = "PAUSED"
elif draft_data.timer:
timer_emoji = ""
timer_text = "Active"
else:
timer_emoji = "⏹️"
timer_text = "Inactive"
embed.add_field(
name="Timer Status",
value=f"{timer_emoji} {timer_text}",
inline=True
)
# Timer duration
embed.add_field(
name="Pick Duration",
value=f"{draft_data.pick_minutes} minutes",
inline=True
)
# Pause status (prominent if paused)
if draft_data.paused:
embed.add_field(
name="Pause Status",
value="🚫 **PAUSED** - No picks allowed\nUse `/draft-admin resume` to allow picks",
inline=False
)
# Channels
ping_channel_value = f"<#{draft_data.ping_channel}>" if draft_data.ping_channel else "Not configured"
embed.add_field(
name="Ping Channel",
value=ping_channel_value,
inline=True
)
result_channel_value = f"<#{draft_data.result_channel}>" if draft_data.result_channel else "Not configured"
embed.add_field(
name="Result Channel",
value=result_channel_value,
inline=True
)
# Deadline
if draft_data.pick_deadline:
deadline_timestamp = int(draft_data.pick_deadline.timestamp())
embed.add_field(
name="Current Deadline",
value=f"<t:{deadline_timestamp}:F>",
inline=True
)
# Current pick owner
if current_pick and current_pick.owner:
embed.add_field(
name="On The Clock",
value=f"{current_pick.owner.abbrev} {current_pick.owner.sname}",
inline=False
)
embed.set_footer(text="Use /draft-admin to modify draft settings")
return embed
async def create_on_clock_announcement_embed(
current_pick: DraftPick,
draft_data: DraftData,
recent_picks: List[DraftPick],
roster_swar: float,
cap_limit: float,
top_roster_players: List[Player]
) -> discord.Embed:
"""
Create announcement embed for when a team is on the clock.
Used to post in the ping channel when:
- Timer is enabled and pick advances
- Auto-draft completes
- Pick is skipped
Args:
current_pick: The current DraftPick (team now on the clock)
draft_data: Current draft configuration (for timer/deadline info)
recent_picks: Last 5 completed picks
roster_swar: Team's current total sWAR
cap_limit: Team's salary cap limit
top_roster_players: Top 5 most expensive players on the team's roster
Returns:
Discord embed announcing team is on the clock
"""
if not current_pick.owner:
raise ValueError("Pick must have owner")
team = current_pick.owner
# Create embed with team color if available
team_color = int(team.color, 16) if team.color else EmbedColors.PRIMARY
embed = EmbedTemplate.create_base_embed(
title=f"{team.lname} On The Clock",
description=format_pick_display(current_pick.overall),
color=team_color
)
# Set team thumbnail
if team.thumbnail:
embed.set_thumbnail(url=team.thumbnail)
# Deadline field (if timer active)
if draft_data.timer and draft_data.pick_deadline:
deadline_timestamp = int(draft_data.pick_deadline.timestamp())
embed.add_field(
name="⏱️ Deadline",
value=f"<t:{deadline_timestamp}:T> (<t:{deadline_timestamp}:R>)",
inline=True
)
# Team sWAR
embed.add_field(
name="💰 Team sWAR",
value=f"{roster_swar:.2f} / {cap_limit:.2f}",
inline=True
)
# Cap space remaining
cap_remaining = cap_limit - roster_swar
embed.add_field(
name="📊 Cap Space",
value=f"{cap_remaining:.2f}",
inline=True
)
# Last 5 picks
if recent_picks:
recent_str = ""
for pick in recent_picks[:5]:
if pick.player and pick.owner:
recent_str += f"**#{pick.overall}** {pick.owner.abbrev} - {pick.player.name}\n"
if recent_str:
embed.add_field(
name="📋 Last 5 Picks",
value=recent_str,
inline=False
)
# Top 5 most expensive players on team roster
if top_roster_players:
expensive_str = ""
for player in top_roster_players[:5]:
pos = player.pos_1 if hasattr(player, 'pos_1') and player.pos_1 else "?"
expensive_str += f"**{player.name}** ({pos}) - {player.wara:.2f}\n"
embed.add_field(
name="🌟 Top Roster sWAR",
value=expensive_str,
inline=False
)
# Footer with pick info
if current_pick.is_traded:
embed.set_footer(text="📝 This pick was acquired via trade")
return embed