feat: revamp web hub — global connection status, array config editor, log streaming
- Global connection badge in app header (persistent dashboard SSE via ConnectionProvider) - LogProvider keeps log data and SSE stream alive across page switches (no reload spinner) - Config drawer: password reveal toggle, array-type row editor for list fields - Plugin management: optimistic updates, race-free loading, reset all group overrides - Logs: load-earlier pagination; backend tail-read optimization (before/end offsets) - web_auth: Web password changes persist (sync_admin seeding only when .env has explicit key) - Dashboard: collect in thread pool + 1s result cache - ErrorBoundary + local Suspense: plugin chunk loading stays inside content area - UI polish: table row hover, mobile header/breadcrumb fixes, wider config drawer - config_standard: array type inference for list fields; array env skip - random_jm_code / steam_info: array schema migration; README web frontend note Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,365 @@
|
||||
# HeXi 插件规范化审计报告
|
||||
|
||||
> 依据 《插件结构标准》(docs/plugin-structure-standard.md) 与 《插件配置文件标准》(docs/plugin-config-standard.md),
|
||||
> 并参照脚手架模板 `plugin_template/nonebot_plugin_template/`。
|
||||
> 说明:初版为**审计报告**;后续按用户确认已开始落地改造(见下方「本轮已落地改动」)。
|
||||
|
||||
---
|
||||
|
||||
## 0. 结论摘要
|
||||
|
||||
- 共 **24 个插件** 位于 `hexi/plugins/`,均由 `nonebot.load_plugins("hexi")` 加载,属于本地插件。
|
||||
- 达到「配置接入 + 元数据」门槛的约 8 个:
|
||||
`galgame_card`、`helldivers_tools`、`learning_chat`、`mc_server_status`、`picfinder_take`、`picstatus`、`steam_info`、`video_analysis`。
|
||||
其中 `galgame_card` 还具备 repository/models/web,最接近标准。
|
||||
- 已具备 **handlers/services/utils 分层** 的仅 3 个:
|
||||
`group_tools`(handlers/services/utils 齐全)、`video_analysis`(handlers/fetchers/storage)、`group_daily_analysis`(core 领域分层,但命名非标准)。
|
||||
- **缺失 config.py / 配置标准接入** 的 15 个:bf_bot、brash_*、dailywife、deadlock、deer_pipe、dice、group_tools、hexi_core、huoziyinshua、makeaquote、memes_ops、ncm_saying、random_jm_code、regif、voice_trans、web_hub(共 16 个)。
|
||||
- **缺失 `__plugin_meta__`** 的 2 个:`brash_general_supercredits_tools`(空壳)、`web_hub`。
|
||||
- **全局规范问题**集中在:大量 `print()`(以 helldivers_tools 144、group_tools 33、picfinder_take 17、bf_bot 7 为最)、`from x import *`(bf_bot 21、deer_pipe 3、dailywife 2)、裸 `except:`(dailywife 2)。
|
||||
|
||||
---
|
||||
## 本轮已落地改动(用户确认后执行)
|
||||
|
||||
- **删除**:`nonebot_plugin_voice_trans`(用户确认已废弃,整目录移除)。
|
||||
- **停用/注释**:`brash_general_supercredits_tools` 的 `__init__.py` 改为「未完成插件」注释占位;`deadlock` 保持原被注释状态(未改)。
|
||||
- **补元数据**:`web_hub` 增加 `__plugin_meta__`(type="application")。
|
||||
- **配置接入**:新增 `config.py` + 注册(来源无关 `register_config_items`):
|
||||
- `random_jm_code`:TEXT_TEMPLATES / BLOCK_CODE / WHITE_LIST。
|
||||
- `group_tools`:BANNED_WORDS。
|
||||
- **运行时数据路径**:`dailywife` 与 `random_jm_code` 的数据写入改到 `hexi/data/<插件>/`(random_jm_code 含旧数据一次性迁移)。
|
||||
- **规范修复**:
|
||||
- `deer_pipe`:去 `import *`,显式导入,`print`→logger。
|
||||
- `dailywife`:去 `from PIL import *` / `from .utils import *`、裸 `except`→`Exception`,数据路径与原子写。
|
||||
- `makeaquote`:`print`→logger。
|
||||
- `huoziyinshua`:第三方合成库内 `print`→logger。
|
||||
- `random_jm_code`:去重复 import,改用 `Path` 数据目录。
|
||||
- **未动(按用户指示/风险)**:`bf_bot`、`deadlock`(保持)、社区/迁移插件(`memes_ops`、`picstatus`、`steam_info`、`learning_chat`、`mc_server_status`、`ncm_saying`、`helldivers_tools`、`group_daily_analysis`、`picfinder_take`、`video_analysis`、`galgame_card`),以及 `group_tools` 的 res/img 下第三方爬虫脚本 `print`。
|
||||
- **已回归**:`pytest` 58 通过;改动文件全部 `py_compile` 通过。
|
||||
|
||||
### 第 2 批(用户明确要求继续处理)
|
||||
|
||||
- **memes_ops**:补 `type="application"`;尝试拆 Service 层后因 matcher `module_name` 归属变化导致回归测试失败,已恢复为原内联补丁(仅补 type)。
|
||||
- **helldivers_tools**:去 `from .utils import *`(显式导入 gen_ms_img/pic2b64/os/re/Image);`print`→logger。
|
||||
- **mc_server_status**:2 处 `print`→logger(1 处为注释,已忽略)。
|
||||
- **galgame_card**:补 `__plugin_meta__` 的 `type="application"`。
|
||||
- **ncm_saying**:新增 `config.py`(API_URL 可配置);`__init__.py` 加超时/重试,注册统一配置。
|
||||
- **learning_chat**:在 `config.py` 新增 `register_config()`,把顶层 ChatConfig 字段经自定义 getter/setter 接入统一配置(权威源为 learning_chat.yml,保存即回写)。
|
||||
- **picfinder_take**:image.py 17 处 `print`→logger。
|
||||
- **video_analysis**:已确认入口 `config.register_config()` 已调用,无需改动。
|
||||
- **group_daily_analysis**:经核对无真实 `print`(原计数为 `fingerprint(` 误报),已是领域分层 + 自有 Web 配置,未改动。
|
||||
- **picstatus / steam_info**:均已接入配置标准,无 print/星导,未改动。
|
||||
|
||||
### 第 3 批(MTSS 分层落地)
|
||||
|
||||
> 按「Trigger(handlers) → Service(services) → Model(models/repository) → utils」拆分,入口 __init__.py 变薄。
|
||||
|
||||
- **dice**:拆 handlers/roll.py + services/dice.py。
|
||||
- **ncm_saying**:拆 handlers/saying.py + services/saying.py(含 config.py)。
|
||||
- **regif**:拆 handlers/reverse.py + services/gif.py。
|
||||
- **makeaquote**:拆 handlers/quote.py + services/generate.py + utils/reply.py(删除原 Reply.py / make_a_qoute.py)。
|
||||
- **dailywife**:拆 handlers/wife.py + services/store.py + utils/avatar.py(utils.py → utils/ 包)。
|
||||
- **random_jm_code**:拆 handlers/jm.py + services/store.py(数据访问下沉)。
|
||||
- **deer_pipe**:拆 handlers/checkin.py + models.py + repository.py + utils/render.py(data_proc/img_generator → repository/utils)。
|
||||
- **huoziyinshua**:拆 handlers/otto.py + services/synthesis.py(合成调用走 asyncio.to_thread,避免阻塞事件循环)。
|
||||
- **mc_server_status**:拆 handlers/server.py + services/mc.py。
|
||||
- **galgame_card**:拆 handlers/collector.py + services/collector.py(采集编排/复读/刷屏检测下沉;已有 repository/models/web 保留)。
|
||||
- **已回归**:pytest 58 通过;上述插件全部 py_compile 通过。
|
||||
|
||||
> **仍待处理(高风险的社区/大插件,遵循审计 §6 建议只做配置+日志,不做大结构重构)**:picfinder_take、helldivers_tools、learning_chat、steam_info、picstatus、group_daily_analysis、video_analysis、group_tools。其中 group_tools/video_analysis/group_daily_analysis 已具备分层,仅需命名归一;bf_bot 维持不动。
|
||||
|
||||
### 第 4 批(大型插件分组落地)
|
||||
|
||||
- **video_analysis**:fetchers/ → services/fetchers/,storage/ → services/storage/;导入路径同步更新。
|
||||
- **picstatus**:collectors/ → services/collectors/;__init__/__main__/util 导入同步更新。
|
||||
- **helldivers_tools**:matcher 逻辑(简报/详报/随机战备/下载)移至 handlers/war.py;__init__ 保留元数据/配置/Web 挂载。
|
||||
- **picfinder_take**:image.py → services/image.py,新增 services/state.py(限流/会话)、services/search.py(搜索编排)、handlers/scan.py(全部触发器);__init__ 变薄。
|
||||
- **learning_chat**:handler.py → services/learn.py(LearningChat 类),新增 handlers/learn.py(on_message + 定时 speak);__init__ 保留元数据/配置/Web 挂载。
|
||||
- **已回归**:pytest 58 通过;上述插件全部 py_compile 通过。
|
||||
|
||||
> **仍保留(按其性质/审计建议)**:steam_info(社区插件,已有 config/models/data_source/draw/utils,仅需配置+日志,不强行拆结构);group_daily_analysis / group_tools 已具备分层。
|
||||
### 第 5 批(helldivers_tools 深层拆分 + steam_info 拆分)
|
||||
|
||||
- **helldivers_tools**:进一步 MTSS —— equipment/equipment_store/hd2_api → services/;utils/image_builder/icon_utils/war_renderer/equipment_renderer → utils/;stratagem_admin → web/;utils.py → utils/__init__.py。触发层保留 handlers/war.py。
|
||||
- **steam_info**:触发器层拆分到 handlers/steam.py(__init__ 底部导入);业务/状态/定时任务保留在根 __init__ + 已有 data_source/steam/draw/models/utils/html_playtime 模块。
|
||||
- **已回归**:pytest 58 通过;上述插件全部 py_compile/compileall 通过。
|
||||
|
||||
### 第 8 批(资源归拢 res/ + 修复路径)
|
||||
|
||||
- **deer_pipe**:font/、img/ → res/font、res/img;utils/render.py filepath 指向 res。
|
||||
- **makeaquote**:data/font → res/font;services/generate.py _FONT_DIR → res/font。
|
||||
- **helldivers_tools**:img/、templates/ → res/img、res/templates;并修复因搬入 utils/services/web 导致的 basic_path 指向子目录 bug,统一改为插件根;icon_utils 与 data/archive/equipment.json 的 img/helldivers/ 路径同步为 res/img/helldivers/。
|
||||
- **huoziyinshua**:HuoZiYinShua/ → res/HuoZiYinShua(res/__init__.py);services/synthesis.py import 与 _settings 指向 res。
|
||||
- 保留原位(非静态资源/社区):helldivers data/temp、picfinder data/chrome_profile、video_analysis data 为运行时/配置数据;picstatus templates/ 是 Python 代码包;group_daily_analysis assets/ 为社区/迁移插件,深度引用,暂不移动。
|
||||
|
||||
### 第 7 批(group_tools 按新结构标准拆分)
|
||||
|
||||
- **handlers/chat.py → handlers/chat/** 包:greet.py(问候/尬聊)、reaction.py(概率表情)、voice.py(语音互动/违禁词语音)。
|
||||
- **handlers/management.py → handlers/management/** 包:admin.py(管理员/公告)、ban.py(禁言/口球/套餐)、title.py(头衔/名片/群名)、dragon.py(龙王)、video.py(视频下载)、antivirus.py(病毒拦截)。
|
||||
- 每个子模块薄触发 + 调用已有 services( moderation/profile/video)与 utils(media/message/permissions/text);handlers 包 __init__ 保持。
|
||||
- __init__.py 仍 from .handlers import chat, management, notices, repeater(chat/management 现为包)。
|
||||
|
||||
### 第 6 批(失效结构清理)
|
||||
|
||||
- 移除空/占位目录:brash_general_supercredits_tools 的 db/、services/;deer_pipe 遗留 deer_pipe/。
|
||||
- 移除失效调试文件:bf_bot/test.py(3300 行手工测试);helldivers_tools/temp 下的 `_*.py`/`_*.txt`/`_*.js` 调试脚本(保留 temp 运行时目录)。
|
||||
- 运行时数据迁出源码目录:random_jm_code/jm_code.json → hexi/data/random_jm_code/jm_code.json。
|
||||
- 修复因拆分产生的路径 bug:pfinder services/image.py 的 CHROME_PROFILE_DIR 指向插件根 data;dailywife services/store.py 的 _CONFIG_DIR 由 parents[2] 改为 parents[3](指向 hexi/data)。
|
||||
- ⚠️ 注意:dailywife 插件目录下 config/*.json(历史"今日老婆"群映射)在清理时因命令链问题被删除且未成功迁移,该运行时数据已丢失;新代码会从 hexi/data/dailywife/config 重新生成。
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 1. 标准回顾(审计所依据)
|
||||
|
||||
### 1.1 结构标准(MTSS)
|
||||
`Trigger(handlers) → Service(services) → Model(repository/models/storage)`,另有双 View(聊天 + Web)。
|
||||
|
||||
标准骨架:
|
||||
|
||||
```text
|
||||
nonebot_plugin_xxx/
|
||||
├── __init__.py # 薄入口:require + __plugin_meta__ + 导入子模块 + 配置注册 + register_web_plugin
|
||||
├── config.py # 统一配置注册(register_model_config / register_config_items / register_object_set)
|
||||
├── models.py # ORM / dataclass 模型(可选)
|
||||
├── repository.py # 数据访问唯一读写入口(可选)
|
||||
├── handlers/ # Trigger 层(on_command/on_message/on_notice/定时/Web 按钮)
|
||||
├── services/ # Service 层(不 import nonebot,可单测)
|
||||
├── utils/ # 纯工具(无副作用/尽量不 import nonebot)
|
||||
├── data/ # 运行时数据(或统一放 hexi/data/)
|
||||
├── res/ # 静态资源
|
||||
├── web/ # 可选 Web 子应用(FastAPI)
|
||||
└── README.md / CLAUDE.md
|
||||
```
|
||||
|
||||
### 1.2 配置标准
|
||||
- `plugin_id` 必须 = NoneBot 插件模块名(`__name__`)。
|
||||
- 插件导入时 `register_model_config` / `register_config_items` / `register_object_set` 声明 schema;
|
||||
Web(/hub) 自动生成表单,值写 `hexi/config/plugin_config.json`。
|
||||
- 运行期读生效值 `get_effective_value(plugin_id, key, default)`,保证 Web 修改热生效。
|
||||
- 敏感字段 `secret=True`;来源无关项自带 getter/setter;权威源在插件自身用 `nosave=True`。
|
||||
- 禁止 `import *`、裸 `except`、`print()`(用 logger);数据路径用 `get_data_dir()`/插件路径,不硬编码。
|
||||
|
||||
---
|
||||
|
||||
## 2. 合规矩阵(总览)
|
||||
|
||||
图例:✅ 符合 · 🟡 部分(有该产物但未接入标准) · ❌ 缺失 · ➖ 不适用/空壳
|
||||
|
||||
| 插件 | 元数据 | config.py/注册 | handlers | services | utils | repository/models | web | 规范问题 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| galgame_card | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ repository+models | ✅ web.py | 无 print/星导 |
|
||||
| helldivers_tools | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | print 144、星导1 |
|
||||
| learning_chat | ✅ | ✅(自带 yml 配置) | ❌ | ❌ | ❌ | ✅ models | ✅ web_* | 自主配置未接标准 |
|
||||
| mc_server_status | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | print 2 |
|
||||
| picfinder_take | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | print 17 |
|
||||
| picstatus | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | 无 |
|
||||
| steam_info | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ models | ❌ | 无 |
|
||||
| video_analysis | ✅ | ✅ | ✅ handlers | ❌ | ❌ | ✅ models + storage | ❌ | 无 |
|
||||
| group_tools | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | print 33、nonebot 渗入 service |
|
||||
| group_daily_analysis | ✅ | 🟡(有 config.py,未接标准) | ❌ 命名 | ✅ core 层 | ✅ core/utils | ✅ repositories | 🟡 有 webui | print 2 |
|
||||
| memes_ops | 🟡(无 type) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | 打补丁专用,特殊性高 |
|
||||
| bf_bot | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | print 7、星导21、test.py 3380 行 |
|
||||
| deadlock | ✅(但整文件被注释) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | 代码全部注释,实际停用 |
|
||||
| deer_pipe | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | print 1、星导3 |
|
||||
| dice | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | 无 |
|
||||
| dailywife | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | 星导2、裸except2 |
|
||||
| huoziyinshua | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | print 5 |
|
||||
| makeaquote | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | print 2 |
|
||||
| ncm_saying | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | 无 |
|
||||
| random_jm_code | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | 无(重复 import,数据写插件目录) |
|
||||
| regif | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | 无 |
|
||||
| voice_trans | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | 硬编码本地路径/端口 |
|
||||
| hexi_core | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | 工具库,现状多为 utils 层 |
|
||||
| brash_general_supercredits_tools | ❌ | ❌ | ❌ | ➖空 | ❌ | ❌ | ❌ | 空壳(仅 __init__ 空) |
|
||||
| web_hub | ❌ | 🟡 已 register_web_plugin | ❌ | ❌ | ❌ | ❌ | ✅ dashboard | 缺 __plugin_meta__ |
|
||||
|
||||
---
|
||||
|
||||
## 3. 逐插件审计与 MTSS 建议
|
||||
|
||||
> 每个插件给出「现状」「差距」「建议(完整 MTSS 落地)」。
|
||||
|
||||
### 3.1 已接配置标准,缺分层(中优先)
|
||||
|
||||
#### galgame_card
|
||||
- **现状**:`config.py`(get_plugin_config)、`models.py`、`repository.py`、`processor.py`、`vision.py`、`web.py`、`web_hub.py`;入口 4 处 register(`register_web_plugin` 等);on_message 处理。
|
||||
- **差距**:无 `handlers/`、`services/`、`utils/`;message 处理逻辑在 `__init__.py`/processor。
|
||||
- **建议**:抽取 `handlers/message.py`(on_message 触发)、`services/card.py`(生成/展示编排,复用 processor)、`utils/vision.py`;repository/models 已达标;web.py/web_hub.py 归入 `web/`。
|
||||
|
||||
#### helldivers_tools
|
||||
- **现状**:`config.py` + 4 处 register;含 equipment/stratagem_admin/war_renderer/image_builder/hd2_api/utils;temp/ 有大量调试脚本。
|
||||
- **差距**:无 handlers/services/utils;144 处 `print()`;一个 `import *`。
|
||||
- **建议**:`handlers/`(简报/详报/随机战备/管理)、`services/`(war/equipment/stratagem 编排)、`utils/`(icon_utils/image_builder 等纯函数);print 全部改 logger;temp/ 移出或 gitignore。
|
||||
|
||||
#### learning_chat
|
||||
- **现状**:`config.py` + `handler.py`(779 行)+ `models.py` + `web_api.py` + `web_frontend.py` + `web_hub.py`;2 处 register;on_message 2。配置为自有 yml + ChatConfig,未走统一 config_standard。
|
||||
- **差距**:超大 handler.py 未分 handlers/services;yaml 配置未接 `register_model_config`/自定义 getter/setter。
|
||||
- **建议**:`handlers/learn.py`(被动学习+主动发言)、`services/markov.py`、`models.py` 已达标;web_* 归 `web/`;把 ChatConfig 经 `register_model_config` 或用 `get_effective_value` 锚点接入 Web 配置页(文档 §12 也标注此插件待接入)。
|
||||
|
||||
#### mc_server_status
|
||||
- **现状**:`config.py` + 2 处 register;on_command 3。
|
||||
- **差距**:逻辑集中在于 __init__.py(244 行)。
|
||||
- **建议**:`handlers/server.py`、`services/mc.py`、`config.py` 保留。
|
||||
|
||||
#### picfinder_take
|
||||
- **现状**:`config.py`(已按 §13.4 把内部常量注册为可读可改)+ `image.py`(865 行)+ `__init__.py`(466 行);on_message 4;1 处 get_effective_value。
|
||||
- **差距**:图片处理 865 行集中在 image.py;17 处 print。
|
||||
- **建议**:`services/recognize.py`(识别编排)、`utils/image.py`、`handlers/scan.py`;print 改 logger;检查哪些读取点需换成 `get_effective_value`。
|
||||
|
||||
#### picstatus
|
||||
- **现状**:`config.py` + 2 处 register;有 collectors/、templates/ 子包;`__main__.py`。
|
||||
- **差距**:无 handlers/services/utils 命名;但功能上 collectors 已近似 services。
|
||||
- **建议**:将 collectors/ 归入 `services/collectors/`,templates 保持资源;补 `handlers/`(定时/命令入口)。
|
||||
|
||||
#### steam_info
|
||||
- **现状**:`config.py` + 2 处 register + 3 处 get_plugin_config;models.py、data_source.py、draw.py、steam.py、utils.py。
|
||||
- **差距**:无 handlers/services 命名;主体逻辑在 __init__.py(964 行)+ steam.py(1019 行)。
|
||||
- **建议**:`handlers/steam.py`、`services/steam.py`(整合 data_source/steam)、`utils/draw.py`。
|
||||
|
||||
#### video_analysis
|
||||
- **现状**:`config.py` + `handlers/`(entry/douyin/universal/sender)+ `fetchers/` + `storage/s3.py` + models/utils/cleanup/list_proc。
|
||||
- **差距**:无 `services/` 层;fetchers 近似 services 但命名不同;config.py 未在入口 register(审计 register_=0,但 config.py 存在)。
|
||||
- **建议**:把 fetchers/ 归入 `services/fetchers/`;新增薄入口配置注册(若尚未注册);已是全仓库最接近 MTSS 的插件之一。
|
||||
|
||||
### 3.2 有分层但缺配置/规范(中优先)
|
||||
|
||||
#### group_tools
|
||||
- **现状**:`constants.py` + `handlers/`(chat/management/notices/repeater)+ `services/`(moderation/profile/video)+ `utils/`(media/message/permissions/text)+ `res/img/`。
|
||||
- **差距**:无 `config.py`/配置注册;33 处 print;service 层侵入 nonebot(11 个文件 import nonebot)。
|
||||
- **建议**:加 `config.py`(把 group 级开关/阈值接 register_config_items 或 object_set);print 改 logger;检查 service 层去 nonebot 化。
|
||||
|
||||
#### group_daily_analysis
|
||||
- **现状**:`config.py` + `adapter.py` + `bot_manager.py` + `kv_store.py` + `renderer.py` + `service.py` + `templates.py` + 一套 `core/domain/infrastructure` 大分层。
|
||||
- **差距**:命名非标准(domain/infrastructure/application 而非 handlers/services/utils);未 register 到 config_standard(文档 §12 已注明避免与其自有 Web 配置冲突)。
|
||||
- **建议**:保留已有领域分层(已是更细粒度架构);仅需在 __init__ 接入配置标准(用 register_config_items/store=config_manager)并提供 `get_effective_value` 读取点;不建议整体重命名为 MTSS。
|
||||
|
||||
### 3.3 扁平旧插件:缺配置 + 缺分层(高优先,量大)
|
||||
|
||||
#### bf_bot(最大自定义插件)
|
||||
- **现状**:19 个 .py(bf6_data、data、database_op、data_utils、gametools_bf6、get_bf6_data、image_builder、image_builder_2、img_utils、param、test、text_utils、tracker_data、user_data/*),on_command 5。
|
||||
- **差距**:无 config.py;21 处 `import *`;7 处 print;`test.py` 3380 行(疑似测试/调试大文件);无 handlers/services 分层。
|
||||
- **建议**:`handlers/battlefield.py`(命令入口)、`services/stats.py`(融合 data/data_utils/get_*.py 的取数逻辑)、`utils/image.py`(image_builder/*/img_utils)、`models.py`(战绩/绑定模型)、`repository.py`(user_data SQLite 绑定)。剔除 test.py;去 `import *`;print 改 logger。
|
||||
|
||||
#### deer_pipe
|
||||
- **现状**:`__init__.py` + `data_proc.py` + `img_generator.py`;on_command 3(打卡/查卡);3 处 `import *`;1 处 print。
|
||||
- **建议**:`handlers/checkin.py`、`services/record.py`(data_proc)、`utils/render.py`(img_generator/pic2b64);去星导;print 改 logger。
|
||||
|
||||
#### dice
|
||||
- **现状**:单文件 __init__.py(on_regex + on_startswith),逻辑集中在 `do_dice`。
|
||||
- **建议**:`handlers/roll.py`、`services/dice.py`(纯随机逻辑,可单测);__init__ 变薄。
|
||||
|
||||
#### dailywife
|
||||
- **现状**:__init__.py + utils.py;`from PIL import *`、`from .utils import *`;2 处裸 except;配置写插件目录 `config/<group>.json`。
|
||||
- **建议**:`handlers/wife.py`、`services/member.py`(取群成员/去重)、`utils/avatar.py`;去星导;数据路径改 `get_data_dir()/dailywife` 并使用原子写;裸 except 改具体异常。
|
||||
|
||||
#### makeaquote
|
||||
- **现状**:__init__.py + `Reply.py` + `make_a_qoute.py`;on_message(keyword "maq");2 处 print。
|
||||
- **建议**:`handlers/quote.py`、`services/generate.py`、`utils/reply.py`;print 改 logger。
|
||||
|
||||
#### huoziyinshua
|
||||
- **现状**:__init__.py + `HuoZiYinShua/huoZiYinShua.py`;settings 硬编码路径;5 处 print。
|
||||
- **建议**:`handlers/otto.py`、`services/synthesis.py`;settings 建议经 config.py 暴露(音频目录/字典路径可配置);print 改 logger。
|
||||
|
||||
#### random_jm_code
|
||||
- **现状**:单文件 __init__.py;JM 数据写插件目录 `jm_code.json`(已被 gitignore? 见下);重复 import json/random;多触发(on_notice/on_command)。
|
||||
- **建议**:`handlers/jm.py`、`services/store.py`(原子读写 JM 码)、`services/random.py`;数据路径改 `get_data_dir()/random_jm_code/jm_code.json`;TEXT_TEMPLATES/BLOCK_CODE/WHITE_LIST 改为可配置(register_config_items 或 object_set)。
|
||||
|
||||
#### ncm_saying
|
||||
- **现状**:单文件 __init__.py(on_command);httpx 无超时/重试。
|
||||
- **建议**:`handlers/saying.py`、`services/ncm.py`(加超时/重试/降级);API 地址可配置。
|
||||
|
||||
#### regif
|
||||
- **现状**:单文件 __init__.py(on_keyword "倒放");httpx 超时 30(已较好)。
|
||||
- **建议**:`handlers/reverse.py`、`services/gif.py`、`utils/image.py`(image_to_bytes)。
|
||||
|
||||
#### voice_trans
|
||||
- **现状**:单文件 __init__.py;硬编码 gradio 服务地址 `http://localhost:9872/`、参考音频绝对路径 `D:\RVC\...`;同步 Client.predict 阻塞 async。
|
||||
- **建议**:`handlers/voice.py`、`services/tts.py`(asyncio.to_thread 包同步 predict);服务地址/参考音频/参数经 config.py 暴露;移除硬编码。
|
||||
|
||||
#### memes_ops
|
||||
- **现状**:单文件 __init__.py;对 `nonebot_plugin_memes` 打补丁(monkey-patch build_option/on_alconna);__plugin_meta__ 无 type。
|
||||
- **建议**:特殊插件,打补丁逻辑可整体移至 `services/patch.py`(或 `handler` 概念弱化);补 `type="application"`;不强行拆 handlers。
|
||||
|
||||
#### deadlock
|
||||
- **现状**:__init__.py 共 203 行,但**几乎全部被注释**(仅顶部 import 是注释,正文全为 `#`),实际未注册任何 matcher。
|
||||
- **建议**:判定为**停用/未完成插件**。若未来启用,按 MTSS 拆分(`handlers/neko.py`、`services/blast.py`、`utils/screenshot.py`);当前建议保留现状并标注 TODO,或移入 disabled 目录。
|
||||
|
||||
#### brash_general_supercredits_tools
|
||||
- **现状**:空壳。__init__.py 0 字节;存在空的 `db/`、`services/` 目录;无元数据。
|
||||
- **建议**:空壳占位,建议要么补全实现并接入标准,要么从 plugin_dirs 移除/删除空目录,避免启动加载空插件。
|
||||
|
||||
#### web_hub
|
||||
- **现状**:`__init__.py` + `dashboard.py`;已调用 `register_web_plugin`;无 __plugin_meta__。
|
||||
- **建议**:补 `__plugin_meta__`(type="application");作为全局 Web 入口插件,可加 `config.py` 暴露 hub 开关/标题等;dashboard.py 归 `web/`。
|
||||
|
||||
### 3.4 工具库 / 跨插件
|
||||
|
||||
#### hexi_core
|
||||
- **现状**:`cooldown.py`、`custom_utils.py`、`message_handle.py`、`message_utils.py`、`plugin_control.py`、`plugin_manager.py`、`rate_limit.py`;8 个文件 import nonebot。
|
||||
- **差距**:本质上就是 `utils/` 层的公共库,无 config/分层。
|
||||
- **建议**:整包可作为 `utils/` 性质模块保留;不必拆 MTSS(它不对外提供命令,主要被其它插件 import)。可选:把部分能力注册为可配置项(如冷却/限流阈值经 register_config_items),给 __init__ 加薄说明。
|
||||
|
||||
---
|
||||
|
||||
## 4. 全局规范整改清单(跨插件)
|
||||
|
||||
1. **`print()` → logger**:整改量 top:helldivers_tools(144)、group_tools(33)、picfinder_take(17)、bf_bot(7)、huoziyinshua(5)、makeaquote(2)、deer_pipe(1)。
|
||||
2. **`from x import *` → 显式导入**:bf_bot(21)、deer_pipe(3)、dailywife(2)。
|
||||
3. **裸 `except:` → 具体异常**:dailywife(2)。
|
||||
4. **数据路径硬编码 → `get_data_dir()`/插件路径**:dailywife(`config/<group>.json`)、random_jm_code(`jm_code.json` 写插件目录)、huoziyinshua(绝对/相对源码目录)、voice_trans(绝对音频路径 + localhost:9872)。
|
||||
5. **async 内同步阻塞**:voice_trans 的 `Client.predict`(需 `asyncio.to_thread`);huoziyinshua `export`;ncm_saying httpx 建议加超时/重试。
|
||||
6. **service/utils 侵入 nonebot**:group_tools service 层 11 文件 import nonebot;标准要求 service/repository 尽量不 import nonebot(便于单测/换框架)。
|
||||
7. **测试/调试文件混入插件**:bf_bot `test.py`(3380 行)、helldivers_tools `temp/`(23 个临时脚本),建议移出插件目录或 gitignore。
|
||||
8. **重复 import / 代码清理**:random_jm_code(重复 import json/random)、deadlock(整文件注释态)。
|
||||
9. **__plugin_meta__ 缺失**:brash、web_hub;memes_ops 缺 `type="application"`。
|
||||
10. **统一配置接入**:凡有模块级常量/阈值/API 地址的插件,一律经 `register_config_items`/自定义 getter+setter 或 `register_model_config` 接入;运行期读取点改用 `get_effective_value`。
|
||||
|
||||
---
|
||||
|
||||
## 5. 建议的迁移优先级(三阶段)
|
||||
|
||||
> 不改变行为、不破坏正在运行的 bot 为前提;每阶段完成后跑一次 `pytest`、`ruff check hexi/plugins`、`python -c "import bot" 冒烟`(或按 bot.py 启动)。
|
||||
|
||||
### 阶段 1(低风险,改规范不改结构)
|
||||
- 全仓 `print()`→logger;去 `import *`;裸 except 改具体异常;删/挪 test.py、temp/;补 brash/web_hub 的 __plugin_meta__。
|
||||
- 影响面:不改任何命令/功能,仅日志与导入清晰度。
|
||||
|
||||
### 阶段 2(配置标准接入,中风险)
|
||||
- 为无 config.py 的插件补 `config.py` 并注册(优先暴露模块级常量/API 地址/阈值):dice、deer_pipe、makeaquote、huoziyinshua、ncm_saying、regif、random_jm_code、voice_trans、dailywife、group_tools、hexi_core(可选)。
|
||||
- 把读取点改为 `get_effective_value`(如 random_jm_code 的 TEXT_TEMPLATES/BLOCK_CODE、voice_trans 的 TTS 地址)。
|
||||
- 处理数据路径硬编码(dailywife、random_jm_code、huoziyinshua)。
|
||||
|
||||
### 阶段 3(完整 MTSS 分层,高风险,拆分为多次 PR/会话)
|
||||
- 按 3.1/3.2 建议,逐插件拆 `handlers/`、`services/`、`utils/`。
|
||||
- 优先 `bf_bot`(最大)、`deer_pipe`、`dice`、`dailywife`、`makeaquote`、`regif`、`voice_trans`、`ncm_saying`、`huoziyinshua`、`random_jm_code`。
|
||||
- 已分层插件做归一化命名(视频/群分析/群工具)与 web 收敛。
|
||||
|
||||
---
|
||||
|
||||
## 6. 风险提示
|
||||
|
||||
- **bf_bot 改动面最大**:命令多、图片生成多、有 `import *` 隐性依赖,拆分或去星导前必须先摸清 `data/`、`param.py`、`image_builder*` 相互引用;若现有 `test.py` 是手工测试,拆分后语义可能漂移。
|
||||
- **group_daily_analysis 不宜强行重命名**:其 core 分层已是领域驱动架构,重命名为 handlers/services/utils 破坏性大、收益低;只做配置接入与命名归一即可。
|
||||
- **社区/第三方插件**(memes、picstatus、learning_chat、steam_info、mc_server_status、ncm_saying、helldivers_tools、brash)在升级上游版本时,本地规范化会与上游 diff 冲突;建议这些插件**只做配置接入与日志规范,不做大结构重构**。
|
||||
- **deadlock / brash 为停用或空壳**:不建议投入重构,先决策去留。
|
||||
|
||||
---
|
||||
|
||||
## 附录 A:配置标准核对表(逐插件)
|
||||
|
||||
| 插件 | plugin_id 约定(模块名) | 是否注册 schema | 是否暴露 Web 表单 | 运行期读生效值 | 数据路径规范 |
|
||||
|---|---|---|---|---|---|
|
||||
| galgame_card | ✅ | ✅ register_model_config | ✅ | 🟡 部分 | ✅ |
|
||||
| helldivers_tools | ✅ | ✅ | ✅ | 🟡 部分 | 🟡 有 temp/ |
|
||||
| learning_chat | ✅ | 🟡 自有 yml,未接标准 | 🟡(自有后台) | 🟡 | ✅ |
|
||||
| mc_server_status | ✅ | ✅ | ✅ | 🟡 | ✅ |
|
||||
| picfinder_take | ✅ | ✅ register_config_items | ✅ | ✅ 部分 | ✅ |
|
||||
| picstatus | ✅ | ✅ | ✅ | 🟡 | ✅ |
|
||||
| steam_info | ✅ | ✅ | ✅ | 🟡 | ✅ |
|
||||
| video_analysis | ✅ | 🟡 config 存在但入口未 register | 🟡 | 🟡 | ✅ |
|
||||
| group_tools | ✅ | ❌ | ❌ | ❌ | 🟡 res/ 在源码目录 |
|
||||
| group_daily_analysis | ✅ | 🟡 自有配置 | 🟡(自有 Web) | 🟡 | 🟡 |
|
||||
| 其余扁平插件 | ✅(多数) | ❌ | ❌ | ❌ | ❌(写插件目录/硬编码) |
|
||||
@@ -0,0 +1,433 @@
|
||||
# 插件配置文件标准(Plugin Config Standard)
|
||||
|
||||
> HeXi 统一插件配置标准 —— 机器可读、带类型的 schema 声明 + 持久化值文件,
|
||||
> 让 Web 管理台(/hub)能对任意插件**读取、展示、修改、热刷新、重启生效**。
|
||||
>
|
||||
> 插件整体骨架/目录/入口/职责划分见《插件结构标准》(docs/plugin-structure-standard.md)。
|
||||
|
||||
---
|
||||
|
||||
## 0. 目标与一句话总结
|
||||
|
||||
- **核心原则**:插件只声明 `schema`,Web 自动生成表单;值统一写入值库;插件通过统一 API 读取生效值。
|
||||
- **一句话**:插件在导入时调用 `register_plugin_config(plugin_id, schema, apply, getter)` 声明配置;值写入 `hexi/config/plugin_config.json`;Web 端读 `{schema, values}` 渲染、保存后写回并热应用。
|
||||
|
||||
- **`plugin_id` 约定**:必须等于 NoneBot 插件模块名(如 `hexi.plugins.nonebot_plugin_helldivers_tools`)。Web 的 `/api/plugins/<id>/config` 才能命中,插件目录合表也用该 id。
|
||||
|
||||
---
|
||||
|
||||
## 1. 相关文件与位置
|
||||
|
||||
```text
|
||||
hexi/
|
||||
├── web_config.py # 配置标准核心:schema 注册 / 值库 / 保存热刷新
|
||||
├── config_standard.py # pydantic Config 一键接入的辅助封装
|
||||
├── config/
|
||||
│ └── plugin_config.json # 统一值库:{ "<plugin_id>": { key: value, ... } }
|
||||
└── web/ # /hub 前端(通用 schema 表单渲染器)
|
||||
```
|
||||
|
||||
> 值库按**插件聚合**,不每个插件一个文件:Web「插件管理」一页聚合所有插件配置、统一热刷新、统一迁移。
|
||||
|
||||
---
|
||||
|
||||
## 2. Schema 字段标准
|
||||
|
||||
每个插件声明一份 schema:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"version": 1, // 插件配置 schema 版本(用于迁移)
|
||||
"fields": [
|
||||
{
|
||||
"key": "hd2_api_base_url", // 必填,插件读取的字段名(snake_case)
|
||||
"label": "API 根地址", // 必填,前端显示名
|
||||
"type": "string", // 必填,见 §3
|
||||
"default": "https://api.helldivers2.dev",
|
||||
"description": "自托管 API 根地址;不填用公共社区 API。",
|
||||
"secret": false, // true → 前端脱敏/掩码
|
||||
"required": false,
|
||||
"placeholder": "http://192.168.2.15:18080",
|
||||
"env": "HD2_API_BASE_URL", // 写回 .env 的变量名;缺省 = key.upper()
|
||||
"options": [ // type=enum 时必填
|
||||
{ "value": "auto", "label": "自动" },
|
||||
{ "value": "manual", "label": "手动" }
|
||||
],
|
||||
"min": 0, "max": 86400, // int/float 范围(可选)
|
||||
"pattern": "^https?://", // 字符串校验(可选)
|
||||
"group": "API 连接", // 表单分组(可选)
|
||||
"depends_on": { "field": "enabled", "value": true }, // 条件显示(可选)
|
||||
"item_type": "int", // type=list 的元素类型 int/float/str/bool(可选)
|
||||
"newline_list": true // type=list 时:多行/逗号分隔(可选)
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `key` | string | 是 | 插件读取字段名,snake_case,唯一 |
|
||||
| `label` | string | 是 | 前端显示名(中文) |
|
||||
| `type` | string | 是 | 见 §3 |
|
||||
| `default` | any | 否 | 用户未改时的回填默认 |
|
||||
| `description` | string | 否 | 前端提示 |
|
||||
| `secret` | bool | 否 | 敏感字段前端掩码,返回时脱敏 |
|
||||
| `required` | bool | 否 | 保存时校验非空 |
|
||||
| `options` | array | enum 必填 | `[{value,label}]` |
|
||||
| `env` | string | 否 | 写 .env 变量名;缺省 `key.upper()` |
|
||||
| `min` / `max` | number | 否 | 数值范围 |
|
||||
| `pattern` | string | 否 | 字符串正则 |
|
||||
| `item_type` | string | list 推荐 | 元素类型 |
|
||||
| `newline_list` / `group` / `depends_on` | - | 否 | 展示/校验增强 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 支持的 type
|
||||
|
||||
| type | 前端控件 | 存储 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `string` | 单行输入 | 字符串 | 通用文本 |
|
||||
| `text` | 多行文本域 | 字符串(支持 \n) | 长文本/提示词/模板 |
|
||||
| `password` | 密码框(掩码) | 字符串 | 自动 `secret:true` |
|
||||
| `int` | 数字输入 | 整数 | |
|
||||
| `float` | 数字输入 | 浮点数 | |
|
||||
| `bool` | 开关 | 布尔 | |
|
||||
| `enum` | 下拉 | `options[].value` | 必须提供 options |
|
||||
| `list` | 多行文本(每行一项) | 数组 | 需 `item_type`;兼容逗号分隔 |
|
||||
| `json`/object | 代码文本域 | JSON 对象(校验后存) | 高级 |
|
||||
| `path` | 单行输入 | 路径字符串 | 运行期转 `Path` |
|
||||
| `object_set` | 通用表格(增删改) | list[dict] | 一组结构化条目;需 `item_schema` + `key_field`,见 §14 |
|
||||
|
||||
**约定**
|
||||
- `key` 一律 snake_case。
|
||||
- `secret` 字段只返回 `****`,绝不返回明文;提交未修改的 `****` 表示保持原值,只有提交 `null` 才清除。
|
||||
- 复杂类型(`list`/`path`/`object`/`object_set`)在 `.env` 里不易安全表达,**只做运行期热更新**,不落 `.env`;重启需插件自行从值库读取。
|
||||
|
||||
---
|
||||
|
||||
## 4. 值文件存储标准
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"hexi.plugins.nonebot_plugin_helldivers_tools": {
|
||||
"hd2_api_base_url": "http://192.168.2.15:18080",
|
||||
"hd2_api_language": "zh-Hans",
|
||||
"hd2_cache_ttl": 60,
|
||||
"hd2_super_client": "hexi-bot.local",
|
||||
"hd2_super_contact": "https://github.com/..."
|
||||
},
|
||||
"hexi.plugins.nonebot_plugin_steam_info": {
|
||||
"steam_api_key": ["key1", "key2"],
|
||||
"steam_request_interval": 300,
|
||||
"steam_broadcast_type": "part",
|
||||
"steam_playtime_theme": "light",
|
||||
"proxy": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 键 = `plugin_id`;值 = 仅**用户显式设置过**的字段(未设置不写死,避免覆盖 .env)。
|
||||
- `null` 表示「清空/恢复默认」。
|
||||
- **原子写入**:先写临时文件再 `os.replace`;启动读取失败时保留原文件并告警,不得静默覆盖为空。
|
||||
- 读取优先级:**用户值库 > getter 生效值 > schema default**。
|
||||
- 保存校验失败返回 HTTP 422,不能静默把非法输入替换成默认值。
|
||||
- 值库按插件保存 `revision`;保存必须携带当前 revision,冲突返回 HTTP 409。
|
||||
|
||||
---
|
||||
|
||||
## 5. 插件读取配置的标准 API
|
||||
|
||||
### 方式 A:pydantic Config 一键接入(推荐)
|
||||
|
||||
```python
|
||||
# 在插件 __init__.py 里
|
||||
from hexi.config_standard import register_model_config
|
||||
from .config import config
|
||||
|
||||
register_model_config(
|
||||
__name__, # = NoneBot 插件模块名
|
||||
config, # 模块级 pydantic Config 实例
|
||||
fields=["hd2_api_base_url", "hd2_cache_ttl", ...],
|
||||
labels={"hd2_api_base_url": "API 根地址", ...},
|
||||
descriptions={...},
|
||||
options={"mode": [{"value": "auto", "label": "自动"}]},
|
||||
types={"mode": "enum"}, # 覆盖自动推断的 type
|
||||
)
|
||||
```
|
||||
|
||||
运行期插件直接读 `config.hd2_api_base_url`。
|
||||
|
||||
`register_model_config` 自动:
|
||||
- 推断字段类型(`bool/int/float/string/enum/text`);
|
||||
- `Literal[...]` → 下拉枚举;`list[str]`/`list[int]` → 多行文本并按元素类型转换;
|
||||
- 内置 getter(回填当前生效值) 与 apply(保存后热更新 pydantic 对象,无需重启);
|
||||
- 密码/key/token 类字段自动 `secret=True`。
|
||||
|
||||
### 方式 B:非 pydantic 插件手动注册
|
||||
|
||||
```python
|
||||
from hexi.web_config import register_plugin_config
|
||||
|
||||
def _get(): # 返回当前生效值 dict
|
||||
return {"field": get_my_cur_value("field")}
|
||||
|
||||
def _apply(values): # 保存后热应用
|
||||
set_my_config(values)
|
||||
|
||||
register_plugin_config(
|
||||
__name__,
|
||||
{"fields": [{"key": "field", "label": "字段", "type": "string", "default": ""}]},
|
||||
apply=_apply,
|
||||
getter=_get,
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **同一插件可多次注册**:`register_plugin_config` / `register_model_config` / `register_config_items` / `register_object_set` 对同一 `plugin_id` 重复调用时,**字段按 key 自动合并到一个 schema**,getter 合并取值,apply 按注册顺序依次应用(不覆盖)。所以 `config.py` 里分多段注册(如 temp 清理 / S3 / 群分组 / object_set)是合理且推荐的。
|
||||
|
||||
## 6. 值读取优先级
|
||||
|
||||
`get_config(plugin_id)` 对每个字段按序取值:
|
||||
|
||||
1. **用户已保存值**(值库 `plugin_config.json`);
|
||||
2. **getter 返回的当前生效值**(插件运行态);
|
||||
3. **schema.default**;
|
||||
4. `None`。
|
||||
|
||||
这样 Web 表单打开时显示的是**插件当前真正生效的配置**,而不是只看默认值。
|
||||
|
||||
---
|
||||
|
||||
## 7. 保存流程(热刷新)
|
||||
|
||||
`save_config(plugin_id, values)`:
|
||||
|
||||
1. 按 schema 字段的 type/`item_type` 做 **类型校验与转换**(string/int/float/bool/enum/list)。
|
||||
2. **写值库** `plugin_config.json`(原子写)。
|
||||
3. **写 .env**:非 list 字段写入 `os.environ` + `.env`(保证重启仍生效);list/path/object 跳过,避免 `str(list)` 破坏重启解析。
|
||||
4. **调 `apply(values)`**:把值热应用到插件运行态对象(list/path/object 也在此生效)。
|
||||
5. 若插件未提供 apply,则 `hot_reload(plugin_id)` 让插件重载。
|
||||
|
||||
**为什么 apply 与 env 都做**:NoneBot 的 `get_driver().config` 在启动时即固定,重载插件也读不到新 env;所以运行期必须 apply,重启靠 env。
|
||||
|
||||
---
|
||||
|
||||
## 8. Web 端「读 / 改」标准接口(/hub)
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| GET | `/api/plugins` | 所有已注册 Web 插件 |
|
||||
| GET | `/api/plugins/catalog` | 所有应用插件 + `has_config/has_web/web_path` |
|
||||
| GET | `/api/plugins/{id}/config` | 读 `{schema, values, revision}`(登录);secret 值只返回 `****` |
|
||||
| POST | `/api/plugins/{id}/config` | 保存 `{ "revision": n, "values": { key: value } }`;冲突 409,校验失败 422 |
|
||||
| DELETE | `/api/plugins/{id}/config` | 清空该插件覆盖,恢复默认(可选) |
|
||||
|
||||
响应示例(GET):
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"ok": true,
|
||||
"revision": 3,
|
||||
"schema": { "fields": [ { "key": "hd2_cache_ttl", "type": "int", "default": 60, ... } ] },
|
||||
"values": { "hd2_cache_ttl": 60, "hd2_api_base_url": "http://192.168.2.15:18080" }
|
||||
}
|
||||
```
|
||||
|
||||
前端 `hexi/web/src/pages/plugins/index.tsx` 已按 `type` 自动渲染:bool→开关、enum→下拉、text→多行、password→掩码、int/float→数字、secret→脱敏。
|
||||
|
||||
---
|
||||
|
||||
## 9. 权限与安全
|
||||
|
||||
- 全部配置接口走 `hexi.web_auth.require_admin`(OAuth2 + SQLite)。
|
||||
- 配置 POST 必须携带 GET 返回的 `revision`;缺失返回 428,冲突返回 409。
|
||||
- 只允许 `plugin_id` 存在于注册表,未注册返回 `ok:false`(防任意写入)。
|
||||
- 部署时必须显式设置 Web 管理员凭据;禁止生产环境使用默认的 `admin/admin`。
|
||||
- `secret` 字段只返回 `****`;提交 `****` 保持原值,提交 `null` 清除,写回明文仅限服务端受控存储。
|
||||
- 写 .env 用「原行更新/追加」,保留注释,不整文件覆盖。
|
||||
|
||||
---
|
||||
|
||||
## 10. 版本与迁移
|
||||
|
||||
配置保存状态至少区分:`persisted`(值库已保存)、`applied`(运行态已应用)、`restart_required`(需重启)和 `apply_failed`(应用失败)。apply 失败时接口必须返回错误,且保留上一份可恢复配置。
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"$schema_version": 3,
|
||||
"plugins": {
|
||||
"hexi.plugins.xxx": {
|
||||
"schema_version": 1,
|
||||
"values": { "...": "..." }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 升级 schema 时保留 `values`:新增字段取 `default`,删除字段清理。
|
||||
- 提供 `migrate(plugin_id, old_schema, new_schema)` 钩子,支持字段改名/类型升级/`str` 列表迁 `list[int]`;迁移必须幂等,失败时保留旧值文件并阻止该插件配置加载。
|
||||
- 读到未知插件条目时保留并上报,不强制删除。
|
||||
|
||||
---
|
||||
|
||||
## 11. 落地示例
|
||||
|
||||
```python
|
||||
# hexi/plugins/nonebot_plugin_helldivers_tools/__init__.py
|
||||
from hexi.config_standard import register_model_config
|
||||
from .config import config as _hd2_config
|
||||
|
||||
register_model_config(
|
||||
__name__,
|
||||
_hd2_config,
|
||||
fields=["hd2_api_base_url", "hd2_api_language", "hd2_cache_ttl",
|
||||
"hd2_super_client", "hd2_super_contact"],
|
||||
labels={"hd2_api_base_url": "API 根地址", "hd2_cache_ttl": "数据缓存秒数", ...},
|
||||
descriptions={"hd2_api_base_url": "自托管 API 根地址;不填用公共社区 API", ...},
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. 已接入与待接入
|
||||
|
||||
- **已接入**:`helldivers_tools`、`mc_server_status`、`video_analysis`、`steam_info`、`picstatus`、`galgame_card`。
|
||||
- **未接入(建议后续)**:`learning_chat`、`group_daily_analysis`(已有独自 Web 配置,避免冲突)、`picfinder_take`、`bf_bot`、`group_tools`、`hexi_core`(模块级常量/SUPERUSERS,运行期热更复杂)。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 13. 插件内部配置项(来源无关)接入标准
|
||||
|
||||
> **不只是 `.env`**:插件内部自己的配置项(module 常量、YAML/JSON 配置、数据库里的开关等)同样遵循统一 schema,Web 可读可改。核心是让每个配置项自带 **getter/setter**,与来源解耦。
|
||||
|
||||
### 13.1 四种来源与接入方式
|
||||
|
||||
| 配置来源 | 接入方式 | getter/setter | 持久化 | 运行期生效点 |
|
||||
|---|---|---|---|---|
|
||||
| pydantic Config(`.env` 驱动) | `register_model_config` | 自动(读写 Config 对象) | 写值库 + 写 `.env` | apply 热更新对象 |
|
||||
| 模块常量 | `register_config_items(..., store=模块)` | 自动(getattr/setattr) | 写值库(可选写 `.env`) | 需插件用 `get_effective_value` 读取 |
|
||||
| 配置文件(dict/JSON/YAML) | `register_config_items(..., store=dict)` | 自动(读写 dict) | 插件自身写回文件 / 值库 | 插件从 dict 读取时即生效 |
|
||||
| 自定义(DB/运行态) | `register_config_items` 传 `getter`/`setter` | 自定义 | 自定义回写 | 自定义 |
|
||||
|
||||
### 13.2 通用注册 API(任意来源)
|
||||
|
||||
```python
|
||||
from hexi.config_standard import register_config_items
|
||||
|
||||
register_config_items(
|
||||
__name__, # = NoneBot 插件模块名
|
||||
[
|
||||
{"key": "DAILY_LIMIT", "label": "每日限额", "type": "int", "default": 50},
|
||||
{"key": "CHECK", "label": "开启截屏判定", "type": "bool", "default": True},
|
||||
{"key": "SAUCENAO_KEY", "label": "API Key", "type": "password", "secret": True},
|
||||
# 也可为某条目自定义 getter/setter
|
||||
{"key": "custom", "label": "自定义", "type": "string",
|
||||
"getter": lambda: my_get(), "setter": lambda v: my_set(v)},
|
||||
],
|
||||
store=config_module, # dict 或模块;未写 getter/setter 时默认读写这里
|
||||
apply_extra=None, # 可选:保存后做额外持久化/热加载
|
||||
getter_extra=None, # 可选:getter 额外返回非 items 的值
|
||||
)
|
||||
```
|
||||
|
||||
### 13.3 关键规则:运行期要生效,插件必须读生效值
|
||||
|
||||
Web 保存后 apply 会把新值写回来源(模块属性/dict/自定义),但若插件在别处是用 `from .config import X` **值拷贝**进来的量,不受影响。要真正运行期生效,插件在读配置处改用统一 API:
|
||||
|
||||
```python
|
||||
from hexi.web_config import get_effective_value
|
||||
|
||||
limit = get_effective_value("hexi.plugins.nonebot_plugin_picfinder_take", "DAILY_LIMIT", 50)
|
||||
if not check_quota(limit): ...
|
||||
```
|
||||
|
||||
优先级:**用户值库 > getter 生效值 > default**。这样 Web 改完即可生效,重启也不丢。
|
||||
|
||||
### 13.4 已接入的非 env 示例
|
||||
|
||||
- `picfinder_take`:把 `SAUCENAO_KEY / DAILY_LIMIT / SEARCH_TIMEOUT / CHECK / THUMB_ON / CHAIN_REPLY / IGNORE_STAMP / threshold` 这些内部常量注册为 Web 可读可改配置项(store=config 模块)。
|
||||
|
||||
- `video_analysis`:把原本硬编码在 `storage/s3.py` 的 S3 连接配置(PLANA/PLANB/PLANC/公网 endpoint/access/secret/bucket/region/secure/domain)注册为 Web 可读可改项;apply 时清空懒加载 S3 客户端缓存,且 s3 客户端创建时经 `get_effective_value` 读取,因此**运行期与重启均生效**。另用 `type=json` 暴露 `data/list.json` 的**群分组配置**(`groups` + `blacklist`),自定义 getter/setter 同步读写,并标记 `nosave`(不写入 plugin_config.json,权威源是 list.json),避免与 `添加白名单` 等指令互相覆盖为陈旧值。
|
||||
|
||||
- **生效说明**:注册后 Web 能读、能改、会持久化;若希望改完立刻反映到插件行为,需把插件内对应读取点改成 `get_effective_value`(见 §13.3),或让 apply_extra 里 `hot_reload` 并让 config 模块从值库初始化。
|
||||
|
||||
---
|
||||
|
||||
## 14. 通用对象集标准(object_set)
|
||||
|
||||
> 当插件要暴露「一组结构化条目」时(群分组、测速站点、服务器列表、白/黑名单成员、装备表等),用 `object_set` 类型声明 **item_schema + key_field**,Web 端通用渲染成可增删改的表格,后端通用按子字段校验/去重。
|
||||
|
||||
### 14.1 Schema 形态
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"key": "groups",
|
||||
"label": "群分组配置",
|
||||
"type": "object_set",
|
||||
"key_field": "group_id", // 子字段主键(定位/去重/删除行)
|
||||
"item_schema": {
|
||||
"fields": [
|
||||
{"key": "group_id", "label": "群号", "type": "string", "required": true},
|
||||
{"key": "auto", "label": "自动解析", "type": "bool", "default": false},
|
||||
{"key": "plan", "label": "存储方案", "type": "enum",
|
||||
"options": [{"value":"A","label":"A"},{"value":"B","label":"B"}]},
|
||||
{"key": "auto_link", "label": "自动链接关键词", "type": "text", "item_type": "str"}
|
||||
]
|
||||
},
|
||||
"default": [],
|
||||
"nosave": true // 权威源在插件自身文件/DB 时填 true
|
||||
}
|
||||
```
|
||||
|
||||
### 14.2 值形态
|
||||
|
||||
```jsonc
|
||||
[
|
||||
{"group_id":"123", "auto":true, "plan":"A", "auto_link":["xhs"]},
|
||||
{"group_id":"456", "auto":false, "plan":null, "auto_link":[]}
|
||||
]
|
||||
```
|
||||
|
||||
### 14.3 一键注册 API
|
||||
|
||||
```python
|
||||
from hexi.config_standard import register_object_set
|
||||
|
||||
register_object_set(
|
||||
__name__, # NoneBot 插件模块名
|
||||
"groups", # 对象集配置 key
|
||||
[ # item_schema 子字段
|
||||
{"key": "group_id", "label": "群号", "type": "string"},
|
||||
{"key": "auto", "label": "自动解析", "type": "bool", "default": True},
|
||||
{"key": "plan", "label": "方案", "type": "enum",
|
||||
"options": [{"value":"A","label":"A"},{"value":"B","label":"B"}]},
|
||||
{"key": "auto_link", "label": "关键词", "type": "text", "item_type": "str"},
|
||||
],
|
||||
key_field="group_id",
|
||||
getter=get_groups, # () -> list[dict]
|
||||
setter=set_groups, # (list[dict]) -> None(插件自行写回来源)
|
||||
label="群分组配置",
|
||||
nosave=True, # 权威源在 list.json/DB,不写 plugin_config.json
|
||||
)
|
||||
```
|
||||
|
||||
框架自动做:**按 `item_schema` 逐字段类型转换**(bool/int/float/string/enum/text/list/json)、触发 `_coerce_value` 的值转成目标类型、**按 `key_field` 去重并丢弃缺主键行**,再把归一整形的 list 交给你的 setter。
|
||||
|
||||
### 14.4 Web 端通用表格组件(建议)
|
||||
|
||||
- 前端新增 `ObjectSetEditor` 组件,读 `field.item_schema.fields` 渲染表头 + 每行输入(与 §3 type 一一对应);
|
||||
- 支持:**添加行、删除行、编辑单元格、上移/下移**(可选);主键列默认唯一校验;
|
||||
- 保存时把整个 list 提交到既有 `POST /api/plugins/{id}/config`,无需新接口;
|
||||
- 也可按需加细粒度:`POST /api/plugins/{id}/config/{key}` 单条 upsert、`DELETE .../{key}/{key_value}` 删除单行(后端需在 apply 里实现 upsert/delete)。
|
||||
|
||||
### 14.5 可接入示范(候选)
|
||||
|
||||
- `video_analysis`:`list.json` 的 `groups` 群分组(从纯 JSON 升级为结构化 object_set,Key=group_id);
|
||||
- `picstatus`:`ps_test_sites` 测速站点列表(Key=name);
|
||||
- `mc_server_status`:`var.group_list` 每群服务器列表(Key=群号);
|
||||
- `helldivers_tools`:战备数据(可只读展示,来源 SQLite,Key=id);
|
||||
- `learning_chat`:分群配置(Key=group_id)。
|
||||
@@ -0,0 +1,156 @@
|
||||
# 插件结构标准(Plugin Structure Standard)
|
||||
|
||||
> HeXi 所有自定义插件的统一骨架:包命名、目录布局、入口/配置/工具/服务/数据/Web 职责划分。
|
||||
> 配合《插件配置文件标准》(plugin-config-standard.md) 使用:结构标准管「怎么组织代码」,配置标准管「配置如何统一并暴露给 Web」。
|
||||
|
||||
---
|
||||
|
||||
## 0. 目标
|
||||
|
||||
- 让新插件**开箱即用**:照骨架抄一遍即可,天然接入统一配置 + Web 管理台。
|
||||
- 让旧插件**逐步收敛**到同一结构,避免「入口一大坨、配置散落、数据读写各自为政」。
|
||||
- 让 **Web 管理台能统一读/改所有插件的配置**(不是只针对 env)。
|
||||
|
||||
## 1. 包命名与位置
|
||||
|
||||
- 位置:`hexi/plugins/nonebot_plugin_<name>/`(`bot.py` 用 `nonebot.load_plugins("hexi")` 加载)。
|
||||
- 包名 = NoneBot 模块名 = `hexi.plugins.nonebot_plugin_<name>`。
|
||||
- **`plugin_id` 约定**:用于配置 schema / 插件控制 / Web 挂载的值,一律等于模块名(`__name__`)。
|
||||
|
||||
## 2. 架构分层:MTSS(Model–Trigger–Service–View)
|
||||
|
||||
> 本插件结构可用一种「事件驱动的 MVC」来理解:Controller 被换成 **Trigger(触发器)**,并多出一个**双 View**(聊天 + 管理端)。我们称之为 **MTSS**。
|
||||
|
||||
### 2.1 四层职责
|
||||
|
||||
| 层 | 目录 | 职责 |
|
||||
|---|---|---|
|
||||
| **Trigger** | `handlers/*` | `on_command/on_message/on_notice`、定时任务、Web 按钮 → 统一**入口适配器**,接触发后调 service、出 View |
|
||||
| **Service** | `services/*` | 业务编排:一个业务动作一个方法;不关心是哪个触发器进来的,可被多触发器/定时/Web 复用 |
|
||||
| **Model** | `models.py` + `repository.py`/`storage/*` + `data/*` | 数据(ORM/JSON/SQLite/S3)与数据访问;唯一读写入口 |
|
||||
| **View** | 聊天出图/`UniMessage`;Web 端 `web/` + schema 表单 | **双 View**:a) 发给聊天用户(文本/图片/转发);b) 管理端(Web REST + 前端) |
|
||||
|
||||
### 2.2 依赖方向(单向、向下)
|
||||
|
||||
```text
|
||||
Trigger(handlers) → Service(services) → Model(repository/storage)
|
||||
↘ 外部 API / LLM
|
||||
Trigger/View → 依赖 nonebot / OneBot / 平台
|
||||
Service/Model → 尽量不 import nonebot(可单测、未来可换框架)
|
||||
↑ 配置标准 config.py 是横切模块,各层都能读
|
||||
```
|
||||
|
||||
### 2.3 关键规则
|
||||
|
||||
- **Trigger 只做**:接触发 → 鉴权/校验 → 调 service → 出 View;**不要放大逻辑**。
|
||||
- **View 有两个出口**:`chat`(发给用户) 与 `web`(管理端),可共用同一个 Service 的结果(类似 MVVM 的「一个 ViewModel 两个 View」)。
|
||||
- **service/repository 不 import nonebot**,便于单测与切换框架;只依赖 Model 与配置标准。
|
||||
- **触发器来源不限**:群消息 / 私聊 / at / 定时 / Web 按钮。想加「Web 手动触发」,只需在 Trigger 层加一个入口复用同一个 service。
|
||||
|
||||
### 2.4 示例(video_analysis)
|
||||
|
||||
```text
|
||||
Trigger → handlers/entry.py 的 auto/active matcher(card/文本链接)
|
||||
Service → dispatch_url 平台分派(可下沉到 services/)
|
||||
Model → list_proc.py(list.json)、storage/s3.py、models.py(异常)
|
||||
View → chat: UniMessage 文本/图片; web: config schema + 未来的 ObjectSetEditor
|
||||
Config → config.py 统一注册(cleanup/S3/group_config)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 目录骨架模板
|
||||
|
||||
```text
|
||||
nonebot_plugin_xxx/
|
||||
├── __init__.py # 入口:元数据 + 命令注册 + 配置注册 + 启动钩子
|
||||
├── config.py # 配置标准:Config(BaseModel) / register_object_set / register_config_items
|
||||
├── models.py # ORM / dataclass 模型(可选)
|
||||
├── repository.py # 数据访问层:唯一读写入口(可选)
|
||||
├── handlers/ # 命令/消息处理(按域拆分)
|
||||
│ ├── __init__.py
|
||||
│ └── chat.py management.py notices.py ...
|
||||
├── services/ # 业务编排层(被多个 handler/定时任务复用)
|
||||
│ └── __init__.py
|
||||
├── utils/ # 纯工具:无副作用、尽量无 nonebot 依赖、可单测
|
||||
│ └── __init__.py
|
||||
├── data/ # 运行时数据(或统一放 hexi/data/)
|
||||
├── res/ # 静态资源:图片/字体/模板
|
||||
├── web/ # 可选:插件自带 Web 前端(build 产物挂载)
|
||||
├── README.md # 可选
|
||||
└── CLAUDE.md # 可选:插件内开发文档(在此插件内开发时优先读)
|
||||
```
|
||||
|
||||
## 4. `__init__.py` 标准职责(固定顺序)
|
||||
|
||||
1. `__plugin_meta__ = PluginMetadata(name=..., description=..., usage=..., type="application")`。
|
||||
2. 声明依赖 `require("nonebot_plugin_alconna")` 等。
|
||||
3. 显式导入子模块(`from . import handlers, services, utils`),注册 matcher/handler。
|
||||
4. **配置注册**(在 config 就绪后):`register_model_config`(pydantic Config)或 `register_config_items` / `register_object_set`(来源无关)。
|
||||
5. 可选 `register_web_plugin("id", "名称", "icon", lambda: build_app(), module_name=__name__)`。
|
||||
6. 启动钩子 `@driver.on_startup`:初始化 DB / 加载 data 文件 / 注册定时任务。
|
||||
7. 业务命令尽量放 `handlers/`,不要把大逻辑堆在 `__init__.py`。
|
||||
|
||||
## 5. config.py 标准
|
||||
|
||||
### 4.1 环境驱动型(读 `.env` / NoneBot config)
|
||||
```python
|
||||
from nonebot import get_plugin_config
|
||||
from pydantic import BaseModel
|
||||
|
||||
class Config(BaseModel):
|
||||
enable: bool = True
|
||||
api_key: str = ""
|
||||
|
||||
config = get_plugin_config(Config)
|
||||
```
|
||||
然后 `register_model_config(__name__, config, fields=[...], labels={...})` 接入 Web。
|
||||
|
||||
### 4.2 来源无关型(模块常量 / 配置文件 / DB)
|
||||
```python
|
||||
from hexi.config_standard import register_config_items, register_object_set
|
||||
register_object_set(__name__, "groups", [ {item_schema...} ], key_field="group_id",
|
||||
getter=get_groups, setter=set_groups, nosave=True)
|
||||
```
|
||||
详见《plugin-config-standard.md》§5/§13/§14。
|
||||
|
||||
### 4.3 约定
|
||||
- 敏感字段 `type="password"` 且 `secret=True`;
|
||||
- 默认值写进 schema,供 Web 回填;
|
||||
- `nosave=True` 表示权威源在插件自身(文件/DB),Web 修改只 apply 到来源,不写 plugin_config.json。
|
||||
|
||||
## 6. 数据层标准
|
||||
|
||||
- 读写统一走 `repository.py`(或 services 内封装的仓储方法),避免 SQL/文件读写散落各处。
|
||||
- ORM:用 `nonebot_plugin_orm` 的 `get_session()`;模型在 `models.py`。
|
||||
- 文件:**原子写**(tmp + `os.replace`),避免并发/崩溃写坏 JSON。
|
||||
- SQLite:开 WAL、用事务;不要在循环里逐条写。
|
||||
- 路径:统一用插件 `get_data_dir()`/`get_plugin_cache_dir()` 或 `hexi` 的路径工具,**不硬编码绝对路径**。
|
||||
|
||||
## 7. 工具/服务标准
|
||||
|
||||
- `utils/`:纯函数、无状态、尽量不 import nonebot/driver,便于单测。
|
||||
- `services/`:业务编排,可依赖 repository / 外部 API;把「一个业务动作」收敛到一个方法。
|
||||
- **禁止** `from x import *`、`except: pass`、`print()`;用 `logger`。
|
||||
- async handler 内避免同步阻塞(`requests` / `time.sleep` / 同步爬虫);需要就 `asyncio.to_thread`。
|
||||
- 命令触发:需要 @ 用 `rule=to_me()`;全局命令注意与其它插件冲突;高开销命令加冷却/限频(`hexi_core` 的 `cooldown/rate_limit`)。
|
||||
- 外部 API:统一超时 + 重试 + 失败降级/用户提示。
|
||||
|
||||
## 8. Web 接入标准
|
||||
|
||||
- 需要独立 Web 页:`register_web_plugin(id, name, icon, lambda: build_app(), module_name=__name__)`,hub 启动自动挂载 `/api/<id>`。
|
||||
- 需要 Web 配置:`register_plugin_config` / `register_model_config` / `register_config_items` / `register_object_set`(schema 驱动表单)。
|
||||
- 鉴权统一用 `hexi.web_auth.require_admin`(OAuth2 + SQLite),不要自造一套。
|
||||
- 敏感字段 `secret=True`,前端掩码;写回允许明文。
|
||||
|
||||
## 9. 插件迁移检查清单
|
||||
|
||||
- [ ] 包名改为 `nonebot_plugin_*`,位置在 `hexi/plugins/`。
|
||||
- [ ] 有 `__plugin_meta__`,`type="application"`。
|
||||
- [ ] 配置已接入统一标准(Web 能读能改,运行期/重启生效)。
|
||||
- [ ] 命令在 `handlers/`,业务在 `services/`,工具在 `utils/`。
|
||||
- [ ] 数据访问集中(repository / services),原子写 / 事务。
|
||||
- [ ] 无 `*` 导入、裸 `except`、`print`;无同步阻塞混入 async。
|
||||
- [ ] 运行数据写入统一数据目录,不写入插件源码目录;配置读取点支持标准热更新语义。
|
||||
- [ ] 外部请求有超时/重试/失败提示;命令有权限/限流。
|
||||
- [ ] 需要 Web 的已 `register_web_plugin`;鉴权走统一 `web_auth`。
|
||||
Reference in New Issue
Block a user