@tencent-connect/openclaw-qqbot 1.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +393 -0
  3. package/README.zh.md +390 -0
  4. package/bin/qqbot-cli.js +243 -0
  5. package/clawdbot.plugin.json +16 -0
  6. package/dist/index.d.ts +17 -0
  7. package/dist/index.js +22 -0
  8. package/dist/src/api.d.ts +138 -0
  9. package/dist/src/api.js +523 -0
  10. package/dist/src/channel.d.ts +3 -0
  11. package/dist/src/channel.js +337 -0
  12. package/dist/src/config.d.ts +25 -0
  13. package/dist/src/config.js +156 -0
  14. package/dist/src/gateway.d.ts +18 -0
  15. package/dist/src/gateway.js +2315 -0
  16. package/dist/src/image-server.d.ts +62 -0
  17. package/dist/src/image-server.js +401 -0
  18. package/dist/src/known-users.d.ts +100 -0
  19. package/dist/src/known-users.js +263 -0
  20. package/dist/src/onboarding.d.ts +10 -0
  21. package/dist/src/onboarding.js +203 -0
  22. package/dist/src/outbound.d.ts +150 -0
  23. package/dist/src/outbound.js +1175 -0
  24. package/dist/src/proactive.d.ts +170 -0
  25. package/dist/src/proactive.js +399 -0
  26. package/dist/src/runtime.d.ts +3 -0
  27. package/dist/src/runtime.js +10 -0
  28. package/dist/src/session-store.d.ts +52 -0
  29. package/dist/src/session-store.js +254 -0
  30. package/dist/src/types.d.ts +145 -0
  31. package/dist/src/types.js +1 -0
  32. package/dist/src/utils/audio-convert.d.ts +73 -0
  33. package/dist/src/utils/audio-convert.js +645 -0
  34. package/dist/src/utils/file-utils.d.ts +46 -0
  35. package/dist/src/utils/file-utils.js +107 -0
  36. package/dist/src/utils/image-size.d.ts +51 -0
  37. package/dist/src/utils/image-size.js +234 -0
  38. package/dist/src/utils/media-tags.d.ts +14 -0
  39. package/dist/src/utils/media-tags.js +120 -0
  40. package/dist/src/utils/payload.d.ts +112 -0
  41. package/dist/src/utils/payload.js +186 -0
  42. package/dist/src/utils/platform.d.ts +126 -0
  43. package/dist/src/utils/platform.js +358 -0
  44. package/dist/src/utils/upload-cache.d.ts +34 -0
  45. package/dist/src/utils/upload-cache.js +93 -0
  46. package/index.ts +27 -0
  47. package/moltbot.plugin.json +16 -0
  48. package/node_modules/@eshaz/web-worker/LICENSE +201 -0
  49. package/node_modules/@eshaz/web-worker/README.md +134 -0
  50. package/node_modules/@eshaz/web-worker/browser.js +17 -0
  51. package/node_modules/@eshaz/web-worker/cjs/browser.js +16 -0
  52. package/node_modules/@eshaz/web-worker/cjs/node.js +219 -0
  53. package/node_modules/@eshaz/web-worker/index.d.ts +4 -0
  54. package/node_modules/@eshaz/web-worker/node.js +223 -0
  55. package/node_modules/@eshaz/web-worker/package.json +54 -0
  56. package/node_modules/@wasm-audio-decoders/common/index.js +5 -0
  57. package/node_modules/@wasm-audio-decoders/common/package.json +36 -0
  58. package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderCommon.js +231 -0
  59. package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderWorker.js +129 -0
  60. package/node_modules/@wasm-audio-decoders/common/src/puff/README +67 -0
  61. package/node_modules/@wasm-audio-decoders/common/src/puff/build_puff.js +31 -0
  62. package/node_modules/@wasm-audio-decoders/common/src/puff/puff.c +863 -0
  63. package/node_modules/@wasm-audio-decoders/common/src/puff/puff.h +35 -0
  64. package/node_modules/@wasm-audio-decoders/common/src/utilities.js +3 -0
  65. package/node_modules/@wasm-audio-decoders/common/types.d.ts +7 -0
  66. package/node_modules/mpg123-decoder/README.md +265 -0
  67. package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js +185 -0
  68. package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js.map +1 -0
  69. package/node_modules/mpg123-decoder/index.js +8 -0
  70. package/node_modules/mpg123-decoder/package.json +58 -0
  71. package/node_modules/mpg123-decoder/src/EmscriptenWasm.js +464 -0
  72. package/node_modules/mpg123-decoder/src/MPEGDecoder.js +200 -0
  73. package/node_modules/mpg123-decoder/src/MPEGDecoderWebWorker.js +21 -0
  74. package/node_modules/mpg123-decoder/types.d.ts +30 -0
  75. package/node_modules/silk-wasm/LICENSE +21 -0
  76. package/node_modules/silk-wasm/README.md +85 -0
  77. package/node_modules/silk-wasm/lib/index.cjs +16 -0
  78. package/node_modules/silk-wasm/lib/index.d.ts +70 -0
  79. package/node_modules/silk-wasm/lib/index.mjs +16 -0
  80. package/node_modules/silk-wasm/lib/silk.wasm +0 -0
  81. package/node_modules/silk-wasm/lib/utils.d.ts +4 -0
  82. package/node_modules/silk-wasm/package.json +39 -0
  83. package/node_modules/simple-yenc/.github/FUNDING.yml +1 -0
  84. package/node_modules/simple-yenc/.prettierignore +1 -0
  85. package/node_modules/simple-yenc/LICENSE +7 -0
  86. package/node_modules/simple-yenc/README.md +163 -0
  87. package/node_modules/simple-yenc/dist/esm.js +1 -0
  88. package/node_modules/simple-yenc/dist/index.js +1 -0
  89. package/node_modules/simple-yenc/package.json +50 -0
  90. package/node_modules/simple-yenc/rollup.config.js +27 -0
  91. package/node_modules/simple-yenc/src/simple-yenc.js +302 -0
  92. package/node_modules/ws/LICENSE +20 -0
  93. package/node_modules/ws/README.md +548 -0
  94. package/node_modules/ws/browser.js +8 -0
  95. package/node_modules/ws/index.js +13 -0
  96. package/node_modules/ws/lib/buffer-util.js +131 -0
  97. package/node_modules/ws/lib/constants.js +19 -0
  98. package/node_modules/ws/lib/event-target.js +292 -0
  99. package/node_modules/ws/lib/extension.js +203 -0
  100. package/node_modules/ws/lib/limiter.js +55 -0
  101. package/node_modules/ws/lib/permessage-deflate.js +528 -0
  102. package/node_modules/ws/lib/receiver.js +706 -0
  103. package/node_modules/ws/lib/sender.js +602 -0
  104. package/node_modules/ws/lib/stream.js +161 -0
  105. package/node_modules/ws/lib/subprotocol.js +62 -0
  106. package/node_modules/ws/lib/validation.js +152 -0
  107. package/node_modules/ws/lib/websocket-server.js +554 -0
  108. package/node_modules/ws/lib/websocket.js +1393 -0
  109. package/node_modules/ws/package.json +69 -0
  110. package/node_modules/ws/wrapper.mjs +8 -0
  111. package/openclaw.plugin.json +16 -0
  112. package/package.json +76 -0
  113. package/scripts/proactive-api-server.ts +356 -0
  114. package/scripts/pull-latest.sh +316 -0
  115. package/scripts/send-proactive.ts +273 -0
  116. package/scripts/set-markdown.sh +156 -0
  117. package/scripts/upgrade-and-run.sh +525 -0
  118. package/scripts/upgrade.sh +127 -0
  119. package/skills/qqbot-cron/SKILL.md +513 -0
  120. package/skills/qqbot-media/SKILL.md +194 -0
  121. package/src/api.ts +704 -0
  122. package/src/channel.ts +368 -0
  123. package/src/config.ts +182 -0
  124. package/src/gateway.ts +2459 -0
  125. package/src/image-server.ts +474 -0
  126. package/src/known-users.ts +353 -0
  127. package/src/onboarding.ts +274 -0
  128. package/src/openclaw-plugin-sdk.d.ts +483 -0
  129. package/src/outbound.ts +1301 -0
  130. package/src/proactive.ts +530 -0
  131. package/src/runtime.ts +14 -0
  132. package/src/session-store.ts +303 -0
  133. package/src/types.ts +153 -0
  134. package/src/utils/audio-convert.ts +738 -0
  135. package/src/utils/file-utils.ts +122 -0
  136. package/src/utils/image-size.ts +266 -0
  137. package/src/utils/media-tags.ts +134 -0
  138. package/src/utils/payload.ts +265 -0
  139. package/src/utils/platform.ts +404 -0
  140. package/src/utils/upload-cache.ts +128 -0
  141. package/tsconfig.json +16 -0
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Session 持久化存储
3
+ * 将 WebSocket 连接状态(sessionId、lastSeq)持久化到文件
4
+ * 支持进程重启后通过 Resume 机制快速恢复连接
5
+ */
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+ import { getQQBotDataDir } from "./utils/platform.js";
9
+ // Session 文件目录
10
+ const SESSION_DIR = getQQBotDataDir("sessions");
11
+ // Session 过期时间(5分钟)- Resume 要求在断开后一定时间内恢复
12
+ const SESSION_EXPIRE_TIME = 5 * 60 * 1000;
13
+ // 写入节流时间(避免频繁写入)
14
+ const SAVE_THROTTLE_MS = 1000;
15
+ // 每个账户的节流状态
16
+ const throttleState = new Map();
17
+ /**
18
+ * 确保目录存在
19
+ */
20
+ function ensureDir() {
21
+ if (!fs.existsSync(SESSION_DIR)) {
22
+ fs.mkdirSync(SESSION_DIR, { recursive: true });
23
+ }
24
+ }
25
+ /**
26
+ * 获取 Session 文件路径
27
+ */
28
+ function getSessionPath(accountId) {
29
+ // 清理 accountId 中的特殊字符
30
+ const safeId = accountId.replace(/[^a-zA-Z0-9_-]/g, "_");
31
+ return path.join(SESSION_DIR, `session-${safeId}.json`);
32
+ }
33
+ /**
34
+ * 加载 Session 状态
35
+ * @param accountId 账户 ID
36
+ * @param expectedAppId 当前使用的 appId,如果与保存时的 appId 不匹配则视为失效
37
+ * @returns Session 状态,如果不存在、已过期或 appId 不匹配返回 null
38
+ */
39
+ export function loadSession(accountId, expectedAppId) {
40
+ const filePath = getSessionPath(accountId);
41
+ try {
42
+ if (!fs.existsSync(filePath)) {
43
+ return null;
44
+ }
45
+ const data = fs.readFileSync(filePath, "utf-8");
46
+ const state = JSON.parse(data);
47
+ // 检查是否过期
48
+ const now = Date.now();
49
+ if (now - state.savedAt > SESSION_EXPIRE_TIME) {
50
+ console.log(`[session-store] Session expired for ${accountId}, age: ${Math.round((now - state.savedAt) / 1000)}s`);
51
+ try {
52
+ fs.unlinkSync(filePath);
53
+ }
54
+ catch {
55
+ // 忽略删除错误
56
+ }
57
+ return null;
58
+ }
59
+ // 检查 appId 是否匹配(凭据变更检测)
60
+ if (expectedAppId && state.appId && state.appId !== expectedAppId) {
61
+ console.log(`[session-store] appId mismatch for ${accountId}: saved=${state.appId}, current=${expectedAppId}. Discarding stale session.`);
62
+ try {
63
+ fs.unlinkSync(filePath);
64
+ }
65
+ catch {
66
+ // 忽略删除错误
67
+ }
68
+ return null;
69
+ }
70
+ // 验证必要字段
71
+ if (!state.sessionId || state.lastSeq === null || state.lastSeq === undefined) {
72
+ console.log(`[session-store] Invalid session data for ${accountId}`);
73
+ return null;
74
+ }
75
+ console.log(`[session-store] Loaded session for ${accountId}: sessionId=${state.sessionId}, lastSeq=${state.lastSeq}, appId=${state.appId ?? "unknown"}, age=${Math.round((now - state.savedAt) / 1000)}s`);
76
+ return state;
77
+ }
78
+ catch (err) {
79
+ console.error(`[session-store] Failed to load session for ${accountId}: ${err}`);
80
+ return null;
81
+ }
82
+ }
83
+ /**
84
+ * 保存 Session 状态(带节流,避免频繁写入)
85
+ * @param state Session 状态
86
+ */
87
+ export function saveSession(state) {
88
+ const { accountId } = state;
89
+ // 获取或初始化节流状态
90
+ let throttle = throttleState.get(accountId);
91
+ if (!throttle) {
92
+ throttle = {
93
+ pendingState: null,
94
+ lastSaveTime: 0,
95
+ throttleTimer: null,
96
+ };
97
+ throttleState.set(accountId, throttle);
98
+ }
99
+ const now = Date.now();
100
+ const timeSinceLastSave = now - throttle.lastSaveTime;
101
+ // 如果距离上次保存时间足够长,立即保存
102
+ if (timeSinceLastSave >= SAVE_THROTTLE_MS) {
103
+ doSaveSession(state);
104
+ throttle.lastSaveTime = now;
105
+ throttle.pendingState = null;
106
+ // 清除待定的节流定时器
107
+ if (throttle.throttleTimer) {
108
+ clearTimeout(throttle.throttleTimer);
109
+ throttle.throttleTimer = null;
110
+ }
111
+ }
112
+ else {
113
+ // 记录待保存的状态
114
+ throttle.pendingState = state;
115
+ // 如果没有设置定时器,设置一个
116
+ if (!throttle.throttleTimer) {
117
+ const delay = SAVE_THROTTLE_MS - timeSinceLastSave;
118
+ throttle.throttleTimer = setTimeout(() => {
119
+ const t = throttleState.get(accountId);
120
+ if (t && t.pendingState) {
121
+ doSaveSession(t.pendingState);
122
+ t.lastSaveTime = Date.now();
123
+ t.pendingState = null;
124
+ }
125
+ if (t) {
126
+ t.throttleTimer = null;
127
+ }
128
+ }, delay);
129
+ }
130
+ }
131
+ }
132
+ /**
133
+ * 实际执行保存操作
134
+ */
135
+ function doSaveSession(state) {
136
+ const filePath = getSessionPath(state.accountId);
137
+ try {
138
+ ensureDir();
139
+ // 更新保存时间
140
+ const stateToSave = {
141
+ ...state,
142
+ savedAt: Date.now(),
143
+ };
144
+ fs.writeFileSync(filePath, JSON.stringify(stateToSave, null, 2), "utf-8");
145
+ console.log(`[session-store] Saved session for ${state.accountId}: sessionId=${state.sessionId}, lastSeq=${state.lastSeq}`);
146
+ }
147
+ catch (err) {
148
+ console.error(`[session-store] Failed to save session for ${state.accountId}: ${err}`);
149
+ }
150
+ }
151
+ /**
152
+ * 清除 Session 状态
153
+ * @param accountId 账户 ID
154
+ */
155
+ export function clearSession(accountId) {
156
+ const filePath = getSessionPath(accountId);
157
+ // 清除节流状态
158
+ const throttle = throttleState.get(accountId);
159
+ if (throttle) {
160
+ if (throttle.throttleTimer) {
161
+ clearTimeout(throttle.throttleTimer);
162
+ }
163
+ throttleState.delete(accountId);
164
+ }
165
+ try {
166
+ if (fs.existsSync(filePath)) {
167
+ fs.unlinkSync(filePath);
168
+ console.log(`[session-store] Cleared session for ${accountId}`);
169
+ }
170
+ }
171
+ catch (err) {
172
+ console.error(`[session-store] Failed to clear session for ${accountId}: ${err}`);
173
+ }
174
+ }
175
+ /**
176
+ * 更新 lastSeq(轻量级更新)
177
+ * @param accountId 账户 ID
178
+ * @param lastSeq 最新的消息序号
179
+ */
180
+ export function updateLastSeq(accountId, lastSeq) {
181
+ const existing = loadSession(accountId);
182
+ if (existing && existing.sessionId) {
183
+ saveSession({
184
+ ...existing,
185
+ lastSeq,
186
+ });
187
+ }
188
+ }
189
+ /**
190
+ * 获取所有保存的 Session 状态
191
+ */
192
+ export function getAllSessions() {
193
+ const sessions = [];
194
+ try {
195
+ ensureDir();
196
+ const files = fs.readdirSync(SESSION_DIR);
197
+ for (const file of files) {
198
+ if (file.startsWith("session-") && file.endsWith(".json")) {
199
+ const filePath = path.join(SESSION_DIR, file);
200
+ try {
201
+ const data = fs.readFileSync(filePath, "utf-8");
202
+ const state = JSON.parse(data);
203
+ sessions.push(state);
204
+ }
205
+ catch {
206
+ // 忽略解析错误
207
+ }
208
+ }
209
+ }
210
+ }
211
+ catch {
212
+ // 目录不存在等错误
213
+ }
214
+ return sessions;
215
+ }
216
+ /**
217
+ * 清理过期的 Session 文件
218
+ */
219
+ export function cleanupExpiredSessions() {
220
+ let cleaned = 0;
221
+ try {
222
+ ensureDir();
223
+ const files = fs.readdirSync(SESSION_DIR);
224
+ const now = Date.now();
225
+ for (const file of files) {
226
+ if (file.startsWith("session-") && file.endsWith(".json")) {
227
+ const filePath = path.join(SESSION_DIR, file);
228
+ try {
229
+ const data = fs.readFileSync(filePath, "utf-8");
230
+ const state = JSON.parse(data);
231
+ if (now - state.savedAt > SESSION_EXPIRE_TIME) {
232
+ fs.unlinkSync(filePath);
233
+ cleaned++;
234
+ console.log(`[session-store] Cleaned expired session: ${file}`);
235
+ }
236
+ }
237
+ catch {
238
+ // 忽略解析错误,但也删除损坏的文件
239
+ try {
240
+ fs.unlinkSync(filePath);
241
+ cleaned++;
242
+ }
243
+ catch {
244
+ // 忽略
245
+ }
246
+ }
247
+ }
248
+ }
249
+ }
250
+ catch {
251
+ // 目录不存在等错误
252
+ }
253
+ return cleaned;
254
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * QQ Bot 配置类型
3
+ */
4
+ export interface QQBotConfig {
5
+ appId: string;
6
+ clientSecret?: string;
7
+ clientSecretFile?: string;
8
+ }
9
+ /**
10
+ * 解析后的 QQ Bot 账户
11
+ */
12
+ export interface ResolvedQQBotAccount {
13
+ accountId: string;
14
+ name?: string;
15
+ enabled: boolean;
16
+ appId: string;
17
+ clientSecret: string;
18
+ secretSource: "config" | "file" | "env" | "none";
19
+ /** 系统提示词 */
20
+ systemPrompt?: string;
21
+ /** 图床服务器公网地址 */
22
+ imageServerBaseUrl?: string;
23
+ /** 是否支持 markdown 消息(默认 true) */
24
+ markdownSupport: boolean;
25
+ config: QQBotAccountConfig;
26
+ }
27
+ /**
28
+ * QQ Bot 账户配置
29
+ */
30
+ export interface QQBotAccountConfig {
31
+ enabled?: boolean;
32
+ name?: string;
33
+ appId?: string;
34
+ clientSecret?: string;
35
+ clientSecretFile?: string;
36
+ dmPolicy?: "open" | "pairing" | "allowlist";
37
+ allowFrom?: string[];
38
+ /** 系统提示词,会添加在用户消息前面 */
39
+ systemPrompt?: string;
40
+ /** 图床服务器公网地址,用于发送图片,例如 http://your-ip:18765 */
41
+ imageServerBaseUrl?: string;
42
+ /** 是否支持 markdown 消息(默认 true,设为 false 可禁用) */
43
+ markdownSupport?: boolean;
44
+ /**
45
+ * @deprecated 请使用 audioFormatPolicy.uploadDirectFormats
46
+ * 可直接上传的音频格式(不转换为 SILK),向后兼容
47
+ */
48
+ voiceDirectUploadFormats?: string[];
49
+ /**
50
+ * 音频格式策略配置
51
+ * 统一管理入站(STT)和出站(上传)的音频格式转换行为
52
+ */
53
+ audioFormatPolicy?: AudioFormatPolicy;
54
+ }
55
+ /**
56
+ * 音频格式策略:控制哪些格式可跳过转换
57
+ */
58
+ export interface AudioFormatPolicy {
59
+ /**
60
+ * STT 模型直接支持的音频格式(入站:跳过 SILK→WAV 转换)
61
+ * 如果 STT 服务支持直接处理某些格式(如 silk/amr),可将其加入此列表
62
+ * 例如: [".silk", ".amr", ".wav", ".mp3", ".ogg"]
63
+ * 默认为空(所有语音都先转换为 WAV 再送 STT)
64
+ */
65
+ sttDirectFormats?: string[];
66
+ /**
67
+ * QQ 平台支持直传的音频格式(出站:跳过→SILK 转换)
68
+ * 默认为 [".wav", ".mp3", ".silk"](QQ Bot API 原生支持的三种格式)
69
+ * 仅当需要覆盖默认值时才配置此项
70
+ */
71
+ uploadDirectFormats?: string[];
72
+ }
73
+ /**
74
+ * 富媒体附件
75
+ */
76
+ export interface MessageAttachment {
77
+ content_type: string;
78
+ filename?: string;
79
+ height?: number;
80
+ width?: number;
81
+ size?: number;
82
+ url: string;
83
+ voice_wav_url?: string;
84
+ }
85
+ /**
86
+ * C2C 消息事件
87
+ */
88
+ export interface C2CMessageEvent {
89
+ author: {
90
+ id: string;
91
+ union_openid: string;
92
+ user_openid: string;
93
+ };
94
+ content: string;
95
+ id: string;
96
+ timestamp: string;
97
+ message_scene?: {
98
+ source: string;
99
+ };
100
+ attachments?: MessageAttachment[];
101
+ }
102
+ /**
103
+ * 频道 AT 消息事件
104
+ */
105
+ export interface GuildMessageEvent {
106
+ id: string;
107
+ channel_id: string;
108
+ guild_id: string;
109
+ content: string;
110
+ timestamp: string;
111
+ author: {
112
+ id: string;
113
+ username?: string;
114
+ bot?: boolean;
115
+ };
116
+ member?: {
117
+ nick?: string;
118
+ joined_at?: string;
119
+ };
120
+ attachments?: MessageAttachment[];
121
+ }
122
+ /**
123
+ * 群聊 AT 消息事件
124
+ */
125
+ export interface GroupMessageEvent {
126
+ author: {
127
+ id: string;
128
+ member_openid: string;
129
+ };
130
+ content: string;
131
+ id: string;
132
+ timestamp: string;
133
+ group_id: string;
134
+ group_openid: string;
135
+ attachments?: MessageAttachment[];
136
+ }
137
+ /**
138
+ * WebSocket 事件负载
139
+ */
140
+ export interface WSPayload {
141
+ op: number;
142
+ d?: unknown;
143
+ s?: number;
144
+ t?: string;
145
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,73 @@
1
+ /**
2
+ * 将 SILK/AMR 语音文件转换为 WAV 格式
3
+ *
4
+ * @param inputPath 输入文件路径(.amr / .silk / .slk)
5
+ * @param outputDir 输出目录(默认与输入文件同目录)
6
+ * @returns 转换后的 WAV 文件路径,失败返回 null
7
+ */
8
+ export declare function convertSilkToWav(inputPath: string, outputDir?: string): Promise<{
9
+ wavPath: string;
10
+ duration: number;
11
+ } | null>;
12
+ /**
13
+ * 判断是否为语音附件(根据 content_type 或文件扩展名)
14
+ */
15
+ export declare function isVoiceAttachment(att: {
16
+ content_type?: string;
17
+ filename?: string;
18
+ }): boolean;
19
+ /**
20
+ * 格式化语音时长为可读字符串
21
+ */
22
+ export declare function formatDuration(durationMs: number): string;
23
+ export declare function isAudioFile(filePath: string): boolean;
24
+ export interface TTSConfig {
25
+ baseUrl: string;
26
+ apiKey: string;
27
+ model: string;
28
+ voice: string;
29
+ /** Azure OpenAI 风格:使用 api-key header 而非 Bearer token */
30
+ authStyle?: "bearer" | "api-key";
31
+ /** 附加在 URL 后的查询参数,如 Azure 的 api-version */
32
+ queryParams?: Record<string, string>;
33
+ /** 自定义速度(默认不传) */
34
+ speed?: number;
35
+ }
36
+ export declare function resolveTTSConfig(cfg: Record<string, unknown>): TTSConfig | null;
37
+ export declare function textToSpeechPCM(text: string, ttsCfg: TTSConfig): Promise<{
38
+ pcmBuffer: Buffer;
39
+ sampleRate: number;
40
+ }>;
41
+ export declare function pcmToSilk(pcmBuffer: Buffer, sampleRate: number): Promise<{
42
+ silkBuffer: Buffer;
43
+ duration: number;
44
+ }>;
45
+ export declare function textToSilk(text: string, ttsCfg: TTSConfig, outputDir: string): Promise<{
46
+ silkPath: string;
47
+ silkBase64: string;
48
+ duration: number;
49
+ }>;
50
+ /**
51
+ * 将本地音频文件转换为 QQ Bot 可上传的 Base64
52
+ *
53
+ * QQ Bot API 支持直传 WAV、MP3、SILK 三种格式,其他格式仍需转换。
54
+ * 转换策略(参考 NapCat/go-cqhttp/Discord/Telegram 的做法):
55
+ *
56
+ * 1. WAV / MP3 / SILK → 直传(跳过转换)
57
+ * 2. 有 ffmpeg → ffmpeg 万能解码为 PCM → silk-wasm 编码
58
+ * 支持: ogg, opus, aac, flac, wma, m4a, pcm 等所有 ffmpeg 支持的格式
59
+ * 3. 无 ffmpeg → WASM fallback(仅支持 pcm, wav)
60
+ *
61
+ * @param directUploadFormats - 自定义直传格式列表,覆盖默认值。传 undefined 使用 QQ_NATIVE_UPLOAD_FORMATS
62
+ */
63
+ export declare function audioFileToSilkBase64(filePath: string, directUploadFormats?: string[]): Promise<string | null>;
64
+ /**
65
+ * 等待文件就绪(轮询直到文件出现且大小稳定)
66
+ * 用于 TTS 生成后等待文件写入完成
67
+ *
68
+ * @param filePath 文件路径
69
+ * @param timeoutMs 最大等待时间(默认 2 分钟)
70
+ * @param pollMs 轮询间隔(默认 500ms)
71
+ * @returns 文件大小(字节),超时或文件始终为空返回 0
72
+ */
73
+ export declare function waitForFile(filePath: string, timeoutMs?: number, pollMs?: number): Promise<number>;