@cxyhhhhh/openclaw-qqbot 1.6.7-alpha.1
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/LICENSE +22 -0
- package/README.md +470 -0
- package/README.zh.md +465 -0
- package/bin/qqbot-cli.js +243 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +26 -0
- package/dist/src/admin-resolver.d.ts +33 -0
- package/dist/src/admin-resolver.js +157 -0
- package/dist/src/api.d.ts +264 -0
- package/dist/src/api.js +777 -0
- package/dist/src/channel.d.ts +29 -0
- package/dist/src/channel.js +452 -0
- package/dist/src/config.d.ts +56 -0
- package/dist/src/config.js +278 -0
- package/dist/src/credential-backup.d.ts +31 -0
- package/dist/src/credential-backup.js +66 -0
- package/dist/src/deliver-debounce.d.ts +74 -0
- package/dist/src/deliver-debounce.js +174 -0
- package/dist/src/gateway.d.ts +18 -0
- package/dist/src/gateway.js +2021 -0
- package/dist/src/group-history.d.ts +136 -0
- package/dist/src/group-history.js +226 -0
- package/dist/src/image-server.d.ts +87 -0
- package/dist/src/image-server.js +570 -0
- package/dist/src/inbound-attachments.d.ts +60 -0
- package/dist/src/inbound-attachments.js +248 -0
- package/dist/src/known-users.d.ts +100 -0
- package/dist/src/known-users.js +263 -0
- package/dist/src/message-gating.d.ts +53 -0
- package/dist/src/message-gating.js +107 -0
- package/dist/src/message-queue.d.ts +86 -0
- package/dist/src/message-queue.js +257 -0
- package/dist/src/onboarding.d.ts +10 -0
- package/dist/src/onboarding.js +203 -0
- package/dist/src/outbound-deliver.d.ts +48 -0
- package/dist/src/outbound-deliver.js +392 -0
- package/dist/src/outbound.d.ts +205 -0
- package/dist/src/outbound.js +926 -0
- package/dist/src/proactive.d.ts +170 -0
- package/dist/src/proactive.js +399 -0
- package/dist/src/ref-index-store.d.ts +70 -0
- package/dist/src/ref-index-store.js +250 -0
- package/dist/src/reply-dispatcher.d.ts +35 -0
- package/dist/src/reply-dispatcher.js +311 -0
- package/dist/src/request-context.d.ts +18 -0
- package/dist/src/request-context.js +30 -0
- package/dist/src/runtime.d.ts +3 -0
- package/dist/src/runtime.js +10 -0
- package/dist/src/session-store.d.ts +52 -0
- package/dist/src/session-store.js +254 -0
- package/dist/src/slash-commands.d.ts +77 -0
- package/dist/src/slash-commands.js +1461 -0
- package/dist/src/startup-greeting.d.ts +30 -0
- package/dist/src/startup-greeting.js +97 -0
- package/dist/src/streaming.d.ts +250 -0
- package/dist/src/streaming.js +914 -0
- package/dist/src/stt.d.ts +21 -0
- package/dist/src/stt.js +70 -0
- package/dist/src/tools/channel.d.ts +16 -0
- package/dist/src/tools/channel.js +234 -0
- package/dist/src/tools/remind.d.ts +2 -0
- package/dist/src/tools/remind.js +248 -0
- package/dist/src/types.d.ts +364 -0
- package/dist/src/types.js +17 -0
- package/dist/src/typing-keepalive.d.ts +27 -0
- package/dist/src/typing-keepalive.js +64 -0
- package/dist/src/update-checker.d.ts +34 -0
- package/dist/src/update-checker.js +160 -0
- package/dist/src/utils/audio-convert.d.ts +98 -0
- package/dist/src/utils/audio-convert.js +755 -0
- package/dist/src/utils/chunked-upload.d.ts +59 -0
- package/dist/src/utils/chunked-upload.js +289 -0
- package/dist/src/utils/file-utils.d.ts +61 -0
- package/dist/src/utils/file-utils.js +172 -0
- package/dist/src/utils/image-size.d.ts +51 -0
- package/dist/src/utils/image-size.js +234 -0
- package/dist/src/utils/media-send.d.ts +148 -0
- package/dist/src/utils/media-send.js +456 -0
- package/dist/src/utils/media-tags.d.ts +14 -0
- package/dist/src/utils/media-tags.js +164 -0
- package/dist/src/utils/payload.d.ts +112 -0
- package/dist/src/utils/payload.js +186 -0
- package/dist/src/utils/pkg-version.d.ts +5 -0
- package/dist/src/utils/pkg-version.js +51 -0
- package/dist/src/utils/platform.d.ts +137 -0
- package/dist/src/utils/platform.js +390 -0
- package/dist/src/utils/ssrf-guard.d.ts +25 -0
- package/dist/src/utils/ssrf-guard.js +91 -0
- package/dist/src/utils/text-parsing.d.ts +32 -0
- package/dist/src/utils/text-parsing.js +69 -0
- package/dist/src/utils/upload-cache.d.ts +34 -0
- package/dist/src/utils/upload-cache.js +93 -0
- package/index.ts +31 -0
- package/node_modules/@eshaz/web-worker/LICENSE +201 -0
- package/node_modules/@eshaz/web-worker/README.md +134 -0
- package/node_modules/@eshaz/web-worker/browser.js +17 -0
- package/node_modules/@eshaz/web-worker/cjs/browser.js +16 -0
- package/node_modules/@eshaz/web-worker/cjs/node.js +219 -0
- package/node_modules/@eshaz/web-worker/index.d.ts +4 -0
- package/node_modules/@eshaz/web-worker/node.js +223 -0
- package/node_modules/@eshaz/web-worker/package.json +54 -0
- package/node_modules/@wasm-audio-decoders/common/index.js +5 -0
- package/node_modules/@wasm-audio-decoders/common/package.json +36 -0
- package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderCommon.js +231 -0
- package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderWorker.js +129 -0
- package/node_modules/@wasm-audio-decoders/common/src/puff/README +67 -0
- package/node_modules/@wasm-audio-decoders/common/src/puff/build_puff.js +31 -0
- package/node_modules/@wasm-audio-decoders/common/src/puff/puff.c +863 -0
- package/node_modules/@wasm-audio-decoders/common/src/puff/puff.h +35 -0
- package/node_modules/@wasm-audio-decoders/common/src/utilities.js +3 -0
- package/node_modules/@wasm-audio-decoders/common/types.d.ts +7 -0
- package/node_modules/mpg123-decoder/README.md +265 -0
- package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js +185 -0
- package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js.map +1 -0
- package/node_modules/mpg123-decoder/index.js +8 -0
- package/node_modules/mpg123-decoder/package.json +58 -0
- package/node_modules/mpg123-decoder/src/EmscriptenWasm.js +464 -0
- package/node_modules/mpg123-decoder/src/MPEGDecoder.js +200 -0
- package/node_modules/mpg123-decoder/src/MPEGDecoderWebWorker.js +21 -0
- package/node_modules/mpg123-decoder/types.d.ts +30 -0
- package/node_modules/silk-wasm/LICENSE +21 -0
- package/node_modules/silk-wasm/README.md +85 -0
- package/node_modules/silk-wasm/lib/index.cjs +16 -0
- package/node_modules/silk-wasm/lib/index.d.ts +70 -0
- package/node_modules/silk-wasm/lib/index.mjs +16 -0
- package/node_modules/silk-wasm/lib/silk.wasm +0 -0
- package/node_modules/silk-wasm/lib/utils.d.ts +4 -0
- package/node_modules/silk-wasm/package.json +39 -0
- package/node_modules/simple-yenc/.github/FUNDING.yml +1 -0
- package/node_modules/simple-yenc/.prettierignore +1 -0
- package/node_modules/simple-yenc/LICENSE +7 -0
- package/node_modules/simple-yenc/README.md +163 -0
- package/node_modules/simple-yenc/dist/esm.js +1 -0
- package/node_modules/simple-yenc/dist/index.js +1 -0
- package/node_modules/simple-yenc/package.json +50 -0
- package/node_modules/simple-yenc/rollup.config.js +27 -0
- package/node_modules/simple-yenc/src/simple-yenc.js +302 -0
- package/node_modules/ws/LICENSE +20 -0
- package/node_modules/ws/README.md +548 -0
- package/node_modules/ws/browser.js +8 -0
- package/node_modules/ws/index.js +13 -0
- package/node_modules/ws/lib/buffer-util.js +131 -0
- package/node_modules/ws/lib/constants.js +19 -0
- package/node_modules/ws/lib/event-target.js +292 -0
- package/node_modules/ws/lib/extension.js +203 -0
- package/node_modules/ws/lib/limiter.js +55 -0
- package/node_modules/ws/lib/permessage-deflate.js +528 -0
- package/node_modules/ws/lib/receiver.js +706 -0
- package/node_modules/ws/lib/sender.js +602 -0
- package/node_modules/ws/lib/stream.js +161 -0
- package/node_modules/ws/lib/subprotocol.js +62 -0
- package/node_modules/ws/lib/validation.js +152 -0
- package/node_modules/ws/lib/websocket-server.js +554 -0
- package/node_modules/ws/lib/websocket.js +1393 -0
- package/node_modules/ws/package.json +69 -0
- package/node_modules/ws/wrapper.mjs +8 -0
- package/openclaw.plugin.json +17 -0
- package/package.json +67 -0
- package/preload.cjs +33 -0
- package/scripts/cleanup-legacy-plugins.sh +124 -0
- package/scripts/link-sdk-core.cjs +185 -0
- package/scripts/postinstall-link-sdk.js +113 -0
- package/scripts/proactive-api-server.ts +369 -0
- package/scripts/send-proactive.ts +293 -0
- package/scripts/set-markdown.sh +156 -0
- package/scripts/test-sendmedia.ts +116 -0
- package/scripts/upgrade-via-npm.ps1 +451 -0
- package/scripts/upgrade-via-npm.sh +528 -0
- package/scripts/upgrade-via-source.sh +916 -0
- package/skills/qqbot-channel/SKILL.md +263 -0
- package/skills/qqbot-channel/references/api_references.md +521 -0
- package/skills/qqbot-media/SKILL.md +60 -0
- package/skills/qqbot-remind/SKILL.md +149 -0
- package/src/admin-resolver.ts +181 -0
- package/src/api.ts +1138 -0
- package/src/channel.ts +477 -0
- package/src/config.ts +347 -0
- package/src/credential-backup.ts +72 -0
- package/src/deliver-debounce.ts +229 -0
- package/src/gateway.ts +2257 -0
- package/src/group-history.ts +328 -0
- package/src/image-server.ts +675 -0
- package/src/inbound-attachments.ts +321 -0
- package/src/known-users.ts +353 -0
- package/src/message-gating.ts +190 -0
- package/src/message-queue.ts +349 -0
- package/src/onboarding.ts +274 -0
- package/src/openclaw-plugin-sdk.d.ts +587 -0
- package/src/outbound-deliver.ts +473 -0
- package/src/outbound.ts +1119 -0
- package/src/proactive.ts +530 -0
- package/src/ref-index-store.ts +335 -0
- package/src/reply-dispatcher.ts +334 -0
- package/src/request-context.ts +39 -0
- package/src/runtime.ts +14 -0
- package/src/session-store.ts +303 -0
- package/src/slash-commands.ts +1615 -0
- package/src/startup-greeting.ts +120 -0
- package/src/streaming.ts +1102 -0
- package/src/stt.ts +86 -0
- package/src/tools/channel.ts +281 -0
- package/src/tools/remind.ts +300 -0
- package/src/types.ts +386 -0
- package/src/typing-keepalive.ts +59 -0
- package/src/update-checker.ts +174 -0
- package/src/utils/audio-convert.ts +859 -0
- package/src/utils/chunked-upload.ts +419 -0
- package/src/utils/file-utils.ts +193 -0
- package/src/utils/image-size.ts +266 -0
- package/src/utils/media-send.ts +585 -0
- package/src/utils/media-tags.ts +182 -0
- package/src/utils/payload.ts +265 -0
- package/src/utils/pkg-version.ts +54 -0
- package/src/utils/platform.ts +435 -0
- package/src/utils/ssrf-guard.ts +102 -0
- package/src/utils/text-parsing.ts +75 -0
- package/src/utils/upload-cache.ts +128 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: qqbot-remind
|
|
3
|
+
description: QQBot 定时提醒。支持一次性和周期性提醒的创建、查询、取消。当通过 QQ 通道通信且涉及提醒/定时任务时使用。
|
|
4
|
+
metadata: {"openclaw":{"emoji":"⏰","requires":{"config":["channels.qqbot"]}}}
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# QQ Bot 定时提醒
|
|
8
|
+
|
|
9
|
+
## ⚠️ 强制规则
|
|
10
|
+
|
|
11
|
+
**当用户提到「提醒」「闹钟」「定时」「X分钟/小时后」「每天X点」「叫我」等任何涉及延时或定时的请求时,你必须调用工具,绝对不能只用自然语言回复说"好的,我会提醒你"!**
|
|
12
|
+
|
|
13
|
+
你没有内存或后台线程,口头承诺"到时候提醒"是无效的——只有调用工具才能真正注册定时任务。
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 推荐流程(优先使用 `qqbot_remind` 工具)
|
|
18
|
+
|
|
19
|
+
**第一步**:调用 `qqbot_remind` 工具,传入简单参数:
|
|
20
|
+
|
|
21
|
+
| 参数 | 说明 | 示例 |
|
|
22
|
+
|------|------|------|
|
|
23
|
+
| `action` | 操作类型 | `"add"` / `"list"` / `"remove"` |
|
|
24
|
+
| `content` | 提醒内容 | `"喝水"` |
|
|
25
|
+
| `to` | 目标地址(可选,系统自动获取,通常无需填写) | — |
|
|
26
|
+
| `time` | 时间(相对时间或 cron 表达式) | `"5m"` / `"1h30m"` / `"0 8 * * *"` |
|
|
27
|
+
| `jobId` | 任务 ID(仅 remove) | `"xxx"` |
|
|
28
|
+
|
|
29
|
+
**第二步**:`qqbot_remind` 会返回 `cronParams`,你必须**立即**将 `cronParams` 的值作为参数调用 `cron` 工具。
|
|
30
|
+
|
|
31
|
+
**第三步**:根据 `cron` 工具的返回结果,回复用户。
|
|
32
|
+
|
|
33
|
+
### 示例
|
|
34
|
+
|
|
35
|
+
用户说:"5分钟后提醒我喝水"
|
|
36
|
+
|
|
37
|
+
1. 调用 `qqbot_remind`:`{ "action": "add", "content": "喝水", "time": "5m" }`
|
|
38
|
+
2. 收到返回的 `cronParams` → 立即调用 `cron` 工具,参数为该 `cronParams`
|
|
39
|
+
3. 回复用户:`⏰ 好的,5分钟后提醒你喝水~`
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 备用方案(直接使用 `cron` 工具)
|
|
44
|
+
|
|
45
|
+
> 仅当 `qqbot_remind` 工具不可用时使用以下方式。
|
|
46
|
+
|
|
47
|
+
### 核心规则
|
|
48
|
+
|
|
49
|
+
> **payload.kind 必须是 `"agentTurn"`,绝对不能用 `"systemEvent"`!**
|
|
50
|
+
> `systemEvent` 只在 AI 会话内部注入文本,用户收不到 QQ 消息。
|
|
51
|
+
|
|
52
|
+
**5 个不可更改字段**:
|
|
53
|
+
|
|
54
|
+
| 字段 | 固定值 | 原因 |
|
|
55
|
+
|------|--------|------|
|
|
56
|
+
| `payload.kind` | `"agentTurn"` | `systemEvent` 不会发 QQ 消息 |
|
|
57
|
+
| `payload.deliver` | `true` | 否则不投递 |
|
|
58
|
+
| `payload.channel` | `"qqbot"` | QQ 通道标识 |
|
|
59
|
+
| `payload.to` | 用户 openid | 从 `To` 字段获取 |
|
|
60
|
+
| `sessionTarget` | `"isolated"` | 隔离会话避免污染 |
|
|
61
|
+
|
|
62
|
+
> `schedule.atMs` 必须是**绝对毫秒时间戳**(如 `1770733800000`),不支持 `"5m"` 等相对字符串。
|
|
63
|
+
> 计算方式:`当前时间戳ms + 延迟毫秒`。
|
|
64
|
+
|
|
65
|
+
### 一次性提醒(schedule.kind = "at")
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"action": "add",
|
|
70
|
+
"job": {
|
|
71
|
+
"name": "{任务名}",
|
|
72
|
+
"schedule": { "kind": "at", "atMs": "{当前时间戳ms + N*60000}" },
|
|
73
|
+
"sessionTarget": "isolated",
|
|
74
|
+
"wakeMode": "now",
|
|
75
|
+
"deleteAfterRun": true,
|
|
76
|
+
"payload": {
|
|
77
|
+
"kind": "agentTurn",
|
|
78
|
+
"message": "你是一个暖心的提醒助手。请用温暖、有趣的方式提醒用户:{提醒内容}。要求:(1) 不要回复HEARTBEAT_OK (2) 不要解释你是谁 (3) 直接输出一条暖心的提醒消息 (4) 可以加一句简短的鸡汤或关怀的话 (5) 控制在2-3句话以内 (6) 用emoji点缀",
|
|
79
|
+
"deliver": true,
|
|
80
|
+
"channel": "qqbot",
|
|
81
|
+
"to": "{openid}"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 周期提醒(schedule.kind = "cron")
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"action": "add",
|
|
92
|
+
"job": {
|
|
93
|
+
"name": "{任务名}",
|
|
94
|
+
"schedule": { "kind": "cron", "expr": "0 8 * * *", "tz": "Asia/Shanghai" },
|
|
95
|
+
"sessionTarget": "isolated",
|
|
96
|
+
"wakeMode": "now",
|
|
97
|
+
"payload": {
|
|
98
|
+
"kind": "agentTurn",
|
|
99
|
+
"message": "你是一个暖心的提醒助手。请用温暖、有趣的方式提醒用户:{提醒内容}。要求:(1) 不要回复HEARTBEAT_OK (2) 不要解释你是谁 (3) 直接输出一条暖心的提醒消息 (4) 可以加一句简短的鸡汤或关怀的话 (5) 控制在2-3句话以内 (6) 用emoji点缀",
|
|
100
|
+
"deliver": true,
|
|
101
|
+
"channel": "qqbot",
|
|
102
|
+
"to": "{openid}"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
> 周期任务**不加** `deleteAfterRun`。群聊 `to` 格式为 `"group:{group_openid}"`。
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## cron 表达式速查
|
|
113
|
+
|
|
114
|
+
| 场景 | expr |
|
|
115
|
+
|------|------|
|
|
116
|
+
| 每天早上8点 | `"0 8 * * *"` |
|
|
117
|
+
| 每天晚上10点 | `"0 22 * * *"` |
|
|
118
|
+
| 工作日早上9点 | `"0 9 * * 1-5"` |
|
|
119
|
+
| 每周一早上9点 | `"0 9 * * 1"` |
|
|
120
|
+
| 每周末上午10点 | `"0 10 * * 0,6"` |
|
|
121
|
+
| 每小时整点 | `"0 * * * *"` |
|
|
122
|
+
|
|
123
|
+
> 周期提醒必须加 `"tz": "Asia/Shanghai"`。
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## AI 决策指南
|
|
128
|
+
|
|
129
|
+
| 用户说法 | action | time 格式 |
|
|
130
|
+
|----------|--------|-----------|
|
|
131
|
+
| "5分钟后提醒我喝水" | `add` | `"5m"` |
|
|
132
|
+
| "1小时后提醒开会" | `add` | `"1h"` |
|
|
133
|
+
| "每天8点提醒我打卡" | `add` | `"0 8 * * *"` |
|
|
134
|
+
| "工作日早上9点提醒" | `add` | `"0 9 * * 1-5"` |
|
|
135
|
+
| "我有哪些提醒" | `list` | — |
|
|
136
|
+
| "取消喝水提醒" | `remove` | — |
|
|
137
|
+
| "修改提醒时间" | `remove` → `add` | — |
|
|
138
|
+
| "提醒我"(无时间) | **需追问** | — |
|
|
139
|
+
|
|
140
|
+
纯相对时间("5分钟后"、"1小时后")可直接计算,无需确认。时间模糊或缺失时需追问。
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 回复模板
|
|
145
|
+
|
|
146
|
+
- 一次性:`⏰ 好的,{时间}后提醒你{内容}~`
|
|
147
|
+
- 周期:`⏰ 收到,{周期}提醒你{内容}~`
|
|
148
|
+
- 查询无结果:`📋 目前没有提醒哦~ 说"5分钟后提醒我xxx"试试?`
|
|
149
|
+
- 删除成功:`✅ 已取消"{名称}"`
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 管理员解析器模块
|
|
3
|
+
* - 管理员 openid 持久化读写
|
|
4
|
+
* - 升级问候目标读写
|
|
5
|
+
* - 启动问候语发送
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import { getQQBotDataDir } from "./utils/platform.js";
|
|
11
|
+
import { listKnownUsers } from "./known-users.js";
|
|
12
|
+
import { getAccessToken, sendProactiveC2CMessage } from "./api.js";
|
|
13
|
+
import { getStartupGreetingPlan, markStartupGreetingSent, markStartupGreetingFailed } from "./startup-greeting.js";
|
|
14
|
+
|
|
15
|
+
// ---- 类型 ----
|
|
16
|
+
|
|
17
|
+
export interface AdminResolverContext {
|
|
18
|
+
accountId: string;
|
|
19
|
+
appId: string;
|
|
20
|
+
clientSecret: string;
|
|
21
|
+
log?: {
|
|
22
|
+
info: (msg: string) => void;
|
|
23
|
+
error: (msg: string) => void;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ---- 文件路径 ----
|
|
28
|
+
|
|
29
|
+
function safeName(id: string): string {
|
|
30
|
+
return id.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** 新版 admin 文件路径(按 accountId + appId 区分) */
|
|
34
|
+
function getAdminMarkerFile(accountId: string, appId: string): string {
|
|
35
|
+
return path.join(getQQBotDataDir("data"), `admin-${safeName(accountId)}-${safeName(appId)}.json`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 旧版 admin 文件路径(仅按 accountId 区分,用于迁移兼容) */
|
|
39
|
+
function getLegacyAdminMarkerFile(accountId: string): string {
|
|
40
|
+
return path.join(getQQBotDataDir("data"), `admin-${accountId}.json`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getUpgradeGreetingTargetFile(accountId: string, appId: string): string {
|
|
44
|
+
return path.join(getQQBotDataDir("data"), `upgrade-greeting-target-${safeName(accountId)}-${safeName(appId)}.json`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ---- 管理员 openid 持久化 ----
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 读取 admin openid(按 accountId + appId 区分)
|
|
51
|
+
* 兼容策略:新路径优先 → fallback 旧路径 → 自动迁移
|
|
52
|
+
*/
|
|
53
|
+
export function loadAdminOpenId(accountId: string, appId: string): string | undefined {
|
|
54
|
+
try {
|
|
55
|
+
// 1. 先尝试新版路径
|
|
56
|
+
const newFile = getAdminMarkerFile(accountId, appId);
|
|
57
|
+
if (fs.existsSync(newFile)) {
|
|
58
|
+
const data = JSON.parse(fs.readFileSync(newFile, "utf8"));
|
|
59
|
+
if (data.openid) return data.openid;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 2. fallback 旧版路径(仅按 accountId)
|
|
63
|
+
const legacyFile = getLegacyAdminMarkerFile(accountId);
|
|
64
|
+
if (fs.existsSync(legacyFile)) {
|
|
65
|
+
const data = JSON.parse(fs.readFileSync(legacyFile, "utf8"));
|
|
66
|
+
if (data.openid) {
|
|
67
|
+
// 自动迁移:写到新路径,删除旧文件
|
|
68
|
+
saveAdminOpenId(accountId, appId, data.openid);
|
|
69
|
+
try { fs.unlinkSync(legacyFile); } catch { /* ignore */ }
|
|
70
|
+
return data.openid;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch { /* 文件损坏视为无 */ }
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function saveAdminOpenId(accountId: string, appId: string, openid: string): void {
|
|
78
|
+
try {
|
|
79
|
+
fs.writeFileSync(
|
|
80
|
+
getAdminMarkerFile(accountId, appId),
|
|
81
|
+
JSON.stringify({ accountId, appId, openid, savedAt: new Date().toISOString() }),
|
|
82
|
+
);
|
|
83
|
+
} catch { /* ignore */ }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ---- 升级问候目标 ----
|
|
87
|
+
|
|
88
|
+
export function loadUpgradeGreetingTargetOpenId(accountId: string, appId: string, log?: { info: (msg: string) => void }): string | undefined {
|
|
89
|
+
try {
|
|
90
|
+
const file = getUpgradeGreetingTargetFile(accountId, appId);
|
|
91
|
+
if (fs.existsSync(file)) {
|
|
92
|
+
const data = JSON.parse(fs.readFileSync(file, "utf8")) as { accountId?: string; appId?: string; openid?: string };
|
|
93
|
+
if (!data.openid) {
|
|
94
|
+
log?.info(`[qqbot:${accountId}] upgrade-greeting-target file found but openid is empty`);
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
if (data.appId && data.appId !== appId) {
|
|
98
|
+
log?.info(`[qqbot:${accountId}] upgrade-greeting-target appId mismatch: file=${data.appId}, current=${appId}`);
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
if (data.accountId && data.accountId !== accountId) {
|
|
102
|
+
log?.info(`[qqbot:${accountId}] upgrade-greeting-target accountId mismatch: file=${data.accountId}, current=${accountId}`);
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
log?.info(`[qqbot:${accountId}] upgrade-greeting-target loaded: openid=${data.openid}`);
|
|
106
|
+
return data.openid;
|
|
107
|
+
} else {
|
|
108
|
+
log?.info(`[qqbot:${accountId}] upgrade-greeting-target file not found: ${file}`);
|
|
109
|
+
}
|
|
110
|
+
} catch (err) {
|
|
111
|
+
log?.info(`[qqbot:${accountId}] upgrade-greeting-target file read error: ${err}`);
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function clearUpgradeGreetingTargetOpenId(accountId: string, appId: string): void {
|
|
117
|
+
try {
|
|
118
|
+
const file = getUpgradeGreetingTargetFile(accountId, appId);
|
|
119
|
+
if (fs.existsSync(file)) {
|
|
120
|
+
fs.unlinkSync(file);
|
|
121
|
+
}
|
|
122
|
+
} catch { /* ignore */ }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ---- 解析管理员 ----
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 解析管理员 openid:
|
|
129
|
+
* 1. 优先读持久化文件(按 accountId + appId 区分)
|
|
130
|
+
* 2. fallback 取第一个私聊用户,并写入文件锁定
|
|
131
|
+
*/
|
|
132
|
+
export function resolveAdminOpenId(ctx: Pick<AdminResolverContext, "accountId" | "appId" | "log">): string | undefined {
|
|
133
|
+
const saved = loadAdminOpenId(ctx.accountId, ctx.appId);
|
|
134
|
+
if (saved) return saved;
|
|
135
|
+
const first = listKnownUsers({ accountId: ctx.accountId, type: "c2c", sortBy: "firstSeenAt", sortOrder: "asc", limit: 1 })[0]?.openid;
|
|
136
|
+
if (first) {
|
|
137
|
+
saveAdminOpenId(ctx.accountId, ctx.appId, first);
|
|
138
|
+
ctx.log?.info(`[qqbot:${ctx.accountId}] Auto-detected admin openid: ${first} (persisted)`);
|
|
139
|
+
}
|
|
140
|
+
return first;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ---- 启动问候语 ----
|
|
144
|
+
|
|
145
|
+
/** 异步发送启动问候语(优先发给升级触发者,fallback 发给管理员) */
|
|
146
|
+
export function sendStartupGreetings(ctx: AdminResolverContext, trigger: "READY" | "RESUMED"): void {
|
|
147
|
+
(async () => {
|
|
148
|
+
const plan = getStartupGreetingPlan(ctx.accountId, ctx.appId);
|
|
149
|
+
if (!plan.shouldSend || !plan.greeting) {
|
|
150
|
+
ctx.log?.info(`[qqbot:${ctx.accountId}] Skipping startup greeting (${plan.reason ?? "debounced"}, trigger=${trigger})`);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const upgradeTargetOpenId = loadUpgradeGreetingTargetOpenId(ctx.accountId, ctx.appId, ctx.log);
|
|
155
|
+
|
|
156
|
+
// 没有 upgrade-greeting-target 文件 → 不是通过 /bot-upgrade 触发的升级
|
|
157
|
+
// (console 手动重启、脚本升级等场景),静默更新 marker 不发消息
|
|
158
|
+
if (!upgradeTargetOpenId) {
|
|
159
|
+
markStartupGreetingSent(ctx.accountId, ctx.appId, plan.version);
|
|
160
|
+
ctx.log?.info(`[qqbot:${ctx.accountId}] Version changed but no upgrade-greeting-target, silently updating marker (trigger=${trigger})`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
ctx.log?.info(`[qqbot:${ctx.accountId}] Sending startup greeting to upgrade-requester (trigger=${trigger}): "${plan.greeting}"`);
|
|
166
|
+
const token = await getAccessToken(ctx.appId, ctx.clientSecret);
|
|
167
|
+
const GREETING_TIMEOUT_MS = 10_000;
|
|
168
|
+
await Promise.race([
|
|
169
|
+
sendProactiveC2CMessage(token, upgradeTargetOpenId, plan.greeting),
|
|
170
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Startup greeting send timeout (10s)")), GREETING_TIMEOUT_MS)),
|
|
171
|
+
]);
|
|
172
|
+
markStartupGreetingSent(ctx.accountId, ctx.appId, plan.version);
|
|
173
|
+
clearUpgradeGreetingTargetOpenId(ctx.accountId, ctx.appId);
|
|
174
|
+
ctx.log?.info(`[qqbot:${ctx.accountId}] Sent startup greeting to upgrade-requester: ${upgradeTargetOpenId}`);
|
|
175
|
+
} catch (err) {
|
|
176
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
177
|
+
markStartupGreetingFailed(ctx.accountId, ctx.appId, plan.version, message);
|
|
178
|
+
ctx.log?.error(`[qqbot:${ctx.accountId}] Failed to send startup greeting: ${message}`);
|
|
179
|
+
}
|
|
180
|
+
})();
|
|
181
|
+
}
|