@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.
Files changed (218) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +470 -0
  3. package/README.zh.md +465 -0
  4. package/bin/qqbot-cli.js +243 -0
  5. package/dist/index.d.ts +17 -0
  6. package/dist/index.js +26 -0
  7. package/dist/src/admin-resolver.d.ts +33 -0
  8. package/dist/src/admin-resolver.js +157 -0
  9. package/dist/src/api.d.ts +264 -0
  10. package/dist/src/api.js +777 -0
  11. package/dist/src/channel.d.ts +29 -0
  12. package/dist/src/channel.js +452 -0
  13. package/dist/src/config.d.ts +56 -0
  14. package/dist/src/config.js +278 -0
  15. package/dist/src/credential-backup.d.ts +31 -0
  16. package/dist/src/credential-backup.js +66 -0
  17. package/dist/src/deliver-debounce.d.ts +74 -0
  18. package/dist/src/deliver-debounce.js +174 -0
  19. package/dist/src/gateway.d.ts +18 -0
  20. package/dist/src/gateway.js +2021 -0
  21. package/dist/src/group-history.d.ts +136 -0
  22. package/dist/src/group-history.js +226 -0
  23. package/dist/src/image-server.d.ts +87 -0
  24. package/dist/src/image-server.js +570 -0
  25. package/dist/src/inbound-attachments.d.ts +60 -0
  26. package/dist/src/inbound-attachments.js +248 -0
  27. package/dist/src/known-users.d.ts +100 -0
  28. package/dist/src/known-users.js +263 -0
  29. package/dist/src/message-gating.d.ts +53 -0
  30. package/dist/src/message-gating.js +107 -0
  31. package/dist/src/message-queue.d.ts +86 -0
  32. package/dist/src/message-queue.js +257 -0
  33. package/dist/src/onboarding.d.ts +10 -0
  34. package/dist/src/onboarding.js +203 -0
  35. package/dist/src/outbound-deliver.d.ts +48 -0
  36. package/dist/src/outbound-deliver.js +392 -0
  37. package/dist/src/outbound.d.ts +205 -0
  38. package/dist/src/outbound.js +926 -0
  39. package/dist/src/proactive.d.ts +170 -0
  40. package/dist/src/proactive.js +399 -0
  41. package/dist/src/ref-index-store.d.ts +70 -0
  42. package/dist/src/ref-index-store.js +250 -0
  43. package/dist/src/reply-dispatcher.d.ts +35 -0
  44. package/dist/src/reply-dispatcher.js +311 -0
  45. package/dist/src/request-context.d.ts +18 -0
  46. package/dist/src/request-context.js +30 -0
  47. package/dist/src/runtime.d.ts +3 -0
  48. package/dist/src/runtime.js +10 -0
  49. package/dist/src/session-store.d.ts +52 -0
  50. package/dist/src/session-store.js +254 -0
  51. package/dist/src/slash-commands.d.ts +77 -0
  52. package/dist/src/slash-commands.js +1461 -0
  53. package/dist/src/startup-greeting.d.ts +30 -0
  54. package/dist/src/startup-greeting.js +97 -0
  55. package/dist/src/streaming.d.ts +250 -0
  56. package/dist/src/streaming.js +914 -0
  57. package/dist/src/stt.d.ts +21 -0
  58. package/dist/src/stt.js +70 -0
  59. package/dist/src/tools/channel.d.ts +16 -0
  60. package/dist/src/tools/channel.js +234 -0
  61. package/dist/src/tools/remind.d.ts +2 -0
  62. package/dist/src/tools/remind.js +248 -0
  63. package/dist/src/types.d.ts +364 -0
  64. package/dist/src/types.js +17 -0
  65. package/dist/src/typing-keepalive.d.ts +27 -0
  66. package/dist/src/typing-keepalive.js +64 -0
  67. package/dist/src/update-checker.d.ts +34 -0
  68. package/dist/src/update-checker.js +160 -0
  69. package/dist/src/utils/audio-convert.d.ts +98 -0
  70. package/dist/src/utils/audio-convert.js +755 -0
  71. package/dist/src/utils/chunked-upload.d.ts +59 -0
  72. package/dist/src/utils/chunked-upload.js +289 -0
  73. package/dist/src/utils/file-utils.d.ts +61 -0
  74. package/dist/src/utils/file-utils.js +172 -0
  75. package/dist/src/utils/image-size.d.ts +51 -0
  76. package/dist/src/utils/image-size.js +234 -0
  77. package/dist/src/utils/media-send.d.ts +148 -0
  78. package/dist/src/utils/media-send.js +456 -0
  79. package/dist/src/utils/media-tags.d.ts +14 -0
  80. package/dist/src/utils/media-tags.js +164 -0
  81. package/dist/src/utils/payload.d.ts +112 -0
  82. package/dist/src/utils/payload.js +186 -0
  83. package/dist/src/utils/pkg-version.d.ts +5 -0
  84. package/dist/src/utils/pkg-version.js +51 -0
  85. package/dist/src/utils/platform.d.ts +137 -0
  86. package/dist/src/utils/platform.js +390 -0
  87. package/dist/src/utils/ssrf-guard.d.ts +25 -0
  88. package/dist/src/utils/ssrf-guard.js +91 -0
  89. package/dist/src/utils/text-parsing.d.ts +32 -0
  90. package/dist/src/utils/text-parsing.js +69 -0
  91. package/dist/src/utils/upload-cache.d.ts +34 -0
  92. package/dist/src/utils/upload-cache.js +93 -0
  93. package/index.ts +31 -0
  94. package/node_modules/@eshaz/web-worker/LICENSE +201 -0
  95. package/node_modules/@eshaz/web-worker/README.md +134 -0
  96. package/node_modules/@eshaz/web-worker/browser.js +17 -0
  97. package/node_modules/@eshaz/web-worker/cjs/browser.js +16 -0
  98. package/node_modules/@eshaz/web-worker/cjs/node.js +219 -0
  99. package/node_modules/@eshaz/web-worker/index.d.ts +4 -0
  100. package/node_modules/@eshaz/web-worker/node.js +223 -0
  101. package/node_modules/@eshaz/web-worker/package.json +54 -0
  102. package/node_modules/@wasm-audio-decoders/common/index.js +5 -0
  103. package/node_modules/@wasm-audio-decoders/common/package.json +36 -0
  104. package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderCommon.js +231 -0
  105. package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderWorker.js +129 -0
  106. package/node_modules/@wasm-audio-decoders/common/src/puff/README +67 -0
  107. package/node_modules/@wasm-audio-decoders/common/src/puff/build_puff.js +31 -0
  108. package/node_modules/@wasm-audio-decoders/common/src/puff/puff.c +863 -0
  109. package/node_modules/@wasm-audio-decoders/common/src/puff/puff.h +35 -0
  110. package/node_modules/@wasm-audio-decoders/common/src/utilities.js +3 -0
  111. package/node_modules/@wasm-audio-decoders/common/types.d.ts +7 -0
  112. package/node_modules/mpg123-decoder/README.md +265 -0
  113. package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js +185 -0
  114. package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js.map +1 -0
  115. package/node_modules/mpg123-decoder/index.js +8 -0
  116. package/node_modules/mpg123-decoder/package.json +58 -0
  117. package/node_modules/mpg123-decoder/src/EmscriptenWasm.js +464 -0
  118. package/node_modules/mpg123-decoder/src/MPEGDecoder.js +200 -0
  119. package/node_modules/mpg123-decoder/src/MPEGDecoderWebWorker.js +21 -0
  120. package/node_modules/mpg123-decoder/types.d.ts +30 -0
  121. package/node_modules/silk-wasm/LICENSE +21 -0
  122. package/node_modules/silk-wasm/README.md +85 -0
  123. package/node_modules/silk-wasm/lib/index.cjs +16 -0
  124. package/node_modules/silk-wasm/lib/index.d.ts +70 -0
  125. package/node_modules/silk-wasm/lib/index.mjs +16 -0
  126. package/node_modules/silk-wasm/lib/silk.wasm +0 -0
  127. package/node_modules/silk-wasm/lib/utils.d.ts +4 -0
  128. package/node_modules/silk-wasm/package.json +39 -0
  129. package/node_modules/simple-yenc/.github/FUNDING.yml +1 -0
  130. package/node_modules/simple-yenc/.prettierignore +1 -0
  131. package/node_modules/simple-yenc/LICENSE +7 -0
  132. package/node_modules/simple-yenc/README.md +163 -0
  133. package/node_modules/simple-yenc/dist/esm.js +1 -0
  134. package/node_modules/simple-yenc/dist/index.js +1 -0
  135. package/node_modules/simple-yenc/package.json +50 -0
  136. package/node_modules/simple-yenc/rollup.config.js +27 -0
  137. package/node_modules/simple-yenc/src/simple-yenc.js +302 -0
  138. package/node_modules/ws/LICENSE +20 -0
  139. package/node_modules/ws/README.md +548 -0
  140. package/node_modules/ws/browser.js +8 -0
  141. package/node_modules/ws/index.js +13 -0
  142. package/node_modules/ws/lib/buffer-util.js +131 -0
  143. package/node_modules/ws/lib/constants.js +19 -0
  144. package/node_modules/ws/lib/event-target.js +292 -0
  145. package/node_modules/ws/lib/extension.js +203 -0
  146. package/node_modules/ws/lib/limiter.js +55 -0
  147. package/node_modules/ws/lib/permessage-deflate.js +528 -0
  148. package/node_modules/ws/lib/receiver.js +706 -0
  149. package/node_modules/ws/lib/sender.js +602 -0
  150. package/node_modules/ws/lib/stream.js +161 -0
  151. package/node_modules/ws/lib/subprotocol.js +62 -0
  152. package/node_modules/ws/lib/validation.js +152 -0
  153. package/node_modules/ws/lib/websocket-server.js +554 -0
  154. package/node_modules/ws/lib/websocket.js +1393 -0
  155. package/node_modules/ws/package.json +69 -0
  156. package/node_modules/ws/wrapper.mjs +8 -0
  157. package/openclaw.plugin.json +17 -0
  158. package/package.json +67 -0
  159. package/preload.cjs +33 -0
  160. package/scripts/cleanup-legacy-plugins.sh +124 -0
  161. package/scripts/link-sdk-core.cjs +185 -0
  162. package/scripts/postinstall-link-sdk.js +113 -0
  163. package/scripts/proactive-api-server.ts +369 -0
  164. package/scripts/send-proactive.ts +293 -0
  165. package/scripts/set-markdown.sh +156 -0
  166. package/scripts/test-sendmedia.ts +116 -0
  167. package/scripts/upgrade-via-npm.ps1 +451 -0
  168. package/scripts/upgrade-via-npm.sh +528 -0
  169. package/scripts/upgrade-via-source.sh +916 -0
  170. package/skills/qqbot-channel/SKILL.md +263 -0
  171. package/skills/qqbot-channel/references/api_references.md +521 -0
  172. package/skills/qqbot-media/SKILL.md +60 -0
  173. package/skills/qqbot-remind/SKILL.md +149 -0
  174. package/src/admin-resolver.ts +181 -0
  175. package/src/api.ts +1138 -0
  176. package/src/channel.ts +477 -0
  177. package/src/config.ts +347 -0
  178. package/src/credential-backup.ts +72 -0
  179. package/src/deliver-debounce.ts +229 -0
  180. package/src/gateway.ts +2257 -0
  181. package/src/group-history.ts +328 -0
  182. package/src/image-server.ts +675 -0
  183. package/src/inbound-attachments.ts +321 -0
  184. package/src/known-users.ts +353 -0
  185. package/src/message-gating.ts +190 -0
  186. package/src/message-queue.ts +349 -0
  187. package/src/onboarding.ts +274 -0
  188. package/src/openclaw-plugin-sdk.d.ts +587 -0
  189. package/src/outbound-deliver.ts +473 -0
  190. package/src/outbound.ts +1119 -0
  191. package/src/proactive.ts +530 -0
  192. package/src/ref-index-store.ts +335 -0
  193. package/src/reply-dispatcher.ts +334 -0
  194. package/src/request-context.ts +39 -0
  195. package/src/runtime.ts +14 -0
  196. package/src/session-store.ts +303 -0
  197. package/src/slash-commands.ts +1615 -0
  198. package/src/startup-greeting.ts +120 -0
  199. package/src/streaming.ts +1102 -0
  200. package/src/stt.ts +86 -0
  201. package/src/tools/channel.ts +281 -0
  202. package/src/tools/remind.ts +300 -0
  203. package/src/types.ts +386 -0
  204. package/src/typing-keepalive.ts +59 -0
  205. package/src/update-checker.ts +174 -0
  206. package/src/utils/audio-convert.ts +859 -0
  207. package/src/utils/chunked-upload.ts +419 -0
  208. package/src/utils/file-utils.ts +193 -0
  209. package/src/utils/image-size.ts +266 -0
  210. package/src/utils/media-send.ts +585 -0
  211. package/src/utils/media-tags.ts +182 -0
  212. package/src/utils/payload.ts +265 -0
  213. package/src/utils/pkg-version.ts +54 -0
  214. package/src/utils/platform.ts +435 -0
  215. package/src/utils/ssrf-guard.ts +102 -0
  216. package/src/utils/text-parsing.ts +75 -0
  217. package/src/utils/upload-cache.ts +128 -0
  218. package/tsconfig.json +16 -0
