fix: 通过本地完整测试发现的 11 个 bug

🔴 致命(5个):
- Bug1: onebot11.json 格式错误(旧 {http:{},ws:{}} → network.httpServers/httppClients)
- Bug2: 端口映射错位(WebUI 6099, OneBot API 6097 独立端口)
- Bug3: 变量名含空格 NAP CAT_HOST → NAPCAT_HOST(14脚本+2SKILL+README)
- Bug4: heredoc 单引号阻止变量展开
- Bug5: process.py 缺失 _get_high_risk_words() 方法

🟡 重要(3个):
- Bug6: onebot11.json 默认不存在(cat > 而非 edit)
- Bug7: API 须登录后才响应(新增扫码引导步骤)
- Bug8: qqrebot systemd 依赖 napcat 服务

🟢 中等(3个):
- Bug9: Skills 占位符缺 YOUR_NAPCAT_HOST sed 覆盖
- Bug10: venv 不可用需降级方案
- Bug11: 端口 25580 冲突处理
已验证全链路:NapCat + qqrebot + 插件 + OpenClaw Gateway + AI 回复
This commit is contained in:
root
2026-05-04 19:47:26 +08:00
parent 79ea8175c4
commit 9dff73e7bc
18 changed files with 171 additions and 131 deletions

251
README.md
View File

