major-domo-v2/commands/voice/CLAUDE.md
Cal Corum 5616cfec3a CLAUDE: Add automatic scorecard unpublishing when voice channels are cleaned up
This enhancement automatically unpublishes scorecards when their associated
voice channels are deleted by the cleanup service, ensuring data synchronization
and reducing unnecessary API calls to Google Sheets for inactive games.

Implementation:
- Added gameplay commands package with scorebug/scorecard functionality
- Created ScorebugService for reading live game data from Google Sheets
- VoiceChannelTracker now stores text_channel_id for voice-to-text association
- VoiceChannelCleanupService integrates ScorecardTracker for automatic cleanup
- LiveScorebugTracker monitors published scorecards and updates displays
- Bot initialization includes gameplay commands and live scorebug tracker

Key Features:
- Voice channels track associated text channel IDs
- cleanup_channel() unpublishes scorecards during normal cleanup
- verify_tracked_channels() unpublishes scorecards for stale entries on startup
- get_voice_channel_for_text_channel() enables reverse lookup
- LiveScorebugTracker logging improved (debug level for missing channels)

Testing:
- Added comprehensive test coverage (2 new tests, 19 total pass)
- Tests verify scorecard unpublishing in cleanup and verification scenarios

Documentation:
- Updated commands/voice/CLAUDE.md with scorecard cleanup integration
- Updated commands/gameplay/CLAUDE.md with background task integration
- Updated tasks/CLAUDE.md with automatic cleanup details

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 07:18:21 -05:00

11 KiB

Voice Channel Commands

This directory contains Discord slash commands for creating and managing voice channels for gameplay.

Files

channels.py

  • Commands:
    • /voice-channel public - Create a public voice channel for gameplay
    • /voice-channel private - Create a private team vs team voice channel
  • Description: Main command implementation with VoiceChannelCommands cog
  • Service Dependencies:
    • team_service.get_teams_by_owner() - Verify user has a team
    • league_service.get_current_state() - Get current season/week info
    • schedule_service.get_team_schedule() - Find opponent for private channels
  • Deprecated Commands:
    • !vc, !voice, !gameplay → Shows migration message to /voice-channel public
    • !private → Shows migration message to /voice-channel private

cleanup_service.py

  • Class: VoiceChannelCleanupService
  • Description: Manages automatic cleanup of bot-created voice channels
  • Features:
    • Restart-resilient channel tracking using JSON persistence
    • Configurable cleanup intervals and empty thresholds
    • Background monitoring loop with error recovery
    • Startup verification to clean stale tracking entries

tracker.py

  • Class: VoiceChannelTracker
  • Description: JSON-based persistent tracking of voice channels
  • Features:
    • Channel creation and status tracking
    • Empty duration monitoring with datetime handling
    • Cleanup candidate identification
    • Automatic stale entry removal

__init__.py

  • Function: setup_voice(bot)
  • Description: Package initialization with resilient cog loading
  • Integration: Follows established bot architecture patterns

Key Features

Public Voice Channels (/voice-channel public)

  • Permissions: Everyone can connect and speak
  • Naming: Random codename generation (e.g., "Gameplay Phoenix", "Gameplay Thunder")
  • Requirements: User must own a Major League team (3-character abbreviations like NYY, BOS)
  • Auto-cleanup: Configurable threshold (default: empty for configured minutes)

Private Voice Channels (/voice-channel private)

  • Permissions:
    • Team members can connect and speak (using team.lname Discord roles)
    • Everyone else can connect but only listen
  • Naming: Automatic "{Away} vs {Home}" format based on current week's schedule
  • Opponent Detection: Uses current league week to find scheduled opponent
  • Requirements:
    • User must own a Major League team (3-character abbreviations like NYY, BOS)
    • Team must have upcoming games in current week
  • Role Integration: Finds Discord roles matching team full names (team.lname)

