# 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 # 或 X-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 ```