Files
AEmotionStudio-ComfyUI-Disc…/bot/config.py
T

213 lines
6.6 KiB
Python

"""
Configuration management for the Discord bot.
Configuration is loaded from (in priority order):
1. Environment variables (highest priority)
2. Config file (bot/config.yaml)
3. Defaults (lowest priority)
"""
import os
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional
import yaml
@dataclass
class DiscordConfig:
"""Discord-related configuration."""
token: str = ""
application_id: Optional[str] = None
@dataclass
class ComfyUIConfig:
"""ComfyUI connection configuration."""
url: str = "http://127.0.0.1:8188"
ws_url: str = "ws://127.0.0.1:8188/ws"
timeout: int = 30
@dataclass
class DefaultsConfig:
"""Default values for bot operations."""
max_queue_per_user: int = 3
progress_update_interval: float = 2.0
workflow_path: Optional[str] = None
default_steps: int = 20
default_cfg: float = 7.0
default_width: int = 512
default_height: int = 512
@dataclass
class DatabaseConfig:
"""Database configuration."""
url: str = ""
def __post_init__(self):
if not self.url:
# Default to SQLite in bot/data directory
bot_dir = Path(__file__).parent
data_dir = bot_dir / "data"
data_dir.mkdir(exist_ok=True)
self.url = f"sqlite+aiosqlite:///{data_dir}/bot.db"
@dataclass
class SecurityConfig:
"""Security configuration."""
allowed_guilds: list[int] = field(default_factory=list)
@dataclass
class BotConfig:
"""Main bot configuration container."""
discord: DiscordConfig = field(default_factory=DiscordConfig)
comfyui: ComfyUIConfig = field(default_factory=ComfyUIConfig)
defaults: DefaultsConfig = field(default_factory=DefaultsConfig)
database: DatabaseConfig = field(default_factory=DatabaseConfig)
security: SecurityConfig = field(default_factory=SecurityConfig)
@classmethod
def load(cls, config_path: Optional[Path] = None) -> "BotConfig":
"""
Load configuration from file and environment variables.
Args:
config_path: Optional path to config.yaml file
Returns:
Loaded BotConfig instance
"""
config = cls()
# Load from config file if exists
if config_path is None:
config_path = Path(__file__).parent / "config.yaml"
if config_path.exists():
config._load_from_file(config_path)
# Override with environment variables
config._load_from_env()
return config
def _load_from_file(self, path: Path) -> None:
"""Load configuration from YAML file."""
with open(path) as f:
data = yaml.safe_load(f) or {}
# Discord config
if "discord" in data:
discord_data = data["discord"]
if "token" in discord_data:
self.discord.token = discord_data["token"]
if "application_id" in discord_data:
self.discord.application_id = discord_data["application_id"]
# ComfyUI config
if "comfyui" in data:
comfyui_data = data["comfyui"]
if "url" in comfyui_data:
self.comfyui.url = comfyui_data["url"]
if "ws_url" in comfyui_data:
self.comfyui.ws_url = comfyui_data["ws_url"]
if "timeout" in comfyui_data:
self.comfyui.timeout = comfyui_data["timeout"]
# Defaults config
if "defaults" in data:
defaults_data = data["defaults"]
if "max_queue_per_user" in defaults_data:
self.defaults.max_queue_per_user = defaults_data["max_queue_per_user"]
if "progress_update_interval" in defaults_data:
self.defaults.progress_update_interval = defaults_data["progress_update_interval"]
if "workflow_path" in defaults_data:
self.defaults.workflow_path = defaults_data["workflow_path"]
if "default_steps" in defaults_data:
self.defaults.default_steps = defaults_data["default_steps"]
if "default_cfg" in defaults_data:
self.defaults.default_cfg = defaults_data["default_cfg"]
if "default_width" in defaults_data:
self.defaults.default_width = defaults_data["default_width"]
if "default_height" in defaults_data:
self.defaults.default_height = defaults_data["default_height"]
# Database config
if "database" in data:
db_data = data["database"]
if "url" in db_data:
self.database.url = db_data["url"]
# Security config
if "security" in data:
security_data = data["security"]
if "allowed_guilds" in security_data:
self.security.allowed_guilds = security_data["allowed_guilds"] or []
def _load_from_env(self) -> None:
"""Load configuration from environment variables."""
# Discord
if token := os.getenv("DISCORDBOT_DISCORD_TOKEN"):
self.discord.token = token
if app_id := os.getenv("DISCORDBOT_APPLICATION_ID"):
self.discord.application_id = app_id
# ComfyUI
if url := os.getenv("DISCORDBOT_COMFYUI_URL"):
self.comfyui.url = url
if ws_url := os.getenv("DISCORDBOT_COMFYUI_WS_URL"):
self.comfyui.ws_url = ws_url
if timeout := os.getenv("DISCORDBOT_COMFYUI_TIMEOUT"):
self.comfyui.timeout = int(timeout)
# Database
if db_url := os.getenv("DISCORDBOT_DATABASE_URL"):
self.database.url = db_url
# Defaults
if max_queue := os.getenv("DISCORDBOT_MAX_QUEUE_PER_USER"):
self.defaults.max_queue_per_user = int(max_queue)
if workflow := os.getenv("DISCORDBOT_WORKFLOW_PATH"):
self.defaults.workflow_path = workflow
def validate(self) -> list[str]:
"""
Validate the configuration.
Returns:
List of validation error messages (empty if valid)
"""
errors = []
if not self.discord.token:
errors.append("Discord token is required (set DISCORDBOT_DISCORD_TOKEN)")
if not self.comfyui.url:
errors.append("ComfyUI URL is required")
return errors
# Global config instance (lazy loaded)
_config: Optional[BotConfig] = None
def get_config() -> BotConfig:
"""Get the global configuration instance."""
global _config
if _config is None:
_config = BotConfig.load()
return _config
def reload_config(config_path: Optional[Path] = None) -> BotConfig:
"""Reload configuration from disk."""
global _config
_config = BotConfig.load(config_path)
return _config