docs: add autman plugin development rules

This commit is contained in:
2026-05-15 21:32:38 +08:00
parent f284a47a0e
commit 5024f17e9c
4 changed files with 629 additions and 0 deletions
+6
View File
@@ -59,3 +59,9 @@ GET /api/sms/stats
## 当前状态
初始通用仓库骨架已创建。等待 BOSS 上传既有 Autman 插件后,再按实际风格补齐实现。
## 文档
- [Autman 插件开发规则与仓库约定](docs/autman-plugin-rules.md)
- [JS 微服务路由插件模板](templates/js-router-plugin-template.js)
- [Python middleware 插件模板](templates/python-middleware-plugin-template.py)
+525
View File
@@ -0,0 +1,525 @@
# 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。
+60
View File
@@ -0,0 +1,60 @@
//[open_source: true]
//[create_at: 2026-01-01 12:00:00]
//[title: 路由插件模板]
//[author: Hermes]
//[service: BOSS]
//[class: 工具类]
//[version: 1.0.0]
//[platform: web,qq,wx,tg,tb]
//[public: false]
//[price: 0]
//[disable: false]
//[description: Autman 微服务路由插件模板,支持 GET/POST、token 鉴权、JSON 返回。]
//[router: /example/api]
//[method: get]
//[method: post]
//[param: {"required":true,"key":"otto.example_token","bool":false,"placeholder":"访问Token","name":"接口鉴权Token","desc":"外部请求必须携带 access_token 或 token"}]
function replyJSON(obj) {
if (typeof response === "function") {
response(obj)
} else if (typeof sender !== "undefined" && sender.reply) {
sender.reply(obj)
} else {
sendText(JSON.stringify(obj))
}
}
function fail(code, msg) {
replyJSON({ ok: false, status: "fail", retcode: code, msg: msg })
}
function ok(data, msg) {
replyJSON({ ok: true, status: "ok", retcode: 0, msg: msg || "success", data: data || {} })
}
var method = (typeof getMethod === "function" ? getMethod() : (typeof getRouterMethod === "function" ? getRouterMethod() : "get"))
var params = typeof getRouterParams === "function" ? (getRouterParams() || {}) : {}
var body = {}
if (String(method).toLowerCase() === "post") {
var raw = typeof getRouterData === "function" ? getRouterData() : (typeof getRouterBody === "function" ? getRouterBody() : "")
if (raw) {
try {
body = JSON.parse(raw)
} catch (e) {
fail(-2, "POST JSON解析失败: " + e.message)
return
}
}
}
var accessToken = body.access_token || body.token || params.access_token || params.token || ""
var configuredToken = get("example_token") || ""
if (configuredToken && configuredToken !== accessToken) {
fail(-1, "token错误")
return
}
ok({ method: method, params: params, body: body }, "接口已启用")
@@ -0,0 +1,38 @@
# [title: Python插件模板]
# [language: python]
# [service: BOSS]
# [disable: false]
# [admin: true]
# [rule: ^模板测试$]
# [platform: qq,wx,tg,tb]
# [open_source: true]
# [version: 1.0.0]
# [public: false]
# [price: 0]
# [description: Autman Python middleware 插件模板。]
import json
import middleware
def main():
sender_id = middleware.getSenderID()
sender = middleware.Sender(sender_id)
message = sender.getMessage()
if not sender.isAdmin():
sender.reply("仅管理员可用")
return
data = {
"message": message,
"user_id": sender.getUserID(),
"user_name": sender.getUserName(),
"im_type": sender.getImtype(),
"chat_id": sender.getChatID(),
}
sender.reply(json.dumps(data, ensure_ascii=False))
if __name__ == "__main__":
main()