@tencent-connect/openclaw-qqbot 1.6.4-alpha.qqchannel → 1.6.4
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 +97 -18
- package/README.zh.md +97 -18
- package/clawdbot.plugin.json +1 -1
- package/dist/index.js +2 -0
- package/dist/src/admin-resolver.d.ts +27 -0
- package/dist/src/admin-resolver.js +122 -0
- package/dist/src/api.js +8 -3
- package/dist/src/channel.d.ts +8 -0
- package/dist/src/channel.js +48 -31
- package/dist/src/credential-backup.d.ts +31 -0
- package/dist/src/credential-backup.js +66 -0
- package/dist/src/gateway.js +172 -1401
- package/dist/src/inbound-attachments.d.ts +58 -0
- package/dist/src/inbound-attachments.js +234 -0
- package/dist/src/message-queue.d.ts +50 -0
- package/dist/src/message-queue.js +115 -0
- package/dist/src/outbound-deliver.d.ts +48 -0
- package/dist/src/outbound-deliver.js +462 -0
- package/dist/src/outbound.js +20 -21
- package/dist/src/ref-index-store.d.ts +1 -1
- package/dist/src/ref-index-store.js +2 -3
- package/dist/src/reply-dispatcher.d.ts +35 -0
- package/dist/src/reply-dispatcher.js +311 -0
- package/dist/src/slash-commands.d.ts +11 -1
- package/dist/src/slash-commands.js +1089 -134
- package/dist/src/startup-greeting.d.ts +30 -0
- package/dist/src/startup-greeting.js +78 -0
- package/dist/src/stt.d.ts +21 -0
- package/dist/src/stt.js +70 -0
- package/dist/src/tools/remind.d.ts +2 -0
- package/dist/src/tools/remind.js +247 -0
- package/dist/src/types.d.ts +8 -2
- package/dist/src/typing-keepalive.d.ts +27 -0
- package/dist/src/typing-keepalive.js +64 -0
- package/dist/src/update-checker.d.ts +16 -4
- package/dist/src/update-checker.js +60 -41
- package/dist/src/user-messages.d.ts +5 -40
- package/dist/src/user-messages.js +5 -62
- package/dist/src/utils/file-utils.d.ts +9 -0
- package/dist/src/utils/file-utils.js +43 -0
- package/dist/src/utils/media-tags.js +6 -0
- package/dist/src/utils/platform.d.ts +10 -0
- package/dist/src/utils/platform.js +16 -0
- package/dist/src/utils/text-parsing.d.ts +32 -0
- package/dist/src/utils/text-parsing.js +80 -0
- package/index.ts +2 -0
- package/moltbot.plugin.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/scripts/cleanup-legacy-plugins.sh +4 -7
- package/scripts/upgrade-via-alt-pkg.sh +307 -0
- package/scripts/upgrade-via-npm.ps1 +296 -0
- package/scripts/upgrade-via-npm.sh +135 -8
- package/scripts/upgrade-via-source.sh +183 -71
- package/skills/{qqbot-cron → qqbot-remind}/SKILL.md +46 -18
- package/src/admin-resolver.ts +140 -0
- package/src/api.ts +8 -3
- package/src/channel.ts +48 -35
- package/src/credential-backup.ts +72 -0
- package/src/gateway.ts +198 -1450
- package/src/inbound-attachments.ts +304 -0
- package/src/message-queue.ts +169 -0
- package/src/outbound-deliver.ts +552 -0
- package/src/outbound.ts +20 -21
- package/src/ref-index-store.ts +3 -4
- package/src/reply-dispatcher.ts +334 -0
- package/src/slash-commands.ts +1144 -139
- package/src/startup-greeting.ts +98 -0
- package/src/stt.ts +86 -0
- package/src/tools/remind.ts +296 -0
- package/src/types.ts +8 -2
- package/src/typing-keepalive.ts +59 -0
- package/src/update-checker.ts +179 -0
- package/src/user-messages.ts +4 -70
- package/src/utils/file-utils.ts +45 -0
- package/src/utils/media-tags.ts +6 -0
- package/src/utils/platform.ts +17 -0
- package/src/utils/text-parsing.ts +82 -0
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
**Connect your AI assistant to QQ — private chat, group chat, and rich media, all in one plugin.**
|
|
12
12
|
|
|
13
|
-
### 🚀 Current Version: `v1.
|
|
13
|
+
### 🚀 Current Version: `v1.6.4`
|
|
14
14
|
|
|
15
15
|
[](./LICENSE)
|
|
16
16
|
[](https://bot.q.qq.com/wiki/)
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
Scan to join the QQ group chat
|
|
27
27
|
|
|
28
|
-
<img width="
|
|
28
|
+
<img width="400" alt="QQ QR Code" src="./docs/images/developer_group.png" />
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
</div>
|
|
@@ -36,13 +36,13 @@ Scan to join the QQ group chat
|
|
|
36
36
|
|
|
37
37
|
| Feature | Description |
|
|
38
38
|
|---------|-------------|
|
|
39
|
-
| 🔒 **Multi-Scene** | C2C private chat, group @messages
|
|
39
|
+
| 🔒 **Multi-Scene** | C2C private chat, group @messages |
|
|
40
40
|
| 🖼️ **Rich Media** | Send & receive images, voice, video, and files |
|
|
41
41
|
| 🎙️ **Voice (STT/TTS)** | Speech-to-text transcription & text-to-speech replies |
|
|
42
|
+
| 🔥 **One-Click Hot Upgrade** | Send `/bot-upgrade` in private chat to upgrade — no server login needed |
|
|
42
43
|
| ⏰ **Scheduled Push** | Proactive message delivery via scheduled tasks |
|
|
43
44
|
| 🔗 **URL Support** | Direct URL sending in private chat (no restrictions) |
|
|
44
45
|
| ⌨️ **Typing Indicator** | "Bot is typing..." status shown in real-time |
|
|
45
|
-
| 🔄 **Hot Reload** | Install via npm with seamless hot updates |
|
|
46
46
|
| 📝 **Markdown** | Full Markdown formatting support |
|
|
47
47
|
| 🛠️ **Commands** | Native OpenClaw command integration |
|
|
48
48
|
| 💬 **Quoted Context** | Resolve QQ `REFIDX_*` quoted messages and inject quote body into AI context |
|
|
@@ -99,7 +99,7 @@ If your main model supports vision (e.g. Tencent Hunyuan `hunyuan-vision`), AI c
|
|
|
99
99
|
>
|
|
100
100
|
> **QQBot**: Here you go! 🐱
|
|
101
101
|
|
|
102
|
-
AI
|
|
102
|
+
AI can send images directly. Supports local paths and URLs. Formats: jpg/png/gif/webp/bmp.
|
|
103
103
|
|
|
104
104
|
<img width="360" src="docs/images/4645f2b3a20822b7f8d6664a708529eb_720.jpg" alt="Image Generation Demo" />
|
|
105
105
|
|
|
@@ -109,7 +109,7 @@ AI sends images via `<qqimg>path</qqimg>`. Supports local paths and URLs. Format
|
|
|
109
109
|
>
|
|
110
110
|
> **QQBot**: *(sends a voice message)*
|
|
111
111
|
|
|
112
|
-
AI
|
|
112
|
+
AI can send voice messages directly. Formats: mp3/wav/silk/ogg. No ffmpeg required.
|
|
113
113
|
|
|
114
114
|
<img width="360" src="docs/images/21dce8bfc553ce23d1bd1b270e9c516c.jpg" alt="TTS Voice Demo" />
|
|
115
115
|
|
|
@@ -129,7 +129,7 @@ This capability depends on OpenClaw cron scheduling and proactive messaging. If
|
|
|
129
129
|
>
|
|
130
130
|
> **QQBot**: *(sends a .txt file)*
|
|
131
131
|
|
|
132
|
-
AI
|
|
132
|
+
AI can send files directly. Any format, up to 20MB.
|
|
133
133
|
|
|
134
134
|
<img width="360" src="docs/images/17cada70df90185d45a2d6dd36e92f2f_720.jpg" alt="File Sending Demo" />
|
|
135
135
|
|
|
@@ -139,21 +139,80 @@ AI sends files via `<qqfile>path</qqfile>`. Any format, up to 20MB.
|
|
|
139
139
|
>
|
|
140
140
|
> **QQBot**: *(sends a video)*
|
|
141
141
|
|
|
142
|
-
AI
|
|
142
|
+
AI can send videos directly. Supports local files and URLs.
|
|
143
143
|
|
|
144
144
|
<img width="360" src="docs/images/85d03b8a216f267ab7b2aee248a18a41_720.jpg" alt="Video Sending Demo" />
|
|
145
145
|
|
|
146
|
-
|
|
146
|
+
> **Under the hood:** Upload dedup caching, ordered queue delivery, and multi-layer audio format fallback.
|
|
147
147
|
|
|
148
|
-
|
|
149
|
-
|-----|-----------|-------|
|
|
150
|
-
| `<qqimg>path</qqimg>` | Send | jpg/png/gif/webp/bmp, local path or URL |
|
|
151
|
-
| `<qqvoice>path</qqvoice>` | Send | mp3/wav/silk/ogg, no ffmpeg required |
|
|
152
|
-
| `<qqfile>path</qqfile>` | Send | Any format, up to 20MB |
|
|
153
|
-
| `<qqvideo>path</qqvideo>` | Send | Local path or URL |
|
|
154
|
-
| Voice / File / Image | Receive | Auto-transcribe (STT), auto-download, or vision analysis |
|
|
148
|
+
### 🛠️ Slash Commands
|
|
155
149
|
|
|
156
|
-
|
|
150
|
+
The plugin provides built-in slash commands that are intercepted before reaching the AI queue, giving instant responses for diagnostics and management.
|
|
151
|
+
|
|
152
|
+
#### `/bot-ping` — Latency Test
|
|
153
|
+
|
|
154
|
+
> **You**: `/bot-ping`
|
|
155
|
+
>
|
|
156
|
+
> **QQBot**: ✅ pong!⏱ Latency: 602ms (network: 602ms, plugin: 0ms)
|
|
157
|
+
|
|
158
|
+
Measures end-to-end latency from QQ server push to plugin response, broken down into network transport and plugin processing time.
|
|
159
|
+
|
|
160
|
+
<img width="360" src="docs/images/slash-ping.jpg" alt="Ping Demo" />
|
|
161
|
+
|
|
162
|
+
#### `/bot-version` — Version Info
|
|
163
|
+
|
|
164
|
+
> **You**: `/bot-version`
|
|
165
|
+
>
|
|
166
|
+
> **QQBot**: 🦞 Framework: OpenClaw 2026.3.13 (61d171a) / 🤖 Plugin: v1.6.3 / 🌟 GitHub repo
|
|
167
|
+
|
|
168
|
+
Shows framework version, plugin version, and a direct link to the official repository.
|
|
169
|
+
|
|
170
|
+
<img width="360" src="docs/images/slash-version.jpg" alt="Version Demo" />
|
|
171
|
+
|
|
172
|
+
#### `/bot-help` — Command List
|
|
173
|
+
|
|
174
|
+
> **You**: `/bot-help`
|
|
175
|
+
>
|
|
176
|
+
> **QQBot**: Lists all available slash commands with clickable shortcuts.
|
|
177
|
+
|
|
178
|
+
<img width="360" src="docs/images/slash-help.jpg" alt="Help Demo" />
|
|
179
|
+
|
|
180
|
+
#### `/bot-upgrade` — One-Click Hot Upgrade
|
|
181
|
+
|
|
182
|
+
> **You**: `/bot-upgrade`
|
|
183
|
+
>
|
|
184
|
+
> **QQBot**: 📌 Current: v1.6.3 / ✅ New version v1.6.4 available / Click button below to confirm
|
|
185
|
+
|
|
186
|
+
Send in private chat to upgrade the plugin without server login. Supported usage:
|
|
187
|
+
|
|
188
|
+
| Command | Description |
|
|
189
|
+
|---------|-------------|
|
|
190
|
+
| `/bot-upgrade` | Check for updates, show confirmation button |
|
|
191
|
+
| `/bot-upgrade --latest` | Confirm upgrade to the latest version |
|
|
192
|
+
| `/bot-upgrade --version 1.6.4` | Upgrade to a specific version |
|
|
193
|
+
| `/bot-upgrade --force` | Force reinstall current version |
|
|
194
|
+
|
|
195
|
+
Credentials are automatically backed up before upgrade. Version existence is verified against npm before proceeding. Auto-recovery on failure.
|
|
196
|
+
|
|
197
|
+
<!-- TODO: add /bot-upgrade screenshot -->
|
|
198
|
+
|
|
199
|
+
#### `/bot-logs` — Log Export
|
|
200
|
+
|
|
201
|
+
> **You**: `/bot-logs`
|
|
202
|
+
>
|
|
203
|
+
> **QQBot**: 📋 Logs packaged (~2000 lines), sending file... *(sends a .txt file)*
|
|
204
|
+
|
|
205
|
+
Exports the last ~2000 lines of gateway logs as a file for quick troubleshooting.
|
|
206
|
+
|
|
207
|
+
<img width="360" src="docs/images/slash-logs.jpg" alt="Logs Demo" />
|
|
208
|
+
|
|
209
|
+
#### Usage Help
|
|
210
|
+
|
|
211
|
+
All commands support a `?` suffix to show usage:
|
|
212
|
+
|
|
213
|
+
> **You**: `/bot-upgrade ?`
|
|
214
|
+
>
|
|
215
|
+
> **QQBot**: 📖 /bot-upgrade usage: …
|
|
157
216
|
|
|
158
217
|
---
|
|
159
218
|
|
|
@@ -379,7 +438,7 @@ STT supports two-level configuration with priority fallback:
|
|
|
379
438
|
- `provider` — references a key in `models.providers` to inherit `baseUrl` and `apiKey`
|
|
380
439
|
- `voice` — voice variant
|
|
381
440
|
- Set `enabled: false` to disable (default: `true`)
|
|
382
|
-
- When configured, AI can
|
|
441
|
+
- When configured, AI can generate and send voice messages
|
|
383
442
|
|
|
384
443
|
---
|
|
385
444
|
|
|
@@ -389,6 +448,26 @@ STT supports two-level configuration with priority fallback:
|
|
|
389
448
|
- [Command Reference](docs/commands.md) — OpenClaw CLI commands
|
|
390
449
|
- [Changelog](CHANGELOG.md) — release notes
|
|
391
450
|
|
|
451
|
+
## 🤝 Contributors
|
|
452
|
+
|
|
453
|
+
Thanks to all the developers who have contributed to this project!
|
|
454
|
+
|
|
455
|
+
<a href="https://github.com/tencent-connect/openclaw-qqbot/graphs/contributors">
|
|
456
|
+
<img src="https://contrib.rocks/image?repo=tencent-connect/openclaw-qqbot" />
|
|
457
|
+
</a>
|
|
458
|
+
|
|
459
|
+
## 💖 Acknowledgements
|
|
460
|
+
|
|
461
|
+
Special thanks to [@sliverp](https://github.com/sliverp) for outstanding contributions to the project!
|
|
462
|
+
|
|
463
|
+
<a href="https://github.com/sliverp"><img src="https://avatars.githubusercontent.com/u/38134380?v=4" width="48" height="48" alt="sliverp" title="sliverp"/></a>
|
|
464
|
+
|
|
465
|
+
Thanks to [Tencent Cloud Lighthouse](https://cloud.tencent.com/product/lighthouse) for the deep collaboration. For raising crawfish, choose Tencent Cloud Lighthouse!
|
|
466
|
+
|
|
467
|
+
<a href="https://cloud.tencent.com/product/lighthouse">
|
|
468
|
+
<img alt="Tencent Cloud Lighthouse" src="./docs/images/lighthouse_head.png" height="500" style="max-width:80%; height:auto;"/>
|
|
469
|
+
</a>
|
|
470
|
+
|
|
392
471
|
## ⭐ Star History
|
|
393
472
|
|
|
394
473
|
<div align="center">
|
package/README.zh.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
**让你的 AI 助手接入 QQ — 私聊、群聊、富媒体,一个插件全搞定。**
|
|
11
11
|
|
|
12
|
-
### 🚀 当前版本: `v1.
|
|
12
|
+
### 🚀 当前版本: `v1.6.4`
|
|
13
13
|
|
|
14
14
|
[](./LICENSE)
|
|
15
15
|
[](https://bot.q.qq.com/wiki/)
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
扫描二维码加入群聊,一起交流
|
|
23
23
|
|
|
24
|
-
<img width="
|
|
24
|
+
<img width="400" alt="QQ 群二维码" src="./docs/images/developer_group.png" />
|
|
25
25
|
|
|
26
26
|
</div>
|
|
27
27
|
|
|
@@ -31,13 +31,13 @@
|
|
|
31
31
|
|
|
32
32
|
| 功能 | 说明 |
|
|
33
33
|
|------|------|
|
|
34
|
-
| 🔒 **多场景支持** | C2C 私聊、群聊
|
|
34
|
+
| 🔒 **多场景支持** | C2C 私聊、群聊 @消息 |
|
|
35
35
|
| 🖼️ **富媒体消息** | 支持图片、语音、视频、文件的收发 |
|
|
36
36
|
| 🎙️ **语音能力 (STT/TTS)** | 语音转文字自动转录 & 文字转语音回复 |
|
|
37
|
+
| 🔥 **一键热更新** | 私聊发送 `/bot-upgrade` 即可完成版本升级,无需登录服务器 |
|
|
37
38
|
| ⏰ **定时推送** | 支持定时任务触发后主动推送消息 |
|
|
38
39
|
| 🔗 **URL 无限制** | 私聊可直接发送 URL |
|
|
39
40
|
| ⌨️ **输入状态** | 实时显示"Bot 正在输入中…"状态 |
|
|
40
|
-
| 🔄 **热更新** | 支持 npm 方式安装和无缝热更新 |
|
|
41
41
|
| 📝 **Markdown** | 完整支持 Markdown 格式消息 |
|
|
42
42
|
| 🛠️ **原生命令** | 支持 OpenClaw 原生命令 |
|
|
43
43
|
| 💬 **引用上下文** | 解析 QQ `REFIDX_*` 引用消息,并将引用内容注入 AI 上下文 |
|
|
@@ -94,7 +94,7 @@ QQ 的引用事件通常只携带索引键(如 `REFIDX_xxx`),不直接返
|
|
|
94
94
|
>
|
|
95
95
|
> **QQBot**:画好啦!一只可爱的简笔小猫咪🐱🎨
|
|
96
96
|
|
|
97
|
-
AI
|
|
97
|
+
AI 可直接发送图片,支持本地文件路径和网络 URL。格式:jpg/png/gif/webp/bmp。
|
|
98
98
|
|
|
99
99
|
<img width="360" src="docs/images/4645f2b3a20822b7f8d6664a708529eb_720.jpg" alt="发图片演示" />
|
|
100
100
|
|
|
@@ -104,7 +104,7 @@ AI 通过 `<qqimg>路径</qqimg>` 发送图片,支持本地文件路径和网
|
|
|
104
104
|
>
|
|
105
105
|
> **QQBot**:*(发送一条语音消息)*
|
|
106
106
|
|
|
107
|
-
AI
|
|
107
|
+
AI 可直接发送语音消息。格式:mp3/wav/silk/ogg,无需安装 ffmpeg。
|
|
108
108
|
|
|
109
109
|
<img width="360" src="docs/images/21dce8bfc553ce23d1bd1b270e9c516c.jpg" alt="发语音演示" />
|
|
110
110
|
|
|
@@ -124,7 +124,7 @@ AI 通过 `<qqvoice>路径</qqvoice>` 发送语音消息。格式:mp3/wav/silk
|
|
|
124
124
|
>
|
|
125
125
|
> **QQBot**:*(发送 .txt 文件)*
|
|
126
126
|
|
|
127
|
-
AI
|
|
127
|
+
AI 可直接发送文件。任意格式,最大 20MB。
|
|
128
128
|
|
|
129
129
|
<img width="360" src="docs/images/17cada70df90185d45a2d6dd36e92f2f_720.jpg" alt="发文件演示" />
|
|
130
130
|
|
|
@@ -134,21 +134,80 @@ AI 通过 `<qqfile>路径</qqfile>` 发送文件。任意格式,最大 20MB。
|
|
|
134
134
|
>
|
|
135
135
|
> **QQBot**:*(发送视频)*
|
|
136
136
|
|
|
137
|
-
AI
|
|
137
|
+
AI 可直接发送视频,支持本地文件和公网 URL。
|
|
138
138
|
|
|
139
139
|
<img width="360" src="docs/images/85d03b8a216f267ab7b2aee248a18a41_720.jpg" alt="发视频演示" />
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
> **底层细节:** 上传去重缓存、有序队列发送、音频格式多层降级。
|
|
142
142
|
|
|
143
|
-
|
|
144
|
-
|------|------|------|
|
|
145
|
-
| `<qqimg>路径</qqimg>` | 发送 | jpg/png/gif/webp/bmp,本地路径或 URL |
|
|
146
|
-
| `<qqvoice>路径</qqvoice>` | 发送 | mp3/wav/silk/ogg,无需 ffmpeg |
|
|
147
|
-
| `<qqfile>路径</qqfile>` | 发送 | 任意格式,最大 20MB |
|
|
148
|
-
| `<qqvideo>路径</qqvideo>` | 发送 | 本地路径或 URL |
|
|
149
|
-
| 语音 / 文件 / 图片 | 接收 | 自动转录(STT)、自动下载、或视觉分析 |
|
|
143
|
+
### 🛠️ 斜杠指令
|
|
150
144
|
|
|
151
|
-
|
|
145
|
+
插件内置一组斜杠指令,在消息进入 AI 队列前拦截处理,即时响应,用于诊断和管理。
|
|
146
|
+
|
|
147
|
+
#### `/bot-ping` — 延迟测试
|
|
148
|
+
|
|
149
|
+
> **你**:`/bot-ping`
|
|
150
|
+
>
|
|
151
|
+
> **QQBot**:✅ pong!⏱ 延迟: 602ms(网络传输: 602ms,插件处理: 0ms)
|
|
152
|
+
|
|
153
|
+
测量从 QQ 服务器推送到插件响应的端到端延迟,细分网络传输和插件处理两段耗时。
|
|
154
|
+
|
|
155
|
+
<img width="360" src="docs/images/slash-ping.jpg" alt="Ping 演示" />
|
|
156
|
+
|
|
157
|
+
#### `/bot-version` — 版本信息
|
|
158
|
+
|
|
159
|
+
> **你**:`/bot-version`
|
|
160
|
+
>
|
|
161
|
+
> **QQBot**:🦞框架版本:OpenClaw 2026.3.13 (61d171a) / 🤖QQBot 插件版本:v1.6.3 / 🌟官方 GitHub 仓库
|
|
162
|
+
|
|
163
|
+
一目了然查看框架版本、插件版本,并可直接跳转官方仓库。
|
|
164
|
+
|
|
165
|
+
<img width="360" src="docs/images/slash-version.jpg" alt="Version 演示" />
|
|
166
|
+
|
|
167
|
+
#### `/bot-help` — 指令列表
|
|
168
|
+
|
|
169
|
+
> **你**:`/bot-help`
|
|
170
|
+
>
|
|
171
|
+
> **QQBot**:列出所有可用的斜杠指令及说明,指令可点击快速输入。
|
|
172
|
+
|
|
173
|
+
<img width="360" src="docs/images/slash-help.jpg" alt="Help 演示" />
|
|
174
|
+
|
|
175
|
+
#### `/bot-upgrade` — 一键热更新
|
|
176
|
+
|
|
177
|
+
> **你**:`/bot-upgrade`
|
|
178
|
+
>
|
|
179
|
+
> **QQBot**:📌当前版本 v1.6.3 / ✅发现新版本 v1.6.4 / 点击下方按钮确认升级
|
|
180
|
+
|
|
181
|
+
在私聊中发送即可完成版本升级,全程无需登录服务器。支持的用法:
|
|
182
|
+
|
|
183
|
+
| 命令 | 说明 |
|
|
184
|
+
|------|------|
|
|
185
|
+
| `/bot-upgrade` | 检查是否有新版本,展示确认按钮 |
|
|
186
|
+
| `/bot-upgrade --latest` | 确认升级到最新版本 |
|
|
187
|
+
| `/bot-upgrade --version 1.6.4` | 升级到指定版本 |
|
|
188
|
+
| `/bot-upgrade --force` | 强制重新安装当前版本 |
|
|
189
|
+
|
|
190
|
+
升级流程自动备份凭证,升级前校验版本是否存在于 npm,升级失败自动恢复。
|
|
191
|
+
|
|
192
|
+
<!-- TODO: 补充 /bot-upgrade 截图 -->
|
|
193
|
+
|
|
194
|
+
#### `/bot-logs` — 日志导出
|
|
195
|
+
|
|
196
|
+
> **你**:`/bot-logs`
|
|
197
|
+
>
|
|
198
|
+
> **QQBot**:📋 日志已打包(约 2000 行),正在发送文件… *(发送 .txt 文件)*
|
|
199
|
+
|
|
200
|
+
导出最近约 2000 行网关日志为文件,方便快速排查问题。
|
|
201
|
+
|
|
202
|
+
<img width="360" src="docs/images/slash-logs.jpg" alt="Logs 演示" />
|
|
203
|
+
|
|
204
|
+
#### 用法查询
|
|
205
|
+
|
|
206
|
+
所有指令都支持 `?` 后缀查看用法说明:
|
|
207
|
+
|
|
208
|
+
> **你**:`/bot-upgrade ?`
|
|
209
|
+
>
|
|
210
|
+
> **QQBot**:📖 /bot-upgrade 用法:…
|
|
152
211
|
|
|
153
212
|
---
|
|
154
213
|
|
|
@@ -374,7 +433,7 @@ STT 支持两级配置,按优先级查找:
|
|
|
374
433
|
- `provider` — 引用 `models.providers` 中的 key,自动继承 `baseUrl` 和 `apiKey`
|
|
375
434
|
- `voice` — 语音音色
|
|
376
435
|
- 设置 `enabled: false` 可禁用(默认:`true`)
|
|
377
|
-
- 配置后,AI
|
|
436
|
+
- 配置后,AI 可生成并发送语音消息
|
|
378
437
|
|
|
379
438
|
---
|
|
380
439
|
|
|
@@ -384,6 +443,26 @@ STT 支持两级配置,按优先级查找:
|
|
|
384
443
|
- [命令参考](docs/commands.md) — OpenClaw CLI 常用命令
|
|
385
444
|
- [更新日志](CHANGELOG.md) — 各版本变更记录
|
|
386
445
|
|
|
446
|
+
## 🤝 贡献者
|
|
447
|
+
|
|
448
|
+
感谢所有为本项目做出贡献的开发者!
|
|
449
|
+
|
|
450
|
+
<a href="https://github.com/tencent-connect/openclaw-qqbot/graphs/contributors">
|
|
451
|
+
<img src="https://contrib.rocks/image?repo=tencent-connect/openclaw-qqbot" />
|
|
452
|
+
</a>
|
|
453
|
+
|
|
454
|
+
## 💖 致谢
|
|
455
|
+
|
|
456
|
+
特别感谢 [@sliverp](https://github.com/sliverp) 对项目的核心贡献!
|
|
457
|
+
|
|
458
|
+
<a href="https://github.com/sliverp"><img src="https://avatars.githubusercontent.com/u/38134380?v=4" width="48" height="48" alt="sliverp" title="sliverp"/></a>
|
|
459
|
+
|
|
460
|
+
感谢[腾讯云Lighthouse](https://cloud.tencent.com/product/lighthouse)的深度合作,养小龙虾,首选腾讯云Lighthouse!
|
|
461
|
+
|
|
462
|
+
<a href="https://cloud.tencent.com/product/lighthouse">
|
|
463
|
+
<img alt="腾讯云 Lighthouse" src="./docs/images/lighthouse_head.png" height="500" style="max-width:80%; height:auto;"/>
|
|
464
|
+
</a>
|
|
465
|
+
|
|
387
466
|
## ⭐ Star History
|
|
388
467
|
|
|
389
468
|
<div align="center">
|
package/clawdbot.plugin.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "OpenClaw QQ Bot",
|
|
4
4
|
"description": "QQ Bot channel plugin with message support, cron jobs, and proactive messaging",
|
|
5
5
|
"channels": ["qqbot"],
|
|
6
|
-
"skills": ["skills/qqbot-channel", "skills/qqbot-
|
|
6
|
+
"skills": ["skills/qqbot-channel", "skills/qqbot-remind", "skills/qqbot-media"],
|
|
7
7
|
"capabilities": {
|
|
8
8
|
"proactiveMessaging": true,
|
|
9
9
|
"cronJobs": true
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
|
|
2
2
|
import { qqbotPlugin } from "./src/channel.js";
|
|
3
3
|
import { setQQBotRuntime } from "./src/runtime.js";
|
|
4
4
|
import { registerChannelTool } from "./src/tools/channel.js";
|
|
5
|
+
import { registerRemindTool } from "./src/tools/remind.js";
|
|
5
6
|
const plugin = {
|
|
6
7
|
id: "openclaw-qqbot",
|
|
7
8
|
name: "QQ Bot",
|
|
@@ -11,6 +12,7 @@ const plugin = {
|
|
|
11
12
|
setQQBotRuntime(api.runtime);
|
|
12
13
|
api.registerChannel({ plugin: qqbotPlugin });
|
|
13
14
|
registerChannelTool(api);
|
|
15
|
+
registerRemindTool(api);
|
|
14
16
|
},
|
|
15
17
|
};
|
|
16
18
|
export default plugin;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 管理员解析器模块
|
|
3
|
+
* - 管理员 openid 持久化读写
|
|
4
|
+
* - 升级问候目标读写
|
|
5
|
+
* - 启动问候语发送
|
|
6
|
+
*/
|
|
7
|
+
export interface AdminResolverContext {
|
|
8
|
+
accountId: string;
|
|
9
|
+
appId: string;
|
|
10
|
+
clientSecret: string;
|
|
11
|
+
log?: {
|
|
12
|
+
info: (msg: string) => void;
|
|
13
|
+
error: (msg: string) => void;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export declare function loadAdminOpenId(accountId: string): string | undefined;
|
|
17
|
+
export declare function saveAdminOpenId(accountId: string, openid: string): void;
|
|
18
|
+
export declare function loadUpgradeGreetingTargetOpenId(accountId: string, appId: string): string | undefined;
|
|
19
|
+
export declare function clearUpgradeGreetingTargetOpenId(accountId: string, appId: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* 解析管理员 openid:
|
|
22
|
+
* 1. 优先读持久化文件(稳定)
|
|
23
|
+
* 2. fallback 取第一个私聊用户,并写入文件锁定
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveAdminOpenId(ctx: Pick<AdminResolverContext, "accountId" | "log">): string | undefined;
|
|
26
|
+
/** 异步发送启动问候语(仅发给管理员) */
|
|
27
|
+
export declare function sendStartupGreetings(ctx: AdminResolverContext, trigger: "READY" | "RESUMED"): void;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 管理员解析器模块
|
|
3
|
+
* - 管理员 openid 持久化读写
|
|
4
|
+
* - 升级问候目标读写
|
|
5
|
+
* - 启动问候语发送
|
|
6
|
+
*/
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import * as fs from "node:fs";
|
|
9
|
+
import { getQQBotDataDir } from "./utils/platform.js";
|
|
10
|
+
import { listKnownUsers } from "./known-users.js";
|
|
11
|
+
import { getAccessToken, sendProactiveC2CMessage } from "./api.js";
|
|
12
|
+
import { getStartupGreetingPlan, markStartupGreetingSent, markStartupGreetingFailed } from "./startup-greeting.js";
|
|
13
|
+
// ---- 文件路径 ----
|
|
14
|
+
function getAdminMarkerFile(accountId) {
|
|
15
|
+
return path.join(getQQBotDataDir("data"), `admin-${accountId}.json`);
|
|
16
|
+
}
|
|
17
|
+
function getUpgradeGreetingTargetFile(accountId, appId) {
|
|
18
|
+
const safeAccountId = accountId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
19
|
+
const safeAppId = appId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
20
|
+
return path.join(getQQBotDataDir("data"), `upgrade-greeting-target-${safeAccountId}-${safeAppId}.json`);
|
|
21
|
+
}
|
|
22
|
+
// ---- 管理员 openid 持久化 ----
|
|
23
|
+
export function loadAdminOpenId(accountId) {
|
|
24
|
+
try {
|
|
25
|
+
const file = getAdminMarkerFile(accountId);
|
|
26
|
+
if (fs.existsSync(file)) {
|
|
27
|
+
const data = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
28
|
+
if (data.openid)
|
|
29
|
+
return data.openid;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch { /* 文件损坏视为无 */ }
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
export function saveAdminOpenId(accountId, openid) {
|
|
36
|
+
try {
|
|
37
|
+
fs.writeFileSync(getAdminMarkerFile(accountId), JSON.stringify({ openid, savedAt: new Date().toISOString() }));
|
|
38
|
+
}
|
|
39
|
+
catch { /* ignore */ }
|
|
40
|
+
}
|
|
41
|
+
// ---- 升级问候目标 ----
|
|
42
|
+
export function loadUpgradeGreetingTargetOpenId(accountId, appId) {
|
|
43
|
+
try {
|
|
44
|
+
const file = getUpgradeGreetingTargetFile(accountId, appId);
|
|
45
|
+
if (fs.existsSync(file)) {
|
|
46
|
+
const data = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
47
|
+
if (!data.openid)
|
|
48
|
+
return undefined;
|
|
49
|
+
if (data.appId && data.appId !== appId)
|
|
50
|
+
return undefined;
|
|
51
|
+
if (data.accountId && data.accountId !== accountId)
|
|
52
|
+
return undefined;
|
|
53
|
+
return data.openid;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch { /* 文件损坏视为无 */ }
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
export function clearUpgradeGreetingTargetOpenId(accountId, appId) {
|
|
60
|
+
try {
|
|
61
|
+
const file = getUpgradeGreetingTargetFile(accountId, appId);
|
|
62
|
+
if (fs.existsSync(file)) {
|
|
63
|
+
fs.unlinkSync(file);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch { /* ignore */ }
|
|
67
|
+
}
|
|
68
|
+
// ---- 解析管理员 ----
|
|
69
|
+
/**
|
|
70
|
+
* 解析管理员 openid:
|
|
71
|
+
* 1. 优先读持久化文件(稳定)
|
|
72
|
+
* 2. fallback 取第一个私聊用户,并写入文件锁定
|
|
73
|
+
*/
|
|
74
|
+
export function resolveAdminOpenId(ctx) {
|
|
75
|
+
const saved = loadAdminOpenId(ctx.accountId);
|
|
76
|
+
if (saved)
|
|
77
|
+
return saved;
|
|
78
|
+
const first = listKnownUsers({ accountId: ctx.accountId, type: "c2c", sortBy: "firstSeenAt", sortOrder: "asc", limit: 1 })[0]?.openid;
|
|
79
|
+
if (first) {
|
|
80
|
+
saveAdminOpenId(ctx.accountId, first);
|
|
81
|
+
ctx.log?.info(`[qqbot:${ctx.accountId}] Auto-detected admin openid: ${first} (persisted)`);
|
|
82
|
+
}
|
|
83
|
+
return first;
|
|
84
|
+
}
|
|
85
|
+
// ---- 启动问候语 ----
|
|
86
|
+
/** 异步发送启动问候语(仅发给管理员) */
|
|
87
|
+
export function sendStartupGreetings(ctx, trigger) {
|
|
88
|
+
(async () => {
|
|
89
|
+
const plan = getStartupGreetingPlan();
|
|
90
|
+
if (!plan.shouldSend || !plan.greeting) {
|
|
91
|
+
ctx.log?.info(`[qqbot:${ctx.accountId}] Skipping startup greeting (${plan.reason ?? "debounced"}, trigger=${trigger})`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const upgradeTargetOpenId = loadUpgradeGreetingTargetOpenId(ctx.accountId, ctx.appId);
|
|
95
|
+
const targetOpenId = upgradeTargetOpenId || resolveAdminOpenId(ctx);
|
|
96
|
+
if (!targetOpenId) {
|
|
97
|
+
markStartupGreetingFailed(plan.version, "no-admin");
|
|
98
|
+
ctx.log?.info(`[qqbot:${ctx.accountId}] Skipping startup greeting (no admin or known user)`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const receiverType = upgradeTargetOpenId ? "upgrade-requester" : "admin";
|
|
103
|
+
ctx.log?.info(`[qqbot:${ctx.accountId}] Sending startup greeting to ${receiverType} (trigger=${trigger}): "${plan.greeting}"`);
|
|
104
|
+
const token = await getAccessToken(ctx.appId, ctx.clientSecret);
|
|
105
|
+
const GREETING_TIMEOUT_MS = 10_000;
|
|
106
|
+
await Promise.race([
|
|
107
|
+
sendProactiveC2CMessage(token, targetOpenId, plan.greeting),
|
|
108
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Startup greeting send timeout (10s)")), GREETING_TIMEOUT_MS)),
|
|
109
|
+
]);
|
|
110
|
+
markStartupGreetingSent(plan.version);
|
|
111
|
+
if (upgradeTargetOpenId) {
|
|
112
|
+
clearUpgradeGreetingTargetOpenId(ctx.accountId, ctx.appId);
|
|
113
|
+
}
|
|
114
|
+
ctx.log?.info(`[qqbot:${ctx.accountId}] Sent startup greeting to ${receiverType}: ${targetOpenId}`);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
118
|
+
markStartupGreetingFailed(plan.version, message);
|
|
119
|
+
ctx.log?.error(`[qqbot:${ctx.accountId}] Failed to send startup greeting: ${message}`);
|
|
120
|
+
}
|
|
121
|
+
})();
|
|
122
|
+
}
|
package/dist/src/api.js
CHANGED
|
@@ -58,8 +58,12 @@ const tokenFetchPromises = new Map();
|
|
|
58
58
|
export async function getAccessToken(appId, clientSecret) {
|
|
59
59
|
const normalizedAppId = String(appId).trim();
|
|
60
60
|
const cachedToken = tokenCacheMap.get(normalizedAppId);
|
|
61
|
-
//
|
|
62
|
-
|
|
61
|
+
// 检查缓存:未过期时复用
|
|
62
|
+
// 提前刷新阈值:取 expiresIn 的 1/3 和 5 分钟的较小值,避免短有效期 token 永远被判定过期
|
|
63
|
+
const REFRESH_AHEAD_MS = cachedToken
|
|
64
|
+
? Math.min(5 * 60 * 1000, (cachedToken.expiresAt - Date.now()) / 3)
|
|
65
|
+
: 0;
|
|
66
|
+
if (cachedToken && Date.now() < cachedToken.expiresAt - REFRESH_AHEAD_MS) {
|
|
63
67
|
return cachedToken.token;
|
|
64
68
|
}
|
|
65
69
|
// Singleflight: 如果当前 appId 已有进行中的 Token 获取请求,复用它
|
|
@@ -159,7 +163,8 @@ export function getTokenStatus(appId) {
|
|
|
159
163
|
if (!cached) {
|
|
160
164
|
return { status: "none", expiresAt: null };
|
|
161
165
|
}
|
|
162
|
-
const
|
|
166
|
+
const remaining = cached.expiresAt - Date.now();
|
|
167
|
+
const isValid = remaining > Math.min(5 * 60 * 1000, remaining / 3);
|
|
163
168
|
return { status: isValid ? "valid" : "expired", expiresAt: cached.expiresAt };
|
|
164
169
|
}
|
|
165
170
|
/**
|
package/dist/src/channel.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import { type ChannelPlugin } from "openclaw/plugin-sdk";
|
|
2
2
|
import type { ResolvedQQBotAccount } from "./types.js";
|
|
3
|
+
/** QQ Bot 单条消息文本长度上限 */
|
|
4
|
+
export declare const TEXT_CHUNK_LIMIT = 5000;
|
|
5
|
+
/**
|
|
6
|
+
* Markdown 感知的文本分块函数
|
|
7
|
+
* 委托给 SDK 内置的 channel.text.chunkMarkdownText
|
|
8
|
+
* 支持代码块自动关闭/重开、括号感知等
|
|
9
|
+
*/
|
|
10
|
+
export declare function chunkText(text: string, limit: number): string[];
|
|
3
11
|
export declare const qqbotPlugin: ChannelPlugin<ResolvedQQBotAccount>;
|