major-domo-v2/models/injury.py
Cal Corum 0c6f7c8ffe CLAUDE: Fix GroupCog interaction bug and GIF display issues
This commit addresses critical bugs in the injury command system and
establishes best practices for Discord command groups.

## Critical Fixes

### 1. GroupCog → app_commands.Group Migration
- **Problem**: `commands.GroupCog` has a duplicate interaction processing bug
  causing "404 Unknown interaction" errors when deferring responses
- **Root Cause**: GroupCog triggers command handler twice, consuming the
  interaction token before the second execution can respond
- **Solution**: Migrated InjuryCog to InjuryGroup using `app_commands.Group`
  pattern (same as ChartManageGroup and ChartCategoryGroup)
- **Result**: Reliable command execution, no more 404 errors

### 2. GiphyService GIF URL Fix
- **Problem**: Giphy service returned web page URLs (https://giphy.com/gifs/...)
  instead of direct image URLs, preventing Discord embed display
- **Root Cause**: Code accessed `data.url` instead of `data.images.original.url`
- **Solution**: Updated both `get_disappointment_gif()` and `get_gif()` methods
  to use correct API response path for embeddable GIF URLs
- **Result**: GIFs now display correctly in Discord embeds

## Documentation

### Command Groups Best Practices (commands/README.md)
Added comprehensive section documenting:
- **Critical Warning**: Never use `commands.GroupCog` - use `app_commands.Group`
- **Technical Explanation**: Why GroupCog fails (duplicate execution bug)
- **Migration Guide**: Step-by-step conversion from GroupCog to Group
- **Comparison Table**: Key differences between the two approaches
- **Working Examples**: References to ChartManageGroup, InjuryGroup patterns

## Architecture Changes

### Injury Commands (`commands/injuries/`)
- Converted from `commands.GroupCog` to `app_commands.Group`
- Registration via `bot.tree.add_command()` instead of `bot.add_cog()`
- Removed workarounds for GroupCog duplicate interaction issues
- Clean defer/response pattern with `@logged_command` decorator

### GiphyService (`services/giphy_service.py`)
- Centralized from `commands/soak/giphy_service.py`
- Now returns direct GIF image URLs for Discord embeds
- Maintains Trump GIF filtering (legacy behavior)
- Added gif_url to log output for debugging

### Configuration (`config.py`)
- Added `giphy_api_key` and `giphy_translate_url` settings
- Environment variable support via `GIPHY_API_KEY`
- Default values provided for out-of-box functionality

## Files Changed
- commands/injuries/: New InjuryGroup with app_commands.Group pattern
- services/giphy_service.py: Centralized service with GIF URL fix
- commands/soak/giphy_service.py: Backwards compatibility wrapper
- commands/README.md: Command groups best practices documentation
- config.py: Giphy configuration settings
- services/__init__.py: GiphyService exports

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-16 22:15:42 -05:00

51 lines
1.7 KiB
Python

"""
Injury model for tracking player injuries
Represents an injury record with game timeline and status information.
"""
from typing import Optional
from pydantic import Field
from models.base import SBABaseModel
class Injury(SBABaseModel):
"""Injury model representing a player injury."""
# Override base model to make id required for database entities
id: int = Field(..., description="Injury ID from database")
season: int = Field(..., description="Season number")
player_id: int = Field(..., description="Player ID who is injured")
total_games: int = Field(..., description="Total games player will be out")
# Injury timeline
start_week: int = Field(..., description="Week injury started")
start_game: int = Field(..., description="Game number injury started (1-4)")
end_week: int = Field(..., description="Week player returns")
end_game: int = Field(..., description="Game number player returns (1-4)")
# Status
is_active: bool = Field(True, description="Whether injury is currently active")
@property
def return_date(self) -> str:
"""Format return date as 'w##g#' string."""
return f'w{self.end_week:02d}g{self.end_game}'
@property
def start_date(self) -> str:
"""Format start date as 'w##g#' string."""
return f'w{self.start_week:02d}g{self.start_game}'
@property
def duration_display(self) -> str:
"""Return a human-readable duration string."""
if self.total_games == 1:
return "1 game"
return f"{self.total_games} games"
def __str__(self):
status = "Active" if self.is_active else "Cleared"
return f"Injury (Season {self.season}, {self.duration_display}, {status})"