From fb78b4b8c64527180d844b8fe3749793e5d6b47e Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Sat, 25 Oct 2025 20:29:16 -0500 Subject: [PATCH] CLAUDE: Add draft period restriction to interactive draft commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restrict interactive draft commands to offseason only (week <= 0) while keeping read-only commands available year-round. Changes: - Add @requires_draft_period decorator to utils/decorators.py - Apply decorator to /draft and all /draft-list-* commands - Keep /draft-board, /draft-status, /draft-on-clock unrestricted - Update commands/draft/CLAUDE.md with restriction documentation Technical details: - Decorator checks league_service.get_current_state().week <= 0 - Shows user-friendly error: "Draft commands are only available in the offseason" - Follows existing @logged_command decorator pattern - No Discord command cache delays - instant restriction 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- commands/draft/CLAUDE.md | 30 ++++++++++++++ commands/draft/list.py | 6 ++- commands/draft/picks.py | 3 +- utils/decorators.py | 84 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 121 insertions(+), 2 deletions(-) diff --git a/commands/draft/CLAUDE.md b/commands/draft/CLAUDE.md index 5a51099..1313318 100644 --- a/commands/draft/CLAUDE.md +++ b/commands/draft/CLAUDE.md @@ -4,11 +4,40 @@ This directory contains Discord slash commands for draft system operations. +## 🚨 Important: Draft Period Restriction + +**Interactive draft commands are restricted to the offseason (week ≤ 0).** + +### Restricted Commands +The following commands can **only be used during the offseason** (when league week ≤ 0): +- `/draft` - Make draft picks +- `/draft-list` - View auto-draft queue +- `/draft-list-add` - Add player to queue +- `/draft-list-remove` - Remove player from queue +- `/draft-list-clear` - Clear entire queue + +**Implementation:** The `@requires_draft_period` decorator automatically checks the current league week and returns an error message if the league is in-season. + +### Unrestricted Commands +These commands remain **available year-round**: +- `/draft-board` - View draft picks by round +- `/draft-status` - View current draft state +- `/draft-on-clock` - View detailed on-the-clock information +- All `/draft-admin` commands (administrator only) + +### User Experience +When a user tries to run a restricted command during the season, they see: +``` +❌ Not Available +Draft commands are only available in the offseason. +``` + ## Files ### `picks.py` - **Command**: `/draft` - **Description**: Make a draft pick with FA player autocomplete +- **Restriction**: Offseason only (week ≤ 0) via `@requires_draft_period` decorator - **Parameters**: - `player` (required): Player name to draft (autocomplete shows FA players with position and sWAR) - **Service Dependencies**: @@ -19,6 +48,7 @@ This directory contains Discord slash commands for draft system operations. - `team_service.get_team_roster()` - `player_service.get_players_by_name()` - `player_service.update_player_team()` + - `league_service.get_current_state()` (for period check) ## Key Features diff --git a/commands/draft/list.py b/commands/draft/list.py index 6e2261c..4a4ebac 100644 --- a/commands/draft/list.py +++ b/commands/draft/list.py @@ -14,7 +14,7 @@ from services.draft_list_service import draft_list_service from services.player_service import player_service from services.team_service import team_service from utils.logging import get_contextual_logger -from utils.decorators import logged_command +from utils.decorators import logged_command, requires_draft_period from views.draft_views import create_draft_list_embed from views.embeds import EmbedTemplate @@ -61,6 +61,7 @@ class DraftListCommands(commands.Cog): name="draft-list", description="View your team's auto-draft queue" ) + @requires_draft_period @logged_command("/draft-list") async def draft_list_view(self, interaction: discord.Interaction): """Display team's draft list.""" @@ -101,6 +102,7 @@ class DraftListCommands(commands.Cog): rank="Position in queue (optional, adds to end if not specified)" ) @discord.app_commands.autocomplete(player=fa_player_autocomplete) + @requires_draft_period @logged_command("/draft-list-add") async def draft_list_add( self, @@ -207,6 +209,7 @@ class DraftListCommands(commands.Cog): player="Player name to remove" ) @discord.app_commands.autocomplete(player=fa_player_autocomplete) + @requires_draft_period @logged_command("/draft-list-remove") async def draft_list_remove( self, @@ -268,6 +271,7 @@ class DraftListCommands(commands.Cog): name="draft-list-clear", description="Clear your entire auto-draft queue" ) + @requires_draft_period @logged_command("/draft-list-clear") async def draft_list_clear(self, interaction: discord.Interaction): """Clear entire draft list.""" diff --git a/commands/draft/picks.py b/commands/draft/picks.py index cf2d824..d13d303 100644 --- a/commands/draft/picks.py +++ b/commands/draft/picks.py @@ -16,7 +16,7 @@ from services.draft_pick_service import draft_pick_service from services.player_service import player_service from services.team_service import team_service from utils.logging import get_contextual_logger -from utils.decorators import logged_command +from utils.decorators import logged_command, requires_draft_period from utils.draft_helpers import validate_cap_space, format_pick_display from views.draft_views import ( create_player_draft_card, @@ -77,6 +77,7 @@ class DraftPicksCog(commands.Cog): player="Player name to draft (autocomplete shows available FA players)" ) @discord.app_commands.autocomplete(player=fa_player_autocomplete) + @requires_draft_period @logged_command("/draft") async def draft_pick( self, diff --git a/utils/decorators.py b/utils/decorators.py index 9c09fe2..1b73305 100644 --- a/utils/decorators.py +++ b/utils/decorators.py @@ -12,6 +12,7 @@ from typing import List, Optional, Callable, Any from utils.logging import set_discord_context, get_contextual_logger cache_logger = logging.getLogger(f'{__name__}.CacheDecorators') +period_check_logger = logging.getLogger(f'{__name__}.PeriodCheckDecorators') def logged_command( @@ -95,6 +96,89 @@ def logged_command( return decorator +def requires_draft_period(func): + """ + Decorator to restrict commands to draft period (week <= 0). + + This decorator checks if the league is in the draft period (offseason) + before allowing the command to execute. If the league is in-season, + it returns an error message to the user. + + Example: + @discord.app_commands.command(name="draft") + @requires_draft_period + @logged_command("/draft") + async def draft_pick(self, interaction, player: str): + # Command only runs during draft period (week <= 0) + pass + + Side Effects: + - Checks league current state via league_service + - Returns error embed if check fails + - Logs restriction events + + Requirements: + - Must be applied to async methods with (self, interaction, ...) signature + - Should be placed before @logged_command decorator + - league_service must be available via import + """ + @wraps(func) + async def wrapper(self, interaction, *args, **kwargs): + # Import here to avoid circular imports + from services.league_service import league_service + from views.embeds import EmbedTemplate + + try: + # Check current league state + current = await league_service.get_current_state() + + if not current: + period_check_logger.error("Could not retrieve league state for draft period check") + embed = EmbedTemplate.error( + "System Error", + "Could not verify draft period status. Please try again later." + ) + await interaction.response.send_message(embed=embed, ephemeral=True) + return + + # Check if in draft period (week <= 0) + if current.week > 0: + period_check_logger.info( + f"Draft command blocked - current week: {current.week}", + extra={ + "user_id": interaction.user.id, + "command": func.__name__, + "current_week": current.week + } + ) + embed = EmbedTemplate.error( + "Not Available", + "Draft commands are only available in the offseason." + ) + await interaction.response.send_message(embed=embed, ephemeral=True) + return + + # Week <= 0, allow command to proceed + period_check_logger.debug( + f"Draft period check passed - week {current.week}", + extra={"user_id": interaction.user.id, "command": func.__name__} + ) + return await func(self, interaction, *args, **kwargs) + + except Exception as e: + period_check_logger.error( + f"Error in draft period check: {e}", + exc_info=True, + extra={"user_id": interaction.user.id, "command": func.__name__} + ) + # Re-raise to let error handling in logged_command handle it + raise + + # Preserve signature for Discord.py command registration + wrapper.__signature__ = inspect.signature(func) # type: ignore + return wrapper + + def cached_api_call(ttl: Optional[int] = None, cache_key_suffix: str = ""): """ Decorator to add Redis caching to service methods that return List[T].