PTPatronus 插件系统设计
本文档记录 PTPatronus 插件能力的目标模型和当前落地方案。整体原则是不照搬 MoviePilot 的 Python 动态导入,而是在 Go 后端中建立可控、可审计、可逐步扩展的插件系统。
目标
- 第一阶段:内置插件闭环。插件有清晰 manifest、启停状态、配置、事件订阅、动作执行、日志和前端管理页。
- 第二阶段:外部进程插件。插件独立进程运行,通过 HTTP/gRPC/JSON-RPC 与主程序通信。
- 第三阶段:插件市场。插件仓库提供索引、压缩包、哈希和签名,主程序负责安装、升级、回滚和安全校验。
当前实现
后端新增 backend/internal/plugins:
Manifest:插件元数据,包括 ID、名称、版本、作者、权限、事件、动作和配置 schema。Manager:负责插件注册、启停、配置保存、动作执行、事件总线订阅和日志记录。Runtime:插件运行时能力,当前提供Config、Log、Notice、Publish。- 内置插件:
event-audit:监听plugin.test和webhook,写入审计日志。notice-forwarder:将指定事件转成全局通知,并复用系统通知渠道。
数据库新增:
plugin_states:插件启停、安装状态、配置 JSON、最近事件时间和最近错误。plugin_logs:插件动作、事件和运行日志。
前端新增:
/plugins插件管理页。- 支持筛选、启停、配置、动作执行、测试事件、日志查看。
API
所有接口位于 /api/v1,需要登录和 VIP 准入;写操作需要管理员权限。
GET /plugins:插件列表。GET /plugins/:id:插件详情。PUT /plugins/:id/config:保存配置,body 为{ "config": {} }。POST /plugins/:id/enable:启用插件。POST /plugins/:id/disable:停用插件。POST /plugins/:id/action:执行动作,body 为{ "action": "name", "input": {} }。GET /plugins/logs?plugin_id=&limit=100:插件日志。POST /plugins/events:发布测试或管理事件,body 为{ "type": "plugin.test", "data": {} }。GET /plugins/market?source=:读取插件市场索引;source留空时读取data/plugin-market.json。POST /plugins/market/install:从市场安装插件,body 为{ "source": "...", "id": "...", "accepted_permissions": [...], "accepted_permissions_by_plugin": [{ "id": "...", "accepted_permissions": [...] }] }。
Manifest 结构
{
"id": "notice-forwarder",
"name": "事件通知桥",
"version": "1.0.0",
"author": "PTPatronus",
"runtime": "builtin",
"built_in": true,
"permissions": ["event:read", "notice:write"],
"events": [{ "type": "plugin.test", "description": "插件系统测试事件" }],
"actions": [{ "name": "send_test_notice", "label": "发送测试通知" }],
"config_schema": [
{ "key": "level", "label": "通知级别", "type": "select", "group": "通知" }
],
"schedule": [
{ "id": "daily_update", "label": "每日拉取订阅", "action": "update_hosts", "cron": "0 6 * * *", "cron_config_key": "cron_update" }
]
}config_schema[].group 可选,把字段归入配置表单的分区(订阅/网络/通知/定时 等),留空归入默认无标题组;仅影响 UI 组织。
插件级定时调度
对标 MoviePilot 的 get_service(),让插件能"自调度"——到点自动调用某个 action,而不必手搓工作流 timer 节点。
声明:在 Manifest.Schedule 列出 ScheduleSpec:
id:稳定键,参与 cron job 命名plugin:<pluginID>:<id>。action:到点要调用的 action 名(必须在Manifest.Actions里)。cron:默认 5 段 cron 表达式。cron_config_key:可选。非空时从config[此键]读 cron 覆盖cron;配置值为空串则该条调度停用——用户在前端清空 cron 字段即停,无需额外开关。input:可选,传给 action 的静态入参。
执行模型:
Manager自持一个独立*scheduler.Scheduler(复用internal/scheduler包装的 robfig/cron/v3,带 panic 恢复 + SkipIfStillRunning),在Start创建并启动,Stop关停。refreshSchedules(id)在Start/Register/SetEnabled/SaveConfig时重建该插件的全部 job;Uninstall清理。幂等。- 到点回调复用
Manager.ExecuteAction:自动做启用检查、构建 Runtime、30s 超时、日志/错误落库。 - 仅当插件整体
enabled=true时才登记 job;插件被禁用则全部摘除。
调度器与全局 site/workflow 调度器相互独立,App.Shutdown 会一并优雅停止。
外部 HTTP 插件
第二阶段已支持 runtime:external-http。主程序会扫描 data/plugins/{plugin_id}/plugin.json,将外部插件注册到同一套插件管理页和 API。
插件包内包含 plugin.json:
{
"id": "example.external",
"name": "外部插件示例",
"version": "1.0.0",
"runtime": "external-http",
"entry": {
"command": "example-plugin",
"args": ["--port", "${PORT}"],
"health": "/health"
},
"permissions": ["event:read", "notice:write"]
}如果插件服务由用户自行托管,也可以不声明 command,改用:
{
"entry": {
"base_url": "http://127.0.0.1:19090",
"health": "/health"
}
}主程序负责:
- 分配端口和临时 token。
- 启动/停止外部进程。
- 调用
/event、/action、/health。 - 对每次调用设置超时和最大 body。
- 将插件日志统一收敛到
plugin_logs。
外部插件 HTTP 约定:
POST /eventPOST /actionGET /health
POST /event 请求:
{
"type": "plugin.test",
"data": { "message": "hello" },
"config": {}
}POST /action 请求:
{
"action": "ping",
"input": { "message": "hello" },
"config": {}
}POST /action 响应:
{
"ok": true,
"output": { "message": "pong" }
}主程序启动外部进程时会注入环境变量:
PTP_PLUGIN_IDPTP_PLUGIN_PORTPTP_PLUGIN_TOKENPTP_PLUGIN_BASE_URLPTP_PLUGIN_DIR
entry.command、entry.args、entry.env 支持占位符:
${PORT}${TOKEN}${PLUGIN_DIR}${PLUGIN_ID}
示例插件见 examples/plugins/external-http-demo。复制到 data/plugins/external-http-demo 后,可在插件管理页执行“重载”加载;通过市场安装时会自动热加载。
插件市场
第三阶段已支持静态市场索引和 zip 安装包。索引可以是 HTTP/HTTPS URL,也可以是本地文件。
默认官方仓库:全新安装首次启动时,后端自动播种官方插件市场仓库 gntv456/ptpatronus-plugin-market 为默认市场源(Manager.EnsureDefaultMarketSource,存储于 SystemConfig key plugin.market_sources)。它和第三方源一样是普通可编辑条目——前端「插件 → 插件市场 → 设置仓库」可删除(删除后不会自动恢复)、可追加任意第三方 GitHub 仓库或 plugin-market.json URL,也可用「恢复官方源」按钮重新加回。多源按列表顺序聚合,相同插件 ID 先列者优先。市场源管理接口:GET/PUT /plugins/market-sources(响应额外含 official 字段,即官方仓库地址)。
前端「插件 → 插件市场」中 source 留空时,后端按 plugin.market_sources 聚合;该列表为空时回退读取本地 data/plugin-market.json。
{
"version": 1,
"name": "PTPatronus 官方插件市场",
"public_key": "base64-encoded-ed25519-public-key",
"plugins": [
{
"id": "example.external",
"name": "外部插件示例",
"version": "1.0.0",
"runtime": "external-http",
"archive": "https://example.com/example.external-1.0.0.zip",
"sha256": "...",
"signature": "...",
"category": "工具"
}
]
}安装流程:
- 拉取市场索引。
- 校验插件 ID、版本、runtime 和权限声明。
- 下载 zip 到临时目录。
- 校验 SHA-256。
- 如果索引提供
public_key且条目提供signature,使用 Ed25519 校验 zip 原始字节签名。 - 安全解压,阻止 zip 路径穿越。
- 校验 zip 内
plugin.json的id和runtime。 - 解压到
data/plugins/{plugin_id}。 - 覆盖安装前备份旧目录,失败自动回滚。
- 立即加载外部插件,返回
restart_needed=false。
当前支持:
runtime=external-http- zip 内必须包含且只能包含一个
plugin.json sha256为空时跳过哈希校验,生产市场必须填写- Ed25519 签名校验:
public_key和signature必须成对提供,均为标准 Base64 category(可选):市场展示用分类标签(如「媒体库」「短剧」「网盘」「工具」),供前端「插件市场」表格分组/筛选。只存在于市场元数据,不参与签名校验(签名仅盖 archive zip 字节),故调整分类/描述/关键词无需重新打包或重签- 外部插件热加载、重载、卸载
暂未启用:
- 签名信任链/多公钥轮换:当前是单市场索引公钥模型。
安全约束
- 插件必须声明权限,UI 明示权限。
- 配置、数据和日志按插件 ID 隔离。
- 外部插件默认不能直接访问主数据库。
- 事件和动作调用必须有超时。
- 插件安装包必须校验哈希;可信市场应同时启用 Ed25519 签名。
- 插件市场安装前必须展示来源、版本、权限和变更。
- 插件报错只记录最近错误和日志,不影响主业务链路继续运行。
插件持久化与站点访问(Runtime 扩展)
为支持复杂插件(失败队列、调度锚点、多站交互),Runtime 在 Config/Log/Notice/Publish 之外扩展:
KVGet/KVSet/KVDelete(key):per-plugin key-value 持久化(plugin_kv表,唯一索引plugin_id+key)。失败队列、织梦邮件时间等运行时状态存这里,不污染用户可见的 ConfigJSON。SiteList() []SiteRef/SiteCookie(id):枚举已配置站点 + 解密 cookie(复用SiteOper的 AES-GCM 解密管线)。喊话/采集类插件据此遍历站点。SetRuntimeConfig(key, val):合并写回 ConfigJSON 单键 + 触发refreshSchedules(动态 cron 用,如织梦zm_cron)。
groupchat 喊话子包
internal/plugins/groupchat/ 是 14 站 PT 喊话自动化引擎(对标 MP groupchatzone 完整移植)。自包含、不 import plugins 包(避免循环;plugins 层用 gcRTAdapter 把 plugins.Runtime 适配成 groupchat.Runtime)。
- 14 站 handler 注册表(
Dispatch按站名子串匹配,NexusPHP 兜底):好学 / 织梦 / LongPT / 青蛙 / 象站 / 幸运 / 藏宝阁 / 天枢 / Moment / PTLGS / PTS / 13City + NexusPHP 兜底。14/14 纯 HTTP(原版 Playwright 路径是死代码)。 - 共享 NexusPHP 基类(Send = GET
/shoutbox.php?shbox_text=&shout=我喊&sent=yes&type=shoutbox;PollFeedback 重拉解析@user行 + 关键词奖励分类),各特化站覆写 Send/PollFeedback。 - 织梦独立 Schedule:cron 由邮件时间+24h 动态算出,
SetRuntimeConfig("zm_cron", cron)触发重排;主任务首次带上织梦引导初始化。 - 失败重试队列(KV 持久化 +
time.AfterFunc递归,至上限)。 - 奖励副作用:青蛙每日福利(
/api/bonus-shop/exchange)、LongPT 抽奖、13City 诸神赐福勋章(/ajax.phpbuyMedal)。
注:无真实样本站点(hxpt/13city/cangbao/ptlgs/dubhe/moment/ptskit/luckpt/vicomo)的 feedback XPath/关键词移植自原版 Python,待真机校准;织梦/LongPT/青蛙/NexusPHP 这 4+1 代理读到了精确端点/字段,可信度高。
forumsignin 论坛签到子包
internal/plugins/forumsignin/ 是论坛/PT 站论坛区每日签到引擎(对标 MP madrays/imaliang 论坛签到插件)。与 groupchat 同构(自包含、不 import plugins 包,fsRTAdapter 适配 Runtime),但凭据由插件配置承载而非 PT 站点列表(NodeSeek/SSDForum 等非 PT 论坛不在站点系统内)。
- 6 站 handler 注册表(
Lookup(key)精确匹配):NodeSeek(POST /api/attendance/checkInJSON{success,message})+ 5 个 NexusPHP 系(蜂巢/飞牛/柠檬/国语视界/SSDForum,共享nexusSignin基类:GET{base}/{path}默认signin.php,HTML 关键词分类 签到成功/今日已签到/cookie 失效)。Supported()按注册顺序返回 key+label。 - 配置文本
forums:每行key|base|cookie[|path](SplitN("|",4),cookie 第 3 列可含除换行外任意字符;path 第 4 列可覆盖默认签到路径,便于逐站校准)。#开头/空行忽略。 - Engine:解析凭据 → 逐站
Signin→ 收集SiteResult→ 通知(成功/失败计数)→ 失败重试(KV 持久化 +time.AfterFunc递归,至上限)。 - Schedule:
每日论坛签到(默认0 8 * * *,cron可改/留空停用);动作run(立即签到,返回每站结果)/labels(支持站点列表)。
注:签到端点/字段名基于社区逆向契约,httptest mock 覆盖,待真机联调(每站 path 可在配置行第 4 列覆盖以适配差异)。
开发新内置插件
- 在
backend/internal/plugins中实现Plugin接口。 - 提供
Manifest()、DefaultConfig()、HandleEvent()、ExecuteAction()。 - 在
NewManager中注册插件。 - 若需要新的系统能力,优先通过
Runtime增加受控方法,不直接把数据库或完整服务暴露给插件。 - 前端无需为每个插件写专属页面;配置和动作由 schema 自动渲染。
- 需要定时自治(如每日签到、定时拉取)时,在
Manifest.Schedule声明调度;若要让用户改 cron,配一个type:"text"的 config 字段并在cron_config_key引用其 key,留空即停。
迁移自 MoviePilot 的内置插件
位于 backend/internal/plugins/migrated_moviepilot.go,用 Go 重写为"被动 action"模型(自带 SSRF/内网守卫),并按上述机制接上分组配置与定时调度:
cloudflare-subscribe:拉取 Cloudflare hosts 订阅。分组 订阅/网络/通知/定时;每日拉取订阅调度(默认0 6 * * *,cron_update可改)。auto-speed:轻量网络测速。分组 测速/网络/通知/定时;定时测速调度(默认每 6 小时,cron_speedtest可改)。xiaomi-router:小米路由器状态/端口映射。分组 连接/通知/快捷预设/定时;定时刷新状态调度(默认每 6 小时,cron_status可改)。group-chat-zone:14 站 PT 喊话自动化平台(对标 MP groupchatzone 完整移植)。分组 基础/执行/站点/网络;两条 Schedule(主任务 + 织梦邮件时间+24h 独立);失败重试队列(KV 持久化);青蛙每日福利/LongPT抽奖/13City诸神赐福勋章。详见下文「groupchat 子包」。twofa-helper:TOTP 验证码生成。分组 账号/参数(按需生成,无定时)。ptp-forum-signin:论坛每日签到(NodeSeek/蜂巢/飞牛/柠檬/国语视界/SSDForum)。凭据由插件配置承载(forums文本框每行key|base|cookie[|path]),不走 PT 站点列表(NodeSeek/SSDForum 等非 PT 论坛)。分组 站点/通用/调度;每日论坛签到调度(默认0 8 * * *,cron可改);失败重试队列(KV 持久化)。详见下文「forumsignin 子包」。
其余 MoviePilot 重型签到插件(勋章墙/朱雀助手等)尚未迁移,属后续独立工作。