@ -6,9 +6,9 @@
## 架构
```
QQ 用户 →→→ NapCat(Docker:6099)
QQ 用户 →→→ NapCat(Docker)
│
│ HTTP POST(事件推送)
│ HTTP POST(事件推送:6097→qqrebot:25580)
▼
qqrebot(HTTP Server :25580) ← 消息接收
│
@ -25,17 +25,17 @@ QQ 用户 →→→ NapCat(Docker:6099)
│
│ HTTP POST(send_private_msg / send_group_msg)
▼
NapCat(:6099) → 回复发送到 QQ
NapCat(Docker:6097)→ 回复发送到 QQ
```
**关键通信链路(全是 HTTP,无 WebSocket):**
| 链路 | 方向 | 协议 | 端口 |
| 链路 | 方向 | 协议 | 地址 |
|---|---|---|---|
| NapCat → qqrebot | 事件推送 | HTTP POST | `qqrebot:25580` |
| NapCat → qqrebot | 事件推送 | HTTP POST | `http://172.17.0.1:25580/` |
| 插件 → OpenClaw | AI 请求 | HTTP POST | `127.0.0.1:18789` |
| 插件 → NapCat | 发送回复 | HTTP POST | `napcat:6099` |
| 脚本 → NapCat | 发消息/管理 | HTTP POST | `napcat:6099` |
| 插件 → NapCat | 发送回复 | HTTP POST | `127.0.0.1:6097` |
| 脚本 → NapCat | 发消息/管理 | HTTP POST | `127.0.0.1:6097` |
## 前提条件
@ -69,7 +69,10 @@ AGENT_WORKSPACE="/home/program/${NEW_AGENT_ID}-workspace"
SHARED_DIR="${SHARED_DIR:-/opt/napcat_shared}"
# NapCat HTTP API 地址(同机部署走 127.0.0.1)
NAP CAT_HOST="127.0.0.1"
NAPCAT_HOST="127.0.0.1"
# OneBot HTTP API 端口(与 WebUI 端口 6099 不同)
NAPCAT_API_PORT="${NAPCAT_API_PORT:-6097}"
# 从 NapCat Docker 容器角度看到的 qqrebot 地址
# 同机部署时 NapCat 通过 Docker 网关访问宿主机
@ -84,44 +87,35 @@ print(cfg.get('gateway', {}).get('auth', {}).get('token', ''))
" 2>/dev/null)
```
> 注意:`NAP CAT_HOST` 是**不含端口**的纯 IP/域名,后面所有用到 `send_url` 的地方拼入端口 `:6099`。
> `QQREBOT_HOST` 带端口 `:25580`,用于 onebot11.json 的 `httpPost.url`。
> 变量命名规则:脚本中的占位符为 `YOUR_NAPCAT_HOST:25570`(带端口),由 sed 替换为 `${NAPCAT_HOST}:${NAPCAT_API_PORT}`。请勿在 bash 变量名中使用空格。
> `QQREBOT_HOST` 带端口 `:25580`,用于 onebot11.json 的 `httpClients` URL。
#### 1.2 向用户确认
只有以下两项无法自动推导,agent 需向用户询问:
```bash
# 向用户提问示例:
# "主人,我需要以下信息来完成部署:
# 1. ADMIN_QQ — 你的管理员 QQ 号(用于白名单和安全上报)
# 2. GATEWAY_TOKEN — 如上面自动读取的为空,请提供"
```
| 变量 | 必须 | 说明 |
|---|---|---|
| `ADMIN_QQ` | ✅ | 管理员 QQ 号,用于白名单和安全告警上报 |
| `GATEWAY_TOKEN` | 看情况 | 自动读取失败时(空值)才需要用户提供 |
> 其余变量(`NEW_AGENT_ID`、`SHARED_DIR`、`NAP CAT_HOST`、`QQREBOT_HOST`)全部使用默认值即可,**无需用户确认**。仅在跨机部署(NapCat 和 OpenClaw 不在同一台机器)时需要用户指定 `NAP CAT_HOST`。
> 其余变量(`NEW_AGENT_ID`、`SHARED_DIR`、`NAPCAT_HOST`、`NAPCAT_API_PORT`、`QQREBOT_HOST`)全部使用默认值即可。仅在跨机部署时需要用户调整 `NAPCAT_HOST` 或 `NAPCAT_API_PORT`。
#### 1.3 应用变量
```bash
# 确认 ADMIN_QQ
# 确认必填项
if [ -z "$ADMIN_QQ" ]; then
echo "请提供 ADMIN_QQ"
exit 1
fi
# 确认 GATEWAY_TOKEN 非空
if [ -z "$GATEWAY_TOKEN" ]; then
echo "自动读取 GATEWAY_TOKEN 失败,请提供"
exit 1
fi
export ADMIN_QQ NEW_AGENT_ID AGENT_WORKSPACE SHARED_DIR NAP_CAT_HOST QQREBOT_HOST GATEWAY_TOKEN
echo "环境变量已就绪:ADMIN_QQ=$ADMIN_QQ, AGENT=$NEW_AGENT_ID, NAPCAT=$NAP CAT_HOST"
export ADMIN_QQ NEW_AGENT_ID AGENT_WORKSPACE SHARED_DIR NAPCAT_HOST NAPCAT_API_PORT QQREBOT_HOST GATEWAY_TOKEN
echo "变量已就绪:ADMIN_QQ=$ADMIN_QQ, AGENT=$NEW_AGENT_ID, NAPCAT=$NAPCAT_HOST:$NAPCAT_API_PORT"
```
---
@ -130,7 +124,18 @@ echo "环境变量已就绪:ADMIN_QQ=$ADMIN_QQ, AGENT=$NEW_AGENT_ID, NAPCAT=$N
NapCat 是 QQ 协议实现,接收 QQ 消息并通过 **HTTP POST** 推送到 qqrebot。
#### 2.1 创建目录结构
#### 2.1 端口说明
| 端口 | 是否必开 | 用途 |
|---|---|---|
| `6097` | ✅ | OneBot HTTP API(脚本发消息/群管理/取数据都用此端口) |
| `6099` | ✅ | WebUI 管理面板(NapCat 默认占用,不能与 API 共用) |
| `3001` | ❌ | WebSocket Server(本教程用 HTTP POST,不启用) |
> NapCat v4 的 WebUI 默认绑定端口 6099。OneBot HTTP API 需要独立端口(本教程使用 6097)。
> WebUI 用于扫码登录和管理界面,首次部署时需保持开启;生产环境可关闭或将 WebUI 绑定到 `127.0.0.1`。
#### 2.2 创建目录结构
```bash
# 共享文件目录(NapCat Docker 内发文件时读取)
@ -142,12 +147,57 @@ mkdir -p /opt/napcat/config
mkdir -p /opt/napcat/qq_data
```
#### 2.2 启动 NapCat Docker 容器
#### 2.3 创建 onebot11.json 配置
> ⚠️ onebot11.json 默认不存在,需手动创建。NapCat v4 使用 `network` 配置格式(非旧版 `http{} ws{}` 格式)。
```bash
cat > /opt/napcat/config/onebot11.json << 'ONEBOTEOF'
{
"network": {
"httpServers": [
{
"name": "http-server",
"enable": true,
"host": "0.0.0.0",
"port": 6097,
"token": "",
"enableCors": true,
"debug": false
}
],
"httpClients": [
{
"name": "http-post",
"enable": true,
"url": "http://${QQREBOT_HOST}/",
"messagePostFormat": "array",
"reportSelfMessage": false,
"token": "",
"debug": false
}
],
"websocketServers": [],
"websocketClients": []
},
"musicSignUrl": "",
"enableLocalFile2Url": true,
"parseMultMsg": false
}
ONEBOTEOF
```
> `httpServers`:OneBot HTTP API,脚本通过此端口发送消息/管理群组。
> `httpClients`:NapCat 将消息事件通过 HTTP POST 推送到 qqrebot。
> `httpClients[0].url` 中 `${QQREBOT_HOST}` 需替换为实际地址(同机部署时为 `172.17.0.1:25580`)。Docker 的默认 bridge 网关是 `172.17.0.1`,可通过 `docker network inspect bridge` 确认。
#### 2.4 启动 NapCat Docker 容器
```bash
docker run -d \
--name napcat \
--restart unless-stopped \
-p 6097:6097 \
-p 6099:6099 \
-v /opt/napcat/config:/app/napcat/config \
-v /opt/napcat/qq_data:/app/.config/QQ \
@ -156,81 +206,38 @@ docker run -d \
mlikiowa/napcat-docker:latest
```
端口说明:
> 必须同时暴露 `6097`(OneBot API)和 `6099`(WebUI),缺一不可。
| 端口 | 是否必开 | 用途 |
|---|---|---|
| `6099` | ✅ | OneBot HTTP API(脚本发消息/群管理/取数据都用这个端口) |
| `3001` | ❌ | WebSocket Server(本教程用 HTTP POST 推送,不需要) |
| `9000` | 按需 | WebUI 管理面板(首次扫码登录可临时用) |
#### 2.5 QQ 登录(扫码)
如果机器防扫描严格,**只开 6099** 即可;WebUI 按需用完即关。
#### 2.3 首次登录配置
> NapCat 首次启动需要扫码登录 QQ 账号。登录数据持久化在 `/opt/napcat/qq_data`。
> ⚠️ OneBot HTTP API 在 QQ 登录成功之前不会启动!
> `curl /get_version_info` 类 API 调用必须在登录后才能成功。
```bash
# 查看登录二维码(日志中会打印链接)
docker logs napcat 2>&1 | grep -o 'http[s]*://[^ ]*\.png'
# 或用浏览器打开 http://<服务器IP>:9000/webui 扫码登录
# 或用浏览器打开 http://<服务器IP>:6099/webui 扫码登录
# 等待登录成功
docker logs napcat -f | grep "登录成功"
# 登录成功后 OneBot 配置会自动加载,可以看到类似日志:
# [Notice] [OneBot11] HTTP服务: 0.0.0.0:6097, : 已启动
# [Notice] [OneBot11] HTTP上报服务: http://172.17.0.1:25580/, : 已启动
```
#### 2.4 配置事件推送(HTTP POST → qqrebot)
NapCat 需要将所有消息事件通过 HTTP POST 推送到 qqrebot(端口 `25580`)。
编辑 `/opt/napcat/config/onebot11.json`:
```json
{
"http": {
"enable": true,
"host": "0.0.0.0",
"port": 6099,
"enableHttpPost": false,
"enableHttpHeartbeat": false
},
"httpPost": [
{
"url": "http://${QQREBOT_HOST}/",
"secret": ""
}
],
"ws": {
"enable": false
},
"heartbeat": {
"enable": false
},
"enableLocalFile2Url": true
}
```
> **关键说明:**
> - `httpPost` 数组中的 URL 是 NapCat 向 qqrebot 推送消息事件的目标地址
> - 如果 NapCat 和 qqrebot 在**同一台宿主机上**,`url` 通常设置为 `http://172.17.0.1:25580/`(Docker 默认网关地址)
> - 如果**不同机器**,填 qqrebot 机器可达的真实 IP
> - `ws.enable = false` — 本教程不用 WebSocket
> - `http.enableHttpPost = false` — httpPost 由专门的 `httpPost` 数组控制
应用配置:
#### 2.6 验证 NapCat
```bash
docker restart napcat
```
#### 2.5 验证 NapCat
```bash
# 验证 HTTP API 可访问
curl -s http://localhost:6099/get_version_info | python3 -m json.tool
# 验证 HTTP API 可访问(仅在登录成功后可调用)
curl -s http://localhost:6097/get_version_info | python3 -m json.tool
# 预期输出:
# {"status": "ok", "data": {"app_name": "NapCat", ...}, ...}
# 验证 WebUI 可访问
curl -s -o /dev/null -w "%{http_code}" http://localhost:6099/webui
# 预期返回 200
```
---
@ -250,14 +257,25 @@ mv chat_rebot-connect-with-onebot-standard- /opt/qqrebot
cd /opt/qqrebot
```
#### 3.2 配置
#### 3.2 检查端口冲突
```bash
# 检查 25580 是否已被占用
if ss -tlnp | grep -q :25580; then
echo "⚠️ 端口 25580 已被占用,请修改为 25581 或其他空闲端口"
echo " 修改 qqrebot config.toml 中的 list_port"
exit 1
fi
```
#### 3.3 配置
编辑 `config/config.toml`:
```toml
[app]
list_port = 25580
send_url = "http://${NAP CAT_HOST}:6099" # → 插件回复时调 NapCat HTTP API 用
send_url = "http://${NAPCAT_HOST}:${NAPCAT_API_PORT}" # 指向 NapCat OneBot API
[rebot]
id = ""
@ -266,12 +284,16 @@ id = ""
dir = ["plugins"]
```
> **`send_url`**:qqrebot 框架用它作为 NapCat HTTP API 的基础地址。插件中的 `ctx.group.url` 来自此值,后续所有 `send_private_msg` / `send_group_msg` 调用都走这个地址。如果 NapCat 和 qqrebot 同机,填 `http://127.0.0.1:6099`。
> `send_url` 是 qqrebot 框架发给插件的 NapCat API 基础地址(即 `ctx.group.url`)。所有 `send_private_msg` / `send_group_msg` 调用都走此地址。
> 如果 NapCat 和 qqrebot 同机,`send_url = "http://127.0.0.1:6097"`。
#### 3.3 创建 systemd 服务
#### 3.4 创建 systemd 服务
```ini
# /etc/systemd/system/qqrebot.service
```bash
# 注意:如果 qqrebot 已有其他实例在运行,请修改端口和服务名
# 此处假设使用端口 25580,服务名 qqrebot
cat > /etc/systemd/system/qqrebot.service << 'SERVICEEOF'
[Unit]
Description=QQ Robot
After=network.target docker.service
@ -288,9 +310,8 @@ StandardError=journal
[Install]
WantedBy=multi-user.target
```
SERVICEEOF
```bash
systemctl daemon-reload
systemctl enable --now qqrebot
```
@ -377,7 +398,7 @@ mkdir -p "${AGENT_WORKSPACE}/files"
### 第6步:获取源码并部署到 agent 的 workspace
> agent 需要在目标服务器上获取 chatrebot_aireply_plug 的 `scripts/`(26 个脚本)和 `skills/`(8 个 skill)目录。
> agent 需要在目标服务器上获得 chatrebot_aireply_plug 的 `scripts/`(26 个脚本)和 `skills/`(8 个 skill)目录。
#### 6.1 在目标服务器上克隆仓库
@ -448,11 +469,9 @@ done
```bash
cd "${AGENT_WORKSPACE}/scripts"
# NapCat HTTP API 地址(端口 6099)
sed -i "s|YOUR_NAP CAT_HOST:25570|${NAP CAT_HOST}:6099|g" *.py
sed -i "s|YOUR_NAP CAT_HOST|${NAP CAT_HOST}:6099|g" *.py
sed -i "s|YOUR_NAPCAT_HOST:25570|${NAP CAT_HOST}:6099|g" *.py
sed -i "s|YOUR_NAPCAT_HOST|${NAP CAT_HOST}:6099|g" *.py
# NapCat HTTP API 地址(端口 ${NAPCAT_API_PORT},非旧版 25570)
sed -i "s|YOUR_NAPCAT_HOST:25570|${NAPCAT_HOST}:${NAPCAT_API_PORT}|g" *.py
sed -i "s|YOUR_NAPCAT_HOST|${NAPCAT_HOST}:${NAPCAT_API_PORT}|g" *.py
# 管理员 QQ
sed -i "s|YOUR_ADMIN_QQ|${ADMIN_QQ}|g" *.py
@ -482,7 +501,7 @@ done
> ⚠️ Skills 的替换很关键:
> - `YOUR_WORKSPACE_PATH` → agent 的 subprocess.run 能找到脚本
> - `YOUR_SHARED_DIR` → 涉及文件发送的 Skill(qq-messenger、qq-napcat-extras)理解的宿主机共享路径
> - 两个 Skill(qq-messenger、qq-napcat-extras)还记录了容器路径 `/app/napcat/share/`,这是一个固定常量,对应 Step 2.2 的 Docker volume `-v SHARED_DIR:/app/napcat/share`。如果修改了 volume 挂载点,需同步修改这些 SKILL.md 和 `qq_send_file.py`、`qq_ocr_image.py` 中的容器路径。
> - 两个 Skill(qq-messenger、qq-napcat-extras)还记录了容器路径 `/app/napcat/share/`,这是一个固定常量,对应 Docker volume `-v SHARED_DIR:/app/napcat/share`。如果修改了 volume 挂载点,需同步修改这些 SKILL.md 和 `qq_send_file.py`、`qq_ocr_image.py` 中的容器路径。
**文件发送全链路路径转换示意:**
@ -491,9 +510,9 @@ agent 工作区 宿主机共享目录 NapCat 容器
/script/file.txt →cp→ /opt/napcat_shared/ ←volume→ /app/napcat/share/
↓
qq_send_file.py 构造
file:///app/napcat/share/file.txt
file:///app/napcat/share/file.txt
↓
NapCat HTTP API → QQ 用户
NapCat HTTP API → QQ 用户
```
> 代理的思维模型:"我要发文件 → 把文件放到共享目录 → 脚本从共享目录发"。Skill 中提到的"发送文件/图片"都隐含了这一步文件复制。
@ -507,7 +526,6 @@ ls -la "${SHARED_DIR}/"
# 手动测试:复制一个文件过去,走 qq_send_file.py
echo hello > /tmp/test.txt
python3 "${AGENT_WORKSPACE}/scripts/qq_send_file.py" "/tmp/test.txt" --target "${ADMIN_QQ}" --private
# 如果 ADMIN_QQ 收到了"hello"文件,路径映射一切正常
```
#### 6.5 验证完整性
@ -516,7 +534,7 @@ python3 "${AGENT_WORKSPACE}/scripts/qq_send_file.py" "/tmp/test.txt" --target "$
# 脚本数量
echo "scripts: $(ls "${AGENT_WORKSPACE}/scripts/" | wc -l) 个"
# 确认无残留占位符
if grep -r "YOUR_ADMIN_QQ\|YOUR_WORKSPACE_PATH" "${AGENT_WORKSPACE}/"; then
if grep -r "YOUR_ADMIN_QQ\|YOUR_WORKSPACE_PATH\|YOUR_NAPCAT_HOST\|YOUR_SHARED_DIR" "${AGENT_WORKSPACE}/"; then
echo "⚠️ 还有未替换的占位符!"
else
echo "✅ 占位符全部替换完毕"
@ -542,10 +560,13 @@ openclaw config get agents.list
### 第8步:端到端验证
1. 在 QQ 上给机器人发消息
2. 检查 qqrebot 日志:`journalctl -u qqrebot -f | grep OpenClawBridge`
3. 检查 OpenClaw 日志:`journalctl -u openclaw -f`
4. 验证 agent 已回复
1. 确认 NapCat QQ 已登录(`docker logs napcat 2>&1 | grep "登录成功"`)
2. 确认 OneBot API 可访问(`curl localhost:6097/get_version_info`)
3. 确认 qqrebot 正在运行(`systemctl status qqrebot`)
4. 在 QQ 上给机器人发消息
5. 检查 qqrebot 日志:`journalctl -u qqrebot -f | grep OpenClawBridge`
6. 检查 OpenClaw 日志:`journalctl -u openclaw -f`
7. 验证 agent 已回复
---
@ -553,12 +574,13 @@ openclaw config get agents.list
### 端口对照
| 组件 | go-cqhttp | NapCat(Docker) |
| 组件 | 旧版 go-cqhttp | NapCat(本教程) |
|---|---|---|
| HTTP API 端口 | `25570` | **6099** |
| WebSocket | `25570`(兼用) | 不启用(本教程) |
| HTTP API 端口 | `25570` | **6097** |
| WebUI | N/A | **6099**(扫码登录用) |
| WebSocket | `25570`(兼用) | 不启用 |
> 脚本中 `CQHTTP_URL = "http://YOUR_NAP CAT_HOST:25570"` 替换后变为 `http://<host>:6099`。
> 脚本中 `CQHTTP_URL = "http://YOUR_NAPCAT_HOST:25570"` 替换后变为 `http://127.0.0.1:6097`。
### 文件发送与共享目录
@ -579,9 +601,8 @@ NapCat 运行在 Docker 内部,宿主机文件路径不能直接访问。解
| 占位符 | 替换值 | 出现位置 |
|---|---|---|
| `YOUR_NAP CAT_HOST:25570` | `${NAP CAT_HOST}:6099` | 所有脚本 |
| `YOUR_NAP CAT_HOST` | `${NAP CAT_HOST}:6099` | 所有脚本 |
| `YOUR_NAPCAT_HOST` | `${NAP CAT_HOST}:6099` | 部分脚本 |
| `YOUR_NAPCAT_HOST:25570` | `${NAPCAT_HOST}:${NAPCAT_API_PORT}` | 所有脚本 |
| `YOUR_NAPCAT_HOST` | `${NAPCAT_HOST}:${NAPCAT_API_PORT}` | 部分脚本 |
| `YOUR_ADMIN_QQ` | `${ADMIN_QQ}` | 脚本 + SKILL.md |
| `YOUR_GATEWAY_TOKEN` | `${GATEWAY_TOKEN}` | 脚本 |
| `YOUR_SHARED_DIR` | `${SHARED_DIR}` | `qq_send_file.py`、`qq_ocr_image.py`、`qq-messenger/SKILL.md`、`qq-napcat-extras/SKILL.md` |
@ -653,7 +674,7 @@ chatrebot_aireply_plug/
│ ├── plugin_modules.py # BasePlugin + MessageContext
│ └── user_module.py # User/Group 封装
├── scripts/ # ✅ 部署时复制到 agent workspace
│ ├── qq_send_msg.py # 发送消息(调 NapCat :6099)
│ ├── qq_send_msg.py # 发送消息(调 NapCat :6097)
│ ├── qq_send_file.py # 发送文件/图片
│ ├── qq_get_groups.py # 获取群列表
│ ├── qq_get_friends.py # 获取好友列表