♻️ 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:
96
plugin.py
96
plugin.py
@@ -8,24 +8,15 @@ from aiohttp import web
|
||||
from ncatbot.plugin import NcatBotPlugin
|
||||
|
||||
from .config import (
|
||||
COMMAND_ALLOWED_GROUPS,
|
||||
COMMAND_ALLOWED_USERS,
|
||||
COMMAND_AT_SENDER,
|
||||
COMMAND_CALLBACK_URL,
|
||||
COMMAND_DENIED_GROUPS,
|
||||
COMMAND_DENIED_USERS,
|
||||
COMMAND_LENGTH_MAX,
|
||||
COMMAND_LENGTH_MIN,
|
||||
COMMAND_LIST_MODE,
|
||||
COMMAND_PREFIX,
|
||||
COMMAND_SCOPE,
|
||||
HOST,
|
||||
PORT,
|
||||
UPLOAD_DIR,
|
||||
WEBHOOK_API_KEY,
|
||||
SETTINGS_YAML_PATH,
|
||||
command,
|
||||
ensure_settings_yaml,
|
||||
reload_settings,
|
||||
)
|
||||
from .db import get_settings, init_db
|
||||
from .handlers.admin import admin_page_handler, api_get_settings, api_reload_settings, api_update_settings
|
||||
from .handlers.command import parse_command, send_command_callback
|
||||
from .handlers.health import health_handler
|
||||
@@ -47,12 +38,12 @@ class WebHookPlugin(NcatBotPlugin):
|
||||
self._webhook_runner: web.AppRunner | None = None
|
||||
self._cleanup_task: asyncio.Task | None = None
|
||||
self._listener_task: asyncio.Task | None = None
|
||||
self._watcher_task: asyncio.Task | None = None
|
||||
|
||||
async def on_load(self):
|
||||
# 初始化数据库并加载动态配置
|
||||
await init_db()
|
||||
settings = await get_settings()
|
||||
reload_settings(settings)
|
||||
# 初始化 settings.yaml 并加载配置
|
||||
ensure_settings_yaml()
|
||||
reload_settings()
|
||||
|
||||
self.logger.info("Webhook 插件已加载")
|
||||
self.logger.info(
|
||||
@@ -61,32 +52,28 @@ class WebHookPlugin(NcatBotPlugin):
|
||||
)
|
||||
self.logger.info(
|
||||
"命令监听: 前缀=%s 长度=%d~%d 范围=%s 名单=%s 回调=%s",
|
||||
COMMAND_PREFIX,
|
||||
COMMAND_LENGTH_MIN,
|
||||
COMMAND_LENGTH_MAX,
|
||||
COMMAND_SCOPE,
|
||||
COMMAND_LIST_MODE,
|
||||
COMMAND_CALLBACK_URL or "未配置",
|
||||
command.prefix,
|
||||
command.length_min,
|
||||
command.length_max,
|
||||
command.scope,
|
||||
command.list_mode,
|
||||
command.callback_url or "未配置",
|
||||
)
|
||||
asyncio.create_task(self._start_webhook())
|
||||
self._cleanup_task = asyncio.create_task(self._cleanup_loop())
|
||||
self._listener_task = asyncio.create_task(self._message_listener())
|
||||
self._watcher_task = asyncio.create_task(self._config_watcher())
|
||||
|
||||
async def on_close(self):
|
||||
if self._listener_task is not None:
|
||||
self._listener_task.cancel()
|
||||
try:
|
||||
await self._listener_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
self._listener_task = None
|
||||
if self._cleanup_task is not None:
|
||||
self._cleanup_task.cancel()
|
||||
try:
|
||||
await self._cleanup_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
self._cleanup_task = None
|
||||
for task_attr in ("_watcher_task", "_listener_task", "_cleanup_task"):
|
||||
task = getattr(self, task_attr)
|
||||
if task is not None:
|
||||
task.cancel()
|
||||
try:
|
||||
await task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
setattr(self, task_attr, None)
|
||||
await self._stop_webhook()
|
||||
self.logger.info("Webhook 插件已卸载")
|
||||
|
||||
@@ -99,6 +86,21 @@ class WebHookPlugin(NcatBotPlugin):
|
||||
except Exception as exc:
|
||||
self.logger.error("清理过期文件失败: %s", exc)
|
||||
|
||||
async def _config_watcher(self) -> None:
|
||||
"""轮询 settings.yaml 的 mtime,变更时热重载配置。"""
|
||||
last_mtime: float = 0.0
|
||||
while True:
|
||||
await asyncio.sleep(2)
|
||||
try:
|
||||
if SETTINGS_YAML_PATH.exists():
|
||||
current_mtime = SETTINGS_YAML_PATH.stat().st_mtime
|
||||
if current_mtime != last_mtime:
|
||||
last_mtime = current_mtime
|
||||
reload_settings()
|
||||
self.logger.info("settings.yaml changed, config reloaded")
|
||||
except Exception as exc:
|
||||
self.logger.error("Config watcher error: %s", exc)
|
||||
|
||||
async def _message_listener(self) -> None:
|
||||
"""监听 QQ 消息,匹配命令模式后转发到外部回调。"""
|
||||
try:
|
||||
@@ -114,25 +116,25 @@ class WebHookPlugin(NcatBotPlugin):
|
||||
|
||||
# 范围过滤:group / private / all
|
||||
is_group = hasattr(event.data, "group_id")
|
||||
if COMMAND_SCOPE == "group" and not is_group:
|
||||
if command.scope == "group" and not is_group:
|
||||
continue
|
||||
if COMMAND_SCOPE == "private" and is_group:
|
||||
if command.scope == "private" and is_group:
|
||||
continue
|
||||
|
||||
# 黑白名单过滤
|
||||
if COMMAND_LIST_MODE == "allow":
|
||||
if command.list_mode == "allow":
|
||||
# 白名单模式:在名单内才放行
|
||||
if COMMAND_ALLOWED_GROUPS and is_group:
|
||||
if event.data.group_id not in COMMAND_ALLOWED_GROUPS:
|
||||
if command.allowed_groups and is_group:
|
||||
if event.data.group_id not in command.allowed_groups:
|
||||
continue
|
||||
if COMMAND_ALLOWED_USERS and event.data.user_id not in COMMAND_ALLOWED_USERS:
|
||||
if command.allowed_users and event.data.user_id not in command.allowed_users:
|
||||
continue
|
||||
elif COMMAND_LIST_MODE == "deny":
|
||||
elif command.list_mode == "deny":
|
||||
# 黑名单模式:在名单内则拒绝
|
||||
if COMMAND_DENIED_GROUPS and is_group:
|
||||
if event.data.group_id in COMMAND_DENIED_GROUPS:
|
||||
if command.denied_groups and is_group:
|
||||
if event.data.group_id in command.denied_groups:
|
||||
continue
|
||||
if COMMAND_DENIED_USERS and event.data.user_id in COMMAND_DENIED_USERS:
|
||||
if command.denied_users and event.data.user_id in command.denied_users:
|
||||
continue
|
||||
|
||||
# 构建回调数据
|
||||
@@ -188,4 +190,4 @@ class WebHookPlugin(NcatBotPlugin):
|
||||
if self._webhook_runner is not None:
|
||||
await self._webhook_runner.cleanup()
|
||||
self._webhook_runner = None
|
||||
self.logger.info("Webhook 已停止")
|
||||
self.logger.info("Webhook 已停止")
|
||||
Reference in New Issue
Block a user