major-domo-v2/views/embeds.py
Cal Corum e6a30af604 CLAUDE: SUCCESSFUL STARTUP - Discord Bot v2.0 fully operational
 **MAJOR MILESTONE**: Bot successfully starts and loads all commands

🔧 **Key Fixes Applied**:
- Fixed Pydantic configuration (SettingsConfigDict vs ConfigDict)
- Resolved duplicate logging with hybrid propagation approach
- Enhanced console logging with detailed format (function:line)
- Eliminated redundant .log file handler (kept console + JSON)
- Fixed Pylance type errors across views and modals
- Added newline termination to JSON logs for better tool compatibility
- Enabled league commands package in bot.py
- Enhanced command tree hashing for proper type support

📦 **New Components Added**:
- Complete views package (base.py, common.py, embeds.py, modals.py)
- League service and commands integration
- Comprehensive test coverage improvements
- Enhanced decorator functionality with proper signature preservation

🎯 **Architecture Improvements**:
- Hybrid logging: detailed console for dev + structured JSON for monitoring
- Type-safe command tree handling for future extensibility
- Proper optional parameter handling in Pydantic models
- Eliminated duplicate log messages while preserving third-party library logs

🚀 **Ready for Production**: Bot loads all command packages successfully with no errors

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 07:36:47 -05:00

391 lines
12 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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
import discord
from constants import SBA_CURRENT_SEASON
class EmbedColors:
"""Standard color palette for embeds."""
PRIMARY = 0xa6ce39 # SBA green
SUCCESS = 0x28a745 # Green
WARNING = 0xffc107 # Yellow
ERROR = 0xdc3545 # Red
INFO = 0x17a2b8 # Blue
SECONDARY = 0x6c757d # Gray
DARK = 0x343a40 # Dark gray
LIGHT = 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 SBA_CURRENT_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 SBA_CURRENT_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 SBA_CURRENT_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 SBA_CURRENT_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