@@ -0,0 +1,17 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
+ declare const plugin: {
3
+ id: string;
4
+ name: string;
5
+ description: string;
6
+ configSchema: unknown;
7
+ register(api: OpenClawPluginApi): void;
8
+ };
9
+ export default plugin;
10
+ export { qqbotPlugin } from "./src/channel.js";
11
+ export { setQQBotRuntime, getQQBotRuntime } from "./src/runtime.js";
12
+ export { qqbotOnboardingAdapter } from "./src/onboarding.js";
13
+ export * from "./src/types.js";
14
+ export * from "./src/api.js";
15
+ export * from "./src/config.js";
16
+ export * from "./src/gateway.js";
17
+ export * from "./src/outbound.js";
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
2
+ import { qqbotPlugin } from "./src/channel.js";
3
+ import { setQQBotRuntime } from "./src/runtime.js";
4
+ import { registerChannelTool } from "./src/tools/channel.js";
5
+ import { registerRemindTool } from "./src/tools/remind.js";
6
+ const plugin = {
7
+ id: "openclaw-qqbot",
8
+ name: "QQ Bot",
9
+ description: "QQ Bot channel plugin",
10
+ configSchema: emptyPluginConfigSchema(),
11
+ register(api) {
12
+ setQQBotRuntime(api.runtime);
13
+ api.registerChannel({ plugin: qqbotPlugin });
14
+ registerChannelTool(api);
15
+ registerRemindTool(api);
16
+ },
17
+ };
18
+ export default plugin;
19
+ export { qqbotPlugin } from "./src/channel.js";
20
+ export { setQQBotRuntime, getQQBotRuntime } from "./src/runtime.js";
21
+ export { qqbotOnboardingAdapter } from "./src/onboarding.js";
22
+ export * from "./src/types.js";
23
+ export * from "./src/api.js";
24
+ export * from "./src/config.js";
25
+ export * from "./src/gateway.js";
26
+ export * from "./src/outbound.js";
@@ -0,0 +1,33 @@
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
+ /**
17
+ * 读取 admin openid(按 accountId + appId 区分)
18
+ * 兼容策略:新路径优先 → fallback 旧路径 → 自动迁移
19
+ */
20
+ export declare function loadAdminOpenId(accountId: string, appId: string): string | undefined;
21
+ export declare function saveAdminOpenId(accountId: string, appId: string, openid: string): void;
22
+ export declare function loadUpgradeGreetingTargetOpenId(accountId: string, appId: string, log?: {
23
+ info: (msg: string) => void;
24
+ }): string | undefined;
25
+ export declare function clearUpgradeGreetingTargetOpenId(accountId: string, appId: string): void;
26
+ /**
27
+ * 解析管理员 openid:
28
+ * 1. 优先读持久化文件(按 accountId + appId 区分)
29
+ * 2. fallback 取第一个私聊用户,并写入文件锁定
30
+ */
31
+ export declare function resolveAdminOpenId(ctx: Pick<AdminResolverContext, "accountId" | "appId" | "log">): string | undefined;
32
+ /** 异步发送启动问候语(优先发给升级触发者,fallback 发给管理员) */
33
+ export declare function sendStartupGreetings(ctx: AdminResolverContext, trigger: "READY" | "RESUMED"): void;
@@ -0,0 +1,157 @@
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 safeName(id) {
15
+ return id.replace(/[^a-zA-Z0-9._-]/g, "_");
16
+ }
17
+ /** 新版 admin 文件路径(按 accountId + appId 区分) */
18
+ function getAdminMarkerFile(accountId, appId) {
19
+ return path.join(getQQBotDataDir("data"), `admin-${safeName(accountId)}-${safeName(appId)}.json`);
20
+ }
21
+ /** 旧版 admin 文件路径(仅按 accountId 区分,用于迁移兼容) */
22
+ function getLegacyAdminMarkerFile(accountId) {
23
+ return path.join(getQQBotDataDir("data"), `admin-${accountId}.json`);
24
+ }
25
+ function getUpgradeGreetingTargetFile(accountId, appId) {
26
+ return path.join(getQQBotDataDir("data"), `upgrade-greeting-target-${safeName(accountId)}-${safeName(appId)}.json`);
27
+ }
28
+ // ---- 管理员 openid 持久化 ----
29
+ /**
30
+ * 读取 admin openid(按 accountId + appId 区分)
31
+ * 兼容策略:新路径优先 → fallback 旧路径 → 自动迁移
32
+ */
33
+ export function loadAdminOpenId(accountId, appId) {
34
+ try {
35
+ // 1. 先尝试新版路径
36
+ const newFile = getAdminMarkerFile(accountId, appId);
37
+ if (fs.existsSync(newFile)) {
38
+ const data = JSON.parse(fs.readFileSync(newFile, "utf8"));
39
+ if (data.openid)
40
+ return data.openid;
41
+ }
42
+ // 2. fallback 旧版路径(仅按 accountId)
43
+ const legacyFile = getLegacyAdminMarkerFile(accountId);
44
+ if (fs.existsSync(legacyFile)) {
45
+ const data = JSON.parse(fs.readFileSync(legacyFile, "utf8"));
46
+ if (data.openid) {
47
+ // 自动迁移:写到新路径,删除旧文件
48
+ saveAdminOpenId(accountId, appId, data.openid);
49
+ try {
50
+ fs.unlinkSync(legacyFile);
51
+ }
52
+ catch { /* ignore */ }
53
+ return data.openid;
54
+ }
55
+ }
56
+ }
57
+ catch { /* 文件损坏视为无 */ }
58
+ return undefined;
59
+ }
60
+ export function saveAdminOpenId(accountId, appId, openid) {
61
+ try {
62
+ fs.writeFileSync(getAdminMarkerFile(accountId, appId), JSON.stringify({ accountId, appId, openid, savedAt: new Date().toISOString() }));
63
+ }
64
+ catch { /* ignore */ }
65
+ }
66
+ // ---- 升级问候目标 ----
67
+ export function loadUpgradeGreetingTargetOpenId(accountId, appId, log) {
68
+ try {
69
+ const file = getUpgradeGreetingTargetFile(accountId, appId);
70
+ if (fs.existsSync(file)) {
71
+ const data = JSON.parse(fs.readFileSync(file, "utf8"));
72
+ if (!data.openid) {
73
+ log?.info(`[qqbot:${accountId}] upgrade-greeting-target file found but openid is empty`);
74
+ return undefined;
75
+ }
76
+ if (data.appId && data.appId !== appId) {
77
+ log?.info(`[qqbot:${accountId}] upgrade-greeting-target appId mismatch: file=${data.appId}, current=${appId}`);
78
+ return undefined;
79
+ }
80
+ if (data.accountId && data.accountId !== accountId) {
81
+ log?.info(`[qqbot:${accountId}] upgrade-greeting-target accountId mismatch: file=${data.accountId}, current=${accountId}`);
82
+ return undefined;
83
+ }
84
+ log?.info(`[qqbot:${accountId}] upgrade-greeting-target loaded: openid=${data.openid}`);
85
+ return data.openid;
86
+ }
87
+ else {
88
+ log?.info(`[qqbot:${accountId}] upgrade-greeting-target file not found: ${file}`);
89
+ }
90
+ }
91
+ catch (err) {
92
+ log?.info(`[qqbot:${accountId}] upgrade-greeting-target file read error: ${err}`);
93
+ }
94
+ return undefined;
95
+ }
96
+ export function clearUpgradeGreetingTargetOpenId(accountId, appId) {
97
+ try {
98
+ const file = getUpgradeGreetingTargetFile(accountId, appId);
99
+ if (fs.existsSync(file)) {
100
+ fs.unlinkSync(file);
101
+ }
102
+ }
103
+ catch { /* ignore */ }
104
+ }
105
+ // ---- 解析管理员 ----
106
+ /**
107
+ * 解析管理员 openid:
108
+ * 1. 优先读持久化文件(按 accountId + appId 区分)
109
+ * 2. fallback 取第一个私聊用户,并写入文件锁定
110
+ */
111
+ export function resolveAdminOpenId(ctx) {
112
+ const saved = loadAdminOpenId(ctx.accountId, ctx.appId);
113
+ if (saved)
114
+ return saved;
115
+ const first = listKnownUsers({ accountId: ctx.accountId, type: "c2c", sortBy: "firstSeenAt", sortOrder: "asc", limit: 1 })[0]?.openid;
116
+ if (first) {
117
+ saveAdminOpenId(ctx.accountId, ctx.appId, first);
118
+ ctx.log?.info(`[qqbot:${ctx.accountId}] Auto-detected admin openid: ${first} (persisted)`);
119
+ }
120
+ return first;
121
+ }
122
+ // ---- 启动问候语 ----
123
+ /** 异步发送启动问候语(优先发给升级触发者,fallback 发给管理员) */
124
+ export function sendStartupGreetings(ctx, trigger) {
125
+ (async () => {
126
+ const plan = getStartupGreetingPlan(ctx.accountId, ctx.appId);
127
+ if (!plan.shouldSend || !plan.greeting) {
128
+ ctx.log?.info(`[qqbot:${ctx.accountId}] Skipping startup greeting (${plan.reason ?? "debounced"}, trigger=${trigger})`);
129
+ return;
130
+ }
131
+ const upgradeTargetOpenId = loadUpgradeGreetingTargetOpenId(ctx.accountId, ctx.appId, ctx.log);
132
+ // 没有 upgrade-greeting-target 文件 → 不是通过 /bot-upgrade 触发的升级
133
+ // (console 手动重启、脚本升级等场景),静默更新 marker 不发消息
134
+ if (!upgradeTargetOpenId) {
135
+ markStartupGreetingSent(ctx.accountId, ctx.appId, plan.version);
136
+ ctx.log?.info(`[qqbot:${ctx.accountId}] Version changed but no upgrade-greeting-target, silently updating marker (trigger=${trigger})`);
137
+ return;
138
+ }
139
+ try {
140
+ ctx.log?.info(`[qqbot:${ctx.accountId}] Sending startup greeting to upgrade-requester (trigger=${trigger}): "${plan.greeting}"`);
141
+ const token = await getAccessToken(ctx.appId, ctx.clientSecret);
142
+ const GREETING_TIMEOUT_MS = 10_000;
143
+ await Promise.race([
144
+ sendProactiveC2CMessage(token, upgradeTargetOpenId, plan.greeting),
145
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Startup greeting send timeout (10s)")), GREETING_TIMEOUT_MS)),
146
+ ]);
147
+ markStartupGreetingSent(ctx.accountId, ctx.appId, plan.version);
148
+ clearUpgradeGreetingTargetOpenId(ctx.accountId, ctx.appId);
149
+ ctx.log?.info(`[qqbot:${ctx.accountId}] Sent startup greeting to upgrade-requester: ${upgradeTargetOpenId}`);
150
+ }
151
+ catch (err) {
152
+ const message = err instanceof Error ? err.message : String(err);
153
+ markStartupGreetingFailed(ctx.accountId, ctx.appId, plan.version, message);
154
+ ctx.log?.error(`[qqbot:${ctx.accountId}] Failed to send startup greeting: ${message}`);
155
+ }
156
+ })();
157
+ }
@@ -0,0 +1,264 @@
1
+ /**
2
+ * QQ Bot API 鉴权和请求封装
3
+ * [修复版] 已重构为支持多实例并发,消除全局变量冲突
4
+ */
5
+ /** API 请求错误,携带 HTTP status code */
6
+ export declare class ApiError extends Error {
7
+ readonly status: number;
8
+ readonly path: string;
9
+ constructor(message: string, status: number, path: string);
10
+ }
11
+ export declare const PLUGIN_USER_AGENT: string;
12
+ /** 出站消息元信息(结构化存储,不做预格式化) */
13
+ export interface OutboundMeta {
14
+ /** 消息文本内容 */
15
+ text?: string;
16
+ /** 媒体类型 */
17
+ mediaType?: "image" | "voice" | "video" | "file";
18
+ /** 媒体来源:在线 URL */
19
+ mediaUrl?: string;
20
+ /** 媒体来源:本地文件路径或文件名 */
21
+ mediaLocalPath?: string;
22
+ /** TTS 原文本(仅 voice 类型有效,用于保存 TTS 前的文本内容) */
23
+ ttsText?: string;
24
+ }
25
+ type OnMessageSentCallback = (refIdx: string, meta: OutboundMeta) => void;
26
+ /**
27
+ * 注册出站消息回调
28
+ * 当消息发送成功且 QQ 返回 ref_idx 时,自动回调此函数
29
+ * 用于在最底层统一缓存 bot 出站消息的 refIdx
30
+ */
31
+ export declare function onMessageSent(callback: OnMessageSentCallback): void;
32
+ /**
33
+ * 初始化 API 配置
34
+ */
35
+ export declare function initApiConfig(options: {
36
+ markdownSupport?: boolean;
37
+ }): void;
38
+ /**
39
+ * 获取当前是否支持 markdown
40
+ */
41
+ export declare function isMarkdownSupport(): boolean;
42
+ /**
43
+ * 获取 AccessToken(带缓存 + singleflight 并发安全)
44
+ *
45
+ * 使用 singleflight 模式:当多个请求同时发现 Token 过期时,
46
+ * 只有第一个请求会真正去获取新 Token,其他请求复用同一个 Promise。
47
+ *
48
+ * 按 appId 隔离,支持多机器人并发请求。
49
+ */
50
+ export declare function getAccessToken(appId: string, clientSecret: string): Promise<string>;
51
+ /**
52
+ * 清除 Token 缓存
53
+ * @param appId 选填。如果有,只清空特定账号的缓存;如果没有,清空所有账号。
54
+ */
55
+ export declare function clearTokenCache(appId?: string): void;
56
+ /**
57
+ * 获取 Token 缓存状态(用于监控)
58
+ */
59
+ export declare function getTokenStatus(appId: string): {
60
+ status: "valid" | "expired" | "refreshing" | "none";
61
+ expiresAt: number | null;
62
+ };
63
+ /**
64
+ * 获取全局唯一的消息序号(范围 0 ~ 65535)
65
+ * 使用毫秒级时间戳低位 + 随机数异或混合,无状态,避免碰撞
66
+ */
67
+ export declare function getNextMsgSeq(_msgId: string): number;
68
+ /**
69
+ * API 请求封装
70
+ */
71
+ export declare function apiRequest<T = unknown>(accessToken: string, method: string, path: string, body?: unknown, timeoutMs?: number): Promise<T>;
72
+ export declare function getGatewayUrl(accessToken: string): Promise<string>;
73
+ /** 回应按钮交互(INTERACTION_CREATE),避免客户端按钮持续 loading */
74
+ export declare function acknowledgeInteraction(accessToken: string, interactionId: string, code?: 0 | 1 | 2 | 3 | 4 | 5, data?: Record<string, unknown>): Promise<void>;
75
+ /** 获取插件版本号(从 package.json 读取,和 PLUGIN_USER_AGENT 同源) */
76
+ export declare function getApiPluginVersion(): string;
77
+ export interface MessageResponse {
78
+ id: string;
79
+ timestamp: number | string;
80
+ /** 消息的引用索引信息(出站时由 QQ 服务端返回) */
81
+ ext_info?: {
82
+ ref_idx?: string;
83
+ };
84
+ }
85
+ export declare function sendC2CMessage(accessToken: string, openid: string, content: string, msgId?: string, messageReference?: string): Promise<MessageResponse>;
86
+ export declare function sendC2CInputNotify(accessToken: string, openid: string, msgId?: string, inputSecond?: number): Promise<{
87
+ refIdx?: string;
88
+ }>;
89
+ export declare function sendChannelMessage(accessToken: string, channelId: string, content: string, msgId?: string): Promise<{
90
+ id: string;
91
+ timestamp: string;
92
+ }>;
93
+ /**
94
+ * 发送频道私信消息
95
+ * @param guildId - 私信会话的 guild_id(由 DIRECT_MESSAGE_CREATE 事件提供)
96
+ * @param msgId - 被动回复时必填
97
+ */
98
+ export declare function sendDmMessage(accessToken: string, guildId: string, content: string, msgId?: string): Promise<{
99
+ id: string;
100
+ timestamp: string;
101
+ }>;
102
+ export declare function sendGroupMessage(accessToken: string, groupOpenid: string, content: string, msgId?: string, messageReference?: string): Promise<MessageResponse>;
103
+ export declare function sendProactiveC2CMessage(accessToken: string, openid: string, content: string): Promise<MessageResponse>;
104
+ export declare function sendProactiveGroupMessage(accessToken: string, groupOpenid: string, content: string): Promise<{
105
+ id: string;
106
+ timestamp: string;
107
+ }>;
108
+ export declare enum MediaFileType {
109
+ IMAGE = 1,
110
+ VIDEO = 2,
111
+ VOICE = 3,
112
+ FILE = 4
113
+ }
114
+ export interface UploadMediaResponse {
115
+ file_uuid: string;
116
+ file_info: string;
117
+ ttl: number;
118
+ id?: string;
119
+ }
120
+ /** 分片信息 */
121
+ export interface UploadPart {
122
+ /** 分片索引(从 1 开始) */
123
+ index: number;
124
+ /** 预签名上传链接 */
125
+ presigned_url: string;
126
+ }
127
+ /** 申请上传响应 */
128
+ export interface UploadPrepareResponse {
129
+ /** 上传任务 ID */
130
+ upload_id: string;
131
+ /** 分块大小(字节) */
132
+ block_size: number;
133
+ /** 分片列表(含预签名链接) */
134
+ parts: UploadPart[];
135
+ }
136
+ /** 完成文件上传响应(与 UploadMediaResponse 一致) */
137
+ export interface MediaUploadResponse {
138
+ /** 文件 UUID */
139
+ file_uuid: string;
140
+ /** 文件信息(用于发送消息),是 InnerUploadRsp 的序列化 */
141
+ file_info: string;
142
+ /** 文件信息过期时长(秒) */
143
+ ttl: number;
144
+ }
145
+ /** 申请上传时的文件哈希信息 */
146
+ export interface UploadPrepareHashes {
147
+ /** 整个文件的 MD5(十六进制) */
148
+ md5: string;
149
+ /** 整个文件的 SHA1(十六进制) */
150
+ sha1: string;
151
+ /** 文件前 10002432 Bytes 的 MD5(十六进制);文件不足该大小时为整文件 MD5 */
152
+ md5_10m: string;
153
+ }
154
+ /**
155
+ * 申请上传(C2C)
156
+ * POST /v2/users/{user_id}/upload_prepare
157
+ *
158
+ * @param accessToken - 访问令牌
159
+ * @param userId - 用户 openid
160
+ * @param fileType - 业务类型(1=图片, 2=视频, 3=语音, 4=文件)
161
+ * @param fileName - 文件名
162
+ * @param fileSize - 文件大小(字节)
163
+ * @param hashes - 文件哈希信息(md5, sha1, md5_10m)
164
+ * @returns 上传任务 ID、分块大小、分片预签名链接列表
165
+ */
166
+ export declare function c2cUploadPrepare(accessToken: string, userId: string, fileType: MediaFileType, fileName: string, fileSize: number, hashes: UploadPrepareHashes): Promise<UploadPrepareResponse>;
167
+ /**
168
+ * 完成分片上传(C2C)
169
+ * POST /v2/users/{user_id}/upload_part_finish
170
+ *
171
+ * @param accessToken - 访问令牌
172
+ * @param userId - 用户 openid
173
+ * @param uploadId - 上传任务 ID
174
+ * @param partIndex - 分片索引(从 1 开始)
175
+ * @param blockSize - 分块大小(字节)
176
+ * @param md5 - 分片数据的 MD5(十六进制)
177
+ */
178
+ export declare function c2cUploadPartFinish(accessToken: string, userId: string, uploadId: string, partIndex: number, blockSize: number, md5: string): Promise<void>;
179
+ /**
180
+ * 完成文件上传(C2C)
181
+ * POST /v2/users/{user_id}/files
182
+ *
183
+ * @param accessToken - 访问令牌
184
+ * @param userId - 用户 openid
185
+ * @param uploadId - 上传任务 ID
186
+ * @returns 文件信息(file_uuid, file_info, ttl)
187
+ */
188
+ export declare function c2cCompleteUpload(accessToken: string, userId: string, uploadId: string): Promise<MediaUploadResponse>;
189
+ /**
190
+ * 申请上传(Group)
191
+ * POST /v2/groups/{group_id}/upload_prepare
192
+ */
193
+ export declare function groupUploadPrepare(accessToken: string, groupId: string, fileType: MediaFileType, fileName: string, fileSize: number, hashes: UploadPrepareHashes): Promise<UploadPrepareResponse>;
194
+ /**
195
+ * 完成分片上传(Group)
196
+ * POST /v2/groups/{group_id}/upload_part_finish
197
+ */
198
+ export declare function groupUploadPartFinish(accessToken: string, groupId: string, uploadId: string, partIndex: number, blockSize: number, md5: string): Promise<void>;
199
+ /**
200
+ * 完成文件上传(Group)
201
+ * POST /v2/groups/{group_id}/files
202
+ */
203
+ export declare function groupCompleteUpload(accessToken: string, groupId: string, uploadId: string): Promise<MediaUploadResponse>;
204
+ export declare function uploadC2CMedia(accessToken: string, openid: string, fileType: MediaFileType, url?: string, fileData?: string, srvSendMsg?: boolean, fileName?: string): Promise<UploadMediaResponse>;
205
+ export declare function uploadGroupMedia(accessToken: string, groupOpenid: string, fileType: MediaFileType, url?: string, fileData?: string, srvSendMsg?: boolean, fileName?: string): Promise<UploadMediaResponse>;
206
+ export declare function sendC2CMediaMessage(accessToken: string, openid: string, fileInfo: string, msgId?: string, content?: string, meta?: OutboundMeta): Promise<MessageResponse>;
207
+ export declare function sendGroupMediaMessage(accessToken: string, groupOpenid: string, fileInfo: string, msgId?: string, content?: string): Promise<{
208
+ id: string;
209
+ timestamp: string;
210
+ }>;
211
+ export declare function sendC2CImageMessage(accessToken: string, openid: string, imageUrl: string, msgId?: string, content?: string, localPath?: string): Promise<MessageResponse>;
212
+ export declare function sendGroupImageMessage(accessToken: string, groupOpenid: string, imageUrl: string, msgId?: string, content?: string): Promise<{
213
+ id: string;
214
+ timestamp: string;
215
+ }>;
216
+ export declare function sendC2CVoiceMessage(accessToken: string, openid: string, voiceBase64?: string, voiceUrl?: string, msgId?: string, ttsText?: string, filePath?: string): Promise<MessageResponse>;
217
+ export declare function sendGroupVoiceMessage(accessToken: string, groupOpenid: string, voiceBase64?: string, voiceUrl?: string, msgId?: string): Promise<{
218
+ id: string;
219
+ timestamp: string;
220
+ }>;
221
+ export declare function sendC2CFileMessage(accessToken: string, openid: string, fileBase64?: string, fileUrl?: string, msgId?: string, fileName?: string, localFilePath?: string): Promise<MessageResponse>;
222
+ export declare function sendGroupFileMessage(accessToken: string, groupOpenid: string, fileBase64?: string, fileUrl?: string, msgId?: string, fileName?: string): Promise<{
223
+ id: string;
224
+ timestamp: string;
225
+ }>;
226
+ export declare function sendC2CVideoMessage(accessToken: string, openid: string, videoUrl?: string, videoBase64?: string, msgId?: string, content?: string, localPath?: string): Promise<MessageResponse>;
227
+ export declare function sendGroupVideoMessage(accessToken: string, groupOpenid: string, videoUrl?: string, videoBase64?: string, msgId?: string, content?: string): Promise<{
228
+ id: string;
229
+ timestamp: string;
230
+ }>;
231
+ interface BackgroundTokenRefreshOptions {
232
+ refreshAheadMs?: number;
233
+ randomOffsetMs?: number;
234
+ minRefreshIntervalMs?: number;
235
+ retryDelayMs?: number;
236
+ log?: {
237
+ info: (msg: string) => void;
238
+ error: (msg: string) => void;
239
+ debug?: (msg: string) => void;
240
+ };
241
+ }
242
+ export declare function startBackgroundTokenRefresh(appId: string, clientSecret: string, options?: BackgroundTokenRefreshOptions): void;
243
+ /**
244
+ * 停止后台 Token 刷新
245
+ * @param appId 选填。如果有,仅停止该账号的定时刷新。
246
+ */
247
+ export declare function stopBackgroundTokenRefresh(appId?: string): void;
248
+ export declare function isBackgroundTokenRefreshRunning(appId?: string): boolean;
249
+ import type { StreamMessageRequest, StreamMessageResponse } from "./types.js";
250
+ /**
251
+ * 发送流式消息(C2C 私聊)
252
+ *
253
+ * 流式协议:
254
+ * - 首次调用时不传 stream_msg_id,由平台返回
255
+ * - 后续分片携带 stream_msg_id 和递增 msg_seq
256
+ * - input_state="1" 表示生成中,"10" 表示生成结束(终结状态)
257
+ *
258
+ * @param accessToken - access_token
259
+ * @param openid - 用户 openid
260
+ * @param req - 流式消息请求体
261
+ * @returns 流式消息响应
262
+ */
263
+ export declare function sendC2CStreamMessage(accessToken: string, openid: string, req: StreamMessageRequest): Promise<StreamMessageResponse>;
264
+ export {};