The /set-image command was failing to persist player image updates to the
database. Investigation revealed a mismatch between how the bot sent PATCH
data versus how the database API expected it.
Root Cause:
- Database API endpoint (/api/v3/players/{id}) expects PATCH data as URL
query parameters, not JSON body
- Bot was sending: PATCH /api/v3/players/12288 {"vanity_card": "url"}
- API expected: PATCH /api/v3/players/12288?vanity_card=url
Changes Made:
1. api/client.py:
- Added use_query_params parameter to patch() method
- When enabled, sends data as URL query parameters instead of JSON body
- Maintains backward compatibility (defaults to JSON body)
2. services/base_service.py:
- Added use_query_params parameter to patch() method
- Passes parameter through to API client
3. services/player_service.py:
- Updated update_player() to use use_query_params=True
- Added documentation note about query parameter requirement
4. commands/profile/images.py:
- Fixed autocomplete to use correct utility function
- Changed from non-existent player_autocomplete_with_team_priority
- Now uses player_autocomplete from utils/autocomplete.py
Documentation Updates:
5. commands/profile/README.md:
- Updated API Integration section
- Documented PATCH endpoint uses query parameters
- Added note about automatic handling in player_service
6. services/README.md:
- Added PATCH vs PUT operations documentation
- Documented use_query_params parameter
- Included usage examples for both modes
Testing:
- Verified /set-image command now successfully persists image URLs
- Confirmed API returns updated player with vanity_card populated
- Validated both fancy-card and headshot updates work correctly
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
13 KiB
Player Image Management Commands
Last Updated: January 2025
Status: ✅ Fully Implemented
Location: commands/profile/
Overview
The Player Image Management system allows users to update player fancy card and headshot images for players on teams they own. Administrators can update any player's images.
Commands
/set-image <image_type> <player_name> <image_url>
Description: Update a player's fancy card or headshot image
Parameters:
image_type(choice): Choose "Fancy Card" or "Headshot"- Fancy Card: Shows as thumbnail in player cards (takes priority)
- Headshot: Shows as thumbnail if no fancy card exists
player_name(string with autocomplete): Player to updateimage_url(string): Direct URL to the image file
Permissions:
- Regular Users: Can update images for players on teams they own (ML/MiL/IL)
- Administrators: Can update any player's images (bypasses organization check)
Usage Examples:
/set-image fancy-card "Mike Trout" https://example.com/cards/trout.png
/set-image headshot "Shohei Ohtani" https://example.com/headshots/ohtani.jpg
Permission System
Regular Users
Users can update images for players in their organization:
- Major League team players - Direct team ownership
- Minor League team players - Owned via organizational affiliation
- Injured List team players - Owned via organizational affiliation
Example: If you own the NYY team, you can update images for players on:
- NYY (Major League)
- NYYMIL (Minor League)
- NYYIL (Injured List)
Administrators
Administrators have unrestricted access to update any player's images regardless of team ownership.
Permission Check Logic
# Check order:
1. Is user an administrator? → Grant access
2. Does user own any teams? → Continue check
3. Does player belong to user's organization? → Grant access
4. Otherwise → Deny access
URL Requirements
Format Validation
URLs must meet the following criteria:
- Protocol: Must start with
http://orhttps:// - Extension: Must end with valid image extension:
.jpg,.jpeg- JPEG format.png- PNG format.gif- GIF format (includes animated GIFs).webp- WebP format
- Length: Maximum 500 characters
- Query parameters: Allowed (e.g.,
?size=large)
Valid Examples:
https://example.com/image.jpg
https://cdn.discord.com/attachments/123/456/player.png
https://i.imgur.com/abc123.webp
https://example.com/image.jpg?size=large&format=original
Invalid Examples:
example.com/image.jpg ❌ Missing protocol
ftp://example.com/image.jpg ❌ Wrong protocol
https://example.com/document.pdf ❌ Wrong extension
https://example.com/page ❌ No extension
Accessibility Testing
After format validation, the bot tests URL accessibility:
- HTTP HEAD Request: Checks if URL is reachable
- Status Code: Must return 200 OK
- Content-Type: Must return
image/*header - Timeout: 5 seconds maximum
Common Accessibility Errors:
404 Not Found- Image doesn't exist at URL403 Forbidden- Permission deniedTimeout- Server too slow or unresponsiveWrong content-type- URL points to webpage, not image
Workflow
Step-by-Step Process
-
User invokes command
/set-image fancy-card "Mike Trout" https://example.com/card.png -
URL Format Validation
- Checks protocol, extension, length
- If invalid: Shows error with requirements
-
URL Accessibility Test
- HTTP HEAD request to URL
- Checks status code and content-type
- If inaccessible: Shows error with troubleshooting tips
-
Player Lookup
- Searches for player by name
- Handles multiple matches (asks for exact name)
- If not found: Shows error
-
Permission Check
- Admin check → Grant access
- Organization ownership check → Grant/deny access
- If denied: Shows permission error
-
Preview with Confirmation
- Shows embed with new image as thumbnail
- Displays current vs new image info
- Confirm Update button → Proceed
- Cancel button → Abort
-
Database Update
- Updates
vanity_cardorheadshotfield - If failure: Shows error
- Updates
-
Success Message
- Confirms update
- Shows new image
- Displays updated player info
Field Mapping
| Choice | Database Field | Display Priority | Notes |
|---|---|---|---|
| Fancy Card | vanity_card |
1st (highest) | Custom fancy player card |
| Headshot | headshot |
2nd | Player headshot photo |
| (default) | team.thumbnail |
3rd (fallback) | Team logo |
Display Logic in Player Cards:
IF player.vanity_card exists:
Show vanity_card as thumbnail
ELSE IF player.headshot exists:
Show headshot as thumbnail
ELSE IF player.team.thumbnail exists:
Show team logo as thumbnail
ELSE:
No thumbnail
Best Practices
For Users
Choosing Image URLs
✅ DO:
- Use reliable image hosting (Discord CDN, Imgur, established hosts)
- Use direct image links (right-click image → "Copy Image Address")
- Test URLs in browser before submitting
- Use permanent URLs, not temporary upload links
❌ DON'T:
- Use image hosting page URLs (must be direct image file)
- Use temporary or expiring URLs
- Use images from unreliable hosts
- Use extremely large images (impacts Discord performance)
Image Recommendations
Fancy Cards:
- Recommended size: 400x600px (or similar 2:3 aspect ratio)
- Format: PNG or JPEG
- File size: < 2MB for best performance
- Style: Custom designs, player stats, artistic renditions
Headshots:
- Recommended size: 256x256px (square aspect ratio)
- Format: PNG or JPEG with transparent background
- File size: < 500KB
- Style: Professional headshot, clean background
Finding Good Image URLs
-
Discord CDN (best option):
- Upload image to Discord
- Right-click → Copy Link
- Paste as image URL
-
Imgur:
- Upload to Imgur
- Right-click image → Copy Image Address
- Use direct link (ends with
.pngor.jpg)
-
Other hosts:
- Ensure stable, permanent hosting
- Verify URL accessibility before using
For Administrators
Managing Player Images
- Set consistent style guidelines for your league
- Use standard image dimensions for uniformity
- Maintain backup copies of custom images
- Document image sources for attribution
Troubleshooting User Issues
Common problems and solutions:
| Issue | Cause | Solution |
|---|---|---|
| "URL not accessible" | Host down, URL expired | Ask for new URL from stable host |
| "Not a valid image" | URL points to webpage | Get direct image link |
| "Permission denied" | User doesn't own team | Verify team ownership |
| "Player not found" | Typo in name | Use autocomplete feature |
Error Messages
Format Errors
❌ Invalid URL Format
URL must start with http:// or https://
Requirements:
• Must start with `http://` or `https://`
• Must end with `.jpg`, `.jpeg`, `.png`, `.gif`, or `.webp`
• Maximum 500 characters
Accessibility Errors
❌ URL Not Accessible
URL returned status 404
Please check:
• URL is correct and not expired
• Image host is online
• URL points directly to an image file
• URL is publicly accessible
Permission Errors
❌ Permission Denied
You don't own a team in the NYY organization
You can only update images for players on teams you own.
Player Not Found
❌ Player Not Found
No player found matching 'Mike Trut' in the current season.
Multiple Players Found
🔍 Multiple Players Found
Multiple players match 'Mike':
• Mike Trout (OF)
• Mike Zunino (C)
Please use the exact name from autocomplete.
Technical Implementation
Architecture
commands/profile/
├── __init__.py # Package setup
├── images.py # Main command implementation
│ ├── validate_url_format() # Format validation
│ ├── test_url_accessibility() # Accessibility testing
│ ├── can_edit_player_image() # Permission checking
│ ├── ImageUpdateConfirmView # Confirmation UI
│ ├── player_name_autocomplete() # Autocomplete function
│ └── ImageCommands # Command cog
└── README.md # This file
Dependencies
aiohttp- Async HTTP requests for URL testingdiscord.py- Discord bot frameworkplayer_service- Player CRUD operationsteam_service- Team queries and ownership- Standard bot utilities (logging, decorators, embeds)
Database Fields
Player Model (models/player.py):
vanity_card: Optional[str] = Field(None, description="Custom vanity card URL")
headshot: Optional[str] = Field(None, description="Player headshot URL")
Both fields are optional and store direct image URLs.
API Integration
Update Operation:
# Update player image
update_data = {"vanity_card": "https://example.com/card.png"}
updated_player = await player_service.update_player(player_id, update_data)
Endpoints Used:
GET /api/v3/players?name={name}&season={season}- Player searchPATCH /api/v3/players/{player_id}?vanity_card={url}- Update player dataGET /api/v3/teams?owner_id={user_id}&season={season}- User's teams
Important Note:
The player PATCH endpoint uses query parameters instead of JSON body for data updates. The player_service.update_player() method automatically handles this by setting use_query_params=True when calling the API client.
Testing
Test Coverage
Test File: tests/test_commands_profile_images.py
Test Categories:
-
URL Format Validation (10 tests)
- Valid formats (JPG, PNG, WebP, with query params)
- Invalid protocols (no protocol, FTP)
- Invalid extensions (PDF, no extension)
- URL length limits
-
URL Accessibility (5 tests)
- Successful access
- 404 errors
- Wrong content-type
- Timeouts
- Connection errors
-
Permission Checking (7 tests)
- Admin access to all players
- User access to owned teams
- User access to MiL/IL players
- Denial for other organizations
- Denial for users without teams
- Players without team assignment
-
Integration Tests (3 tests)
- Command structure validation
- Field mapping logic
Running Tests
# Run all image management tests
python -m pytest tests/test_commands_profile_images.py -v
# Run specific test class
python -m pytest tests/test_commands_profile_images.py::TestURLValidation -v
# Run with coverage
python -m pytest tests/test_commands_profile_images.py --cov=commands.profile
Future Enhancements
Planned Features (Post-Launch)
- Image size validation: Check image dimensions
- Image upload support: Upload images directly instead of URLs
- Bulk image updates: Update multiple players at once
- Image preview history: See previous images
- Image moderation: Admin approval queue for user submissions
- Default images: Set default fancy cards per team
- Image gallery: View all player images for a team
Potential Improvements
- Automatic image optimization: Resize/compress large images
- CDN integration: Auto-upload to Discord CDN for permanence
- Image templates: Pre-designed templates users can fill in
- Batch operations: Admin tool to set multiple images
- Image analytics: Track which images are most viewed
Troubleshooting
Common Issues
Problem: "URL not accessible" but URL works in browser
- Cause: Content-Delivery-Network (CDN) may require browser headers
- Solution: Use Discord CDN or Imgur instead
Problem: Permission denied even though I own the team
- Cause: Season mismatch or ownership data not synced
- Solution: Contact admin to verify team ownership data
Problem: Image appears broken in Discord
- Cause: Discord can't load the image (blocked, wrong format, too large)
- Solution: Try different host or smaller file size
Problem: Autocomplete doesn't show player
- Cause: Player doesn't exist in current season
- Solution: Verify player name and season
Support
For issues or questions:
- Check this README for solutions
- Review error messages carefully (they include troubleshooting steps)
- Contact server administrators
- Check bot logs for detailed error information
Implementation Details:
- Commands:
commands/profile/images.py - Tests:
tests/test_commands_profile_images.py - Models:
models/player.py(vanity_card, headshot fields) - Services:
services/player_service.py,services/team_service.py
Related Documentation:
- Bot Architecture:
/discord-app-v2/CLAUDE.md - Command Patterns:
/discord-app-v2/commands/README.md - Testing Guide:
/discord-app-v2/tests/README.md