# config.py # Modern configuration management using environment variables import os import discord from discord import Color from dotenv import load_dotenv from typing import Optional # Load environment variables from .env file load_dotenv() # =================================== # Environment Detection # =================================== ENVIRONMENT = os.getenv("ENVIRONMENT", "dev").lower() IS_PRODUCTION = ENVIRONMENT == "live" IS_DEVELOPMENT = ENVIRONMENT == "dev" # =================================== # Discord Configuration # =================================== def get_discord_token() -> str: """Get the appropriate Discord token based on environment""" if IS_PRODUCTION: token = os.getenv("DISCORD_TOKEN_LIVE") if not token: raise ValueError("DISCORD_TOKEN_LIVE not found in environment!") return token else: token = os.getenv("DISCORD_TOKEN_DEV") if not token: raise ValueError("DISCORD_TOKEN_DEV not found in environment!") return token def get_prefix() -> str: """Get command prefix (default: =)""" return os.getenv("DISCORD_PREFIX", "=") # =================================== # Spotify Configuration # =================================== def get_spotify_creds() -> tuple[str, str]: """Get Spotify API credentials""" client_id = os.getenv("SPOTIFY_CLIENT_ID") client_secret = os.getenv("SPOTIFY_CLIENT_SECRET") if not client_id or not client_secret: raise ValueError("Spotify credentials not found in environment!") return client_id, client_secret # =================================== # Database Configuration # =================================== def get_db_path() -> str: """Get SQLite database path""" return os.getenv("DB_PATH", "./data/music.db") # Future PostgreSQL config def get_postgres_url() -> Optional[str]: """Get PostgreSQL connection URL (for future migration)""" host = os.getenv("DB_HOST") port = os.getenv("DB_PORT", "5432") name = os.getenv("DB_NAME") user = os.getenv("DB_USER") password = os.getenv("DB_PASSWORD") if all([host, name, user, password]): return f"postgresql://{user}:{password}@{host}:{port}/{name}" return None # =================================== # Bot Status/Presence # =================================== def get_status() -> discord.Activity: """Get bot status/presence""" status_type = os.getenv("STATUS_TYPE", "listening").lower() status_text = os.getenv("STATUS_TEXT", "Zilean's Theme") status_url = os.getenv("STATUS_URL") return translate_status(status_type, status_text, status_url) def translate_status( status_type: str, status_text: str, status_url: Optional[str] = None ) -> discord.Activity: """Convert status type string to Discord Activity""" status_map = { "playing": discord.ActivityType.playing, "streaming": discord.ActivityType.streaming, "listening": discord.ActivityType.listening, "watching": discord.ActivityType.watching, "competing": discord.ActivityType.competing, } activity_type = status_map.get(status_type) if not activity_type: raise ValueError(f"Invalid status type: {status_type}") # Streaming requires URL if status_type == "streaming": if not status_url: raise ValueError("Streaming status requires STATUS_URL") return discord.Activity( type=activity_type, name=status_text, url=status_url ) return discord.Activity(type=activity_type, name=status_text) # =================================== # Color Scheme # =================================== def get_color(color_name: str) -> Color: """Get color from environment (hex format)""" color_map = { "primary": os.getenv("COLOR_PRIMARY", "#7289DA"), "success": os.getenv("COLOR_SUCCESS", "#43B581"), "error": os.getenv("COLOR_ERROR", "#F04747"), "warning": os.getenv("COLOR_WARNING", "#FAA61A"), } hex_value = color_map.get(color_name.lower()) if not hex_value: # Default to Discord blurple hex_value = "#7289DA" return Color.from_str(hex_value) # =================================== # Logging Configuration # =================================== def get_log_level() -> str: """Get logging level from environment""" return os.getenv("LOG_LEVEL", "INFO").upper() # =================================== # Legacy Support (for backward compatibility) # =================================== def get_login(bot: str) -> str: """Legacy function - maps to new get_discord_token()""" # Ignore the 'bot' parameter, use ENVIRONMENT instead return get_discord_token() # =================================== # Validation # =================================== def validate_config(): """Validate that all required config is present""" errors = [] # Check Discord token try: get_discord_token() except ValueError as e: errors.append(str(e)) # Check Spotify creds try: get_spotify_creds() except ValueError as e: errors.append(str(e)) if errors: error_msg = "\n".join(errors) raise ValueError(f"Configuration errors:\n{error_msg}") print(f"✅ Configuration validated (Environment: {ENVIRONMENT})") # =================================== # Startup Info # =================================== def print_config_info(): """Print configuration summary (without secrets!)""" print("=" * 50) print("🎵 Groovy-Zilean Configuration") print("=" * 50) print(f"Environment: {ENVIRONMENT.upper()}") print(f"Prefix: {get_prefix()}") print(f"Database: {get_db_path()}") print(f"Log Level: {get_log_level()}") print(f"Spotify: {'Configured ✅' if os.getenv('SPOTIFY_CLIENT_ID') else 'Not configured ❌'}") print("=" * 50)