Closes#90
Replace sequential awaits with asyncio.gather() in all locations identified
in the issue:
- commands/gameplay/scorebug.py: parallel team lookups in publish_scorecard
and scorebug commands; also fix missing await on async scorecard_tracker calls
- commands/league/submit_scorecard.py: parallel away/home team lookups
- tasks/live_scorebug_tracker.py: parallel team lookups inside per-scorecard
loop (compounds across multiple active games); fix missing await on
get_all_scorecards
- commands/injuries/management.py: parallel get_current_state() +
search_players() in injury_roll, injury_set_new, and injury_clear
- services/trade_builder.py: parallel per-participant roster validation in
validate_trade()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Codebase audit identified ~50 lazy imports. Moved 42 unnecessary ones to
top-level imports — only keeping those justified by circular imports,
init-order dependencies, or optional dependency guards. Updated test mock
patch targets where needed. See #57 for remaining DI candidates.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevents users from managing injuries for players not on their team.
Admins bypass the check; org affiliates (MiL/IL) are recognized.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Ran `ruff check --select F401 --fix` to auto-remove 221 unused imports,
manually removed 4 unused `import discord` from package __init__.py files,
and fixed test import for DISAPPOINTMENT_TIERS to reference canonical location.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove hardcoded Giphy API key from config.py, load from env var (#19)
- URL-encode query parameters in APIClient._add_params (#20)
- URL-encode Giphy search phrases before building request URLs (#21)
- Replace internal exception details with generic messages to users (#22)
- Replace all bare except: with except Exception: (#23)
- Guard interaction.guild access in has_player_role (#24)
- Replace MD5 with SHA-256 for command change detection hash (#32)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix /injury roll and /injury clear commands crashing when player.team is None
- Add team fetching logic after search_players() for all injury commands
- The search_players() API endpoint returns team as ID only (not nested object)
- Added null checks and explicit team fetching using team_service.get_team()
- Add Gitea Actions workflow for automated Docker builds
- Implements semantic version validation on PRs
- Builds and pushes to Docker Hub on main branch merges
- Sends Discord notifications on success/failure
- Generates 3 image tags: latest, v{VERSION}, v{VERSION}-{COMMIT}
Fixes AttributeError: 'NoneType' object has no attribute 'roster_type'
Fixes AttributeError: 'NoneType' object has no attribute 'thumbnail'
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The search_players() API endpoint returns team as an ID only (not nested object),
causing player.team to be None. Added null check before accessing team.thumbnail.
Fixes AttributeError: 'NoneType' object has no attribute 'thumbnail'
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changed /injury roll, /injury set-new, and /injury clear to use
search_players() instead of get_players_by_name(). The /players?name=
API endpoint fails to find some players (e.g., Gunnar Henderson) while
the /players/search?q= endpoint works correctly.
This fixes the "Player Not Found" error when users try to clear
injuries for players that exist in the database.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Security fix: Remove user_id from ConfirmationView so only the player's
team GM(s) can click "Log Injury" button. Anyone can still run /injury roll
to see the result, but only authorized GMs can record it.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements decorator-based permission system to support bot scaling across
multiple Discord servers with different command access requirements.
Key Features:
- @global_command() - Available in all servers
- @league_only() - Restricted to league server only
- @requires_team() - Requires user to have a league team
- @admin_only() - Requires server admin permissions
- @league_admin_only() - Requires admin in league server
Implementation:
- utils/permissions.py - Core permission decorators and validation
- utils/permissions_examples.py - Comprehensive usage examples
- Automatic caching via TeamService.get_team_by_owner() (30-min TTL)
- User-friendly error messages for permission failures
Applied decorators to:
- League commands (league, standings, schedule, team, roster)
- Admin commands (management, league management, users)
- Draft system commands
- Transaction commands (dropadd, ilmove, management)
- Injury management
- Help system
- Custom commands
- Voice channels
- Gameplay (scorebug)
- Utilities (weather)
Benefits:
- Maximum flexibility - easy to change command scopes
- Built-in caching - ~80% reduction in API calls
- Combinable decorators for complex permissions
- Clean migration path for existing commands
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added data consistency check to automatically clear stale injury records
where is_active=True but il_return=None. This prevents old injury records
from blocking new injury creation when players should not be injured.
- Detects mismatch between injury table and player il_return field
- Auto-clears stale injury records with warning log
- Allows legitimate injuries with matching il_return to block command
- Logs both warning (stale data found) and info (cleared) for debugging
Fixes issue where Ronald Acuna Jr had active injury record but no il_return.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed bug where responder_team variable was only assigned conditionally
but always referenced, causing UnboundLocalError when clearing injuries
for major league players.
- Initialize responder_team = None before conditional check
- Ensures variable is defined for both ML and non-ML teams
- Conditional expression on line 674 now works correctly
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added /jump command for baserunner stealing mechanics with pickoff/balk detection.
Enhanced dice rolling commands with team color support in embeds.
Improved /admin-sync with local/global options and prefix command fallback.
Fixed type safety issues in admin commands and injury management.
Updated config for expanded draft rounds and testing mode.
Key changes:
- commands/dice/rolls.py: New /jump and !j commands with special cases for pickoff (d20=1) and balk (d20=2)
- commands/dice/rolls.py: Added team/channel color support to /ab and dice embeds
- commands/dice/rolls.py: Added pitcher position to /fielding command with proper range/error charts
- commands/admin/management.py: Enhanced /admin-sync with local/clear options and !admin-sync prefix fallback
- commands/admin/management.py: Fixed Member type checking and channel type validation
- commands/injuries/management.py: Fixed responder team detection for injury clearing
- models/custom_command.py: Made creator_id optional for execute endpoint compatibility
- config.py: Updated draft_rounds to 32 and enabled testing mode
- services/transaction_builder.py: Adjusted ML roster limit to 26
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added confirmation embed before clearing injury
- Shows player info, team, expected return, games missed
- User prompted: "Is {Player} cleared to return?"
- Buttons: "Clear Injury" / "Cancel"
- 3-minute timeout for response
- Team GMs can also confirm/cancel
- Added player autocomplete to /injury clear command
- Uses same autocomplete as /injury roll
- Prioritizes user's team players
- Updated commands/injuries/CLAUDE.md documentation
- Documented new confirmation flow
- Added autocomplete parameter details
- Clarified user flow and responders
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
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>