@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,234 @@
1
+ /**
2
+ * 图片尺寸工具
3
+ * 用于获取图片尺寸,生成 QQBot 的 markdown 图片格式
4
+ *
5
+ * QQBot markdown 图片格式: ![#宽px #高px](url)
6
+ */
7
+ import { Buffer } from "buffer";
8
+ /** 默认图片尺寸(当无法获取时使用) */
9
+ export const DEFAULT_IMAGE_SIZE = { width: 512, height: 512 };
10
+ /**
11
+ * 从 PNG 文件头解析图片尺寸
12
+ * PNG 文件头结构: 前 8 字节是签名,IHDR 块从第 8 字节开始
13
+ * IHDR 块: 长度(4) + 类型(4, "IHDR") + 宽度(4) + 高度(4) + ...
14
+ */
15
+ function parsePngSize(buffer) {
16
+ // PNG 签名: 89 50 4E 47 0D 0A 1A 0A
17
+ if (buffer.length < 24)
18
+ return null;
19
+ if (buffer[0] !== 0x89 || buffer[1] !== 0x50 || buffer[2] !== 0x4E || buffer[3] !== 0x47) {
20
+ return null;
21
+ }
22
+ // IHDR 块从第 8 字节开始,宽度在第 16-19 字节,高度在第 20-23 字节
23
+ const width = buffer.readUInt32BE(16);
24
+ const height = buffer.readUInt32BE(20);
25
+ return { width, height };
26
+ }
27
+ /**
28
+ * 从 JPEG 文件解析图片尺寸
29
+ * JPEG 尺寸在 SOF0/SOF2 块中
30
+ */
31
+ function parseJpegSize(buffer) {
32
+ // JPEG 签名: FF D8 FF
33
+ if (buffer.length < 4)
34
+ return null;
35
+ if (buffer[0] !== 0xFF || buffer[1] !== 0xD8) {
36
+ return null;
37
+ }
38
+ let offset = 2;
39
+ while (offset < buffer.length - 9) {
40
+ if (buffer[offset] !== 0xFF) {
41
+ offset++;
42
+ continue;
43
+ }
44
+ const marker = buffer[offset + 1];
45
+ // SOF0 (0xC0) 或 SOF2 (0xC2) 包含图片尺寸
46
+ if (marker === 0xC0 || marker === 0xC2) {
47
+ // 格式: FF C0 长度(2) 精度(1) 高度(2) 宽度(2)
48
+ if (offset + 9 <= buffer.length) {
49
+ const height = buffer.readUInt16BE(offset + 5);
50
+ const width = buffer.readUInt16BE(offset + 7);
51
+ return { width, height };
52
+ }
53
+ }
54
+ // 跳过当前块
55
+ if (offset + 3 < buffer.length) {
56
+ const blockLength = buffer.readUInt16BE(offset + 2);
57
+ offset += 2 + blockLength;
58
+ }
59
+ else {
60
+ break;
61
+ }
62
+ }
63
+ return null;
64
+ }
65
+ /**
66
+ * 从 GIF 文件头解析图片尺寸
67
+ * GIF 文件头: GIF87a 或 GIF89a (6字节) + 宽度(2) + 高度(2)
68
+ */
69
+ function parseGifSize(buffer) {
70
+ if (buffer.length < 10)
71
+ return null;
72
+ const signature = buffer.toString("ascii", 0, 6);
73
+ if (signature !== "GIF87a" && signature !== "GIF89a") {
74
+ return null;
75
+ }
76
+ const width = buffer.readUInt16LE(6);
77
+ const height = buffer.readUInt16LE(8);
78
+ return { width, height };
79
+ }
80
+ /**
81
+ * 从 WebP 文件解析图片尺寸
82
+ * WebP 文件头: RIFF(4) + 文件大小(4) + WEBP(4) + VP8/VP8L/VP8X(4) + ...
83
+ */
84
+ function parseWebpSize(buffer) {
85
+ if (buffer.length < 30)
86
+ return null;
87
+ // 检查 RIFF 和 WEBP 签名
88
+ const riff = buffer.toString("ascii", 0, 4);
89
+ const webp = buffer.toString("ascii", 8, 12);
90
+ if (riff !== "RIFF" || webp !== "WEBP") {
91
+ return null;
92
+ }
93
+ const chunkType = buffer.toString("ascii", 12, 16);
94
+ // VP8 (有损压缩)
95
+ if (chunkType === "VP8 ") {
96
+ // VP8 帧头从第 23 字节开始,检查签名 9D 01 2A
97
+ if (buffer.length >= 30 && buffer[23] === 0x9D && buffer[24] === 0x01 && buffer[25] === 0x2A) {
98
+ const width = buffer.readUInt16LE(26) & 0x3FFF;
99
+ const height = buffer.readUInt16LE(28) & 0x3FFF;
100
+ return { width, height };
101
+ }
102
+ }
103
+ // VP8L (无损压缩)
104
+ if (chunkType === "VP8L") {
105
+ // VP8L 签名: 0x2F
106
+ if (buffer.length >= 25 && buffer[20] === 0x2F) {
107
+ const bits = buffer.readUInt32LE(21);
108
+ const width = (bits & 0x3FFF) + 1;
109
+ const height = ((bits >> 14) & 0x3FFF) + 1;
110
+ return { width, height };
111
+ }
112
+ }
113
+ // VP8X (扩展格式)
114
+ if (chunkType === "VP8X") {
115
+ if (buffer.length >= 30) {
116
+ // 宽度和高度在第 24-26 和 27-29 字节(24位小端)
117
+ const width = (buffer[24] | (buffer[25] << 8) | (buffer[26] << 16)) + 1;
118
+ const height = (buffer[27] | (buffer[28] << 8) | (buffer[29] << 16)) + 1;
119
+ return { width, height };
120
+ }
121
+ }
122
+ return null;
123
+ }
124
+ /**
125
+ * 从图片数据 Buffer 解析尺寸
126
+ */
127
+ export function parseImageSize(buffer) {
128
+ // 尝试各种格式
129
+ return parsePngSize(buffer)
130
+ ?? parseJpegSize(buffer)
131
+ ?? parseGifSize(buffer)
132
+ ?? parseWebpSize(buffer);
133
+ }
134
+ /**
135
+ * 从公网 URL 获取图片尺寸
136
+ * 只下载前 64KB 数据,足够解析大部分图片格式的头部
137
+ */
138
+ export async function getImageSizeFromUrl(url, timeoutMs = 5000) {
139
+ try {
140
+ const controller = new AbortController();
141
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
142
+ // 使用 Range 请求只获取前 64KB
143
+ const response = await fetch(url, {
144
+ signal: controller.signal,
145
+ headers: {
146
+ "Range": "bytes=0-65535",
147
+ "User-Agent": "QQBot-Image-Size-Detector/1.0",
148
+ },
149
+ });
150
+ clearTimeout(timeoutId);
151
+ if (!response.ok && response.status !== 206) {
152
+ console.log(`[image-size] Failed to fetch ${url}: ${response.status}`);
153
+ return null;
154
+ }
155
+ const arrayBuffer = await response.arrayBuffer();
156
+ const buffer = Buffer.from(arrayBuffer);
157
+ const size = parseImageSize(buffer);
158
+ if (size) {
159
+ console.log(`[image-size] Got size from URL: ${size.width}x${size.height} - ${url.slice(0, 60)}...`);
160
+ }
161
+ return size;
162
+ }
163
+ catch (err) {
164
+ console.log(`[image-size] Error fetching ${url.slice(0, 60)}...: ${err}`);
165
+ return null;
166
+ }
167
+ }
168
+ /**
169
+ * 从 Base64 Data URL 获取图片尺寸
170
+ */
171
+ export function getImageSizeFromDataUrl(dataUrl) {
172
+ try {
173
+ // 格式: data:image/png;base64,xxxxx
174
+ const matches = dataUrl.match(/^data:image\/[^;]+;base64,(.+)$/);
175
+ if (!matches) {
176
+ return null;
177
+ }
178
+ const base64Data = matches[1];
179
+ const buffer = Buffer.from(base64Data, "base64");
180
+ const size = parseImageSize(buffer);
181
+ if (size) {
182
+ console.log(`[image-size] Got size from Base64: ${size.width}x${size.height}`);
183
+ }
184
+ return size;
185
+ }
186
+ catch (err) {
187
+ console.log(`[image-size] Error parsing Base64: ${err}`);
188
+ return null;
189
+ }
190
+ }
191
+ /**
192
+ * 获取图片尺寸(自动判断来源)
193
+ * @param source - 图片 URL 或 Base64 Data URL
194
+ * @returns 图片尺寸,失败返回 null
195
+ */
196
+ export async function getImageSize(source) {
197
+ if (source.startsWith("data:")) {
198
+ return getImageSizeFromDataUrl(source);
199
+ }
200
+ if (source.startsWith("http://") || source.startsWith("https://")) {
201
+ return getImageSizeFromUrl(source);
202
+ }
203
+ return null;
204
+ }
205
+ /**
206
+ * 生成 QQBot markdown 图片格式
207
+ * 格式: ![#宽px #高px](url)
208
+ *
209
+ * @param url - 图片 URL
210
+ * @param size - 图片尺寸,如果为 null 则使用默认尺寸
211
+ * @returns QQBot markdown 图片字符串
212
+ */
213
+ export function formatQQBotMarkdownImage(url, size) {
214
+ const { width, height } = size ?? DEFAULT_IMAGE_SIZE;
215
+ return `![#${width}px #${height}px](${url})`;
216
+ }
217
+ /**
218
+ * 检查 markdown 图片是否已经包含 QQBot 格式的尺寸信息
219
+ * 格式: ![#宽px #高px](url)
220
+ */
221
+ export function hasQQBotImageSize(markdownImage) {
222
+ return /!\[#\d+px\s+#\d+px\]/.test(markdownImage);
223
+ }
224
+ /**
225
+ * 从已有的 QQBot 格式 markdown 图片中提取尺寸
226
+ * 格式: ![#宽px #高px](url)
227
+ */
228
+ export function extractQQBotImageSize(markdownImage) {
229
+ const match = markdownImage.match(/!\[#(\d+)px\s+#(\d+)px\]/);
230
+ if (match) {
231
+ return { width: parseInt(match[1], 10), height: parseInt(match[2], 10) };
232
+ }
233
+ return null;
234
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * 富媒体标签解析与发送队列
3
+ *
4
+ * 提供媒体标签(qqimg / qqvoice / qqvideo / qqfile / qqmedia)的检测、
5
+ * 拆分、路径编码修复,以及统一的发送队列执行器。
6
+ */
7
+ import { type MediaTargetContext } from "../outbound.js";
8
+ import type { ResolvedQQBotAccount } from "../types.js";
9
+ /** 发送队列项 */
10
+ export interface SendQueueItem {
11
+ type: "text" | "image" | "voice" | "video" | "file" | "media";
12
+ content: string;
13
+ }
14
+ /** 统一的媒体标签正则 — 匹配标准化后的 6 种标签 */
15
+ export declare const MEDIA_TAG_REGEX: RegExp;
16
+ /** 创建一个新的全局标签正则实例(每次调用 reset lastIndex) */
17
+ export declare function createMediaTagRegex(): RegExp;
18
+ /** 媒体发送上下文(统一的,供流式和普通模式共用) */
19
+ export interface MediaSendContext {
20
+ /** 媒体目标上下文(用于 sendPhoto/sendVoice 等) */
21
+ mediaTarget: MediaTargetContext;
22
+ /** qualifiedTarget(格式 "qqbot:c2c:xxx" 或 "qqbot:group:xxx",用于 sendMediaAuto) */
23
+ qualifiedTarget: string;
24
+ /** 账户配置 */
25
+ account: ResolvedQQBotAccount;
26
+ /** 事件消息 ID(用于被动回复) */
27
+ replyToId?: string;
28
+ /** 日志 */
29
+ log?: {
30
+ info: (msg: string) => void;
31
+ error: (msg: string) => void;
32
+ debug?: (msg: string) => void;
33
+ };
34
+ }
35
+ /**
36
+ * 修复路径编码问题(双反斜杠、八进制转义、UTF-8 双重编码)
37
+ *
38
+ * 这是由于 LLM 输出路径时可能引入的编码问题:
39
+ * - Markdown 转义导致双反斜杠
40
+ * - 八进制转义序列(来自某些 shell 工具的输出)
41
+ * - UTF-8 双重编码(中文路径经过多层处理后的乱码)
42
+ *
43
+ * 此方法在 gateway.ts deliver 回调、outbound.ts sendText、
44
+ * streaming.ts sendMediaQueue 中共用。
45
+ */
46
+ export declare function fixPathEncoding(mediaPath: string, log?: {
47
+ debug?: (msg: string) => void;
48
+ error?: (msg: string) => void;
49
+ }): string;
50
+ /**
51
+ * 检测文本是否包含富媒体标签
52
+ */
53
+ export declare function hasMediaTags(text: string): boolean;
54
+ /** findFirstClosedMediaTag 的返回值 */
55
+ export interface FirstClosedMediaTag {
56
+ /** 标签前的纯文本(已 trim) */
57
+ textBefore: string;
58
+ /** 标签类型(小写,如 "qqvoice") */
59
+ tagName: string;
60
+ /** 标签内的媒体路径(已 trim、去 MEDIA: 前缀、修复编码) */
61
+ mediaPath: string;
62
+ /** 标签在输入文本中的结束索引(紧接标签后的第一个字符位置) */
63
+ tagEndIndex: number;
64
+ /** 映射后的发送队列项类型 */
65
+ itemType: SendQueueItem["type"];
66
+ }
67
+ /**
68
+ * 在文本中查找**第一个**完整闭合的媒体标签
69
+ *
70
+ * 与 splitByMediaTags 不同,此函数只匹配一个标签就停止,
71
+ * 用于流式场景的"循环消费"模式:每次处理一个标签,更新偏移,再找下一个。
72
+ *
73
+ * @param text 待检查的文本(应已 normalize 过)
74
+ * @returns 第一个闭合标签的信息,没有则返回 null
75
+ */
76
+ export declare function findFirstClosedMediaTag(text: string, log?: {
77
+ info?: (msg: string) => void;
78
+ debug?: (msg: string) => void;
79
+ error?: (msg: string) => void;
80
+ }): FirstClosedMediaTag | null;
81
+ /**
82
+ * 媒体标签拆分结果
83
+ */
84
+ export interface MediaSplitResult {
85
+ /** 是否包含媒体标签 */
86
+ hasMediaTags: boolean;
87
+ /** 媒体标签前的纯文本 */
88
+ textBeforeFirstTag: string;
89
+ /** 媒体标签后的剩余文本 */
90
+ textAfterLastTag: string;
91
+ /** 完整的发送队列(标签间的文本 + 媒体项) */
92
+ mediaQueue: SendQueueItem[];
93
+ }
94
+ /**
95
+ * 将文本按富媒体标签拆分为三部分
96
+ *
97
+ * 用于两个场景:
98
+ * 1. 流式模式:中断-恢复流程(标签前文本 → 结束流式 → 发送媒体 → 新流式 → 标签后文本)
99
+ * 2. 普通模式:构建按顺序发送的队列
100
+ */
101
+ export declare function splitByMediaTags(text: string, log?: {
102
+ info?: (msg: string) => void;
103
+ debug?: (msg: string) => void;
104
+ error?: (msg: string) => void;
105
+ }): MediaSplitResult;
106
+ /**
107
+ * 从文本中解析出完整的发送队列(含标签前后的纯文本)
108
+ *
109
+ * 与 splitByMediaTags 的区别:
110
+ * - splitByMediaTags 分为 before / queue / after 三段(供流式模式的中断-恢复)
111
+ * - parseMediaTagsToSendQueue 返回一个扁平的完整队列(供普通模式按顺序发送)
112
+ *
113
+ * 适用于 gateway.ts deliver 回调和 outbound.ts sendText。
114
+ */
115
+ export declare function parseMediaTagsToSendQueue(text: string, log?: {
116
+ info?: (msg: string) => void;
117
+ debug?: (msg: string) => void;
118
+ error?: (msg: string) => void;
119
+ }): {
120
+ hasMediaTags: boolean;
121
+ sendQueue: SendQueueItem[];
122
+ };
123
+ /**
124
+ * 统一执行发送队列
125
+ *
126
+ * 遍历 sendQueue,按类型调用对应的发送函数。
127
+ * 文本项通过 onSendText 回调处理(不同场景的文本发送方式不同)。
128
+ * 媒体发送失败时,通过 onSendText 发送兜底文本通知用户。
129
+ */
130
+ export declare function executeSendQueue(queue: SendQueueItem[], ctx: MediaSendContext, options?: {
131
+ /** 文本发送回调(每种场景的文本发送方式不同) */
132
+ onSendText?: (text: string) => Promise<void>;
133
+ /** 是否跳过 inter-tag 文本(流式模式下通常跳过,由新流式会话处理) */
134
+ skipInterTagText?: boolean;
135
+ }): Promise<void>;
136
+ /**
137
+ * 从文本中剥离所有媒体标签(用于最终显示)
138
+ */
139
+ export declare function stripMediaTags(text: string): string;
140
+ /**
141
+ * 检测文本中是否有未闭合的媒体标签,如果有则截断到安全位置。
142
+ *
143
+ * 流式输出中 LLM 逐 token 吐出媒体标签,中间态不应直接发给用户。
144
+ * 只检查最后一行,从右到左扫描 `<`,找到第一个有意义的媒体标签片段并判断是否完整。
145
+ *
146
+ * 核心原则:截断只能截到**开标签**前面;闭合标签前缀若找不到对应开标签则原样返回。
147
+ */
148
+ export declare function stripIncompleteMediaTag(text: string): [safeText: string, hasIncomplete: boolean];