""" config.py ========= Configuration module for the Discord ComfyUI bot. This module centralizes all constants, magic strings, and environment variable loading to make configuration management easier and more maintainable. """ from __future__ import annotations import os from dataclasses import dataclass from pathlib import Path from typing import Optional try: from dotenv import load_dotenv load_dotenv() except Exception: pass # ======================================== # Command and Argument Constants # ======================================== COMMAND_PREFIX = os.getenv("BOT_PREFIX", "ttr!") """The command prefix used for Discord bot commands.""" ARG_PROMPT_KEY = "prompt:" """The keyword marker for prompt arguments in commands.""" ARG_NEG_PROMPT_KEY = "negative_prompt:" """The keyword marker for negative prompt arguments in commands.""" ARG_TYPE_KEY = "type:" """The keyword marker for type arguments in commands.""" ARG_QUEUE_KEY = "queue:" """The keyword marker for queue count arguments in commands.""" # ======================================== # Discord and Message Constants # ======================================== MAX_IMAGES_PER_RESPONSE = 4 """Maximum number of images to include in a single Discord response.""" DEFAULT_UPLOAD_TYPE = "input" """Default folder type for ComfyUI image uploads.""" MESSAGE_AUTO_DELETE_TIMEOUT = 60.0 """Default timeout in seconds for auto-deleting temporary messages.""" # ======================================== # Error Messages # ======================================== COMFY_NOT_CONFIGURED_MSG = "ComfyUI client is not configured. Please set environment variables." """Error message displayed when ComfyUI client is not properly configured.""" # ======================================== # Default Configuration Values # ======================================== DEFAULT_COMFY_HISTORY_LIMIT = 10 """Default number of generation history entries to keep.""" # Resolve paths relative to this file's location so both the bot project and # the portable ComfyUI folder only need to share the same parent directory. # Layout assumed: # / # ComfyUI_windows_portable/ComfyUI/output ← default output # ComfyUI_windows_portable/ComfyUI/input ← default input # the-third-rev/ ← this project _COMFY_PORTABLE_ROOT = Path(__file__).resolve().parent.parent / "ComfyUI_windows_portable" / "ComfyUI" DEFAULT_COMFY_OUTPUT_PATH = str(_COMFY_PORTABLE_ROOT / "output") DEFAULT_COMFY_INPUT_PATH = str(_COMFY_PORTABLE_ROOT / "input") # ======================================== # Configuration Class # ======================================== @dataclass class BotConfig: """ Configuration container for the Discord ComfyUI bot. This dataclass holds all configuration values loaded from environment variables. Use the `from_env()` class method to create an instance with values loaded from the environment. Attributes ---------- discord_bot_token : str Discord bot authentication token (required). comfy_server : str ComfyUI server address in format "hostname:port" (required). comfy_output_path : str Path to ComfyUI output directory for reading generated files. comfy_history_limit : int Number of generation history entries to keep in memory. workflow_file : Optional[str] Path to a workflow JSON file to load at startup (optional). """ discord_bot_token: str comfy_server: str comfy_output_path: str comfy_input_path: str comfy_history_limit: int comfy_input_channel_id: int = 1475791295665405962 comfy_service_name: str = "ComfyUI" comfy_start_bat: str = "" comfy_log_dir: str = "" comfy_log_max_mb: int = 10 comfy_autostart: bool = True workflow_file: Optional[str] = None log_channel_id: Optional[int] = None zip_password: Optional[str] = None media_upload_user: Optional[str] = None media_upload_pass: Optional[str] = None # Web UI fields web_enabled: bool = True web_host: str = "0.0.0.0" web_port: int = 8080 web_secret_key: str = "" web_token_file: str = "invite_tokens.json" web_jwt_expire_hours: int = 8 web_secure_cookie: bool = True admin_password: Optional[str] = None @classmethod def from_env(cls) -> BotConfig: """ Create a BotConfig instance by loading values from environment variables. Environment Variables --------------------- DISCORD_BOT_TOKEN : str (required) Discord bot authentication token. COMFY_SERVER : str (required) ComfyUI server address (e.g., "localhost:8188" or "example.com:8188"). COMFY_OUTPUT_PATH : str (optional) Path to ComfyUI output directory. Defaults to DEFAULT_COMFY_OUTPUT_PATH if not specified. COMFY_HISTORY_LIMIT : int (optional) Number of generation history entries to keep. Defaults to DEFAULT_COMFY_HISTORY_LIMIT if not specified or invalid. WORKFLOW_FILE : str (optional) Path to a workflow JSON file to load at startup. Returns ------- BotConfig A configured BotConfig instance. Raises ------ RuntimeError If required environment variables (DISCORD_BOT_TOKEN or COMFY_SERVER) are not set. """ # Load required variables discord_token = os.getenv("DISCORD_BOT_TOKEN") if not discord_token: raise RuntimeError( "DISCORD_BOT_TOKEN environment variable is required. " "Please set it in your .env file or environment." ) comfy_server = os.getenv("COMFY_SERVER") if not comfy_server: raise RuntimeError( "COMFY_SERVER environment variable is required. " "Please set it in your .env file or environment." ) # Load optional variables with defaults comfy_output_path = os.getenv("COMFY_OUTPUT_PATH", DEFAULT_COMFY_OUTPUT_PATH) comfy_input_path = os.getenv("COMFY_INPUT_PATH", DEFAULT_COMFY_INPUT_PATH) # Parse history limit with fallback to default try: comfy_history_limit = int(os.getenv("COMFY_HISTORY_LIMIT", str(DEFAULT_COMFY_HISTORY_LIMIT))) except ValueError: comfy_history_limit = DEFAULT_COMFY_HISTORY_LIMIT workflow_file = os.getenv("WORKFLOW_FILE") log_channel_id_str = os.getenv("LOG_CHANNEL_ID", "1475408462740721809") try: log_channel_id = int(log_channel_id_str) if log_channel_id_str else None except ValueError: log_channel_id = None zip_password = os.getenv("ZIP_PASSWORD", "0Revel512796@") media_upload_user = os.getenv("MEDIA_UPLOAD_USER") or None media_upload_pass = os.getenv("MEDIA_UPLOAD_PASS") or None try: comfy_input_channel_id = int(os.getenv("COMFY_INPUT_CHANNEL_ID", "1475791295665405962")) except ValueError: comfy_input_channel_id = 1475791295665405962 comfy_service_name = os.getenv("COMFY_SERVICE_NAME", "ComfyUI") default_bat = str(_COMFY_PORTABLE_ROOT.parent / "run_nvidia_gpu.bat") comfy_start_bat = os.getenv("COMFY_START_BAT", default_bat) default_log_dir = str(_COMFY_PORTABLE_ROOT.parent / "logs") comfy_log_dir = os.getenv("COMFY_LOG_DIR", default_log_dir) try: comfy_log_max_mb = int(os.getenv("COMFY_LOG_MAX_MB", "10")) except ValueError: comfy_log_max_mb = 10 comfy_autostart = os.getenv("COMFY_AUTOSTART", "true").lower() not in ("false", "0", "no") # Web UI config web_enabled = os.getenv("WEB_ENABLED", "true").lower() not in ("false", "0", "no") web_host = os.getenv("WEB_HOST", "0.0.0.0") try: web_port = int(os.getenv("WEB_PORT", "8080")) except ValueError: web_port = 8080 web_secret_key = os.getenv("WEB_SECRET_KEY", "") web_token_file = os.getenv("WEB_TOKEN_FILE", "invite_tokens.json") try: web_jwt_expire_hours = int(os.getenv("WEB_JWT_EXPIRE_HOURS", "8")) except ValueError: web_jwt_expire_hours = 8 web_secure_cookie = os.getenv("WEB_SECURE_COOKIE", "true").lower() not in ("false", "0", "no") admin_password = os.getenv("ADMIN_PASSWORD") or None return cls( discord_bot_token=discord_token, comfy_server=comfy_server, comfy_output_path=comfy_output_path, comfy_input_path=comfy_input_path, comfy_history_limit=comfy_history_limit, comfy_input_channel_id=comfy_input_channel_id, comfy_service_name=comfy_service_name, comfy_start_bat=comfy_start_bat, comfy_log_dir=comfy_log_dir, comfy_log_max_mb=comfy_log_max_mb, comfy_autostart=comfy_autostart, workflow_file=workflow_file, log_channel_id=log_channel_id, zip_password=zip_password, media_upload_user=media_upload_user, media_upload_pass=media_upload_pass, web_enabled=web_enabled, web_host=web_host, web_port=web_port, web_secret_key=web_secret_key, web_token_file=web_token_file, web_jwt_expire_hours=web_jwt_expire_hours, web_secure_cookie=web_secure_cookie, admin_password=admin_password, ) def __repr__(self) -> str: """Return a string representation with sensitive data masked.""" return ( f"BotConfig(" f"discord_bot_token='***masked***', " f"comfy_server='{self.comfy_server}', " f"comfy_output_path='{self.comfy_output_path}', " f"comfy_input_path='{self.comfy_input_path}', " f"comfy_history_limit={self.comfy_history_limit}, " f"comfy_input_channel_id={self.comfy_input_channel_id}, " f"workflow_file={self.workflow_file!r}, " f"log_channel_id={self.log_channel_id!r}, " f"zip_password={'***masked***' if self.zip_password else None})" )