"""Bot 全局配置:启动时从环境变量一次性读齐。""" import os from dataclasses import dataclass from pathlib import Path # 内嵌键盘上限 5 行 × 每行 5 个;歌曲占行 + 1 行翻页 → 每页最多 20 首 _MAX_MENU_PAGE_SIZE = 20 _LOG_LEVELS = ("TRACE", "DEBUG", "INFO", "SUCCESS", "WARNING", "ERROR", "CRITICAL") def _flag(name: str, default: bool) -> bool: """读布尔环境变量:on/true/1/yes 为真,off/false/0/no 为假。""" raw = os.getenv(name, "").strip().lower() if not raw: return default return raw in ("1", "true", "on", "yes") def _send_mode() -> str: """SEND_MODE:下载发送方式。 file 先上传群文件、失败回退直链;link 直接发直链(跳过上传,可避开 "仅群主和管理员上传文件" 的 40093005)。非法值按默认 link 处理。 """ raw = os.getenv("SEND_MODE", "").strip().lower() return raw if raw in ("file", "link") else "link" @dataclass(frozen=True, slots=True) class BotConfig: music_server_url: str music_quality: str music_source: str menu_page_size: int = 10 # 选歌菜单每页候选数(1~20) withdraw: bool = True # 自动撤回旧菜单/卡片是否开启 send_mode: str = "link" # 下载发送方式:"file" 先传文件 / "link" 直接发直链 command_panel: bool = True # 启动/重连时自动注册指令面板(输入框 / 菜单) log_level: str = "INFO" # 控制台与文件日志级别 log_dir: Path = Path("logs") # 日志目录;写不进去时自动退化为仅控制台 reload: bool = False # 热重载(开发用;--reload 亦可开启) @classmethod def load(cls) -> "BotConfig": page_size = int(os.getenv("MENU_PAGE_SIZE", "10")) return cls( music_server_url=os.getenv("MUSIC_SERVER_URL", "http://127.0.0.1:16688"), music_quality=os.getenv("MUSIC_QUALITY", "128k"), music_source=os.getenv("MUSIC_SOURCE", "tx"), menu_page_size=min(max(1, page_size), _MAX_MENU_PAGE_SIZE), withdraw=_flag("WITHDRAW", True), send_mode=_send_mode(), command_panel=_flag("COMMAND_PANEL", True), log_level=_log_level(), log_dir=Path(os.getenv("LOG_DIR", "logs")), reload=_flag("RELOAD", False), ) def _log_level() -> str: """LOG_LEVEL:日志级别,非法值按 INFO。""" raw = os.getenv("LOG_LEVEL", "").strip().upper() return raw if raw in _LOG_LEVELS else "INFO"