Automatic Cleanup System

  • Monitoring Interval: Configurable (default: 60 seconds)
  • Empty Threshold: Configurable (default: 5 minutes empty before deletion)
  • Restart Resilience: JSON file persistence survives bot restarts
  • Startup Verification: Validates tracked channels still exist on bot startup
  • Graceful Error Handling: Continues operation even if individual operations fail
  • Scorecard Cleanup: Automatically unpublishes scorecards when associated voice channels are deleted

Architecture

Command Flow

  1. Major League Team Verification: Check user owns a Major League team using team_service
  2. Channel Creation: Create voice channel with appropriate permissions
  3. Tracking Registration: Add channel to cleanup service tracking
  4. User Feedback: Send success embed with channel details

Team Validation Logic

The voice channel system validates that users own Major League teams specifically:

async def _get_user_major_league_team(self, user_id: int, season: Optional[int] = None):
    """Get the user's Major League team for schedule/game purposes."""
    teams = await team_service.get_teams_by_owner(user_id, season)

    # Filter to only Major League teams (3-character abbreviations)
    major_league_teams = [team for team in teams if team.roster_type() == RosterType.MAJOR_LEAGUE]

    return major_league_teams[0] if major_league_teams else None

Team Types:

  • Major League: 3-character abbreviations (e.g., NYY, BOS, LAD) - Required for voice channels
  • Minor League: 4+ characters ending in "MIL" (e.g., NYYMIL, BOSMIL) - Not eligible
  • Injured List: Ending in "IL" (e.g., NYYIL, BOSIL) - Not eligible

Rationale: Only Major League teams participate in weekly scheduled games, so voice channel creation is restricted to active Major League team owners.

Permission System

# Public channels - everyone can speak
overwrites = {
    guild.default_role: discord.PermissionOverwrite(speak=True, connect=True)
}

# Private channels - team roles only can speak
overwrites = {
    guild.default_role: discord.PermissionOverwrite(speak=False, connect=True),
    user_team_role: discord.PermissionOverwrite(speak=True, connect=True),
    opponent_team_role: discord.PermissionOverwrite(speak=True, connect=True)
}

Cleanup Service Integration

# Bot initialization (bot.py)
from commands.voice.cleanup_service import VoiceChannelCleanupService
self.voice_cleanup_service = VoiceChannelCleanupService()
asyncio.create_task(self.voice_cleanup_service.start_monitoring(self))

# Channel tracking
if hasattr(self.bot, 'voice_cleanup_service'):
    cleanup_service = self.bot.voice_cleanup_service
    cleanup_service.tracker.add_channel(channel, channel_type, interaction.user.id)

Scorecard Cleanup Integration

When a voice channel is cleaned up (deleted after being empty for the configured threshold), the cleanup service automatically unpublishes any scorecard associated with that voice channel's text channel. This prevents the live scorebug tracker from continuing to update scores for games that no longer have active voice channels.

Cleanup Flow:

  1. Voice channel becomes empty and exceeds empty threshold
  2. Cleanup service deletes the voice channel
  3. Service checks if voice channel has associated text_channel_id
  4. If found, unpublishes scorecard from that text channel
  5. Live scorebug tracker stops updating that scorecard

Integration Points:

  • cleanup_service.py imports ScorecardTracker from commands.gameplay.scorecard_tracker
  • Scorecard unpublishing happens in three scenarios:
    • Normal cleanup (channel deleted after being empty)
    • Stale channel cleanup (channel already deleted externally)
    • Startup verification (channel no longer exists when bot starts)

Logging:

✅ Cleaned up empty voice channel: Gameplay Phoenix (ID: 123456789)
📋 Unpublished scorecard from text channel 987654321 (voice channel cleanup)

JSON Data Structure

{
  "voice_channels": {
    "123456789": {
      "channel_id": "123456789",
      "guild_id": "987654321",
      "name": "Gameplay Phoenix",
      "type": "public",
      "created_at": "2025-01-15T10:30:00",
      "last_checked": "2025-01-15T10:35:00",
      "empty_since": "2025-01-15T10:32:00",
      "creator_id": "111222333",
      "text_channel_id": "555666777"
    }
  }
}

