Full source for the-third-rev: Discord bot (discord.py), FastAPI web UI (React/TS/Vite/Tailwind), ComfyUI integration, generation history DB, preset manager, workflow inspector, and all supporting modules. Excluded from tracking: .env, invite_tokens.json, *.db (SQLite), current-workflow-changes.json, user_settings/, presets/, logs/, web-static/ (build output), frontend/node_modules/. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
284 lines
10 KiB
Python
284 lines
10 KiB
Python
"""
|
|
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:
|
|
# <parent>/
|
|
# 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})"
|
|
)
|