✅ **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>
311 lines
11 KiB
Python
311 lines
11 KiB
Python
"""
|
|
Migration Example: Before and After Views v2.0
|
|
|
|
Shows how to upgrade existing commands to use the modern view system.
|
|
This demonstrates the transformation from basic embeds to interactive views.
|
|
"""
|
|
from typing import Optional, List
|
|
|
|
import discord
|
|
from discord.ext import commands
|
|
|
|
from services.team_service import team_service
|
|
from models.team import Team
|
|
from constants import SBA_CURRENT_SEASON
|
|
from utils.logging import get_contextual_logger
|
|
from utils.decorators import logged_command
|
|
|
|
# Import new view components
|
|
from views import (
|
|
SBAEmbedTemplate,
|
|
EmbedTemplate,
|
|
EmbedColors,
|
|
TeamSelectionView,
|
|
PaginationView,
|
|
ConfirmationView,
|
|
DetailedInfoView
|
|
)
|
|
|
|
|
|
class MigrationExampleCommands(commands.Cog):
|
|
"""Example showing before/after migration to Views v2.0."""
|
|
|
|
def __init__(self, bot: commands.Bot):
|
|
self.bot = bot
|
|
self.logger = get_contextual_logger(f'{__name__}.MigrationExampleCommands')
|
|
|
|
# ========================================
|
|
# BEFORE: Traditional approach
|
|
# ========================================
|
|
|
|
@discord.app_commands.command(
|
|
name="teams-old",
|
|
description="List teams (old style - basic embed)"
|
|
)
|
|
@logged_command("/teams-old")
|
|
async def teams_old_style(self, interaction: discord.Interaction, season: Optional[int] = None):
|
|
"""Old style team listing - basic embed only."""
|
|
await interaction.response.defer()
|
|
|
|
season = season or SBA_CURRENT_SEASON
|
|
teams = await team_service.get_teams_by_season(season)
|
|
|
|
if not teams:
|
|
embed = discord.Embed(
|
|
title="No Teams Found",
|
|
description=f"No teams found for season {season}",
|
|
color=0xff6b6b
|
|
)
|
|
await interaction.followup.send(embed=embed)
|
|
return
|
|
|
|
# Sort teams by abbreviation
|
|
teams.sort(key=lambda t: t.abbrev)
|
|
|
|
# Create basic embed
|
|
embed = discord.Embed(
|
|
title=f"SBA Teams - Season {season}",
|
|
color=0xa6ce39
|
|
)
|
|
|
|
# Simple list - limited functionality
|
|
team_list = "\n".join([f"**{team.abbrev}** - {team.lname}" for team in teams[:20]])
|
|
if len(teams) > 20:
|
|
team_list += f"\n... and {len(teams) - 20} more teams"
|
|
|
|
embed.add_field(name="Teams", value=team_list, inline=False)
|
|
embed.set_footer(text=f"Total: {len(teams)} teams")
|
|
|
|
await interaction.followup.send(embed=embed)
|
|
|
|
# ========================================
|
|
# AFTER: Modern Views v2.0 approach
|
|
# ========================================
|
|
|
|
@discord.app_commands.command(
|
|
name="teams-new",
|
|
description="List teams (new style - interactive with views)"
|
|
)
|
|
@logged_command("/teams-new")
|
|
async def teams_new_style(self, interaction: discord.Interaction, season: Optional[int] = None):
|
|
"""New style team listing - interactive with pagination and selection."""
|
|
await interaction.response.defer()
|
|
|
|
season = season or SBA_CURRENT_SEASON
|
|
teams = await team_service.get_teams_by_season(season)
|
|
|
|
if not teams:
|
|
embed = EmbedTemplate.warning(
|
|
title="No Teams Found",
|
|
description=f"No teams found for season {season}"
|
|
)
|
|
await interaction.followup.send(embed=embed)
|
|
return
|
|
|
|
# Sort teams by abbreviation
|
|
teams.sort(key=lambda t: t.abbrev)
|
|
|
|
# Create paginated view with team selection
|
|
await self._create_interactive_team_list(interaction, teams, season)
|
|
|
|
async def _create_interactive_team_list(
|
|
self,
|
|
interaction: discord.Interaction,
|
|
teams: List[Team],
|
|
season: int
|
|
):
|
|
"""Create interactive team list with pagination and selection."""
|
|
teams_per_page = 10
|
|
pages = []
|
|
|
|
# Create pages
|
|
for i in range(0, len(teams), teams_per_page):
|
|
page_teams = teams[i:i + teams_per_page]
|
|
|
|
embed = SBAEmbedTemplate.league_status(
|
|
season=season,
|
|
teams_count=len(teams),
|
|
additional_info=f"Showing teams {i + 1}-{min(i + teams_per_page, len(teams))} of {len(teams)}"
|
|
)
|
|
embed.title = f"🏟️ SBA Teams - Season {season}"
|
|
|
|
# Group teams by division if available
|
|
if any(getattr(team, 'division_id', None) for team in page_teams):
|
|
divisions = {}
|
|
for team in page_teams:
|
|
div_id = getattr(team, 'division_id', 0) or 0
|
|
if div_id not in divisions:
|
|
divisions[div_id] = []
|
|
divisions[div_id].append(team)
|
|
|
|
for div_id, div_teams in sorted(divisions.items()):
|
|
div_name = f"Division {div_id}" if div_id > 0 else "Unassigned"
|
|
team_list = "\n".join([
|
|
f"**{team.abbrev}** - {team.lname}"
|
|
for team in div_teams
|
|
])
|
|
embed.add_field(name=div_name, value=team_list, inline=True)
|
|
else:
|
|
# Simple list if no divisions
|
|
team_list = "\n".join([
|
|
f"**{team.abbrev}** - {team.lname}"
|
|
for team in page_teams
|
|
])
|
|
embed.add_field(name="Teams", value=team_list, inline=False)
|
|
|
|
pages.append(embed)
|
|
|
|
# Create pagination view
|
|
view = PaginationView(
|
|
pages=pages,
|
|
user_id=interaction.user.id,
|
|
show_page_numbers=True
|
|
)
|
|
|
|
# Add team selection dropdown to first row
|
|
if len(teams) <= 25: # Discord limit for select options
|
|
team_select = TeamSelectionView(
|
|
teams=teams,
|
|
user_id=interaction.user.id,
|
|
callback=self._handle_team_selection
|
|
)
|
|
|
|
# Combine pagination with selection (would need custom view for this)
|
|
# For now, show them separately
|
|
|
|
await interaction.followup.send(embed=view.get_current_embed(), view=view)
|
|
|
|
# Also provide team selection if reasonable number
|
|
if len(teams) <= 25:
|
|
await self._add_team_selection_followup(interaction, teams)
|
|
|
|
async def _add_team_selection_followup(
|
|
self,
|
|
interaction: discord.Interaction,
|
|
teams: List[Team]
|
|
):
|
|
"""Add team selection as follow-up message."""
|
|
view = TeamSelectionView(
|
|
teams=teams,
|
|
user_id=interaction.user.id,
|
|
callback=self._handle_team_selection
|
|
)
|
|
|
|
embed = EmbedTemplate.info(
|
|
title="Team Selection",
|
|
description="Select a team below to view detailed information:"
|
|
)
|
|
|
|
await interaction.followup.send(embed=embed, view=view, ephemeral=True)
|
|
|
|
async def _handle_team_selection(
|
|
self,
|
|
interaction: discord.Interaction,
|
|
team: Team
|
|
):
|
|
"""Handle team selection from dropdown."""
|
|
# Get additional team data
|
|
standings_data = await team_service.get_team_standings_position(team.id, team.season)
|
|
|
|
# Create detailed team embed
|
|
embed = SBAEmbedTemplate.team_info(
|
|
team_abbrev=team.abbrev,
|
|
team_name=team.lname,
|
|
season=team.season,
|
|
short_name=getattr(team, 'sname', None),
|
|
stadium=getattr(team, 'stadium', None),
|
|
division=f"Division {team.division_id}" if getattr(team, 'division_id', None) else None,
|
|
team_color=getattr(team, 'color', None),
|
|
team_thumbnail=getattr(team, 'thumbnail', None)
|
|
)
|
|
|
|
# Add standings info if available
|
|
if standings_data:
|
|
try:
|
|
wins = standings_data.get('wins', 'N/A')
|
|
losses = standings_data.get('losses', 'N/A')
|
|
pct = standings_data.get('pct', 'N/A')
|
|
gb = standings_data.get('gb', 'N/A')
|
|
|
|
record_text = f"{wins}-{losses}"
|
|
if pct != 'N/A':
|
|
record_text += f" ({pct:.3f})"
|
|
if gb != 'N/A' and gb != 0:
|
|
record_text += f" • {gb} GB"
|
|
|
|
embed.add_field(name="Record", value=record_text, inline=False)
|
|
except (KeyError, TypeError):
|
|
pass
|
|
|
|
# Create detailed info view with actions
|
|
async def refresh_team_data(interaction: discord.Interaction) -> discord.Embed:
|
|
"""Refresh team data."""
|
|
updated_standings = await team_service.get_team_standings_position(team.id, team.season)
|
|
# Recreate embed with updated data
|
|
return embed # Simplified for example
|
|
|
|
async def show_roster(interaction: discord.Interaction):
|
|
"""Show team roster."""
|
|
roster_embed = EmbedTemplate.info(
|
|
title=f"{team.abbrev} Roster",
|
|
description="Roster functionality would go here..."
|
|
)
|
|
await interaction.response.send_message(embed=roster_embed, ephemeral=True)
|
|
|
|
view = DetailedInfoView(
|
|
embed=embed,
|
|
user_id=interaction.user.id,
|
|
show_refresh=True,
|
|
show_details=True,
|
|
refresh_callback=refresh_team_data,
|
|
details_callback=show_roster
|
|
)
|
|
|
|
await interaction.response.edit_message(embed=embed, view=view)
|
|
|
|
# ========================================
|
|
# Additional Examples
|
|
# ========================================
|
|
|
|
@discord.app_commands.command(
|
|
name="confirmation-example",
|
|
description="Example of confirmation dialog"
|
|
)
|
|
@logged_command("/confirmation-example")
|
|
async def confirmation_example(self, interaction: discord.Interaction):
|
|
"""Example of modern confirmation dialog."""
|
|
embed = EmbedTemplate.warning(
|
|
title="Confirm Action",
|
|
description="This is an example confirmation dialog. Do you want to proceed?"
|
|
)
|
|
|
|
async def handle_confirm(interaction: discord.Interaction):
|
|
"""Handle confirmation."""
|
|
success_embed = EmbedTemplate.success(
|
|
title="Action Confirmed",
|
|
description="The action has been completed successfully!"
|
|
)
|
|
await interaction.response.edit_message(embed=success_embed, view=None)
|
|
|
|
async def handle_cancel(interaction: discord.Interaction):
|
|
"""Handle cancellation."""
|
|
cancel_embed = EmbedTemplate.error(
|
|
title="Action Cancelled",
|
|
description="The action has been cancelled."
|
|
)
|
|
await interaction.response.edit_message(embed=cancel_embed, view=None)
|
|
|
|
view = ConfirmationView(
|
|
user_id=interaction.user.id,
|
|
confirm_callback=handle_confirm,
|
|
cancel_callback=handle_cancel,
|
|
confirm_label="Yes, Proceed",
|
|
cancel_label="No, Cancel"
|
|
)
|
|
|
|
await interaction.response.send_message(embed=embed, view=view)
|
|
|
|
|
|
async def setup(bot: commands.Bot):
|
|
"""Load the migration example commands cog."""
|
|
await bot.add_cog(MigrationExampleCommands(bot)) |