Note: The text_channel_id field links the voice channel to its associated text channel, enabling automatic scorecard cleanup when the voice channel is deleted.

Configuration

Cleanup Service Settings

  • cleanup_interval: How often to check channels (default: 60 seconds)
  • empty_threshold: Minutes empty before deletion (default: 5 minutes)
  • data_file: JSON persistence file path (default: "data/voice_channels.json")

Channel Categories

  • Channels are created in the "Voice Channels" category if it exists
  • Falls back to no category if "Voice Channels" category not found

Random Codenames

CODENAMES = [
    "Phoenix", "Thunder", "Lightning", "Storm", "Blaze", "Frost", "Shadow", "Nova",
    "Viper", "Falcon", "Wolf", "Eagle", "Tiger", "Shark", "Bear", "Dragon",
    "Alpha", "Beta", "Gamma", "Delta", "Echo", "Foxtrot", "Golf", "Hotel",
    "Crimson", "Azure", "Emerald", "Golden", "Silver", "Bronze", "Platinum", "Diamond"
]

Error Handling

Common Scenarios

  • No Team Found: User-friendly message directing to contact league administrator
  • No Upcoming Games: Informative message about being between series
  • Missing Discord Roles: Warning in embed about teams without speaking permissions
  • Permission Errors: Clear message to contact server administrator
  • League Info Unavailable: Graceful fallback with retry suggestion

Service Dependencies

  • Graceful Degradation: Voice channels work without cleanup service
  • API Failures: Comprehensive error handling for external service calls
  • Discord Errors: Specific handling for Forbidden, NotFound, etc.

Testing Coverage

Test Files

  • tests/test_commands_voice.py: Comprehensive test suite covering:
    • VoiceChannelTracker JSON persistence and datetime handling
    • VoiceChannelCleanupService restart resilience and monitoring
    • VoiceChannelCommands slash command functionality
    • Error scenarios and edge cases
    • Deprecated command migration messages

Mock Objects

  • Discord guild, channels, roles, and interactions
  • Team service responses and player data
  • Schedule service responses and game data
  • League service current state information

Integration Points

Bot Integration

  • Package Loading: Integrated into bot.py command package loading sequence
  • Background Tasks: Cleanup service started in _setup_background_tasks()
  • Shutdown Handling: Cleanup service stopped in bot.close()

Service Layer

  • Team Service: User team verification and ownership lookup
  • League Service: Current season/week information retrieval
  • Schedule Service: Team schedule and opponent detection

Discord Integration

  • Application Commands: Modern slash command interface with command groups
  • Permission Overwrites: Fine-grained voice channel permission control
  • Embed Templates: Consistent styling using established embed patterns
  • Error Handling: Integration with global application command error handler

Usage Examples

Creating Public Channel

/voice-channel public

Result: Creates "Gameplay [Codename]" with public speaking permissions

Creating Private Channel

/voice-channel private

Result: Creates "[Away] vs [Home]" with team-only speaking permissions

Migration from Old Commands

!vc

Result: Shows deprecation message suggesting /voice-channel public

Future Enhancements

Potential Features

  • Channel Limits: Per-user or per-team channel creation limits
  • Custom Names: Allow users to specify custom channel names
  • Extended Permissions: More granular permission control options
  • Channel Templates: Predefined setups for different game types
  • Integration Webhooks: Notifications when channels are created/deleted

Configuration Options

  • Environment Variables: Make cleanup intervals configurable via env vars
  • Per-Guild Settings: Different settings for different Discord servers
  • Role Mapping: Custom role name patterns for team permissions

Last Updated: January 2025 Architecture: Modern async Discord.py with JSON persistence Dependencies: discord.py, team_service, league_service, schedule_service