♻️ refactor(command): 配置系统从 SQLite 迁移至 YAML 并修复白名单失效

- 用 CommandConfig dataclass 单例替代模块级变量,解决 from import 造成的本地绑定不随 global 更新的 bug
- 删除 db.py,改用 settings.yaml 存储动态配置,首次启动自动创建并合并 .env 默认值
- 新增文件轮询 watcher(2 秒),检测 YAML 变更自动热重载
- 管理界面 API 改为直接读写 YAML,即时生效
- 依赖 aiosqlite 替换为 pyyaml
This commit is contained in:
2026-05-03 18:23:29 +08:00
parent 9ffe78a9c2
commit f82363f45f
8 changed files with 314 additions and 270 deletions

View File

@@ -4,7 +4,7 @@ import re
import aiohttp
from ..config import COMMAND_AT_SENDER, COMMAND_CALLBACK_TIMEOUT, COMMAND_CALLBACK_URL, COMMAND_LENGTH_MAX, COMMAND_LENGTH_MIN, COMMAND_PREFIX, UPLOAD_DIR
from ..config import UPLOAD_DIR, command
from ..handlers.message import _resolve_url
@@ -18,7 +18,7 @@ def build_command_pattern() -> re.Pattern:
- 其他非空白字符 = 1
"""
return re.compile(
rf"^{re.escape(COMMAND_PREFIX)}(\S{{{COMMAND_LENGTH_MIN},{COMMAND_LENGTH_MAX}}})(?:\s+(.+))?$",
rf"^{re.escape(command.prefix)}(\S{{{command.length_min},{command.length_max}}})(?:\s+(.+))?$",
re.DOTALL,
)
@@ -56,28 +56,28 @@ async def send_command_callback(data: dict, event, api, logger) -> None:
{"type": "file", "url": "..."},
{"type": "video", "url": "..."}
],
"at_sender": true // 是否 @发送者(默认取 COMMAND_AT_SENDER 配置,仅群聊)
"at_sender": true // 是否 @发送者(默认取配置,仅群聊)
}
所有字段均为可选,无回复内容时返回空 JSON 即可。
回复会引用触发命令的原消息。
"""
if not COMMAND_CALLBACK_URL:
logger.warning("COMMAND_CALLBACK_URL 未配置,跳过命令回调")
if not command.callback_url:
logger.warning("callback_url 未配置,跳过命令回调")
return
try:
async with aiohttp.ClientSession() as session:
async with session.post(
COMMAND_CALLBACK_URL,
command.callback_url,
json=data,
timeout=aiohttp.ClientTimeout(total=COMMAND_CALLBACK_TIMEOUT),
timeout=aiohttp.ClientTimeout(total=command.callback_timeout),
) as resp:
if resp.status >= 400:
body = await resp.text()
logger.error(
"命令回调失败: status=%d url=%s body=%s",
resp.status, COMMAND_CALLBACK_URL, body[:200],
resp.status, command.callback_url, body[:200],
)
return
@@ -93,13 +93,13 @@ async def send_command_callback(data: dict, event, api, logger) -> None:
await _handle_reply(result, event.data, api, logger)
except Exception as exc:
logger.error("命令回调异常: url=%s error=%s", COMMAND_CALLBACK_URL, exc)
logger.error("命令回调异常: url=%s error=%s", command.callback_url, exc)
async def _handle_reply(result: dict, msg_event, api, logger) -> None:
"""处理回调响应引用原消息自动回复。msg_event 是 GroupMessageEvent / PrivateMessageEvent。"""
# at_sender: 回调响应中的值优先,未指定则使用全局配置
at_sender = result.get("at_sender", COMMAND_AT_SENDER)
at_sender = result.get("at_sender", command.at_sender)
messages = result.get("messages")
reply = result.get("reply")
group_id = getattr(msg_event, "group_id", None)