@sliverp/qqbot 1.3.6 → 1.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +150 -114
- package/bin/qqbot-cli.js +2 -2
- package/dist/index.js +2 -2
- package/dist/src/channel.d.ts +1 -1
- package/dist/src/channel.js +140 -5
- package/dist/src/config.js +8 -0
- package/dist/src/gateway.d.ts +1 -0
- package/dist/src/gateway.js +893 -140
- package/dist/src/onboarding.js +5 -0
- package/package.json +1 -1
- package/src/channel.ts +14 -12
- package/src/config.ts +10 -0
- package/src/gateway.ts +28 -5
- package/src/onboarding.ts +8 -0
package/README.md
CHANGED
|
@@ -1,106 +1,96 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
1
3
|
# QQ Bot Channel Plugin for Moltbot
|
|
2
4
|
|
|
3
|
-
QQ 开放平台Bot API 的 Moltbot 渠道插件,支持 C2C 私聊、群聊 @消息、频道消息。
|
|
5
|
+
QQ 开放平台 Bot API 的 Moltbot 渠道插件,支持 C2C 私聊、群聊 @消息、频道消息。
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@sliverp/qqbot)
|
|
8
|
+
[](./LICENSE)
|
|
9
|
+
[](https://bot.q.qq.com/wiki/)
|
|
10
|
+
[](https://github.com/sliverp/moltbot)
|
|
11
|
+
[](https://nodejs.org/)
|
|
12
|
+
[](https://www.typescriptlang.org/)
|
|
13
|
+
|
|
14
|
+
</div>
|
|
4
15
|
|
|
5
|
-
|
|
16
|
+
---
|
|
6
17
|
|
|
7
|
-
|
|
8
|
-
- **自动重连**:WebSocket 断连后自动重连,支持 Session Resume
|
|
9
|
-
- **消息去重**:自动管理 `msg_seq`,支持对同一消息多次回复
|
|
10
|
-
- **系统提示词**:可配置自定义系统提示词注入到 AI 请求
|
|
11
|
-
- **错误提示**:AI 无响应时自动提示用户检查配置
|
|
18
|
+
## ⭐ Star 趋势
|
|
12
19
|
|
|
13
|
-
|
|
14
|
-
<img width="952" height="582" alt="image" src="https://github.com/user-attachments/assets/a16d582b-708c-473e-b3a2-e0c4c503a0c8" />
|
|
20
|
+
<!-- 在这里放置 Star 数量变化图 -->
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
**使用`openclaw plugins list`来产看插件版本,建议使用最新版的版本**
|
|
18
|
-
<img width="902" height="248" alt="Clipboard_Screenshot_1769739939" src="https://github.com/user-attachments/assets/d6f37458-900c-4de9-8fdc-f8e6bf5c7ee5" />
|
|
22
|
+
---
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
- 支持Markdonw格式
|
|
24
|
+
## 📸 使用示例
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
### 1.3.0-2026.02.03
|
|
25
|
-
- 支持图片收发等功能
|
|
26
|
-
<img width="924" height="428" alt="Clipboard_Screenshot_1770112572" src="https://github.com/user-attachments/assets/80f38ae9-dc40-4545-ad17-e7e254064cf4" />
|
|
26
|
+
<img width="600" alt="使用示例" src="https://github.com/user-attachments/assets/6f1704ab-584b-497e-8937-96f84ce2958f" />
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
<img width="930" height="288" alt="Clipboard_Screenshot_1770112539" src="https://github.com/user-attachments/assets/9674cda0-91e9-4860-8dcc-bc50007862a2" />
|
|
30
|
-
- 优化一些已知问题
|
|
28
|
+
---
|
|
31
29
|
|
|
30
|
+
## ✨ 功能特性
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
-
|
|
37
|
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
- 优化未收到未收到大模型响应时的提示信息
|
|
32
|
+
- 🔒 **多场景支持** - C2C 私聊、群聊 @消息、频道消息、频道私信
|
|
33
|
+
- 🖼️ **富媒体消息** - 支持图片收发、文件发送
|
|
34
|
+
- ⏰ **定时推送** - 支持定时任务到时后主动推送
|
|
35
|
+
- 🔗 **URL 无限制** - 私聊可直接发送 URL
|
|
36
|
+
- ⌨️ **输入状态** - Bot 正在输入中状态提示
|
|
37
|
+
- 🔄 **热更新** - 支持 npm 方式安装和热更新
|
|
38
|
+
- 📝 **Markdown** - 支持 Markdown 格式(即将更新)
|
|
41
39
|
|
|
40
|
+
---
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
- 支持发送文件
|
|
45
|
-
- 支持openclaw、moltbot命令行
|
|
46
|
-
- 修复[health]检查提示: [health] refresh failed: Cannot read properties of undefined (reading 'appId')的问题(不影响使用)
|
|
47
|
-
- 修复文件发送后clawdbot无法读取的问题
|
|
42
|
+
## 📦 安装
|
|
48
43
|
|
|
49
|
-
###
|
|
50
|
-
- 解决了长时间使用会断联的问题
|
|
51
|
-
- 解决了频繁重连的问题
|
|
52
|
-
- 增加了大模型调用失败后的提示消息
|
|
44
|
+
### 方式一:腾讯云 Lighthouse 镜像(最简单)
|
|
53
45
|
|
|
46
|
+
[](https://cloud.tencent.com/product/lighthouse)
|
|
54
47
|
|
|
55
|
-
|
|
56
|
-
- 解决了一些url会被拦截的问题
|
|
57
|
-
- 解决了多轮消息会发送失败的问题
|
|
58
|
-
- 修复了部分图片无法接受的问题
|
|
59
|
-
- 增加支持onboard的方式配置AppId 和 AppSecret
|
|
48
|
+
直接使用预装好的腾讯云 Lighthouse 镜像,开箱即用,无需手动安装配置。
|
|
60
49
|
|
|
50
|
+
### 方式二:npm 安装(推荐)
|
|
61
51
|
|
|
62
|
-
|
|
52
|
+
```bash
|
|
53
|
+
openclaw plugins install @sliverp/qqbot@1.3.7
|
|
54
|
+
```
|
|
63
55
|
|
|
64
|
-
|
|
56
|
+
### 方式三:源码安装
|
|
65
57
|
|
|
66
58
|
```bash
|
|
67
59
|
git clone https://github.com/sliverp/qqbot.git && cd qqbot
|
|
68
|
-
clawdbot plugins install .
|
|
60
|
+
clawdbot plugins install .
|
|
69
61
|
```
|
|
70
62
|
|
|
71
|
-
|
|
63
|
+
> 💡 安装过程需要一些时间,尤其是小内存机器,请耐心等待
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## ⚙️ 配置
|
|
72
68
|
|
|
73
69
|
### 1. 获取 QQ 机器人凭证
|
|
74
70
|
|
|
75
71
|
1. 访问 [QQ 开放平台](https://q.qq.com/)
|
|
76
72
|
2. 创建机器人应用
|
|
77
73
|
3. 获取 `AppID` 和 `AppSecret`(ClientSecret)
|
|
78
|
-
4. Token
|
|
74
|
+
4. Token 格式:`AppID:AppSecret`
|
|
79
75
|
|
|
80
76
|
### 2. 添加配置
|
|
81
77
|
|
|
82
|
-
|
|
78
|
+
**交互式配置:**
|
|
83
79
|
|
|
84
80
|
```bash
|
|
85
81
|
clawdbot channels add
|
|
86
82
|
# 选择 qqbot,按提示输入 Token
|
|
87
83
|
```
|
|
88
84
|
|
|
89
|
-
|
|
85
|
+
**命令行配置:**
|
|
90
86
|
|
|
91
87
|
```bash
|
|
92
88
|
clawdbot channels add --channel qqbot --token "AppID:AppSecret"
|
|
93
89
|
```
|
|
94
90
|
|
|
95
|
-
示例:
|
|
96
|
-
|
|
97
|
-
```bash
|
|
98
|
-
clawdbot channels add --channel qqbot --token "102146862:xxxxxxxx"
|
|
99
|
-
```
|
|
100
|
-
|
|
101
91
|
### 3. 手动编辑配置(可选)
|
|
102
92
|
|
|
103
|
-
|
|
93
|
+
编辑 `~/.clawdbot/clawdbot.json`:
|
|
104
94
|
|
|
105
95
|
```json
|
|
106
96
|
{
|
|
@@ -108,27 +98,28 @@ clawdbot channels add --channel qqbot --token "102146862:xxxxxxxx"
|
|
|
108
98
|
"qqbot": {
|
|
109
99
|
"enabled": true,
|
|
110
100
|
"appId": "你的AppID",
|
|
111
|
-
"clientSecret": "你的AppSecret"
|
|
112
|
-
"systemPrompt": "你是一个友好的助手"
|
|
101
|
+
"clientSecret": "你的AppSecret"
|
|
113
102
|
}
|
|
114
103
|
}
|
|
115
104
|
}
|
|
116
105
|
```
|
|
117
106
|
|
|
107
|
+
---
|
|
118
108
|
|
|
119
|
-
|
|
120
|
-
## 配置项说明
|
|
109
|
+
## 📋 配置项说明
|
|
121
110
|
|
|
122
111
|
| 配置项 | 类型 | 必填 | 说明 |
|
|
123
112
|
|--------|------|------|------|
|
|
124
|
-
| `appId` | string |
|
|
125
|
-
| `clientSecret` | string |
|
|
126
|
-
| `clientSecretFile` | string |
|
|
127
|
-
| `enabled` | boolean |
|
|
128
|
-
| `name` | string |
|
|
129
|
-
| `systemPrompt` | string |
|
|
113
|
+
| `appId` | string | ✅ | QQ 机器人 AppID |
|
|
114
|
+
| `clientSecret` | string | ✅* | AppSecret,与 `clientSecretFile` 二选一 |
|
|
115
|
+
| `clientSecretFile` | string | ✅* | AppSecret 文件路径 |
|
|
116
|
+
| `enabled` | boolean | ❌ | 是否启用,默认 `true` |
|
|
117
|
+
| `name` | string | ❌ | 账户显示名称 |
|
|
118
|
+
| `systemPrompt` | string | ❌ | 自定义系统提示词 |
|
|
130
119
|
|
|
131
|
-
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 📨 支持的消息类型
|
|
132
123
|
|
|
133
124
|
| 事件类型 | 说明 | Intent |
|
|
134
125
|
|----------|------|--------|
|
|
@@ -137,17 +128,17 @@ clawdbot channels add --channel qqbot --token "102146862:xxxxxxxx"
|
|
|
137
128
|
| `AT_MESSAGE_CREATE` | 频道 @机器人消息 | `1 << 30` |
|
|
138
129
|
| `DIRECT_MESSAGE_CREATE` | 频道私信 | `1 << 12` |
|
|
139
130
|
|
|
140
|
-
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## 🚀 使用
|
|
141
134
|
|
|
142
|
-
###
|
|
135
|
+
### 启动服务
|
|
143
136
|
|
|
144
|
-
后台启动
|
|
145
137
|
```bash
|
|
138
|
+
# 后台启动
|
|
146
139
|
clawdbot gateway restart
|
|
147
|
-
```
|
|
148
140
|
|
|
149
|
-
|
|
150
|
-
```bash
|
|
141
|
+
# 前台启动(查看日志)
|
|
151
142
|
clawdbot gateway --port 18789 --verbose
|
|
152
143
|
```
|
|
153
144
|
|
|
@@ -158,23 +149,35 @@ clawdbot onboard
|
|
|
158
149
|
# 选择 QQ Bot 进行交互式配置
|
|
159
150
|
```
|
|
160
151
|
|
|
161
|
-
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## ⚠️ 注意事项
|
|
155
|
+
|
|
156
|
+
- **群消息**:需要在群内 @机器人 才能触发回复
|
|
157
|
+
- **沙箱模式**:新创建的机器人默认在沙箱模式,需要添加测试用户
|
|
162
158
|
|
|
163
|
-
|
|
164
|
-
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 🔄 升级
|
|
162
|
+
|
|
163
|
+
### npm 热更新
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
npx -y @sliverp/qqbot@1.3.7 upgrade
|
|
167
|
+
```
|
|
165
168
|
|
|
166
|
-
|
|
169
|
+
> 热更新后无需重新配置 AppId 和 AppSecret
|
|
167
170
|
|
|
168
|
-
|
|
171
|
+
### 源码热更新
|
|
169
172
|
|
|
170
173
|
```bash
|
|
171
174
|
git clone https://github.com/sliverp/qqbot.git && cd qqbot
|
|
172
175
|
|
|
173
|
-
#
|
|
176
|
+
# 运行升级脚本
|
|
174
177
|
bash ./scripts/upgrade.sh
|
|
175
178
|
|
|
176
|
-
#
|
|
177
|
-
clawdbot plugins install .
|
|
179
|
+
# 重新安装
|
|
180
|
+
clawdbot plugins install .
|
|
178
181
|
|
|
179
182
|
# 重新配置
|
|
180
183
|
clawdbot channels add --channel qqbot --token "AppID:AppSecret"
|
|
@@ -183,49 +186,82 @@ clawdbot channels add --channel qqbot --token "AppID:AppSecret"
|
|
|
183
186
|
clawdbot gateway restart
|
|
184
187
|
```
|
|
185
188
|
|
|
186
|
-
|
|
187
|
-
- 删除 `~/.clawdbot/extensions/qqbot` 目录
|
|
188
|
-
- 清理 `clawdbot.json` 中的 qqbot 相关配置
|
|
189
|
+
升级脚本会自动清理旧版本和配置。
|
|
189
190
|
|
|
190
|
-
|
|
191
|
+
---
|
|
191
192
|
|
|
192
|
-
```bash
|
|
193
|
-
# 安装依赖
|
|
194
|
-
npm install
|
|
195
193
|
|
|
196
|
-
# 编译
|
|
197
|
-
npm run build
|
|
198
194
|
|
|
199
|
-
|
|
200
|
-
npm run dev
|
|
201
|
-
```
|
|
195
|
+
---
|
|
202
196
|
|
|
203
|
-
##
|
|
197
|
+
## 📚 版本历史
|
|
204
198
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
199
|
+
<details>
|
|
200
|
+
<summary><b>v1.4.0(即将更新)</b></summary>
|
|
201
|
+
|
|
202
|
+
- 支持 Markdown 格式
|
|
203
|
+
|
|
204
|
+
</details>
|
|
205
|
+
|
|
206
|
+
<details>
|
|
207
|
+
<summary><b>v1.3.0 - 2026.02.03</b></summary>
|
|
208
|
+
|
|
209
|
+
- ✨ 支持图片收发等功能
|
|
210
|
+
- ✨ 支持定时任务到时后主动推送
|
|
211
|
+
- ✨ 支持使用 npm 等方式安装和升级
|
|
212
|
+
- 🐛 优化一些已知问题
|
|
213
|
+
|
|
214
|
+
</details>
|
|
215
|
+
|
|
216
|
+
<details>
|
|
217
|
+
<summary><b>v1.2.5 - 2026.02.02</b></summary>
|
|
218
|
+
|
|
219
|
+
- ✨ 解除 URL 发送限制
|
|
220
|
+
- ✨ 更新 Bot 正在输入中状态
|
|
221
|
+
- ✨ 提供主动推送能力
|
|
222
|
+
- 🐛 优化一些已知问题
|
|
223
|
+
|
|
224
|
+
</details>
|
|
222
225
|
|
|
223
|
-
|
|
226
|
+
<details>
|
|
227
|
+
<summary><b>v1.2.2 - 2026.01.31</b></summary>
|
|
228
|
+
|
|
229
|
+
- ✨ 支持发送文件
|
|
230
|
+
- ✨ 支持 openclaw、moltbot 命令行
|
|
231
|
+
- 🐛 修复 health 检查提示问题
|
|
232
|
+
- 🐛 修复文件发送后 clawdbot 无法读取的问题
|
|
233
|
+
|
|
234
|
+
</details>
|
|
235
|
+
|
|
236
|
+
<details>
|
|
237
|
+
<summary><b>v1.2.1</b></summary>
|
|
238
|
+
|
|
239
|
+
- 🐛 解决长时间使用会断联的问题
|
|
240
|
+
- 🐛 解决频繁重连的问题
|
|
241
|
+
- ✨ 增加大模型调用失败后的提示消息
|
|
242
|
+
|
|
243
|
+
</details>
|
|
244
|
+
|
|
245
|
+
<details>
|
|
246
|
+
<summary><b>v1.1.0</b></summary>
|
|
247
|
+
|
|
248
|
+
- 🐛 解决 URL 被拦截的问题
|
|
249
|
+
- 🐛 解决多轮消息发送失败的问题
|
|
250
|
+
- 🐛 修复部分图片无法接收的问题
|
|
251
|
+
- ✨ 增加支持 onboard 配置方式
|
|
252
|
+
|
|
253
|
+
</details>
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## 🔗 相关链接
|
|
224
258
|
|
|
225
259
|
- [QQ 机器人官方文档](https://bot.q.qq.com/wiki/)
|
|
226
260
|
- [QQ 开放平台](https://q.qq.com/)
|
|
227
261
|
- [API v2 文档](https://bot.q.qq.com/wiki/develop/api-v2/)
|
|
228
262
|
|
|
229
|
-
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## 📄 License
|
|
230
266
|
|
|
231
267
|
MIT
|
package/bin/qqbot-cli.js
CHANGED
|
@@ -164,8 +164,8 @@ function upgrade() {
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
console.log('\n=== 升级完成 ===');
|
|
167
|
-
console.log(`\n
|
|
168
|
-
console.log(` ${foundInstallation} gateway --verbose`);
|
|
167
|
+
console.log(`\n可以运行以下命令前台运行启动机器人:`);
|
|
168
|
+
console.log(` ${foundInstallation} gateway stop && ${foundInstallation} gateway --port 18789 --verbose`);
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
// 安装命令
|
package/dist/index.js
CHANGED
|
@@ -3,8 +3,8 @@ import { qqbotPlugin } from "./src/channel.js";
|
|
|
3
3
|
import { setQQBotRuntime } from "./src/runtime.js";
|
|
4
4
|
const plugin = {
|
|
5
5
|
id: "qqbot",
|
|
6
|
-
name: "QQ Bot
|
|
7
|
-
description: "QQ Bot channel plugin
|
|
6
|
+
name: "QQ Bot",
|
|
7
|
+
description: "QQ Bot channel plugin",
|
|
8
8
|
configSchema: emptyPluginConfigSchema(),
|
|
9
9
|
register(api) {
|
|
10
10
|
setQQBotRuntime(api.runtime);
|
package/dist/src/channel.d.ts
CHANGED
package/dist/src/channel.js
CHANGED
|
@@ -1,8 +1,38 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { applyAccountNameToChannelSection, deleteAccountFromConfigSection, setAccountEnabledInConfigSection, } from "openclaw/plugin-sdk";
|
|
2
|
+
import { DEFAULT_ACCOUNT_ID, listQQBotAccountIds, resolveQQBotAccount, applyQQBotAccountConfig, resolveDefaultQQBotAccountId } from "./config.js";
|
|
3
|
+
import { sendText, sendMedia } from "./outbound.js";
|
|
3
4
|
import { startGateway } from "./gateway.js";
|
|
4
5
|
import { qqbotOnboardingAdapter } from "./onboarding.js";
|
|
5
|
-
|
|
6
|
+
import { getQQBotRuntime } from "./runtime.js";
|
|
7
|
+
/**
|
|
8
|
+
* 简单的文本分块函数
|
|
9
|
+
* 用于预先分块长文本
|
|
10
|
+
*/
|
|
11
|
+
function chunkText(text, limit) {
|
|
12
|
+
if (text.length <= limit)
|
|
13
|
+
return [text];
|
|
14
|
+
const chunks = [];
|
|
15
|
+
let remaining = text;
|
|
16
|
+
while (remaining.length > 0) {
|
|
17
|
+
if (remaining.length <= limit) {
|
|
18
|
+
chunks.push(remaining);
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
// 尝试在换行处分割
|
|
22
|
+
let splitAt = remaining.lastIndexOf("\n", limit);
|
|
23
|
+
if (splitAt <= 0 || splitAt < limit * 0.5) {
|
|
24
|
+
// 没找到合适的换行,尝试在空格处分割
|
|
25
|
+
splitAt = remaining.lastIndexOf(" ", limit);
|
|
26
|
+
}
|
|
27
|
+
if (splitAt <= 0 || splitAt < limit * 0.5) {
|
|
28
|
+
// 还是没找到,强制在 limit 处分割
|
|
29
|
+
splitAt = limit;
|
|
30
|
+
}
|
|
31
|
+
chunks.push(remaining.slice(0, splitAt));
|
|
32
|
+
remaining = remaining.slice(splitAt).trimStart();
|
|
33
|
+
}
|
|
34
|
+
return chunks;
|
|
35
|
+
}
|
|
6
36
|
export const qqbotPlugin = {
|
|
7
37
|
id: "qqbot",
|
|
8
38
|
meta: {
|
|
@@ -15,9 +45,14 @@ export const qqbotPlugin = {
|
|
|
15
45
|
},
|
|
16
46
|
capabilities: {
|
|
17
47
|
chatTypes: ["direct", "group"],
|
|
18
|
-
media:
|
|
48
|
+
media: true,
|
|
19
49
|
reactions: false,
|
|
20
50
|
threads: false,
|
|
51
|
+
/**
|
|
52
|
+
* blockStreaming: true 表示该 Channel 支持块流式
|
|
53
|
+
* 框架会收集流式响应,然后通过 deliver 回调发送
|
|
54
|
+
*/
|
|
55
|
+
blockStreaming: false,
|
|
21
56
|
},
|
|
22
57
|
reload: { configPrefixes: ["channels.qqbot"] },
|
|
23
58
|
// CLI onboarding wizard
|
|
@@ -47,7 +82,22 @@ export const qqbotPlugin = {
|
|
|
47
82
|
config: {
|
|
48
83
|
listAccountIds: (cfg) => listQQBotAccountIds(cfg),
|
|
49
84
|
resolveAccount: (cfg, accountId) => resolveQQBotAccount(cfg, accountId),
|
|
50
|
-
defaultAccountId: () =>
|
|
85
|
+
defaultAccountId: (cfg) => resolveDefaultQQBotAccountId(cfg),
|
|
86
|
+
// 新增:设置账户启用状态
|
|
87
|
+
setAccountEnabled: ({ cfg, accountId, enabled }) => setAccountEnabledInConfigSection({
|
|
88
|
+
cfg,
|
|
89
|
+
sectionKey: "qqbot",
|
|
90
|
+
accountId,
|
|
91
|
+
enabled,
|
|
92
|
+
allowTopLevel: true,
|
|
93
|
+
}),
|
|
94
|
+
// 新增:删除账户
|
|
95
|
+
deleteAccount: ({ cfg, accountId }) => deleteAccountFromConfigSection({
|
|
96
|
+
cfg,
|
|
97
|
+
sectionKey: "qqbot",
|
|
98
|
+
accountId,
|
|
99
|
+
clearBaseFields: ["appId", "clientSecret", "clientSecretFile", "name"],
|
|
100
|
+
}),
|
|
51
101
|
isConfigured: (account) => Boolean(account?.appId && account?.clientSecret),
|
|
52
102
|
describeAccount: (account) => ({
|
|
53
103
|
accountId: account?.accountId ?? DEFAULT_ACCOUNT_ID,
|
|
@@ -56,8 +106,30 @@ export const qqbotPlugin = {
|
|
|
56
106
|
configured: Boolean(account?.appId && account?.clientSecret),
|
|
57
107
|
tokenSource: account?.secretSource,
|
|
58
108
|
}),
|
|
109
|
+
// 关键:解析 allowFrom 配置,用于命令授权
|
|
110
|
+
resolveAllowFrom: ({ cfg, accountId }) => {
|
|
111
|
+
const account = resolveQQBotAccount(cfg, accountId);
|
|
112
|
+
const allowFrom = account.config?.allowFrom ?? [];
|
|
113
|
+
console.log(`[qqbot] resolveAllowFrom: accountId=${accountId}, allowFrom=${JSON.stringify(allowFrom)}`);
|
|
114
|
+
return allowFrom.map((entry) => String(entry));
|
|
115
|
+
},
|
|
116
|
+
// 格式化 allowFrom 条目(移除 qqbot: 前缀,统一大写)
|
|
117
|
+
formatAllowFrom: ({ allowFrom }) => allowFrom
|
|
118
|
+
.map((entry) => String(entry).trim())
|
|
119
|
+
.filter(Boolean)
|
|
120
|
+
.map((entry) => entry.replace(/^qqbot:/i, ""))
|
|
121
|
+
.map((entry) => entry.toUpperCase()), // QQ openid 是大写的
|
|
59
122
|
},
|
|
60
123
|
setup: {
|
|
124
|
+
// 新增:规范化账户 ID
|
|
125
|
+
resolveAccountId: ({ accountId }) => accountId?.trim().toLowerCase() || DEFAULT_ACCOUNT_ID,
|
|
126
|
+
// 新增:应用账户名称
|
|
127
|
+
applyAccountName: ({ cfg, accountId, name }) => applyAccountNameToChannelSection({
|
|
128
|
+
cfg,
|
|
129
|
+
channelKey: "qqbot",
|
|
130
|
+
accountId,
|
|
131
|
+
name,
|
|
132
|
+
}),
|
|
61
133
|
validateInput: ({ input }) => {
|
|
62
134
|
if (!input.token && !input.tokenFile && !input.useEnv) {
|
|
63
135
|
return "QQBot requires --token (format: appId:clientSecret) or --use-env";
|
|
@@ -79,11 +151,14 @@ export const qqbotPlugin = {
|
|
|
79
151
|
clientSecret,
|
|
80
152
|
clientSecretFile: input.tokenFile,
|
|
81
153
|
name: input.name,
|
|
154
|
+
imageServerBaseUrl: input.imageServerBaseUrl,
|
|
82
155
|
});
|
|
83
156
|
},
|
|
84
157
|
},
|
|
85
158
|
outbound: {
|
|
86
159
|
deliveryMode: "direct",
|
|
160
|
+
chunker: chunkText,
|
|
161
|
+
chunkerMode: "markdown",
|
|
87
162
|
textChunkLimit: 2000,
|
|
88
163
|
sendText: async ({ to, text, accountId, replyToId, cfg }) => {
|
|
89
164
|
const account = resolveQQBotAccount(cfg, accountId);
|
|
@@ -94,6 +169,15 @@ export const qqbotPlugin = {
|
|
|
94
169
|
error: result.error ? new Error(result.error) : undefined,
|
|
95
170
|
};
|
|
96
171
|
},
|
|
172
|
+
sendMedia: async ({ to, text, mediaUrl, accountId, replyToId, cfg }) => {
|
|
173
|
+
const account = resolveQQBotAccount(cfg, accountId);
|
|
174
|
+
const result = await sendMedia({ to, text: text ?? "", mediaUrl: mediaUrl ?? "", accountId, replyToId, account });
|
|
175
|
+
return {
|
|
176
|
+
channel: "qqbot",
|
|
177
|
+
messageId: result.messageId,
|
|
178
|
+
error: result.error ? new Error(result.error) : undefined,
|
|
179
|
+
};
|
|
180
|
+
},
|
|
97
181
|
},
|
|
98
182
|
gateway: {
|
|
99
183
|
startAccount: async (ctx) => {
|
|
@@ -122,6 +206,44 @@ export const qqbotPlugin = {
|
|
|
122
206
|
},
|
|
123
207
|
});
|
|
124
208
|
},
|
|
209
|
+
// 新增:登出账户(清除配置中的凭证)
|
|
210
|
+
logoutAccount: async ({ accountId, cfg }) => {
|
|
211
|
+
const nextCfg = { ...cfg };
|
|
212
|
+
const nextQQBot = cfg.channels?.qqbot ? { ...cfg.channels.qqbot } : undefined;
|
|
213
|
+
let cleared = false;
|
|
214
|
+
let changed = false;
|
|
215
|
+
if (nextQQBot) {
|
|
216
|
+
const qqbot = nextQQBot;
|
|
217
|
+
if (accountId === DEFAULT_ACCOUNT_ID && qqbot.clientSecret) {
|
|
218
|
+
delete qqbot.clientSecret;
|
|
219
|
+
cleared = true;
|
|
220
|
+
changed = true;
|
|
221
|
+
}
|
|
222
|
+
const accounts = qqbot.accounts;
|
|
223
|
+
if (accounts && accountId in accounts) {
|
|
224
|
+
const entry = accounts[accountId];
|
|
225
|
+
if (entry && "clientSecret" in entry) {
|
|
226
|
+
delete entry.clientSecret;
|
|
227
|
+
cleared = true;
|
|
228
|
+
changed = true;
|
|
229
|
+
}
|
|
230
|
+
if (entry && Object.keys(entry).length === 0) {
|
|
231
|
+
delete accounts[accountId];
|
|
232
|
+
changed = true;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (changed && nextQQBot) {
|
|
237
|
+
nextCfg.channels = { ...nextCfg.channels, qqbot: nextQQBot };
|
|
238
|
+
const runtime = getQQBotRuntime();
|
|
239
|
+
const configApi = runtime.config;
|
|
240
|
+
await configApi.writeConfigFile(nextCfg);
|
|
241
|
+
}
|
|
242
|
+
const resolved = resolveQQBotAccount(changed ? nextCfg : cfg, accountId);
|
|
243
|
+
const loggedOut = resolved.secretSource === "none";
|
|
244
|
+
const envToken = Boolean(process.env.QQBOT_CLIENT_SECRET);
|
|
245
|
+
return { ok: true, cleared, envToken, loggedOut };
|
|
246
|
+
},
|
|
125
247
|
},
|
|
126
248
|
status: {
|
|
127
249
|
defaultRuntime: {
|
|
@@ -130,7 +252,18 @@ export const qqbotPlugin = {
|
|
|
130
252
|
connected: false,
|
|
131
253
|
lastConnectedAt: null,
|
|
132
254
|
lastError: null,
|
|
255
|
+
lastInboundAt: null,
|
|
256
|
+
lastOutboundAt: null,
|
|
133
257
|
},
|
|
258
|
+
// 新增:构建通道摘要
|
|
259
|
+
buildChannelSummary: ({ snapshot }) => ({
|
|
260
|
+
configured: snapshot.configured ?? false,
|
|
261
|
+
tokenSource: snapshot.tokenSource ?? "none",
|
|
262
|
+
running: snapshot.running ?? false,
|
|
263
|
+
connected: snapshot.connected ?? false,
|
|
264
|
+
lastConnectedAt: snapshot.lastConnectedAt ?? null,
|
|
265
|
+
lastError: snapshot.lastError ?? null,
|
|
266
|
+
}),
|
|
134
267
|
buildAccountSnapshot: ({ account, runtime }) => ({
|
|
135
268
|
accountId: account?.accountId ?? DEFAULT_ACCOUNT_ID,
|
|
136
269
|
name: account?.name,
|
|
@@ -141,6 +274,8 @@ export const qqbotPlugin = {
|
|
|
141
274
|
connected: runtime?.connected ?? false,
|
|
142
275
|
lastConnectedAt: runtime?.lastConnectedAt ?? null,
|
|
143
276
|
lastError: runtime?.lastError ?? null,
|
|
277
|
+
lastInboundAt: runtime?.lastInboundAt ?? null,
|
|
278
|
+
lastOutboundAt: runtime?.lastOutboundAt ?? null,
|
|
144
279
|
}),
|
|
145
280
|
},
|
|
146
281
|
};
|
package/dist/src/config.js
CHANGED
|
@@ -104,11 +104,15 @@ export function resolveQQBotAccount(cfg, accountId) {
|
|
|
104
104
|
export function applyQQBotAccountConfig(cfg, accountId, input) {
|
|
105
105
|
const next = { ...cfg };
|
|
106
106
|
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
107
|
+
// 如果没有设置过 allowFrom,默认设置为 ["*"]
|
|
108
|
+
const existingConfig = next.channels?.qqbot || {};
|
|
109
|
+
const allowFrom = existingConfig.allowFrom ?? ["*"];
|
|
107
110
|
next.channels = {
|
|
108
111
|
...next.channels,
|
|
109
112
|
qqbot: {
|
|
110
113
|
...(next.channels?.qqbot || {}),
|
|
111
114
|
enabled: true,
|
|
115
|
+
allowFrom,
|
|
112
116
|
...(input.appId ? { appId: input.appId } : {}),
|
|
113
117
|
...(input.clientSecret
|
|
114
118
|
? { clientSecret: input.clientSecret }
|
|
@@ -121,6 +125,9 @@ export function applyQQBotAccountConfig(cfg, accountId, input) {
|
|
|
121
125
|
};
|
|
122
126
|
}
|
|
123
127
|
else {
|
|
128
|
+
// 如果没有设置过 allowFrom,默认设置为 ["*"]
|
|
129
|
+
const existingAccountConfig = next.channels?.qqbot?.accounts?.[accountId] || {};
|
|
130
|
+
const allowFrom = existingAccountConfig.allowFrom ?? ["*"];
|
|
124
131
|
next.channels = {
|
|
125
132
|
...next.channels,
|
|
126
133
|
qqbot: {
|
|
@@ -131,6 +138,7 @@ export function applyQQBotAccountConfig(cfg, accountId, input) {
|
|
|
131
138
|
[accountId]: {
|
|
132
139
|
...(next.channels?.qqbot?.accounts?.[accountId] || {}),
|
|
133
140
|
enabled: true,
|
|
141
|
+
allowFrom,
|
|
134
142
|
...(input.appId ? { appId: input.appId } : {}),
|
|
135
143
|
...(input.clientSecret
|
|
136
144
|
? { clientSecret: input.clientSecret }
|