Remove redundant sba_current_season and pd_current_season config values. All code now uses sba_season and pd_season, which properly read from environment variables. Fixes /team command defaulting to Season 12. - Remove duplicate *_current_season constants from config.py - Update 100+ references across commands, services, and utils - sba_season defaults to 13, pd_season defaults to 10 - Environment variables SBA_SEASON/PD_SEASON now work correctly 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
394 lines
12 KiB
Python
394 lines
12 KiB
Python
"""
|
||
Embed Templates for Discord Bot v2.0
|
||
|
||
Provides consistent embed styling and templates for common use cases.
|
||
"""
|
||
from typing import Optional, Union, Any, List
|
||
from datetime import datetime
|
||
from dataclasses import dataclass
|
||
|
||
from config import get_config
|
||
|
||
import discord
|
||
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class EmbedColors:
|
||
"""Standard color palette for embeds."""
|
||
PRIMARY: int = 0xa6ce39 # SBA green
|
||
SUCCESS: int = 0x28a745 # Green
|
||
WARNING: int = 0xffc107 # Yellow
|
||
ERROR: int = 0xdc3545 # Red
|
||
INFO: int = 0x17a2b8 # Blue
|
||
SECONDARY: int = 0x6c757d # Gray
|
||
DARK: int = 0x343a40 # Dark gray
|
||
LIGHT: int = 0xf8f9fa # Light gray
|
||
|
||
|
||
class EmbedTemplate:
|
||
"""Base embed template with consistent styling."""
|
||
|
||
@staticmethod
|
||
def create_base_embed(
|
||
title: Optional[str] = None,
|
||
description: Optional[str] = None,
|
||
color: Union[int, discord.Color] = EmbedColors.PRIMARY,
|
||
timestamp: bool = True
|
||
) -> discord.Embed:
|
||
"""Create a base embed with standard formatting."""
|
||
embed = discord.Embed(
|
||
title=title,
|
||
description=description,
|
||
color=color
|
||
)
|
||
|
||
if timestamp:
|
||
embed.timestamp = discord.utils.utcnow()
|
||
|
||
return embed
|
||
|
||
@staticmethod
|
||
def success(
|
||
title: str = "Success",
|
||
description: Optional[str] = None,
|
||
**kwargs
|
||
) -> discord.Embed:
|
||
"""Create a success embed."""
|
||
return EmbedTemplate.create_base_embed(
|
||
title=f"✅ {title}",
|
||
description=description,
|
||
color=EmbedColors.SUCCESS,
|
||
**kwargs
|
||
)
|
||
|
||
@staticmethod
|
||
def error(
|
||
title: str = "Error",
|
||
description: Optional[str] = None,
|
||
**kwargs
|
||
) -> discord.Embed:
|
||
"""Create an error embed."""
|
||
return EmbedTemplate.create_base_embed(
|
||
title=f"❌ {title}",
|
||
description=description,
|
||
color=EmbedColors.ERROR,
|
||
**kwargs
|
||
)
|
||
|
||
@staticmethod
|
||
def warning(
|
||
title: str = "Warning",
|
||
description: Optional[str] = None,
|
||
**kwargs
|
||
) -> discord.Embed:
|
||
"""Create a warning embed."""
|
||
return EmbedTemplate.create_base_embed(
|
||
title=f"⚠️ {title}",
|
||
description=description,
|
||
color=EmbedColors.WARNING,
|
||
**kwargs
|
||
)
|
||
|
||
@staticmethod
|
||
def info(
|
||
title: str = "Information",
|
||
description: Optional[str] = None,
|
||
**kwargs
|
||
) -> discord.Embed:
|
||
"""Create an info embed."""
|
||
return EmbedTemplate.create_base_embed(
|
||
title=f"ℹ️ {title}",
|
||
description=description,
|
||
color=EmbedColors.INFO,
|
||
**kwargs
|
||
)
|
||
|
||
@staticmethod
|
||
def loading(
|
||
title: str = "Loading",
|
||
description: Optional[str] = None,
|
||
**kwargs
|
||
) -> discord.Embed:
|
||
"""Create a loading embed."""
|
||
return EmbedTemplate.create_base_embed(
|
||
title=f"⏳ {title}",
|
||
description=description,
|
||
color=EmbedColors.SECONDARY,
|
||
**kwargs
|
||
)
|
||
|
||
|
||
class SBAEmbedTemplate(EmbedTemplate):
|
||
"""SBA-specific embed templates."""
|
||
|
||
@staticmethod
|
||
def player_card(
|
||
player_name: str,
|
||
position: str,
|
||
team_abbrev: Optional[str] = None,
|
||
team_name: Optional[str] = None,
|
||
wara: Optional[float] = None,
|
||
season: Optional[int] = None,
|
||
player_image: Optional[str] = None,
|
||
team_color: Optional[str] = None,
|
||
additional_fields: Optional[List[dict]] = None
|
||
) -> discord.Embed:
|
||
"""Create a player card embed."""
|
||
color = int(team_color, 16) if team_color else EmbedColors.PRIMARY
|
||
|
||
embed = EmbedTemplate.create_base_embed(
|
||
title=f"🏟️ {player_name}",
|
||
color=color
|
||
)
|
||
|
||
# Basic player info
|
||
embed.add_field(name="Position", value=position, inline=True)
|
||
|
||
if team_abbrev and team_name:
|
||
embed.add_field(name="Team", value=f"{team_abbrev} - {team_name}", inline=True)
|
||
elif team_abbrev:
|
||
embed.add_field(name="Team", value=team_abbrev, inline=True)
|
||
|
||
if wara is not None:
|
||
embed.add_field(name="WARA", value=f"{wara:.1f}", inline=True)
|
||
|
||
embed.add_field(
|
||
name="Season",
|
||
value=str(season or get_config().sba_season),
|
||
inline=True
|
||
)
|
||
|
||
# Add additional fields if provided
|
||
if additional_fields:
|
||
for field in additional_fields:
|
||
embed.add_field(
|
||
name=field.get("name", "Field"),
|
||
value=field.get("value", "N/A"),
|
||
inline=field.get("inline", True)
|
||
)
|
||
|
||
# Set player image
|
||
if player_image:
|
||
embed.set_thumbnail(url=player_image)
|
||
|
||
return embed
|
||
|
||
@staticmethod
|
||
def team_info(
|
||
team_abbrev: str,
|
||
team_name: str,
|
||
season: Optional[int] = None,
|
||
short_name: Optional[str] = None,
|
||
stadium: Optional[str] = None,
|
||
division: Optional[str] = None,
|
||
record: Optional[str] = None,
|
||
team_color: Optional[str] = None,
|
||
team_thumbnail: Optional[str] = None,
|
||
additional_fields: Optional[List[dict]] = None
|
||
) -> discord.Embed:
|
||
"""Create a team information embed."""
|
||
color = int(team_color, 16) if team_color else EmbedColors.PRIMARY
|
||
|
||
embed = EmbedTemplate.create_base_embed(
|
||
title=f"{team_abbrev} - {team_name}",
|
||
description=f"Season {season or get_config().sba_season} Team Information",
|
||
color=color
|
||
)
|
||
|
||
# Basic team info
|
||
if short_name:
|
||
embed.add_field(name="Short Name", value=short_name, inline=True)
|
||
|
||
embed.add_field(name="Abbreviation", value=team_abbrev, inline=True)
|
||
embed.add_field(name="Season", value=str(season or get_config().sba_season), inline=True)
|
||
|
||
if stadium:
|
||
embed.add_field(name="Stadium", value=stadium, inline=True)
|
||
|
||
if division:
|
||
embed.add_field(name="Division", value=division, inline=True)
|
||
|
||
if record:
|
||
embed.add_field(name="Record", value=record, inline=True)
|
||
|
||
# Add additional fields if provided
|
||
if additional_fields:
|
||
for field in additional_fields:
|
||
embed.add_field(
|
||
name=field.get("name", "Field"),
|
||
value=field.get("value", "N/A"),
|
||
inline=field.get("inline", True)
|
||
)
|
||
|
||
# Set team thumbnail
|
||
if team_thumbnail:
|
||
embed.set_thumbnail(url=team_thumbnail)
|
||
|
||
return embed
|
||
|
||
@staticmethod
|
||
def league_status(
|
||
season: Optional[int] = None,
|
||
week: Optional[int] = None,
|
||
phase: Optional[str] = None,
|
||
additional_info: Optional[str] = None,
|
||
teams_count: Optional[int] = None,
|
||
active_players: Optional[int] = None
|
||
) -> discord.Embed:
|
||
"""Create a league status embed."""
|
||
embed = EmbedTemplate.create_base_embed(
|
||
title="🏆 SBA League Status",
|
||
color=EmbedColors.PRIMARY
|
||
)
|
||
|
||
if season:
|
||
embed.add_field(name="Season", value=str(season), inline=True)
|
||
|
||
if week:
|
||
embed.add_field(name="Week", value=str(week), inline=True)
|
||
|
||
if phase:
|
||
embed.add_field(name="Phase", value=phase, inline=True)
|
||
|
||
if teams_count:
|
||
embed.add_field(name="Teams", value=str(teams_count), inline=True)
|
||
|
||
if active_players:
|
||
embed.add_field(name="Active Players", value=str(active_players), inline=True)
|
||
|
||
if additional_info:
|
||
embed.add_field(name="Additional Info", value=additional_info, inline=False)
|
||
|
||
return embed
|
||
|
||
@staticmethod
|
||
def roster_display(
|
||
team_abbrev: str,
|
||
team_name: str,
|
||
roster_type: str = "Full Roster",
|
||
season: Optional[int] = None,
|
||
team_color: Optional[str] = None,
|
||
player_groups: Optional[dict] = None
|
||
) -> discord.Embed:
|
||
"""Create a roster display embed."""
|
||
color = int(team_color, 16) if team_color else EmbedColors.PRIMARY
|
||
|
||
embed = EmbedTemplate.create_base_embed(
|
||
title=f"{team_abbrev} - {roster_type}",
|
||
description=f"{team_name} • Season {season or get_config().sba_season}",
|
||
color=color
|
||
)
|
||
|
||
if player_groups:
|
||
for group_name, players in player_groups.items():
|
||
if players:
|
||
player_list = "\n".join([
|
||
f"• {player.get('name', 'Unknown')} ({player.get('position', 'N/A')})"
|
||
for player in players[:10] # Limit to 10 players per field
|
||
])
|
||
|
||
if len(players) > 10:
|
||
player_list += f"\n... and {len(players) - 10} more"
|
||
|
||
embed.add_field(
|
||
name=f"{group_name} ({len(players)})",
|
||
value=player_list or "No players",
|
||
inline=True
|
||
)
|
||
|
||
return embed
|
||
|
||
@staticmethod
|
||
def search_results(
|
||
search_term: str,
|
||
results: List[dict],
|
||
result_type: str = "Results",
|
||
max_results: int = 10
|
||
) -> discord.Embed:
|
||
"""Create a search results embed."""
|
||
embed = EmbedTemplate.create_base_embed(
|
||
title=f"🔍 Search Results for '{search_term}'",
|
||
color=EmbedColors.INFO
|
||
)
|
||
|
||
if not results:
|
||
embed.description = "No results found."
|
||
embed.color = EmbedColors.WARNING
|
||
return embed
|
||
|
||
# Show limited results
|
||
displayed_results = results[:max_results]
|
||
result_text = "\n".join([
|
||
f"• {result.get('name', 'Unknown')} ({result.get('detail', 'N/A')})"
|
||
for result in displayed_results
|
||
])
|
||
|
||
if len(results) > max_results:
|
||
result_text += f"\n\n... and {len(results) - max_results} more results"
|
||
|
||
embed.add_field(
|
||
name=f"{result_type} ({len(results)} found)",
|
||
value=result_text,
|
||
inline=False
|
||
)
|
||
|
||
embed.set_footer(text="Please be more specific if you see multiple results.")
|
||
|
||
return embed
|
||
|
||
|
||
class EmbedBuilder:
|
||
"""Fluent interface for building complex embeds."""
|
||
|
||
def __init__(self, embed: Optional[discord.Embed] = None):
|
||
self._embed = embed or discord.Embed()
|
||
|
||
def title(self, title: str) -> 'EmbedBuilder':
|
||
"""Set embed title."""
|
||
self._embed.title = title
|
||
return self
|
||
|
||
def description(self, description: str) -> 'EmbedBuilder':
|
||
"""Set embed description."""
|
||
self._embed.description = description
|
||
return self
|
||
|
||
def color(self, color: Union[int, discord.Color]) -> 'EmbedBuilder':
|
||
"""Set embed color."""
|
||
self._embed.color = color
|
||
return self
|
||
|
||
def field(self, name: str, value: str, inline: bool = True) -> 'EmbedBuilder':
|
||
"""Add a field to the embed."""
|
||
self._embed.add_field(name=name, value=value, inline=inline)
|
||
return self
|
||
|
||
def thumbnail(self, url: str) -> 'EmbedBuilder':
|
||
"""Set embed thumbnail."""
|
||
self._embed.set_thumbnail(url=url)
|
||
return self
|
||
|
||
def image(self, url: str) -> 'EmbedBuilder':
|
||
"""Set embed image."""
|
||
self._embed.set_image(url=url)
|
||
return self
|
||
|
||
def footer(self, text: str, icon_url: Optional[str] = None) -> 'EmbedBuilder':
|
||
"""Set embed footer."""
|
||
self._embed.set_footer(text=text, icon_url=icon_url)
|
||
return self
|
||
|
||
def timestamp(self, timestamp: Optional[datetime] = None) -> 'EmbedBuilder':
|
||
"""Set embed timestamp."""
|
||
self._embed.timestamp = timestamp or discord.utils.utcnow()
|
||
return self
|
||
|
||
def author(self, name: str, url: Optional[str] = None, icon_url: Optional[str] = None) -> 'EmbedBuilder':
|
||
"""Set embed author."""
|
||
self._embed.set_author(name=name, url=url, icon_url=icon_url)
|
||
return self
|
||
|
||
def build(self) -> discord.Embed:
|
||
"""Build and return the embed."""
|
||
return self._embed |