Initial commit — ComfyUI Discord bot + web UI
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>
This commit is contained in:
283
config.py
Normal file
283
config.py
Normal file
@@ -0,0 +1,283 @@
|
||||
"""
|
||||
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})"
|
||||
)
|
||||
Reference in New Issue
Block a user