major-domo-v2/utils/injury_log.py
Cal Corum 62541ac750 Add injury log posting and fix view interaction permissions
Features:
- Post injury announcements to #sba-network-news when injuries are logged
- Update #injury-log channel with two embeds:
  - All injuries grouped by Major League team with return dates
  - All injuries grouped by return week, sorted ascending
- Auto-purge old messages before posting updated injury log

Bug Fixes:
- Fix BaseView interaction_check logic that incorrectly rejected command users
  - Old: Rejected if (not user_id match) OR (not in responders)
  - New: Allow if (user_id match) OR (in responders)
- Filter None values from responders list (handles missing gmid2)

Changes:
- services/injury_service.py: Add get_all_active_injuries_raw() method
- utils/injury_log.py: New utility for injury channel posting
- views/modals.py: Call injury posting after successful injury logging
- views/base.py: Fix interaction authorization logic
- config.py: Update to Season 13 Players role

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

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

358 lines
11 KiB
Python

"""
Injury Log Posting Utility
Provides functions for posting injury information to Discord channels:
- #injury-log: Two embeds showing current injuries by team and by return week
- #sba-network-news: Individual injury announcements
"""
from typing import Optional, Dict, List, Any
from collections import defaultdict
import discord
from config import get_config
from models.player import Player
from models.team import Team
from services.injury_service import injury_service
from services.team_service import team_service
from views.embeds import EmbedTemplate, EmbedColors
from utils.logging import get_contextual_logger
logger = get_contextual_logger(f'{__name__}')
async def get_major_league_team_name(team_data: dict, season: int) -> str:
"""
Get the Major League team name from player's team data.
Args:
team_data: Team dictionary from API response
season: Current season number
Returns:
Major League team short name (sname)
"""
if not team_data:
return "Unknown"
abbrev = team_data.get('abbrev', '')
# If abbreviation is 3 chars or less, it's already ML
if len(abbrev) <= 3:
return team_data.get('sname', abbrev)
# Extract base abbreviation for MiL/IL teams
abbrev_lower = abbrev.lower()
if abbrev_lower.endswith('mil'):
base_abbrev = abbrev[:-3]
elif abbrev_lower.endswith('il'):
base_abbrev = abbrev[:-2]
else:
return team_data.get('sname', abbrev)
# Look up the ML team
try:
ml_team = await team_service.get_team_by_abbrev(base_abbrev, season)
if ml_team:
return ml_team.sname
except Exception as e:
logger.warning(f"Could not get ML team for {abbrev}: {e}")
return team_data.get('sname', abbrev)
async def update_injury_log_channel(
bot: discord.Client,
season: int
) -> bool:
"""
Update the #injury-log channel with current injuries.
Creates two embeds:
1. Current injuries grouped by Major League team
2. Current injuries grouped by return week
Args:
bot: Discord bot instance
season: Current season number
Returns:
True if successful, False otherwise
"""
try:
config = get_config()
guild = bot.get_guild(config.guild_id)
if not guild:
logger.warning(f"Could not find guild {config.guild_id}")
return False
channel = discord.utils.get(guild.text_channels, name='injury-log')
if not channel:
logger.warning("Could not find #injury-log channel")
return False
# Get all active injuries
injuries_raw = await injury_service.get_all_active_injuries_raw(season)
if not injuries_raw:
logger.info("No active injuries found for injury log update")
# Still update the channel with "no injuries" message
await _clear_and_post_no_injuries(channel, season)
return True
# Group injuries by team and by week
injuries_by_team: Dict[str, List[dict]] = defaultdict(list)
injuries_by_week: Dict[int, List[dict]] = defaultdict(list)
for injury in injuries_raw:
player = injury.get('player', {})
team_data = player.get('team', {})
# Get ML team name for grouping
ml_team_name = await get_major_league_team_name(team_data, season)
injuries_by_team[ml_team_name].append({
'name': player.get('name', 'Unknown'),
'il_return': player.get('il_return', 'TBD'),
'end_week': injury.get('end_week', 0)
})
end_week = injury.get('end_week', 0)
injuries_by_week[end_week].append({
'name': player.get('name', 'Unknown'),
'il_return': player.get('il_return', 'TBD')
})
# Create team embed
team_embed = EmbedTemplate.create_base_embed(
title="🏥 Current Injuries by Team",
description="Player Name (Return Date)",
color=EmbedColors.WARNING,
timestamp=True
)
team_embed.set_thumbnail(url=config.sba_logo_url)
# Sort teams alphabetically and add fields
for team_name in sorted(injuries_by_team.keys()):
players = injuries_by_team[team_name]
team_string = '\n'.join(
f"{p['name']} ({p['il_return']})"
for p in players
)
# Discord field value limit is 1024 chars
if len(team_string) > 1024:
team_string = team_string[:1020] + "..."
team_embed.add_field(
name=f"{team_name} ({len(players)})",
value=team_string,
inline=True
)
team_embed.set_footer(
text=f"SBa Season {season}{len(injuries_raw)} active injuries",
icon_url=config.sba_logo_url
)
# Create week embed
week_embed = EmbedTemplate.create_base_embed(
title="📅 Current Injuries by Return Week",
description="Player Name (Return Date)",
color=EmbedColors.INFO,
timestamp=True
)
week_embed.set_thumbnail(url=config.sba_logo_url)
# Sort weeks numerically and add fields
for week_num in sorted(injuries_by_week.keys()):
players = injuries_by_week[week_num]
week_string = '\n'.join(
f"{p['name']} ({p['il_return']})"
for p in players
)
# Discord field value limit is 1024 chars
if len(week_string) > 1024:
week_string = week_string[:1020] + "..."
week_embed.add_field(
name=f"Week {week_num} ({len(players)})",
value=week_string,
inline=True
)
week_embed.set_footer(
text=f"SBa Season {season} • Sorted by earliest return",
icon_url=config.sba_logo_url
)
# Clear old messages and post new ones
try:
await channel.purge(limit=25)
except discord.errors.Forbidden:
logger.warning("Could not purge messages in #injury-log (missing permissions)")
except Exception as e:
logger.warning(f"Error purging messages in #injury-log: {e}")
await channel.send(embed=team_embed)
await channel.send(embed=week_embed)
logger.info(f"Updated injury log: {len(injuries_raw)} injuries across {len(injuries_by_team)} teams")
return True
except Exception as e:
logger.error(f"Error updating injury log channel: {e}")
return False
async def _clear_and_post_no_injuries(channel: discord.TextChannel, season: int) -> None:
"""Post a 'no injuries' message when there are no active injuries."""
config = get_config()
try:
await channel.purge(limit=25)
except Exception:
pass
embed = EmbedTemplate.create_base_embed(
title="🏥 Current Injuries",
description="No active injuries at this time.",
color=EmbedColors.SUCCESS,
timestamp=True
)
embed.set_thumbnail(url=config.sba_logo_url)
embed.set_footer(
text=f"SBa Season {season}",
icon_url=config.sba_logo_url
)
await channel.send(embed=embed)
async def post_injury_news(
bot: discord.Client,
player: Player,
injury_games: int,
return_date: str,
season: int
) -> bool:
"""
Post an injury announcement to #sba-network-news.
Args:
bot: Discord bot instance
player: Player object who was injured
injury_games: Number of games player will miss
return_date: Return date in w##g# format
season: Current season number
Returns:
True if successful, False otherwise
"""
try:
config = get_config()
guild = bot.get_guild(config.guild_id)
if not guild:
logger.warning(f"Could not find guild {config.guild_id}")
return False
channel = discord.utils.get(
guild.text_channels,
name=config.sba_network_news_channel
)
if not channel:
logger.warning(f"Could not find #{config.sba_network_news_channel} channel")
return False
# Determine team info
team_name = "Unknown Team"
team_color = EmbedColors.WARNING
team_thumbnail = None
if player.team:
team_name = player.team.sname or player.team.lname
if player.team.color:
try:
team_color = int(player.team.color, 16)
except ValueError:
pass
team_thumbnail = player.team.thumbnail
# Create news embed
embed = EmbedTemplate.create_base_embed(
title="🚑 Injury Report",
description=f"**{player.name}** has been placed on the injured list.",
color=team_color,
timestamp=True
)
embed.add_field(
name="Player",
value=f"{player.name} ({player.primary_position})",
inline=True
)
embed.add_field(
name="Team",
value=team_name,
inline=True
)
embed.add_field(
name="Duration",
value=f"{injury_games} game{'s' if injury_games != 1 else ''}",
inline=True
)
embed.add_field(
name="Expected Return",
value=return_date,
inline=True
)
if team_thumbnail:
embed.set_thumbnail(url=team_thumbnail)
embed.set_footer(
text=f"SBa Season {season}",
icon_url=config.sba_logo_url
)
await channel.send(embed=embed)
logger.info(f"Posted injury news for {player.name}: {injury_games} games")
return True
except Exception as e:
logger.error(f"Error posting injury news: {e}")
return False
async def post_injury_and_update_log(
bot: discord.Client,
player: Player,
injury_games: int,
return_date: str,
season: int
) -> None:
"""
Convenience function to post injury news and update injury log.
This is the main entry point for injury logging after an injury is recorded.
It handles both the news announcement and the full injury log update.
Args:
bot: Discord bot instance
player: Player object who was injured
injury_games: Number of games player will miss
return_date: Return date in w##g# format
season: Current season number
"""
# Post to sba-network-news
await post_injury_news(bot, player, injury_games, return_date, season)
# Update injury-log channel with all current injuries
await update_injury_log_channel(bot, season)