758 lines
30 KiB
Markdown
758 lines
30 KiB
Markdown
# Autman 插件开发规则与仓库约定
|
||
|
||
> 来源:已参考 BOSS 上传到本仓库的插件示例,以及 autMan 官方论坛教程:<https://bbs.autman.cn/>。重点参考帖子:插件编写规则、插件编程-头注篇、插件编写概论、微服务插件编写、Python 简单插件编写、消息类型说明、实时 IM 消息监听、适配器插件编写指南。
|
||
|
||
## 1. 仓库用途
|
||
|
||
本仓库统一存放 BOSS 的 Autman 插件、参考示例、文档和模板。以后新增插件优先按下面目录组织:
|
||
|
||
```text
|
||
js/ # ECMAScript5 / 内置 JS 插件,通常放 autMan plugin/replies
|
||
py/ # Python 插件,通常放 autMan plugin/scripts
|
||
plugins/ # 我们新写的正式插件;按功能再分目录或命名
|
||
examples/ # 调用示例、配置示例
|
||
templates/ # 可复制的插件模板
|
||
docs/ # 规则、接口说明、研究记录
|
||
```
|
||
|
||
已有历史插件可保留原路径。新开发插件建议优先放到 `plugins/`,稳定后可按 Autman 实际导入目录复制到 `js/` 或 `py/`。
|
||
|
||
## 2. 插件类型
|
||
|
||
Autman 支持多种插件/脚本形态:
|
||
|
||
| 类型 | 说明 | 常见位置/用途 |
|
||
|---|---|---|
|
||
| HTML 插件 | 由 Autman Web 服务托管页面 | `plugin/web`,主要自用 |
|
||
| ES5 插件 | Autman 内置 ES5 环境运行,内置大量函数 | `plugin/replies`,适合关键词、路由、轻量集成 |
|
||
| Python/Node/TS/PHP/Shell/Go 等外部脚本 | Autman 调用宿主系统环境运行,通过 `middleware` 与 Autman 交互 | `plugin/scripts`,适合复杂逻辑、依赖第三方库 |
|
||
| 微服务路由插件 | 使用 `router` / `method` 头注暴露 HTTP 接口 | 对外接收 Webhook/API 调用 |
|
||
| 适配器插件 | 对接新的社交媒体/IM,将外部消息转换为 Autman 消息格式 | 需要处理 HTTP/WS 通信和消息格式转换 |
|
||
|
||
注意:除 HTML 插件外,其他插件运行通常要求 Autman 授权有效。
|
||
|
||
## 3. 头注规则
|
||
|
||
头注必须放在插件文件头部,用于生成插件元数据和触发条件。
|
||
|
||
### 3.1 市场/基础元数据
|
||
|
||
JS 示例:
|
||
|
||
```js
|
||
//[open_source: true]
|
||
//[create_at: 2026-01-01 12:00:00]
|
||
//[title: 插件名称]
|
||
//[author: 作者]
|
||
//[service: 售后联系方式]
|
||
//[class: 工具类]
|
||
//[version: 1.0.0]
|
||
//[platform: qq,wx,tg,tb,web]
|
||
//[public: false]
|
||
//[price: 0]
|
||
//[disable: false]
|
||
//[description: 插件说明,尽量写清楚用法]
|
||
//[icon: https://example.com/icon.png]
|
||
```
|
||
|
||
Python 示例:
|
||
|
||
```python
|
||
# [title: 插件名称]
|
||
# [language: python]
|
||
# [service: 售后联系方式]
|
||
# [disable: false]
|
||
# [admin: true]
|
||
# [rule: ^指令$]
|
||
# [platform: qq,wx,tg,tb]
|
||
# [open_source: true]
|
||
# [version: 1.0.0]
|
||
# [public: false]
|
||
# [price: 0]
|
||
# [description: 插件说明]
|
||
```
|
||
|
||
字段说明:
|
||
|
||
| 字段 | 说明 |
|
||
|---|---|
|
||
| `title` | 插件标题 |
|
||
| `author` | 作者 |
|
||
| `service` | 售后/联系方式;上架通常必填 |
|
||
| `class` | 分类,如工具类、查询类、娱乐类、生活类、图片类等 |
|
||
| `platform` | 适用平台,多个用英文逗号分隔 |
|
||
| `public` | 是否公开显示到市场;开发测试建议 `false` |
|
||
| `disable` | `true` 禁用,`false` 启用 |
|
||
| `version` | 版本号 |
|
||
| `price` | 价格 |
|
||
| `description` | 说明,建议写清楚指令、参数、注意事项 |
|
||
| `icon` | 图标 URL,建议 48px 正方形,支持 http/https |
|
||
|
||
### 3.2 关键词/命令触发
|
||
|
||
```js
|
||
//[rule: ^菜单$]
|
||
//[rule: ^菜单管理$]
|
||
//[admin: true]
|
||
//[priority: 300]
|
||
//[cron: 0 7 * * *]
|
||
//[bypass: false]
|
||
```
|
||
|
||
规则:
|
||
|
||
- `rule` 支持正则,可写多行表示多个触发规则。
|
||
- `cron` 支持 5 位或 6 位 cron 表达式。
|
||
- `admin: true` 表示仅管理员可执行。
|
||
- `priority` 数字越大优先级越高。
|
||
- `bypass: true` 常用于全拦截规则,例如 `[rule: ?]`,表示旁路并行处理。
|
||
- 可用 `imType+` / `imType-`、`userId+` / `userId-`、`groupId+` / `groupId-` 做白名单/黑名单。
|
||
|
||
示例:
|
||
|
||
```js
|
||
//[rule: ^sim状态$]
|
||
//[rule: ^sim短信\s+(.+?)\s+([\s\S]+)$]
|
||
//[admin: true]
|
||
//[platform: qq,wx,tg,tb]
|
||
//[priority: 100]
|
||
```
|
||
|
||
### 3.3 微服务路由触发
|
||
|
||
微服务插件通过 HTTP 路由触发,新增/修改这类插件后通常需要重启 Autman 才生效。
|
||
|
||
```js
|
||
//[router: /simadmin/status]
|
||
//[method: get]
|
||
//[method: post]
|
||
```
|
||
|
||
规则:
|
||
|
||
- `router` 路径必须以 `/` 开头。
|
||
- `method` 可写多个,常见为 `get`、`post`,也可支持 `put/delete`。
|
||
- GET 参数从 `getRouterParams()` 取。
|
||
- POST body 从 `getRouterData()` / `getRouterBody()` 取。
|
||
- 最终必须用 `response(obj)` 或兼容方式返回,避免请求挂起。
|
||
|
||
### 3.4 配参头注
|
||
|
||
用于让 Autman 后台渲染插件配置项。
|
||
|
||
```js
|
||
//[param: {"required":true,"key":"otto.simadmin_base_url","bool":false,"placeholder":"http://192.168.1.10:3000","name":"SimAdmin地址","desc":"不要以 / 结尾"}]
|
||
//[param: {"required":true,"key":"otto.simadmin_token","bool":false,"placeholder":"访问Token","name":"接口鉴权Token","desc":"外部请求必须携带 access_token"}]
|
||
//[param: {"required":false,"key":"otto.simadmin_push_master","bool":true,"placeholder":"","name":"是否推送管理员","desc":""}]
|
||
//[param: {"spliter":true}]
|
||
```
|
||
|
||
约定:
|
||
|
||
- `key` 建议使用统一前缀,例如 `otto.simadmin_xxx` 或 `otto.<插件名>_xxx`。
|
||
- 密钥/token 不要写死在源码里,放到配参或环境变量。
|
||
- `bool: true` 用于开关项。
|
||
- `required` 只表示配置 UI 要求,不等于运行期一定存在;代码里仍需校验。
|
||
|
||
## 4. 常见平台/消息类型
|
||
|
||
论坛中整理的常见类型:
|
||
|
||
| 类型 | 含义 |
|
||
|---|---|
|
||
| `qq` | QQ 框架 |
|
||
| `qb` | QQ 频道机器人 |
|
||
| `wx` | 微信外接框架 |
|
||
| `wb` | 内置微信机器人 |
|
||
| `tg` | Telegram 客户端 |
|
||
| `tb` | Telegram 官方机器人 |
|
||
| `wm` | 微信订阅号 |
|
||
| `wv` | 微信服务号 |
|
||
| `wk` | 微信客服 |
|
||
| `dd` | 钉钉机器人 |
|
||
| `dc` | Discord 机器人 |
|
||
| `sk` | Slack 机器人 |
|
||
| `fs` | 飞书机器人 |
|
||
| `kk` | Kook 机器人 |
|
||
| `qh` | 群晖 Synology Chat |
|
||
| `we` | 页面对话 |
|
||
| `rt` | router 消息 / 微信服务插件消息 |
|
||
| `terminal` | 交互模式终端对话 |
|
||
|
||
## 5. ES5 插件内置函数与用法
|
||
|
||
### 5.1 基础信息
|
||
|
||
```js
|
||
var name = call("name")()
|
||
var port = call("port")()
|
||
var version = call("version")()
|
||
var machineId = call("machineId")()
|
||
var id = call("uuid")()
|
||
var sign = call("md5")("text")
|
||
var now = call("timeFormat")("yyyy-MM-dd hh:mm:ss")
|
||
var textTime = call("unixTimeFormat")(second, nanosecond, "yyyy-MM-dd hh:mm:ss")
|
||
var ok = cancall("someFunc")
|
||
var coffeeOk = coffee()
|
||
```
|
||
|
||
### 5.2 消息上下文
|
||
|
||
常见函数/对象:
|
||
|
||
```js
|
||
var content = GetContent()
|
||
var is_admin = isAdmin()
|
||
sendText("回复文本")
|
||
var msg = input() // 等待用户输入,适合交互式插件
|
||
sender.reply({ok:true, msg:"success"})
|
||
Debug("调试日志")
|
||
console.log("普通日志")
|
||
```
|
||
|
||
建议:
|
||
|
||
- 管理类指令必须先判断 `isAdmin()`。
|
||
- `input()` 应处理 `null/undefined/空字符串/q退出`。
|
||
- JSON 返回优先用对象,便于 Web/API 调用方解析。
|
||
|
||
### 5.3 数据桶
|
||
|
||
```js
|
||
bucketSet("bucket", "key", "value")
|
||
var value = bucketGet("bucket", "key")
|
||
bucketDel("bucket", "key")
|
||
var keys = bucketKeys("bucket")
|
||
```
|
||
|
||
约定:
|
||
|
||
- 桶名和 key 统一前缀,避免和其他插件冲突。
|
||
- 从 `bucketGet` 读出的复杂对象需要 `JSON.parse`,必须 try/catch。
|
||
- 写入复杂对象用 `JSON.stringify`。
|
||
|
||
### 5.4 获取配置参数
|
||
|
||
配参 key 形如 `otto.simadmin_base_url` 时,代码中通常按后半段读取:
|
||
|
||
```js
|
||
var baseUrl = get("simadmin_base_url")
|
||
var token = get("simadmin_token")
|
||
```
|
||
|
||
实际以 BOSS 上传的插件为准:`qlck.js` 中 `key: "otto.vhook_qlck_token"`,读取为 `get("vhook_qlck_token")`。
|
||
|
||
### 5.5 网络请求
|
||
|
||
回调风格:
|
||
|
||
```js
|
||
request({
|
||
useproxy: false,
|
||
url: "https://example.com/api",
|
||
headers: {"Content-Type":"application/json"},
|
||
method: "post",
|
||
dataType: "json",
|
||
body: {a: 1},
|
||
timeOut: 10000
|
||
}, function(error, response, header, body) {
|
||
// 处理结果
|
||
})
|
||
```
|
||
|
||
同步/直接返回风格(BOSS 上传的 `js/qlck.js` 已使用):
|
||
|
||
```js
|
||
var resp = request({
|
||
url: baseUrl + "/api/health",
|
||
method: "get",
|
||
dataType: "json",
|
||
timeOut: 10000
|
||
})
|
||
```
|
||
|
||
约定:
|
||
|
||
- HTTP API 插件优先使用 `dataType: "json"`。
|
||
- 外部服务业务失败时不一定是 HTTP 非 200,要检查返回 JSON 内的状态字段。
|
||
- 请求外部服务前清理 URL 尾部 `/`:`baseUrl.replace(/\/+$/, "")`。
|
||
- 超时必须设置,避免插件卡死。
|
||
|
||
### 5.6 推送消息
|
||
|
||
```js
|
||
push({
|
||
imType: "qq",
|
||
groupCode: "123456",
|
||
content: "群消息"
|
||
})
|
||
|
||
push({
|
||
imType: "wx",
|
||
userID: "user-id",
|
||
content: "私聊消息"
|
||
})
|
||
```
|
||
|
||
字段:
|
||
|
||
- 群组:`groupCode` 或部分平台使用 `chatID`。
|
||
- 私聊:`userID`。
|
||
- 平台:`imType`。
|
||
|
||
## 6. 微服务插件运行规则
|
||
|
||
官方示例提到的微服务函数:
|
||
|
||
```js
|
||
getRouterPath()
|
||
getRouterMethod()
|
||
getRouterHeaders()
|
||
getRouterCookies()
|
||
getRouterParams()
|
||
getRouterBody()
|
||
response(obj)
|
||
```
|
||
|
||
BOSS 上传插件和论坛示例中也常见兼容函数名:
|
||
|
||
```js
|
||
var method = getMethod()
|
||
var params = getRouterParams() || {}
|
||
var data = getRouterData()
|
||
```
|
||
|
||
建议写法:
|
||
|
||
```js
|
||
function jsonResponse(obj) {
|
||
if (typeof response === "function") {
|
||
response(obj)
|
||
} else if (typeof sender !== "undefined" && sender.reply) {
|
||
sender.reply(obj)
|
||
} else {
|
||
sendText(JSON.stringify(obj))
|
||
}
|
||
}
|
||
```
|
||
|
||
外部 API 插件必须注意:
|
||
|
||
1. 先做 token 鉴权,鉴权失败后立即 `return`。
|
||
2. GET/POST 都支持时,参数读取顺序建议:query 参数 -> POST JSON 覆盖。
|
||
3. POST JSON 解析必须 try/catch。
|
||
4. 对发短信、重启、开关网络等敏感操作必须管理员或 token 控制。
|
||
5. 返回统一 JSON:
|
||
|
||
```json
|
||
{"ok":true,"status":"ok","retcode":0,"msg":"success","data":{}}
|
||
```
|
||
|
||
错误:
|
||
|
||
```json
|
||
{"ok":false,"status":"fail","retcode":-1,"msg":"token错误"}
|
||
```
|
||
|
||
## 7. Python 插件规则
|
||
|
||
Python 插件通过 `middleware` 与 Autman 交互。常见头注:
|
||
|
||
```python
|
||
# [title: 测试]
|
||
# [language: python]
|
||
# [disable: false]
|
||
# [platform: qq,wx]
|
||
# [rule: ^测试(.*)$]
|
||
# [admin: false]
|
||
# [version: 1.0.0]
|
||
```
|
||
|
||
常见初始化:
|
||
|
||
```python
|
||
import middleware
|
||
|
||
sender_id = middleware.getSenderID()
|
||
sender = middleware.Sender(sender_id)
|
||
message = sender.getMessage()
|
||
user_id = sender.getUserID()
|
||
user_name = sender.getUserName()
|
||
im_type = sender.getImtype()
|
||
chat_id = sender.getChatID()
|
||
is_admin = sender.isAdmin()
|
||
```
|
||
|
||
回复/监听:
|
||
|
||
```python
|
||
sender.reply("请输入内容,q退出")
|
||
user_input = sender.listen(120000) # 毫秒
|
||
if user_input == "q":
|
||
sender.reply("退出")
|
||
exit(0)
|
||
```
|
||
|
||
数据桶:
|
||
|
||
```python
|
||
middleware.bucketSet("bucket", user_id, "value")
|
||
value = middleware.bucketGet("bucket", user_id)
|
||
middleware.bucketDel("bucket", user_id)
|
||
```
|
||
|
||
依赖:
|
||
|
||
- Python 插件可自动安装依赖,但要谨慎,避免每次运行都安装。
|
||
- 建议在代码里先 `importlib.import_module` 检测,再提示管理员安装或自动安装。
|
||
- 网络请求可用 `requests` / `aiohttp`,但要设置 timeout。
|
||
|
||
## 8. 适配器插件要点
|
||
|
||
适配器用于接入新 IM/社交平台。官方建议:
|
||
|
||
1. 选择不冲突的 `im_type`,尽量 2 个字母,如 `tt`。
|
||
2. HTTP 回调地址一般为:
|
||
|
||
```text
|
||
http://<autman-host>:<port>/<im_type>/receive
|
||
```
|
||
|
||
3. Autman 与适配器通信的 WS 地址一般为:
|
||
|
||
```text
|
||
http://<autman-host>:<port>/<im_type>/adapter
|
||
```
|
||
|
||
4. 适配器需要把外部平台消息转换为 Autman 消息格式:
|
||
|
||
```json
|
||
{
|
||
"event": "message",
|
||
"event_data": {},
|
||
"guild_id": "",
|
||
"guild_name": "",
|
||
"bot_id": "",
|
||
"bot_name": "",
|
||
"chat_id": "",
|
||
"chat_name": "",
|
||
"user_id": "",
|
||
"user_name": "",
|
||
"im_type": "tt",
|
||
"message_id": "",
|
||
"content": "消息内容"
|
||
}
|
||
```
|
||
|
||
5. Autman 返回动作格式:
|
||
|
||
```json
|
||
{
|
||
"aut_action": "send_msg",
|
||
"aut_echo": "",
|
||
"aut_params": {}
|
||
}
|
||
```
|
||
|
||
适配器要把 Autman 动作翻译成外部平台 API 调用,并在需要时回执消息 ID。
|
||
|
||
## 9. 实时 IM 消息监听
|
||
|
||
Autman 支持本地 SSE 监听实时 IM 消息,官方接口:
|
||
|
||
```text
|
||
POST /otto/msghook
|
||
Accept: text/event-stream
|
||
Content-Type: application/json
|
||
```
|
||
|
||
请求 body 示例:
|
||
|
||
```json
|
||
{"imtype":"qq","chatid":"11543756","userid":"282617666"}
|
||
```
|
||
|
||
处理要点:
|
||
|
||
- 仅限本地访问更安全。
|
||
- SSE 数据按行读取,关注 `event:` 和 `data:`。
|
||
- 需要处理断线重连。
|
||
- 解析 `data:` 后再做业务逻辑。
|
||
|
||
## 10. 安全与质量约定
|
||
|
||
开发新插件时必须遵守:
|
||
|
||
1. **不要硬编码密钥**:token、cookie、API key 放配参、环境变量或本地不提交文件。
|
||
2. **外部路由必须鉴权**:尤其是发短信、推送、修改配置、重启、网络开关。
|
||
3. **鉴权失败必须 return**:避免只回复错误但继续执行后续逻辑。
|
||
4. **敏感操作默认管理员限定**:聊天指令使用 `[admin:true]` 或代码内判断。
|
||
5. **统一 JSON 返回**:便于外部系统调用和排错。
|
||
6. **请求设置 timeout**:避免 Autman 插件阻塞。
|
||
7. **兼容 GET/POST**:微服务接口可同时支持 query 和 JSON body,但 POST JSON 优先。
|
||
8. **严格校验参数**:手机号、URL、路径、开关布尔值、枚举值都要校验。
|
||
9. **日志不要打印密钥**:token/cookie/API key 必须脱敏。
|
||
10. **插件说明写清楚**:头注 description 和 README 中都要写指令、参数、示例。
|
||
|
||
## 11. 新插件开发流程
|
||
|
||
1. 先在 `plugins/<plugin-name>/` 下写源码和 README。
|
||
2. 根据插件类型复制 `templates/` 里的模板。
|
||
3. 本地检查头注、鉴权、参数校验和错误返回。
|
||
4. 如需适配 BOSS 已有插件风格,优先参考:
|
||
- `js/qlck.js`:微服务 API + 配参 + token 鉴权 + request 调外部 API。
|
||
- `py/nazha.py`:Python middleware 初始化、依赖检测、平台/用户上下文。
|
||
5. 提交前确认没有 `.env`、token、cookie、日志、运行时数据。
|
||
6. 推送后验证 raw 文件可访问。
|
||
|
||
## 12. SimAdmin 插件专项约定
|
||
|
||
后续实现 SimAdmin 插件时建议:
|
||
|
||
- 配参:
|
||
- `otto.simadmin_base_url`
|
||
- `otto.simadmin_access_token`
|
||
- `otto.simadmin_timeout_ms`
|
||
- 路由:
|
||
- `/simadmin/status`
|
||
- `/simadmin/sms/send`
|
||
- `/simadmin/sms/list`
|
||
- 聊天指令:
|
||
- `sim状态`
|
||
- `sim短信 <手机号> <内容>`
|
||
- SimAdmin API 业务失败要检查 JSON 内 `status === "error"`。
|
||
- 发送短信必须 token 或管理员控制。
|
||
- Base URL 默认不带尾部 `/`,代码内统一 trim。
|
||
## 13. 账号/Cookie 类插件菜单设计经验(QDReader 复盘)
|
||
|
||
最近制作 `plugins/qdreader/qdreader_sign_autman.js` 时沉淀出一套适用于账号/Cookie 管理类插件的交互与数据规则,后续新插件优先复用:
|
||
|
||
### 13.1 交互结构
|
||
|
||
- 优先做“一级菜单 + 二级编号选择”,不要堆很多平铺子命令。
|
||
- 账号管理和执行查询分离:
|
||
- `xxx账号`:只做账号列表、添加/更新、详情/备注、删除、清空。
|
||
- `xxx查询`:只做最后执行日志/最近记录、单账号最近记录、立即执行等执行侧动作;除非明确实现实时接口,否则不要伪装成实时查询。
|
||
- 高频输入可以保留快速通道,例如 `xxxck <cookie>`,但快速通道只做快速提交,不再混入查询/导出/批量导入。
|
||
- 用户选择账号时展示编号列表,让用户回复编号;不要让用户手动复制/输入 UID。
|
||
- 添加/更新账号成功后立刻询问是否添加备注;备注修改放在账号详情里,不单独做一个割裂的备注菜单。
|
||
|
||
### 13.2 数据结构
|
||
|
||
账号/Cookie 类插件不要把 Cookie、用户绑定、备注、执行状态混在一起,建议拆成独立 map:
|
||
|
||
```json
|
||
// cookie_json
|
||
{ "uid": "full cookie" }
|
||
|
||
// user_bind_json
|
||
{ "uid": "sender.getUserID()" }
|
||
|
||
// remark_json
|
||
{ "uid": "主号" }
|
||
|
||
// last_result_json:单账号最后执行结果
|
||
{ "uid": "{\"ok\":true,\"msg\":\"成功\",\"time\":\"...\",\"source\":\"cron\",\"runId\":\"...\"}" }
|
||
|
||
// last_run_json:最近一次执行批次日志
|
||
{ "source": "cron", "finishedAt": "...", "okCount": 1, "failCount": 0, "total": 1, "items": [] }
|
||
|
||
// archive_json:按日期归档后的批次日志,用于每日结算/历史判断
|
||
{ "2026-05-18": { "source": "cron", "finishedAt": "...", "okCount": 1, "failCount": 0, "total": 1, "items": [] } }
|
||
```
|
||
|
||
这样删除、过滤、展示和权限校验更安全。
|
||
|
||
### 13.3 数据桶
|
||
|
||
- 新插件应创建自己的 bucket,例如 `hermes_qidian`,不要所有插件都塞进 `otto`。
|
||
- 如需兼容旧数据,读顺序建议:
|
||
|
||
```text
|
||
bucketGet("插件专属bucket", key)
|
||
↓ fallback
|
||
get(key)
|
||
↓ fallback
|
||
bucketGet("otto", key)
|
||
```
|
||
|
||
- 写入新数据时写到专属 bucket,可同时写普通 `set(key)` 保持兼容。
|
||
|
||
### 13.4 多用户隔离
|
||
|
||
- 保存账号时绑定当前用户 ID。
|
||
- 账号列表、详情、删除、手动执行只处理当前用户绑定的账号。
|
||
- `删除全部` 只删除当前用户账号,不清空全局数据。
|
||
- cron/系统触发通常没有用户上下文,是否执行全部账号要显式传 `allUsers` 参数,并在代码和测试里说明。
|
||
|
||
### 13.5 Autman JS 用户身份与历史兜底绑定(QDReader 复盘)
|
||
|
||
QDReader 这次踩坑说明:账号 ID 识别成功,不代表 Autman 会话用户绑定一定正确。JS 插件里不能只照 Python 示例写 `sender.getUserID()`,也不能在取不到用户时随便兜底。
|
||
|
||
建议统一写 `currentUserId()`,按顺序尝试:
|
||
|
||
1. 官方 ES5 全局函数 `GetUserID()`。
|
||
2. `Sender.GetUserID()`。
|
||
3. 兼容旧/小写 sender 方法:`sender.getUserID()`、`sender.getUserId()`、`sender.getUserid()`、`sender.getSenderID()`、`sender.getFromUserId()` 等。
|
||
4. 明确的用户全局变量:`userId`、`userID`、`senderID`、`senderId`、`fromUserId`、`fromUserID`。
|
||
|
||
不要把 `GetChatID()`、`sender.getChatID()`、`chatId` 当用户 owner。群聊里这是群 ID,会让多个成员绑成同一个 owner,只能用于诊断输出。
|
||
|
||
保存 CK 时的安全策略:
|
||
|
||
- 获取不到真实用户 ID:拒绝保存,并输出脱敏诊断,不写 `cookie_json` / `user_bind_json`。
|
||
- `cookie_json[uid]` 不存在但 `user_bind_json[uid]` 还存在:这是手动删桶后留下的孤儿绑定,应清理旧绑定后允许重新保存。
|
||
- `cookie_json[uid]` 和 `user_bind_json[uid]` 都存在,但 owner 是历史兜底标识(如早期 `__qdreader_default_owner__` 或 `chickliu` 这类非数字短标识):管理员触发时可自动修复为当前真实用户,并继续更新 CK。
|
||
- `cookie_json[uid]` 和真实其他用户绑定都存在:必须拒绝覆盖,防止抢绑。
|
||
|
||
这类修复要补回归测试:
|
||
|
||
- 官方 `GetUserID()` 可正常绑定。
|
||
- 旧 sender 字段可兼容绑定。
|
||
- 无用户 ID 时拒绝写入。
|
||
- CK 被手动删除但绑定残留时可重新添加。
|
||
- 管理员可修复历史兜底 owner。
|
||
- 非管理员不可修复历史兜底 owner。
|
||
- 真实其他用户绑定仍不可覆盖。
|
||
|
||
### 13.6 安全与测试
|
||
|
||
- `xxx查询` 的数据来源必须写清楚:如果没有实时接口,就命名为“最近签到记录/最后执行日志”,读取 `last_run_json` 和 `last_result_json`,不要让用户误以为是实时查询。
|
||
- 执行动作要同时写批次日志和单账号日志;成功、业务失败、请求异常都要落日志,异常不能只显示在当次回复里。
|
||
- 如果插件需要按天判断执行情况,不要把归档和运行任务放在同一个 cron 入口。优先用一个插件文件;如果两个动作只相差小时、分钟相同,可合并为一个 cron(如 QDReader 用 `1 0,7 * * *`),入口按小时分流:0 点分支只归档上一份批次日志到 `archive_json` 并清空 `last_run_json` / `last_result_json`,7 点分支只执行签到/业务动作并写 `last_run_json` / `last_result_json`。如果分钟不同或无法安全按小时分流,再使用多个独立 cron;只有在当前 Autman 运行环境确认不可靠支持单插件多 `[cron]` 时,才退而拆成两个插件文件。查询菜单优先展示当日/当前批次;当日日志为空时回退最近归档,按归档结果反馈成功/失败。
|
||
- 普通回复永远不要输出完整 Cookie;只展示脱敏 UID、备注、Cookie key 摘要或长度。
|
||
- 高风险的导出/批量导入功能默认不做,除非 BOSS 明确需要。
|
||
- Node VM 测试应模拟:`sender.reply`、`sender.listen`、`sender.getUserID`、`get/set`、`bucketGet/bucketSet`、`request`。
|
||
- 测试必须覆盖:快速提交、菜单添加、备注提示、详情改备注、编号选择删除/查询、旧 bucket 兼容、新 bucket 写入、用户隔离、手动执行只跑当前用户、cron 跑全部账号。
|
||
|
||
|
||
## 12. QDReader 账号级任务开关约定
|
||
|
||
启点/QDReader 这类多账号签到插件的附加任务(广告、抽奖、兑换、章节卡、大咖荐书等)不要再放全局开关里统一控制。BOSS 确认更合适的模型是“直接写进账号里,对每个账号单独管理”。仓库实现约定如下:
|
||
|
||
- Cookie、用户绑定、备注仍分别保存;附加任务开关保存为 `qdreader_task_pref_json`。
|
||
- `qdreader_task_pref_json` 结构为 `uid -> { taskKey: boolean }`,例如 `{ "12345": { "adv": true } }`。
|
||
- `启点账号` 菜单内提供“账号任务配置”,先选择账号,再切换该账号的任务开关;支持 `all on` / `all off` 一键开启或关闭该账号全部可选任务。
|
||
- 定时/手动签到执行时按 uid 读取自己的任务配置;一个账号开启广告任务不影响其它账号。
|
||
- 删除单个账号或清空当前用户账号时,同步删除该 uid 的任务配置,避免残留配置误作用于重新添加的账号。
|
||
- 旧全局开关 key 可留作历史数据,但不要作为运行依据;测试必须覆盖“一个账号开启,另一个账号不执行”的隔离场景。
|
||
|
||
|
||
QDReader 签到结果判定补充:
|
||
|
||
- 上游返回“今日已签到/已经签到/重复签到”应视为成功,不应把账号计入失败。
|
||
- 签名网关返回 `url 不在允许的接口列表内` 说明本地可选任务接口尚未被网关白名单支持;对这类可选任务应显示为“已跳过”,不要让整个账号失败。
|
||
- 保留原始错误到任务对象的 `rawMsg` 即可,聊天摘要只显示短的跳过原因,避免刷屏。
|
||
|
||
## 12. QDReader 上游完全体任务修复记录
|
||
|
||
- 不要只凭接口名猜测“激励任务/每日任务/大咖荐书”的调用链;应先反混淆上游 `Yuheng0101/X/Tasks/QDReader/qdreader.js`,再按真实流程移植。
|
||
- 激励任务 `finishWatch` 可能返回 `result=9` 或 `Result=9`,上游将其视为可继续/已受理,再回查 `mainPage` 判断任务是否完成;插件不能把大小写不同的 `result=9` 直接计为失败。
|
||
- 每日任务不是简单取任务列表第一个。上游会先处理 `packagelist` 里的惊喜/前置任务,再从任务列表按标题定位“每日任务”,执行多次 `finishWatch` 后回查完成状态。
|
||
- 大咖荐书不是一次 `pullmessage` 即结束。消息 token 需要由 `UserId` 经过 `(uid * 4 & 0x7ffffffffffffffc) ^ 0x113a4f` 后转 base62;流程是:拉会话列表(`appappend` + `categoryIds=0`)→ 对红点会话带 `fromId` 拉消息 → 从最近消息的 `ActionUrl/RefUrl` 提取 `orderId` → `/argus/api/v1/interaction/rewarddetail`(`refresh=0`)→ 根据 `ChanceCount/BookStatus` 走 `/argus/api/v1/interaction/rewardcallback` 或 `/argus/api/v1/interaction/rewardaddshelf`。如果直接用十进制 token、旧的 `token/type/senderId` 参数,或旧的 `/argus/api/v2/recommend/...` 路径,实测会偏离当前上游并容易返回“参数错误”。
|
||
- 大咖荐书应限制在北京时间 `20:00-22:00` 窗口执行;窗口外要标记为跳过并避免调用消息接口,否则晚间窗口后容易返回“参数错误”并污染整账号结果。
|
||
- “无可领取礼包/今日已签到”属于幂等成功类提示;这类提示不应让整个账号汇总失败。
|
||
- 每次修复这类链路必须补 VM 回归测试:请求路径、签名 payload 里的参数、特殊返回码分类、最终账号成功/失败汇总。
|
||
|
||
## 14. QDReader 定时签到成功复盘:从“代码看起来对”到“现场确实跑通”
|
||
|
||
本次 `2.10.x` 修复最终确认启点定时签到已成功运行,关键经验如下,后续遇到“定时任务似乎没跑/跑了但结果异常”时按这个顺序复核:
|
||
|
||
### 14.1 先确认 Autman 实际能调度到插件入口
|
||
|
||
- 插件必须保留简单明确的 cron 头注,例如 `//[cron: 1 7 * * *]`,避免未验证的复杂多时间表达式。
|
||
- JS 插件顶层必须真的调用 `main()`;cron 触发时通常没有聊天内容,入口要把“空 content”解释为系统定时运行,而不是当作非法输入退出。
|
||
- cron 分支要只做一件事:执行签到。归档应在签到结束后立即完成,不再依赖另一个午夜 cron 或拆分插件。
|
||
- 手动 `启点签到` 与 cron 应复用同一条执行链,只区分账号范围:手动只跑当前用户账号,cron 跑全部账号。这样手动跑通才有诊断价值。
|
||
|
||
### 14.2 状态判断不能只看“最近日志”
|
||
|
||
- 当前实现签到结束后会把 `last_result_json` / `last_run_json` 复制到 `archive_json` 后清空近期日志;所以“最近日志为空”不代表没跑。
|
||
- 查询逻辑必须在近期日志为空时回退读取 `archive_json`,并展示归档批次、来源、时间、成功/失败/总数、单账号结果。
|
||
- 归档 key 和展示时间必须按北京时间生成,避免 UTC 边界导致今天的签到归到昨天。
|
||
|
||
### 14.3 完全体任务要有矩阵,而不是靠猜
|
||
|
||
默认安全任务:
|
||
|
||
| 任务 | 默认 | 时间/条件 | 接口 |
|
||
|---|---:|---|---|
|
||
| 主签到 | 开 | cron / 手动 | `/argus/api/v3/checkin/checkin` |
|
||
| 领取奖励 | 开 | 签到后固定执行 | `/argus/api/v3/checkin/receivegifts` |
|
||
|
||
可选任务必须按账号保存在 `qdreader_task_pref_json`,默认关闭:
|
||
|
||
| 任务 | 时间/条件 | 接口链 |
|
||
|---|---|---|
|
||
| 激励任务 | 账号开启后执行 | `video/adv/mainPage` → `video/adv/finishWatch` → 回查 `mainPage` |
|
||
| 每日任务 | 账号开启后执行 | `checkin/packagelist` → `finishWatch` → 回查 |
|
||
| 每日抽奖 | 账号开启后执行 | `checkin/detail` → `checkin/lottery` |
|
||
| 每周兑换 | 账号开启且北京时间周日 | `checkin/checkinexchangepage` → `checkin/exchangegoods` |
|
||
| 章节卡片 | 账号开启后执行 | `readtime/scoremall/chaptercardwithbook` |
|
||
| 大咖荐书 | 账号开启且北京时间 20:00-22:00 | `magev6.if.qidian.com/.../pullmessage` → `getrewarddetail` → `rewardcallback/addbookshelf` |
|
||
|
||
### 14.4 上游返回和签名网关要按实测分类
|
||
|
||
- `今日已签到`、`重复签到`、`已领取`、`已完成`、`无可领取礼包` 等幂等提示应视为成功类,不应让账号失败。
|
||
- 激励任务 `finishWatch` 的 `Result/result = 9` 是上游可接受状态,要继续回查完成状态,而不是直接失败。
|
||
- BOSS 明确要求 QDReader 激励任务“依照原项目,不做我以为的逻辑修改”。原项目的激励任务控制流是:读取 `VideoRewardTab.TaskList`;如果最后一项 `IsFinished === 1` 直接完成;否则遍历所有未完成且有 `TaskId` 的列表项,每项提交一次 `/argus/api/v1/video/adv/finishWatch`;最后再回查 `mainPage`,仍以回查后列表最后一项 `IsFinished === 1` 作为完成判断。
|
||
- 因此不要在激励任务里加入本地推测过滤,例如跳过“每日签到/其它入口任务”、只校验本轮提交的 TaskId、按 `NeedCount - Count` 计算剩余次数、或者输出“跳过 N 个需其它入口任务”。这些看起来更严格/更安全,但已经被现场结果证明会和原项目语义偏离,导致误判未完成。
|
||
- 如果修改了激励任务,一定补三类 VM 回归:① 非最后项已完成但最后项未完成时仍失败;② 回查最后项完成时即成功,即使前面提交过的条目仍显示未完成;③ 看起来像其它入口/签到的条目只要未完成且有 `TaskId`,也按上游循环提交一次,不做本地过滤。
|
||
- 可选任务如果被签名网关返回 `url 不在允许的接口列表内`,应标记为跳过 `⏭`,不要污染基础签到成功率。
|
||
- 大咖荐书必须用 base62 token,并带 `senderId` 拉取具体会话;单次 `pullmessage` 或十进制 token 容易导致“参数错误”。
|
||
|
||
### 14.5 Autman JS 兼容性要作为上线前检查
|
||
|
||
- Autman 内置 JS 运行时不一定等同当前 Node。即使 Node `--check` 通过,也要避免不必要的新语法。
|
||
- QDReader token 计算需要 `BigInt`,但不要写 `0n` / `62n` 这类 BigInt 字面量;使用 `BigInt(0)` / `BigInt(62)`,降低旧解析器失败风险。
|
||
- 需要同时检查并同步两份维护副本:`plugins/qdreader/qdreader_sign_autman.js` 和 `js/qdreader_sign_autman.js`。
|
||
|
||
### 14.6 每次修复必须落地验证
|
||
|
||
最小验证集:
|
||
|
||
```bash
|
||
cd /Users/chick/.Hermes/workspace/autman-plugins
|
||
node --check plugins/qdreader/qdreader_sign_autman.js
|
||
node --check js/qdreader_sign_autman.js
|
||
node tests/qdreader_plugin.test.mjs
|
||
git diff --check
|
||
cmp -s plugins/qdreader/qdreader_sign_autman.js js/qdreader_sign_autman.js
|
||
```
|
||
|
||
推送后还要验证 Gitea raw:
|
||
|
||
- raw 正式插件存在并包含新版本号。
|
||
- raw `js/` 副本存在并与正式插件一致。
|
||
- raw README/文档包含任务矩阵、执行时间窗口和关键接口。
|
||
- raw 插件中不再包含 `0n` / `62n` 等 BigInt 字面量。
|
||
|
||
### 14.7 给 BOSS 的最终回复要包含什么
|
||
|
||
- 明确下一次 cron 时间,例如“每天 07:01”。
|
||
- 明确默认只跑安全签到 + 领取奖励;可选任务需要在 `启点账号 → 账号任务配置` 按账号开启。
|
||
- 明确查询结果从归档判断,不要把清空近期日志误解释成没运行。
|
||
- 给出测试、commit、push、raw 验证结果,避免只说“应该可以”。
|
||
|
||
### 10.6 QDReader 原项目接口转写:字段大小写必须逐项核对
|
||
|
||
2026-05 复核启点激励任务时发现:原脚本能完成、插件不能完成的根因不是任务流程,而是 `finishWatchAdv` 的 POST payload 被转写错了字段名。上游提交字段为:
|
||
|
||
```text
|
||
taskId=<TaskId>&BanId=0&BanMessage=&CaptchaAId=&CaptchaURL=&CaptchaType=0&Challenge=&Gt=&NewCaptcha=0&Offline=0&PhoneNumber=&SessionKey=
|
||
```
|
||
|
||
插件曾误写成:
|
||
|
||
```text
|
||
TaskId=<TaskId>&BanId=0&BanMessage=&Challenge=&CaptchaType=0&Fee=&Gif=&Gt=&NewCaptcha=0&Offline=0&PhoneTicket=&SessionKey=
|
||
```
|
||
|
||
注意点:
|
||
|
||
- `taskId` 是小写 `t`,不是 `TaskId`。
|
||
- 必须保留上游的空字段 `CaptchaAId`、`CaptchaURL`、`PhoneNumber`。
|
||
- 不要凭字段含义替换成 `Fee`、`Gif`、`PhoneTicket`。
|
||
- 复刻上游混淆脚本时,除流程外还要用测试断言实际请求 `body` 的完整字符串,避免只断言 URL/次数导致参数漂移漏检。
|
||
- QDReader 激励/每日任务都共用 `finishWatchAdv`,修改 payload 时必须覆盖两类任务测试。
|