Files
webhook/README.md
zhilv ed6e27f162 feat(command): 添加监听范围过滤和回复 @控制
- 新增 COMMAND_SCOPE 配置,支持 all/group/private 过滤消息来源
- 新增 COMMAND_ALLOWED_GROUPS 群号白名单,逗号分隔,留空不限制
- 新增 COMMAND_ALLOWED_USERS QQ 号白名单,逗号分隔,留空不限制
- 新增 COMMAND_AT_SENDER 配置,控制回复时是否 @发送者(默认 true)
- 回调响应中 at_sender 字段可覆盖全局配置
- 更新 .env.example 和 README.md 文档
2026-05-03 12:26:44 +08:00

323 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Webhook Plugin
NcatBot 插件,对外暴露 HTTP 接口,接收外部消息转发至 QQ。
## 功能特性
- 发送群消息 / 私聊消息(文本、图片、文件、视频)
- 批量发送消息,自动组合消息段
- 文件上传接口,支持本地文件发送
- API Key 鉴权,未配置时自动生成随机密钥
- 上传文件自动清理(超过 24 小时删除)
- 完善的错误处理与重试机制
## 快速开始
```bash
# 安装依赖
uv sync
# 复制环境变量模板
cp .env.example .env
# 编辑 .env 配置(可选,不设置 API_KEY 会自动生成)
vim .env
# 启动 NcatBot
uv run python -m ncatbot
```
启动后日志会打印 `WEBHOOK_API_KEY`,未配置时为自动生成的 UUID。
## 环境变量
| 变量 | 必填 | 默认值 | 说明 |
|---|---|---|---|
| `WEBHOOK_API_KEY` | 否 | 自动生成 | API 鉴权密钥,未设置时自动生成 UUIDv4 |
| `WEBHOOK_HOST` | 否 | `0.0.0.0` | 监听地址 |
| `WEBHOOK_PORT` | 否 | `8081` | 监听端口 |
| `UPLOAD_DIR` | 否 | `./uploads` | 上传文件保存目录 |
| `MAX_UPLOAD_SIZE` | 否 | `20971520` | 单文件最大字节数(默认 20 MB |
| `ALLOWED_EXTENSIONS` | 否 | 空(不限) | 允许的扩展名,逗号分隔,如 `jpg,png,pdf` |
| `QQ_API_TIMEOUT` | 否 | `10` | QQ API 超时秒数 |
| `QQ_API_MAX_RETRIES` | 否 | `2` | QQ API 失败重试次数 |
| `COMMAND_PREFIX` | 否 | `#` | 命令前缀 |
| `COMMAND_LENGTH_MIN` | 否 | `2` | 命令名最小字符数 |
| `COMMAND_LENGTH_MAX` | 否 | `4` | 命令名最大字符数 |
| `COMMAND_SCOPE` | 否 | `all` | 监听范围:`all`(群+私)、`group`(仅群)、`private`(仅私) |
| `COMMAND_ALLOWED_GROUPS` | 否 | 空(不限) | 允许的群号,逗号分隔,如 `123456,789012` |
| `COMMAND_ALLOWED_USERS` | 否 | 空(不限) | 允许的 QQ 号,逗号分隔,如 `111111,222222` |
| `COMMAND_AT_SENDER` | 否 | `true` | 回复时是否 @发送者 |
| `COMMAND_CALLBACK_TIMEOUT` | 否 | `180` | 回调超时秒数 |
| `COMMAND_CALLBACK_URL` | 否 | 空(不监听) | 命令匹配后的回调 URL |
## 接口说明
所有接口响应格式统一:
```json
// 成功
{"code": 0, "msg": "ok", "data": {...}}
// 失败
{"code": 400, "msg": "invalid json", "data": null}
```
鉴权方式(除 `/healthz` 外必填):
```
Authorization: Bearer <API_KEY>
# 或
X-API-Key: <API_KEY>
```
### GET /healthz
健康检查,无需鉴权。
**响应:**
```json
{"code": 0, "msg": "ok", "data": {"health": "ok"}}
```
### POST /upload
上传文件到服务器,返回文件 ID 供后续消息引用。
**请求:** `multipart/form-data`,字段名不限,带 `filename` 即可。
**响应:**
```json
{
"code": 0,
"msg": "ok",
"data": {
"files": ["document.pdf", "photo.jpg"],
"path": "document.pdf"
}
}
```
- `files`:所有上传文件的相对路径(文件 ID
- `path`:单文件上传时的快捷字段
上传的文件超过 24 小时会被自动清理。
### POST /webhook
发送 QQ 消息,支持单条和批量两种模式。
#### 单条发送
**请求:**
```json
{
"group_id": "123456789",
"type": "text",
"msg": "Hello!"
}
```
| 字段 | 必填 | 说明 |
|---|---|---|
| `group_id` | 二选一 | 群号 |
| `user_id` | 二选一 | QQ 号(私聊) |
| `type` | 否 | 消息类型:`text`(默认)、`image``file``video` |
| `msg` | text 时必填 | 文本内容 |
| `url` | image/file/video 时必填 | 资源链接或上传返回的文件 ID |
**示例:**
```bash
# 发送文本
curl -X POST http://localhost:8081/webhook \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{"group_id": "123456789", "type": "text", "msg": "Hello!"}'
# 发送图片URL
curl -X POST http://localhost:8081/webhook \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{"group_id": "123456789", "type": "image", "url": "https://example.com/photo.jpg"}'
# 发送文件(本地已上传)
curl -X POST http://localhost:8081/webhook \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{"group_id": "123456789", "type": "file", "url": "document.pdf"}'
```
#### 批量发送
使用 `messages` 数组批量发送多条消息。`text``image``video` 会组合成一条消息发送(框架自动处理冲突拆分),`file` 每条单独发送。
**请求:**
```json
{
"group_id": "123456789",
"messages": [
{"type": "text", "msg": "文件已发送"},
{"type": "image", "url": "preview.jpg"},
{"type": "file", "url": "document.pdf"},
{"type": "video", "url": "demo.mp4"}
]
}
```
**响应:**
```json
{
"code": 0,
"msg": "ok",
"data": {
"total": 4,
"success": 4,
"failed": 0,
"results": [
{"index": 0, "success": true},
{"index": 1, "success": true},
{"index": 2, "success": true},
{"index": 3, "success": true}
]
}
}
```
每条消息独立处理,一条失败不影响其他消息。`results` 数组按原始 `index` 排序,包含每条消息的发送结果。
### 命令监听
插件会自动监听 QQ 消息,当消息以 `#命令名` 开头时,将命令内容 POST 到 `COMMAND_CALLBACK_URL`
**匹配规则:**
```
#测试命令 你好世界
│ │ │ │
│ │ │ └── 命令内容content可选
│ └── 空格分隔(可选)
└── 命令名2~4个字符
└── 前缀(默认 #
```
- 命令名支持中文、数字、字母等任意非空白字符,每个字符计 1
- 前缀通过 `COMMAND_PREFIX` 配置,长度范围通过 `COMMAND_LENGTH_MIN` / `COMMAND_LENGTH_MAX` 配置
- `#测试命令``#1a``#abc` 均可触发
- 不配置 `COMMAND_CALLBACK_URL` 则不监听
**监听范围过滤:**
- `COMMAND_SCOPE`:控制监听范围
- `all`(默认):群聊 + 私聊都监听
- `group`:仅监听群聊
- `private`:仅监听私聊
- `COMMAND_ALLOWED_GROUPS`:群号白名单,逗号分隔,留空不限制
- `COMMAND_ALLOWED_USERS`QQ 号白名单,逗号分隔,留空不限制
- 三个条件同时生效,必须全部满足才触发回调
**回调请求体POST JSON**
```json
{
"command": "测试命令",
"content": "你好世界",
"raw_message": "#测试命令 你好世界",
"user_id": "123456",
"group_id": "789012",
"message_id": "abc123"
}
```
| 字段 | 说明 |
|---|---|
| `command` | 命令名2~4个字符 |
| `content` | 命令后的内容 |
| `raw_message` | 原始消息文本 |
| `user_id` | 发送者 QQ 号 |
| `group_id` | 群号(私聊消息无此字段) |
| `message_id` | 消息 ID |
**回调响应格式(自动回复到 QQ**
回调服务器返回 JSON插件会自动将内容回复到原消息来源群/私聊)。
纯文本回复:
```json
{"reply": "收到你的命令了"}
```
批量回复text/image/video 组合发送file 单独发送):
```json
{
"messages": [
{"type": "text", "msg": "处理结果如下"},
{"type": "image", "url": "https://example.com/result.png"},
{"type": "file", "url": "report.pdf"}
]
}
```
| 字段 | 说明 |
|---|---|
| `reply` | 纯文本回复(与 `messages` 二选一,`messages` 优先) |
| `messages` | 批量回复数组,格式同 `/webhook``messages` 字段 |
| `at_sender` | 是否 @发送者(默认取 `COMMAND_AT_SENDER` 配置,仅群聊生效) |
| `group_id` | 可选,覆盖回复目标群号(默认回复到原群) |
| `user_id` | 可选,覆盖回复目标 QQ 号(默认回复到原发送者) |
不需要回复时返回 `{}` 或空响应即可。
## 项目结构
```
├── plugin.py # 插件入口,组装并启动 HTTP 服务
├── config.py # 配置(环境变量)
├── middleware.py # 鉴权 & 请求 ID 中间件
├── response.py # 统一响应格式
├── handlers/
│ ├── __init__.py
│ ├── command.py # 命令监听匹配与回调
│ ├── health.py # GET /healthz
│ ├── message.py # POST /webhook
│ └── upload.py # POST /upload
├── manifest.toml # NcatBot 插件元数据
├── .env.example # 环境变量模板
└── pyproject.toml # Python 依赖声明
```
## 部署建议
- 放在 Nginx/Caddy 反向代理后,启用 HTTPS
- 配置请求限流,防止滥用
- 使用 systemd 管理进程:
```ini
# /etc/systemd/system/ncatbot.service
[Unit]
Description=NcatBot Webhook Plugin
After=network.target
[Service]
Type=simple
User=ncatbot
WorkingDirectory=/opt/ncatbot
EnvironmentFile=/opt/ncatbot/.env
ExecStart=/opt/ncatbot/.venv/bin/python -m ncatbot
Restart=on-failure
[Install]
WantedBy=multi-user.target
```
日志默认输出到 stdout可由 `journald` 收集查看:
```bash
journalctl -u ncatbot -f
```