@tencent-connect/openclaw-qqbot 1.0.0-alpha.0 → 1.0.2-alpha.0
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 +54 -65
- package/README.zh.md +54 -65
- package/bin/qqbot-cli.js +6 -6
- package/dist/index.js +12 -0
- package/dist/src/api.d.ts +13 -1
- package/dist/src/api.js +31 -11
- package/dist/src/channel.d.ts +5 -0
- package/dist/src/channel.js +20 -6
- package/dist/src/config.js +10 -3
- package/dist/src/constants.d.ts +10 -0
- package/dist/src/constants.js +9 -0
- package/dist/src/gateway.js +1420 -813
- package/dist/src/onboarding.js +6 -1
- package/dist/src/outbound.d.ts +47 -1
- package/dist/src/outbound.js +253 -87
- package/dist/src/tools/channel-list.d.ts +10 -0
- package/dist/src/tools/channel-list.js +357 -0
- package/dist/src/tools/forum-thread.d.ts +11 -0
- package/dist/src/tools/forum-thread.js +262 -0
- package/dist/src/tools/guild-announces.d.ts +5 -0
- package/dist/src/tools/guild-announces.js +182 -0
- package/dist/src/tools/guild-list.d.ts +7 -0
- package/dist/src/tools/guild-list.js +136 -0
- package/dist/src/tools/guild-member.d.ts +9 -0
- package/dist/src/tools/guild-member.js +235 -0
- package/dist/src/tools/schedule.d.ts +5 -0
- package/dist/src/tools/schedule.js +208 -0
- package/dist/src/types.d.ts +36 -0
- package/dist/src/types.js +10 -1
- package/dist/src/utils/media-tags.d.ts +79 -2
- package/dist/src/utils/media-tags.js +234 -3
- package/index.ts +12 -0
- package/package.json +6 -2
- package/scripts/{upgrade.sh → cleanup-legacy-plugins.sh} +3 -3
- package/scripts/proactive-api-server.ts +17 -4
- package/scripts/send-proactive.ts +24 -4
- package/scripts/set-markdown.sh +20 -20
- package/scripts/upgrade-via-npm.sh +204 -0
- package/scripts/{upgrade-and-run.sh → upgrade-via-source.sh} +212 -49
- package/skills/qqbot-cron/SKILL.md +0 -15
- package/skills/qqbot-media/SKILL.md +16 -1
- package/src/api.ts +45 -12
- package/src/channel.ts +21 -6
- package/src/config.ts +10 -3
- package/src/constants.ts +13 -0
- package/src/gateway.ts +1408 -782
- package/src/onboarding.ts +8 -1
- package/src/openclaw-plugin-sdk.d.ts +51 -0
- package/src/outbound.ts +284 -99
- package/src/tools/channel-list.ts +486 -0
- package/src/tools/forum-thread.ts +409 -0
- package/src/tools/guild-announces.ts +261 -0
- package/src/tools/guild-list.ts +207 -0
- package/src/tools/guild-member.ts +355 -0
- package/src/tools/schedule.ts +314 -0
- package/src/types.ts +39 -0
- package/src/utils/media-tags.ts +280 -3
- package/tsconfig.json +1 -1
- package/scripts/pull-latest.sh +0 -316
package/README.md
CHANGED
|
@@ -97,6 +97,16 @@ AI sends voice via `<qqvoice>path</qqvoice>`. Formats: mp3/wav/silk/ogg. No ffmp
|
|
|
97
97
|
|
|
98
98
|
<img width="360" src="docs/images/21dce8bfc553ce23d1bd1b270e9c516c.jpg" alt="TTS Voice Demo" />
|
|
99
99
|
|
|
100
|
+
### ⏰ Scheduled Reminder (Proactive Message)
|
|
101
|
+
|
|
102
|
+
> **You**: Remind me to eat in 5 minutes
|
|
103
|
+
>
|
|
104
|
+
> **QQBot**: confirms the reminder first, then proactively sends a voice + text reminder when time is up
|
|
105
|
+
|
|
106
|
+
This capability depends on OpenClaw cron scheduling and proactive messaging. If no reminder arrives, a common reason is QQ-side interception of bot proactive messages.
|
|
107
|
+
|
|
108
|
+
<img width="360" src="docs/images/reminder.jpg" alt="Scheduled Reminder Demo" />
|
|
109
|
+
|
|
100
110
|
### 📎 File Sending
|
|
101
111
|
|
|
102
112
|
> **You**: Extract chapter 1 of War and Peace and send it as a file
|
|
@@ -142,65 +152,74 @@ AI sends videos via `<qqvideo>path</qqvideo>`. Supports local files and URLs. La
|
|
|
142
152
|
2. After scanning, tap **Agree** on your phone — you'll land on the bot configuration page.
|
|
143
153
|
3. Click **Create Bot** to create a new QQ bot.
|
|
144
154
|
|
|
145
|
-
<img width="
|
|
155
|
+
<img width="720" alt="Create Bot" src="docs/images/create_robot.png" />
|
|
156
|
+
|
|
157
|
+
> ⚠️ The bot will automatically appear in your QQ message list and send a first message. However, it will reply "The bot has gone to Mars" until you complete the configuration steps below.
|
|
158
|
+
|
|
159
|
+
<img width="400" alt="Bot Say Hello" src="docs/images/bot_say_hello.jpg" />
|
|
146
160
|
|
|
147
161
|
4. Find **AppID** and **AppSecret** on the bot's page, click **Copy** for each, and save them somewhere safe (e.g., a notepad). **AppSecret is not stored in plaintext — if you leave the page without saving it, you'll have to regenerate a new one.**
|
|
148
162
|
|
|
149
|
-
<img width="
|
|
163
|
+
<img width="720" alt="Find AppID and AppSecret" src="docs/images/find_appid_secret.png" />
|
|
150
164
|
|
|
151
165
|
> For a step-by-step walkthrough with screenshots, see the [official guide](https://cloud.tencent.com/developer/article/2626045).
|
|
152
166
|
|
|
153
|
-
|
|
167
|
+
### Step 2 — Install / Upgrade the Plugin
|
|
154
168
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
**Option A: One-Click Install & Run (Recommended)**
|
|
169
|
+
**Option A: Remote One-Liner (Easiest, no clone required)**
|
|
158
170
|
|
|
159
171
|
```bash
|
|
160
|
-
|
|
161
|
-
bash
|
|
172
|
+
curl -fsSL https://raw.githubusercontent.com/tencent-connect/openclaw-qqbot/main/scripts/upgrade-via-npm.sh \
|
|
173
|
+
| bash -s -- --appid YOUR_APPID --secret YOUR_SECRET
|
|
162
174
|
```
|
|
163
175
|
|
|
164
|
-
|
|
176
|
+
One command does it all: download script → cleanup old plugins → install → configure channel → restart service. Once done, open QQ and start chatting!
|
|
177
|
+
|
|
178
|
+
> `--appid` and `--secret` are **required for first-time install**. For subsequent upgrades:
|
|
179
|
+
> ```bash
|
|
180
|
+
> curl -fsSL https://raw.githubusercontent.com/tencent-connect/openclaw-qqbot/main/scripts/upgrade-via-npm.sh | bash
|
|
181
|
+
> ```
|
|
165
182
|
|
|
166
|
-
**Option B:
|
|
183
|
+
**Option B: Local Script (if you've cloned the repo)**
|
|
167
184
|
|
|
168
185
|
```bash
|
|
169
|
-
|
|
170
|
-
npm
|
|
171
|
-
|
|
186
|
+
# Via npm
|
|
187
|
+
bash ./scripts/upgrade-via-npm.sh --appid YOUR_APPID --secret YOUR_SECRET
|
|
188
|
+
|
|
189
|
+
# Or via source
|
|
190
|
+
bash ./scripts/upgrade-via-source.sh --appid YOUR_APPID --secret YOUR_SECRET
|
|
172
191
|
```
|
|
173
192
|
|
|
174
|
-
|
|
193
|
+
**Common flags:**
|
|
175
194
|
|
|
176
|
-
|
|
195
|
+
| Flag | Description |
|
|
196
|
+
|------|-------------|
|
|
197
|
+
| `--appid <id> --secret <secret>` | Configure channel (required for first install, or to change credentials) |
|
|
198
|
+
| `--version <version>` | Install a specific version (npm script only) |
|
|
199
|
+
| `--self-version` | Install the version from local `package.json` (npm script only) |
|
|
200
|
+
| `-h` / `--help` | Show full usage |
|
|
177
201
|
|
|
178
|
-
|
|
179
|
-
openclaw channels add --channel qqbot --token "AppID:AppSecret"
|
|
180
|
-
```
|
|
202
|
+
> Environment variables `QQBOT_APPID` / `QQBOT_SECRET` are also supported.
|
|
181
203
|
|
|
182
|
-
**Option
|
|
204
|
+
**Option C: Manual Install / Upgrade**
|
|
183
205
|
|
|
184
|
-
|
|
206
|
+
```bash
|
|
207
|
+
# Uninstall old plugins (skip if first install)
|
|
208
|
+
openclaw plugins uninstall qqbot
|
|
209
|
+
openclaw plugins uninstall openclaw-qqbot
|
|
185
210
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
"channels": {
|
|
189
|
-
"qqbot": {
|
|
190
|
-
"enabled": true,
|
|
191
|
-
"appId": "Your AppID",
|
|
192
|
-
"clientSecret": "Your AppSecret"
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
```
|
|
211
|
+
# Install latest
|
|
212
|
+
openclaw plugins install @tencent-connect/openclaw-qqbot@latest
|
|
197
213
|
|
|
198
|
-
|
|
214
|
+
# Configure channel (first install only)
|
|
215
|
+
openclaw channels add --channel qqbot --token "AppID:AppSecret"
|
|
199
216
|
|
|
200
|
-
|
|
201
|
-
openclaw gateway
|
|
217
|
+
# Start / restart
|
|
218
|
+
openclaw gateway restart
|
|
202
219
|
```
|
|
203
220
|
|
|
221
|
+
### Step 3 — Test
|
|
222
|
+
|
|
204
223
|
Open QQ, find your bot, and send a message!
|
|
205
224
|
|
|
206
225
|
<div align="center">
|
|
@@ -348,39 +367,9 @@ STT supports two-level configuration with priority fallback:
|
|
|
348
367
|
|
|
349
368
|
---
|
|
350
369
|
|
|
351
|
-
## 🔄 Upgrade
|
|
352
|
-
|
|
353
|
-
Run the one-click script to upgrade and restart:
|
|
354
|
-
|
|
355
|
-
```bash
|
|
356
|
-
bash ./scripts/upgrade-and-run.sh
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
When no `--appid` / `--secret` is provided, the script reads existing config from `~/.openclaw/openclaw.json` automatically.
|
|
360
|
-
|
|
361
|
-
```bash
|
|
362
|
-
# First-time or override credentials
|
|
363
|
-
bash ./scripts/upgrade-and-run.sh --appid YOUR_APPID --secret YOUR_SECRET
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
<details>
|
|
367
|
-
<summary>Full Options</summary>
|
|
368
|
-
|
|
369
|
-
| Option | Description |
|
|
370
|
-
|--------|-------------|
|
|
371
|
-
| `--appid <id>` | QQ Bot AppID |
|
|
372
|
-
| `--secret <secret>` | QQ Bot AppSecret |
|
|
373
|
-
| `--markdown <yes\|no>` | Enable Markdown format (default: no) |
|
|
374
|
-
| `-h, --help` | Show help |
|
|
375
|
-
|
|
376
|
-
Environment variables `QQBOT_APPID`, `QQBOT_SECRET`, `QQBOT_TOKEN` (AppID:Secret) are also supported.
|
|
377
|
-
|
|
378
|
-
</details>
|
|
379
|
-
|
|
380
|
-
---
|
|
381
|
-
|
|
382
370
|
## 📚 Documentation & Links
|
|
383
371
|
|
|
372
|
+
- [Upgrade Guide](docs/UPGRADE_GUIDE.md) — full upgrade paths and migration notes
|
|
384
373
|
- [Command Reference](docs/commands.md) — OpenClaw CLI commands
|
|
385
374
|
- [Changelog](CHANGELOG.md) — release notes
|
|
386
375
|
|
package/README.zh.md
CHANGED
|
@@ -94,6 +94,16 @@ AI 通过 `<qqvoice>路径</qqvoice>` 发送语音消息。格式:mp3/wav/silk
|
|
|
94
94
|
|
|
95
95
|
<img width="360" src="docs/images/21dce8bfc553ce23d1bd1b270e9c516c.jpg" alt="发语音演示" />
|
|
96
96
|
|
|
97
|
+
### ⏰ 定时提醒(主动消息)
|
|
98
|
+
|
|
99
|
+
> **你**:5分钟后提醒我吃饭
|
|
100
|
+
>
|
|
101
|
+
> **QQBot**:先确认已创建提醒,到点后再主动推送语音 + 文本提醒
|
|
102
|
+
|
|
103
|
+
该能力依赖 OpenClaw cron 调度与主动消息能力。若未收到提醒,常见原因是 QQ 侧拦截了机器人主动消息。
|
|
104
|
+
|
|
105
|
+
<img width="360" src="docs/images/reminder.jpg" alt="定时提醒演示" />
|
|
106
|
+
|
|
97
107
|
### 📎 文件发送
|
|
98
108
|
|
|
99
109
|
> **你**:战争与和平的第一章截取一下发文件给我
|
|
@@ -139,65 +149,74 @@ AI 通过 `<qqvideo>路径</qqvideo>` 发送视频,支持本地文件和公网
|
|
|
139
149
|
2. 手机 QQ 扫码后选择**同意**,即完成注册,进入 QQ 机器人配置页。
|
|
140
150
|
3. 点击**创建机器人**,即可直接新建一个 QQ 机器人。
|
|
141
151
|
|
|
142
|
-
<img width="
|
|
152
|
+
<img width="720" alt="创建机器人" src="docs/images/create_robot.png" />
|
|
153
|
+
|
|
154
|
+
> ⚠️ 机器人创建后会自动出现在你的 QQ 消息列表中,并发送第一条消息。但在完成下面的配置之前,发消息会提示"该机器人去火星了",属于正常现象。
|
|
155
|
+
|
|
156
|
+
<img width="400" alt="机器人打招呼" src="docs/images/bot_say_hello.jpg" />
|
|
143
157
|
|
|
144
158
|
4. 在机器人页面中找到 **AppID** 和 **AppSecret**,分别点击右侧**复制**按钮,保存到记事本或备忘录中。**AppSecret 不支持明文保存,离开页面后再查看会强制重置,请务必妥善保存。**
|
|
145
159
|
|
|
146
|
-
<img width="
|
|
160
|
+
<img width="720" alt="找到 AppID 和 AppSecret" src="docs/images/find_appid_secret.png" />
|
|
147
161
|
|
|
148
162
|
> 详细图文教程请参阅 [官方指南](https://cloud.tencent.com/developer/article/2626045)。
|
|
149
163
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
### 第二步 — 安装插件
|
|
164
|
+
### 第二步 — 安装 / 升级插件
|
|
153
165
|
|
|
154
|
-
|
|
166
|
+
**方式一:远程一键执行(最简单,无需 clone 仓库)**
|
|
155
167
|
|
|
156
168
|
```bash
|
|
157
|
-
|
|
158
|
-
bash
|
|
169
|
+
curl -fsSL https://raw.githubusercontent.com/tencent-connect/openclaw-qqbot/main/scripts/upgrade-via-npm.sh \
|
|
170
|
+
| bash -s -- --appid YOUR_APPID --secret YOUR_SECRET
|
|
159
171
|
```
|
|
160
172
|
|
|
161
|
-
|
|
173
|
+
一行命令搞定:下载脚本 → 清理旧插件 → 安装 → 配置通道 → 启动服务。完成后打开 QQ 即可开始聊天!
|
|
174
|
+
|
|
175
|
+
> 首次安装**必须**传 `--appid` 和 `--secret`。后续升级如已有配置:
|
|
176
|
+
> ```bash
|
|
177
|
+
> curl -fsSL https://raw.githubusercontent.com/tencent-connect/openclaw-qqbot/main/scripts/upgrade-via-npm.sh | bash
|
|
178
|
+
> ```
|
|
162
179
|
|
|
163
|
-
|
|
180
|
+
**方式二:本地脚本(已 clone 仓库时使用)**
|
|
164
181
|
|
|
165
182
|
```bash
|
|
166
|
-
|
|
167
|
-
npm
|
|
168
|
-
|
|
183
|
+
# 通过 npm 安装
|
|
184
|
+
bash ./scripts/upgrade-via-npm.sh --appid YOUR_APPID --secret YOUR_SECRET
|
|
185
|
+
|
|
186
|
+
# 或通过源码安装
|
|
187
|
+
bash ./scripts/upgrade-via-source.sh --appid YOUR_APPID --secret YOUR_SECRET
|
|
169
188
|
```
|
|
170
189
|
|
|
171
|
-
|
|
190
|
+
**常用参数:**
|
|
172
191
|
|
|
173
|
-
|
|
192
|
+
| 参数 | 说明 |
|
|
193
|
+
|------|------|
|
|
194
|
+
| `--appid <id> --secret <secret>` | 配置通道(首次安装必填,或更换凭证时使用) |
|
|
195
|
+
| `--version <版本号>` | 安装指定版本(仅 npm 脚本) |
|
|
196
|
+
| `--self-version` | 安装本地 `package.json` 中的版本(仅 npm 脚本) |
|
|
197
|
+
| `-h` / `--help` | 查看完整用法 |
|
|
174
198
|
|
|
175
|
-
|
|
176
|
-
openclaw channels add --channel qqbot --token "AppID:AppSecret"
|
|
177
|
-
```
|
|
199
|
+
> 也可通过环境变量 `QQBOT_APPID` / `QQBOT_SECRET` 设置。
|
|
178
200
|
|
|
179
|
-
|
|
201
|
+
**方式三:手动安装 / 升级**
|
|
180
202
|
|
|
181
|
-
|
|
203
|
+
```bash
|
|
204
|
+
# 卸载旧插件(首次安装可跳过)
|
|
205
|
+
openclaw plugins uninstall qqbot
|
|
206
|
+
openclaw plugins uninstall openclaw-qqbot
|
|
182
207
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
"channels": {
|
|
186
|
-
"qqbot": {
|
|
187
|
-
"enabled": true,
|
|
188
|
-
"appId": "你的 AppID",
|
|
189
|
-
"clientSecret": "你的 AppSecret"
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
```
|
|
208
|
+
# 安装最新版本
|
|
209
|
+
openclaw plugins install @tencent-connect/openclaw-qqbot@latest
|
|
194
210
|
|
|
195
|
-
|
|
211
|
+
# 配置通道(首次安装必做)
|
|
212
|
+
openclaw channels add --channel qqbot --token "AppID:AppSecret"
|
|
196
213
|
|
|
197
|
-
|
|
198
|
-
openclaw gateway
|
|
214
|
+
# 启动 / 重启
|
|
215
|
+
openclaw gateway restart
|
|
199
216
|
```
|
|
200
217
|
|
|
218
|
+
### 第三步 — 测试
|
|
219
|
+
|
|
201
220
|
打开 QQ,找到你的机器人,发条消息试试!
|
|
202
221
|
|
|
203
222
|
<div align="center">
|
|
@@ -345,39 +364,9 @@ STT 支持两级配置,按优先级查找:
|
|
|
345
364
|
|
|
346
365
|
---
|
|
347
366
|
|
|
348
|
-
## 🔄 升级
|
|
349
|
-
|
|
350
|
-
运行一键脚本即可升级并重启:
|
|
351
|
-
|
|
352
|
-
```bash
|
|
353
|
-
bash ./scripts/upgrade-and-run.sh
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
不传 `--appid` / `--secret` 参数时,脚本会自动读取 `~/.openclaw/openclaw.json` 中已有的配置。
|
|
357
|
-
|
|
358
|
-
```bash
|
|
359
|
-
# 首次配置或需要覆盖时
|
|
360
|
-
bash ./scripts/upgrade-and-run.sh --appid YOUR_APPID --secret YOUR_SECRET
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
<details>
|
|
364
|
-
<summary>完整选项</summary>
|
|
365
|
-
|
|
366
|
-
| 选项 | 说明 |
|
|
367
|
-
|------|------|
|
|
368
|
-
| `--appid <id>` | QQ 机器人 AppID |
|
|
369
|
-
| `--secret <secret>` | QQ 机器人 AppSecret |
|
|
370
|
-
| `--markdown <yes\|no>` | 是否启用 Markdown 消息格式(默认: no) |
|
|
371
|
-
| `-h, --help` | 显示帮助 |
|
|
372
|
-
|
|
373
|
-
也支持环境变量:`QQBOT_APPID`、`QQBOT_SECRET`、`QQBOT_TOKEN`(AppID:Secret)。
|
|
374
|
-
|
|
375
|
-
</details>
|
|
376
|
-
|
|
377
|
-
---
|
|
378
|
-
|
|
379
367
|
## 📚 文档与链接
|
|
380
368
|
|
|
369
|
+
- [升级指南](docs/UPGRADE_GUIDE.zh.md) — 完整升级路径与迁移说明
|
|
381
370
|
- [命令参考](docs/commands.md) — OpenClaw CLI 常用命令
|
|
382
371
|
- [更新日志](CHANGELOG.md) — 各版本变更记录
|
|
383
372
|
|
package/bin/qqbot-cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* qqbot CLI - 用于升级和管理 qqbot 插件
|
|
5
5
|
*
|
|
6
6
|
* 用法:
|
|
7
7
|
* npx openclaw-qqbot upgrade # 升级插件
|
|
@@ -131,7 +131,7 @@ function runCommand(cmd, args = []) {
|
|
|
131
131
|
|
|
132
132
|
// 升级命令
|
|
133
133
|
function upgrade() {
|
|
134
|
-
console.log('===
|
|
134
|
+
console.log('=== qqbot 插件升级脚本 ===');
|
|
135
135
|
|
|
136
136
|
let foundInstallation = null;
|
|
137
137
|
let savedConfig = null;
|
|
@@ -175,7 +175,7 @@ function upgrade() {
|
|
|
175
175
|
}
|
|
176
176
|
} else {
|
|
177
177
|
console.log('未找到已保存的 qqbot 配置,请手动配置:');
|
|
178
|
-
console.log(` ${foundInstallation} channels add --channel qqbot --token "
|
|
178
|
+
console.log(` ${foundInstallation} channels add --channel qqbot --token "appid:appsecret"`);
|
|
179
179
|
return;
|
|
180
180
|
}
|
|
181
181
|
|
|
@@ -186,7 +186,7 @@ function upgrade() {
|
|
|
186
186
|
|
|
187
187
|
// 安装命令
|
|
188
188
|
function install() {
|
|
189
|
-
console.log('===
|
|
189
|
+
console.log('=== qqbot 插件安装 ===');
|
|
190
190
|
|
|
191
191
|
const cmd = detectInstallation();
|
|
192
192
|
if (!cmd) {
|
|
@@ -200,13 +200,13 @@ function install() {
|
|
|
200
200
|
|
|
201
201
|
console.log('\n=== 安装完成 ===');
|
|
202
202
|
console.log('\n请配置机器人通道:');
|
|
203
|
-
console.log(` ${cmd} channels add --channel qqbot --token "
|
|
203
|
+
console.log(` ${cmd} channels add --channel qqbot --token "appid:appsecret"`);
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
// 显示帮助
|
|
207
207
|
function showHelp() {
|
|
208
208
|
console.log(`
|
|
209
|
-
|
|
209
|
+
qqbot CLI - QQ机器人插件管理工具
|
|
210
210
|
|
|
211
211
|
用法:
|
|
212
212
|
npx openclaw-qqbot <命令>
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
|
2
2
|
import { qqbotPlugin } from "./src/channel.js";
|
|
3
3
|
import { setQQBotRuntime } from "./src/runtime.js";
|
|
4
|
+
import { registerGuildTools } from "./src/tools/guild-list.js";
|
|
5
|
+
import { registerChannelTools } from "./src/tools/channel-list.js";
|
|
6
|
+
import { registerGuildAnnouncesTool } from "./src/tools/guild-announces.js";
|
|
7
|
+
import { registerGuildMemberTools } from "./src/tools/guild-member.js";
|
|
8
|
+
// import { registerScheduleTools } from "./src/tools/schedule.js";
|
|
9
|
+
import { registerForumThreadTools } from "./src/tools/forum-thread.js";
|
|
4
10
|
const plugin = {
|
|
5
11
|
id: "openclaw-qqbot",
|
|
6
12
|
name: "QQ Bot",
|
|
@@ -9,6 +15,12 @@ const plugin = {
|
|
|
9
15
|
register(api) {
|
|
10
16
|
setQQBotRuntime(api.runtime);
|
|
11
17
|
api.registerChannel({ plugin: qqbotPlugin });
|
|
18
|
+
registerGuildTools(api);
|
|
19
|
+
registerChannelTools(api);
|
|
20
|
+
registerGuildAnnouncesTool(api);
|
|
21
|
+
registerGuildMemberTools(api);
|
|
22
|
+
// registerScheduleTools(api);
|
|
23
|
+
registerForumThreadTools(api);
|
|
12
24
|
},
|
|
13
25
|
};
|
|
14
26
|
export default plugin;
|
package/dist/src/api.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* QQ Bot API 鉴权和请求封装
|
|
3
3
|
* [修复版] 已重构为支持多实例并发,消除全局变量冲突
|
|
4
4
|
*/
|
|
5
|
+
import { type StreamConfig } from "./types.js";
|
|
5
6
|
/**
|
|
6
7
|
* 初始化 API 配置
|
|
7
8
|
* @param options.markdownSupport - 是否支持 markdown 消息(默认 false,需要机器人具备该权限才能启用)
|
|
@@ -47,13 +48,24 @@ export declare function getGatewayUrl(accessToken: string): Promise<string>;
|
|
|
47
48
|
export interface MessageResponse {
|
|
48
49
|
id: string;
|
|
49
50
|
timestamp: number | string;
|
|
51
|
+
/** 流式消息ID,用于后续分片 */
|
|
52
|
+
stream_id?: string;
|
|
50
53
|
}
|
|
51
|
-
export declare function sendC2CMessage(accessToken: string, openid: string, content: string, msgId?: string): Promise<MessageResponse>;
|
|
54
|
+
export declare function sendC2CMessage(accessToken: string, openid: string, content: string, msgId?: string, stream?: StreamConfig): Promise<MessageResponse>;
|
|
52
55
|
export declare function sendC2CInputNotify(accessToken: string, openid: string, msgId?: string, inputSecond?: number): Promise<void>;
|
|
53
56
|
export declare function sendChannelMessage(accessToken: string, channelId: string, content: string, msgId?: string): Promise<{
|
|
54
57
|
id: string;
|
|
55
58
|
timestamp: string;
|
|
56
59
|
}>;
|
|
60
|
+
/**
|
|
61
|
+
* 发送频道私信消息
|
|
62
|
+
* @param guildId - 私信会话的 guild_id(由 DIRECT_MESSAGE_CREATE 事件提供)
|
|
63
|
+
* @param msgId - 被动回复时必填
|
|
64
|
+
*/
|
|
65
|
+
export declare function sendDmMessage(accessToken: string, guildId: string, content: string, msgId?: string): Promise<{
|
|
66
|
+
id: string;
|
|
67
|
+
timestamp: string;
|
|
68
|
+
}>;
|
|
57
69
|
export declare function sendGroupMessage(accessToken: string, groupOpenid: string, content: string, msgId?: string): Promise<MessageResponse>;
|
|
58
70
|
export declare function sendProactiveC2CMessage(accessToken: string, openid: string, content: string): Promise<{
|
|
59
71
|
id: string;
|
package/dist/src/api.js
CHANGED
|
@@ -35,28 +35,29 @@ const tokenFetchPromises = new Map();
|
|
|
35
35
|
* 按 appId 隔离,支持多机器人并发请求。
|
|
36
36
|
*/
|
|
37
37
|
export async function getAccessToken(appId, clientSecret) {
|
|
38
|
-
const
|
|
38
|
+
const normalizedAppId = String(appId).trim();
|
|
39
|
+
const cachedToken = tokenCacheMap.get(normalizedAppId);
|
|
39
40
|
// 检查缓存:未过期 且 appId 未变化 时复用
|
|
40
41
|
if (cachedToken && Date.now() < cachedToken.expiresAt - 5 * 60 * 1000) {
|
|
41
42
|
return cachedToken.token;
|
|
42
43
|
}
|
|
43
44
|
// Singleflight: 如果当前 appId 已有进行中的 Token 获取请求,复用它
|
|
44
|
-
let fetchPromise = tokenFetchPromises.get(
|
|
45
|
+
let fetchPromise = tokenFetchPromises.get(normalizedAppId);
|
|
45
46
|
if (fetchPromise) {
|
|
46
|
-
console.log(`[qqbot-api:${
|
|
47
|
+
console.log(`[qqbot-api:${normalizedAppId}] Token fetch in progress, waiting for existing request...`);
|
|
47
48
|
return fetchPromise;
|
|
48
49
|
}
|
|
49
50
|
// 创建新的 Token 获取 Promise(singleflight 入口)
|
|
50
51
|
fetchPromise = (async () => {
|
|
51
52
|
try {
|
|
52
|
-
return await doFetchToken(
|
|
53
|
+
return await doFetchToken(normalizedAppId, clientSecret);
|
|
53
54
|
}
|
|
54
55
|
finally {
|
|
55
56
|
// 无论成功失败,都清除 Promise 缓存
|
|
56
|
-
tokenFetchPromises.delete(
|
|
57
|
+
tokenFetchPromises.delete(normalizedAppId);
|
|
57
58
|
}
|
|
58
59
|
})();
|
|
59
|
-
tokenFetchPromises.set(
|
|
60
|
+
tokenFetchPromises.set(normalizedAppId, fetchPromise);
|
|
60
61
|
return fetchPromise;
|
|
61
62
|
}
|
|
62
63
|
/**
|
|
@@ -116,8 +117,9 @@ async function doFetchToken(appId, clientSecret) {
|
|
|
116
117
|
*/
|
|
117
118
|
export function clearTokenCache(appId) {
|
|
118
119
|
if (appId) {
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
const normalizedAppId = String(appId).trim();
|
|
121
|
+
tokenCacheMap.delete(normalizedAppId);
|
|
122
|
+
console.log(`[qqbot-api:${normalizedAppId}] Token cache cleared manually.`);
|
|
121
123
|
}
|
|
122
124
|
else {
|
|
123
125
|
tokenCacheMap.clear();
|
|
@@ -246,7 +248,7 @@ export async function getGatewayUrl(accessToken) {
|
|
|
246
248
|
const data = await apiRequest(accessToken, "GET", "/gateway");
|
|
247
249
|
return data.url;
|
|
248
250
|
}
|
|
249
|
-
function buildMessageBody(content, msgId, msgSeq) {
|
|
251
|
+
function buildMessageBody(content, msgId, msgSeq, stream) {
|
|
250
252
|
const body = currentMarkdownSupport
|
|
251
253
|
? {
|
|
252
254
|
markdown: { content },
|
|
@@ -261,11 +263,18 @@ function buildMessageBody(content, msgId, msgSeq) {
|
|
|
261
263
|
if (msgId) {
|
|
262
264
|
body.msg_id = msgId;
|
|
263
265
|
}
|
|
266
|
+
if (stream) {
|
|
267
|
+
body.stream = {
|
|
268
|
+
state: stream.state,
|
|
269
|
+
index: stream.index,
|
|
270
|
+
...(stream.id ? { id: stream.id } : {}),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
264
273
|
return body;
|
|
265
274
|
}
|
|
266
|
-
export async function sendC2CMessage(accessToken, openid, content, msgId) {
|
|
275
|
+
export async function sendC2CMessage(accessToken, openid, content, msgId, stream) {
|
|
267
276
|
const msgSeq = msgId ? getNextMsgSeq(msgId) : 1;
|
|
268
|
-
const body = buildMessageBody(content, msgId, msgSeq);
|
|
277
|
+
const body = buildMessageBody(content, msgId, msgSeq, stream);
|
|
269
278
|
return apiRequest(accessToken, "POST", `/v2/users/${openid}/messages`, body);
|
|
270
279
|
}
|
|
271
280
|
export async function sendC2CInputNotify(accessToken, openid, msgId, inputSecond = 60) {
|
|
@@ -287,6 +296,17 @@ export async function sendChannelMessage(accessToken, channelId, content, msgId)
|
|
|
287
296
|
...(msgId ? { msg_id: msgId } : {}),
|
|
288
297
|
});
|
|
289
298
|
}
|
|
299
|
+
/**
|
|
300
|
+
* 发送频道私信消息
|
|
301
|
+
* @param guildId - 私信会话的 guild_id(由 DIRECT_MESSAGE_CREATE 事件提供)
|
|
302
|
+
* @param msgId - 被动回复时必填
|
|
303
|
+
*/
|
|
304
|
+
export async function sendDmMessage(accessToken, guildId, content, msgId) {
|
|
305
|
+
return apiRequest(accessToken, "POST", `/dms/${guildId}/messages`, {
|
|
306
|
+
content,
|
|
307
|
+
...(msgId ? { msg_id: msgId } : {}),
|
|
308
|
+
});
|
|
309
|
+
}
|
|
290
310
|
export async function sendGroupMessage(accessToken, groupOpenid, content, msgId) {
|
|
291
311
|
const msgSeq = msgId ? getNextMsgSeq(msgId) : 1;
|
|
292
312
|
const body = buildMessageBody(content, msgId, msgSeq);
|
package/dist/src/channel.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import { type ChannelPlugin } from "openclaw/plugin-sdk";
|
|
2
2
|
import type { ResolvedQQBotAccount } from "./types.js";
|
|
3
|
+
import { createStreamSender, StreamSender } from "./outbound.js";
|
|
3
4
|
export declare const qqbotPlugin: ChannelPlugin<ResolvedQQBotAccount>;
|
|
5
|
+
/**
|
|
6
|
+
* 导出流式消息工具函数,供外部使用(仅 C2C 私聊支持)
|
|
7
|
+
*/
|
|
8
|
+
export { createStreamSender, StreamSender };
|
package/dist/src/channel.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { applyAccountNameToChannelSection, deleteAccountFromConfigSection, setAccountEnabledInConfigSection, } from "openclaw/plugin-sdk";
|
|
2
2
|
import { DEFAULT_ACCOUNT_ID, listQQBotAccountIds, resolveQQBotAccount, applyQQBotAccountConfig, resolveDefaultQQBotAccountId } from "./config.js";
|
|
3
|
-
import { sendText, sendMedia } from "./outbound.js";
|
|
3
|
+
import { sendText, sendMedia, createStreamSender, StreamSender } from "./outbound.js";
|
|
4
4
|
import { startGateway } from "./gateway.js";
|
|
5
5
|
import { qqbotOnboardingAdapter } from "./onboarding.js";
|
|
6
6
|
import { getQQBotRuntime } from "./runtime.js";
|
|
@@ -48,11 +48,21 @@ export const qqbotPlugin = {
|
|
|
48
48
|
media: true,
|
|
49
49
|
reactions: false,
|
|
50
50
|
threads: false,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
blockStreaming: true,
|
|
52
|
+
},
|
|
53
|
+
/**
|
|
54
|
+
* 流式配置
|
|
55
|
+
* blockStreamingCoalesceDefaults: 控制 block streaming 的合并策略
|
|
56
|
+
* - minChars: 至少累积多少字符后才触发 block deliver
|
|
57
|
+
* - idleMs: 空闲多少毫秒后强制 flush 当前 block
|
|
58
|
+
*
|
|
59
|
+
* 对于 QQ Bot:
|
|
60
|
+
* - C2C 私聊(streamSupport=true)通过 deliver 回调中的 StreamSender 实现增量流式
|
|
61
|
+
* disableBlockStreaming=true,不走 coalesce
|
|
62
|
+
* - 群聊/频道(或 C2C streamSupport=false)走框架的 block streaming pipeline,使用此 coalesce 配置
|
|
63
|
+
*/
|
|
64
|
+
streaming: {
|
|
65
|
+
blockStreamingCoalesceDefaults: { minChars: 800, idleMs: 800 },
|
|
56
66
|
},
|
|
57
67
|
reload: { configPrefixes: ["channels.qqbot"] },
|
|
58
68
|
// CLI onboarding wizard
|
|
@@ -335,3 +345,7 @@ export const qqbotPlugin = {
|
|
|
335
345
|
}),
|
|
336
346
|
},
|
|
337
347
|
};
|
|
348
|
+
/**
|
|
349
|
+
* 导出流式消息工具函数,供外部使用(仅 C2C 私聊支持)
|
|
350
|
+
*/
|
|
351
|
+
export { createStreamSender, StreamSender };
|
package/dist/src/config.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
export const DEFAULT_ACCOUNT_ID = "default";
|
|
2
|
+
function normalizeAppId(raw) {
|
|
3
|
+
if (raw === null || raw === undefined)
|
|
4
|
+
return "";
|
|
5
|
+
return String(raw).trim();
|
|
6
|
+
}
|
|
2
7
|
/**
|
|
3
8
|
* 列出所有 QQBot 账户 ID
|
|
4
9
|
*/
|
|
@@ -59,14 +64,15 @@ export function resolveQQBotAccount(cfg, accountId) {
|
|
|
59
64
|
systemPrompt: qqbot?.systemPrompt,
|
|
60
65
|
imageServerBaseUrl: qqbot?.imageServerBaseUrl,
|
|
61
66
|
markdownSupport: qqbot?.markdownSupport ?? true,
|
|
67
|
+
streamSupport: qqbot?.streamSupport,
|
|
62
68
|
};
|
|
63
|
-
appId = qqbot?.appId
|
|
69
|
+
appId = normalizeAppId(qqbot?.appId);
|
|
64
70
|
}
|
|
65
71
|
else {
|
|
66
72
|
// 命名账户从 accounts 读取
|
|
67
73
|
const account = qqbot?.accounts?.[resolvedAccountId];
|
|
68
74
|
accountConfig = account ?? {};
|
|
69
|
-
appId = account?.appId
|
|
75
|
+
appId = normalizeAppId(account?.appId);
|
|
70
76
|
}
|
|
71
77
|
// 解析 clientSecret
|
|
72
78
|
if (accountConfig.clientSecret) {
|
|
@@ -83,7 +89,7 @@ export function resolveQQBotAccount(cfg, accountId) {
|
|
|
83
89
|
}
|
|
84
90
|
// AppId 也可以从环境变量读取
|
|
85
91
|
if (!appId && process.env.QQBOT_APP_ID && resolvedAccountId === DEFAULT_ACCOUNT_ID) {
|
|
86
|
-
appId = process.env.QQBOT_APP_ID;
|
|
92
|
+
appId = normalizeAppId(process.env.QQBOT_APP_ID);
|
|
87
93
|
}
|
|
88
94
|
return {
|
|
89
95
|
accountId: resolvedAccountId,
|
|
@@ -95,6 +101,7 @@ export function resolveQQBotAccount(cfg, accountId) {
|
|
|
95
101
|
systemPrompt: accountConfig.systemPrompt,
|
|
96
102
|
imageServerBaseUrl: accountConfig.imageServerBaseUrl || process.env.QQBOT_IMAGE_SERVER_BASE_URL,
|
|
97
103
|
markdownSupport: accountConfig.markdownSupport !== false,
|
|
104
|
+
streamSupport: accountConfig.streamSupport === true,
|
|
98
105
|
config: accountConfig,
|
|
99
106
|
};
|
|
100
107
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** C2C 私聊消息 */
|
|
2
|
+
export declare const MSG_TYPE_C2C: "c2c";
|
|
3
|
+
/** 频道公开消息 */
|
|
4
|
+
export declare const MSG_TYPE_GUILD: "guild";
|
|
5
|
+
/** 频道私信 */
|
|
6
|
+
export declare const MSG_TYPE_DM: "dm";
|
|
7
|
+
/** 群聊消息 */
|
|
8
|
+
export declare const MSG_TYPE_GROUP: "group";
|
|
9
|
+
/** 消息类型联合类型 */
|
|
10
|
+
export type MessageType = typeof MSG_TYPE_C2C | typeof MSG_TYPE_GUILD | typeof MSG_TYPE_DM | typeof MSG_TYPE_GROUP;
|