@tencent-connect/openclaw-qqbot 1.7.0 → 1.7.2

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 (45) hide show
  1. package/README.md +216 -49
  2. package/README.zh.md +216 -4
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +1 -0
  5. package/dist/src/api.d.ts +6 -0
  6. package/dist/src/api.js +33 -4
  7. package/dist/src/approval-handler.d.ts +47 -0
  8. package/dist/src/approval-handler.js +372 -0
  9. package/dist/src/channel.js +72 -0
  10. package/dist/src/config.d.ts +5 -1
  11. package/dist/src/config.js +12 -2
  12. package/dist/src/gateway.js +175 -170
  13. package/dist/src/slash-commands.d.ts +7 -2
  14. package/dist/src/slash-commands.js +354 -3
  15. package/dist/src/tools/channel.js +1 -4
  16. package/dist/src/tools/remind.js +0 -1
  17. package/dist/src/transport/index.d.ts +10 -0
  18. package/dist/src/transport/index.js +9 -0
  19. package/dist/src/transport/webhook-transport.d.ts +67 -0
  20. package/dist/src/transport/webhook-transport.js +245 -0
  21. package/dist/src/transport/webhook-verify.d.ts +48 -0
  22. package/dist/src/transport/webhook-verify.js +98 -0
  23. package/dist/src/types.d.ts +85 -0
  24. package/dist/src/utils/audio-convert.js +37 -9
  25. package/index.ts +1 -0
  26. package/package.json +1 -1
  27. package/scripts/postinstall-link-sdk.js +44 -0
  28. package/scripts/upgrade-via-npm.sh +358 -62
  29. package/scripts/upgrade-via-source.sh +122 -85
  30. package/src/api.ts +50 -5
  31. package/src/approval-handler.ts +505 -0
  32. package/src/channel.ts +76 -0
  33. package/src/config.ts +15 -2
  34. package/src/gateway.ts +181 -169
  35. package/src/onboarding.ts +8 -0
  36. package/src/openclaw-plugin-sdk.d.ts +127 -2
  37. package/src/slash-commands.ts +390 -5
  38. package/src/tools/channel.ts +1 -7
  39. package/src/tools/remind.ts +0 -2
  40. package/src/transport/index.ts +11 -0
  41. package/src/transport/webhook-transport.ts +332 -0
  42. package/src/transport/webhook-verify.ts +119 -0
  43. package/src/types.ts +100 -1
  44. package/src/typings/openclaw-webhook-ingress.d.ts +66 -0
  45. package/src/utils/audio-convert.ts +37 -9
@@ -1,9 +1,30 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { execFile } from "node:child_process";
4
- import { decode, encode, isSilk } from "silk-wasm";
5
4
  import { detectFfmpeg, isWindows } from "./platform.js";
6
5
 
6
+ // silk-wasm 动态加载(可选依赖,未安装时降级为不支持语音编解码)
7
+ let silkWasm: { decode: typeof import("silk-wasm").decode; encode: typeof import("silk-wasm").encode; isSilk: typeof import("silk-wasm").isSilk } | null = null;
8
+ let silkWasmLoaded = false;
9
+
10
+ async function loadSilkWasm() {
11
+ if (silkWasmLoaded) return silkWasm;
12
+ silkWasmLoaded = true;
13
+ try {
14
+ silkWasm = await import("silk-wasm");
15
+ } catch {
16
+ console.warn("[audio-convert] silk-wasm not available, voice encoding/decoding disabled");
17
+ silkWasm = null;
18
+ }
19
+ return silkWasm;
20
+ }
21
+
22
+ // 同步版本的 isSilk(用于不方便 await 的场景),首次调用前需先 loadSilkWasm
23
+ function isSilkSync(data: Uint8Array): boolean {
24
+ if (!silkWasm) return false;
25
+ return silkWasm.isSilk(data);
26
+ }
27
+
7
28
  /**
8
29
  * 检查文件是否为 SILK 格式(QQ/微信语音常用格式)
9
30
  * QQ 语音文件通常以 .amr 扩展名保存,但实际编码可能是 SILK v3
@@ -12,7 +33,7 @@ import { detectFfmpeg, isWindows } from "./platform.js";
12
33
  function isSilkFile(filePath: string): boolean {
13
34
  try {
14
35
  const buf = fs.readFileSync(filePath);
15
- return isSilk(new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
36
+ return isSilkSync(new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
16
37
  } catch {
17
38
  return false;
18
39
  }
@@ -91,14 +112,15 @@ export async function convertSilkToWav(
91
112
  const rawData = new Uint8Array(strippedBuf.buffer, strippedBuf.byteOffset, strippedBuf.byteLength);
92
113
 
93
114
  // 验证是否为 SILK 格式
94
- if (!isSilk(rawData)) {
115
+ const silk = await loadSilkWasm();
116
+ if (!silk || !silk.isSilk(rawData)) {
95
117
  return null;
96
118
  }
97
119
 
98
120
  // SILK 解码为 PCM (s16le)
99
121
  // QQ 语音通常采样率为 24000Hz
100
122
  const sampleRate = 24000;
101
- const result = await decode(rawData, sampleRate);
123
+ const result = await silk.decode(rawData, sampleRate);
102
124
 
103
125
  // PCM → WAV
104
126
  const wavBuffer = pcmToWav(result.data, sampleRate);
@@ -380,8 +402,12 @@ export async function pcmToSilk(
380
402
  pcmBuffer: Buffer,
381
403
  sampleRate: number,
382
404
  ): Promise<{ silkBuffer: Buffer; duration: number }> {
405
+ const silk = await loadSilkWasm();
406
+ if (!silk) {
407
+ throw new Error("silk-wasm not available, cannot encode PCM to SILK. Install silk-wasm to enable voice sending.");
408
+ }
383
409
  const pcmData = new Uint8Array(pcmBuffer.buffer, pcmBuffer.byteOffset, pcmBuffer.byteLength);
384
- const result = await encode(pcmData, sampleRate);
410
+ const result = await silk.encode(pcmData, sampleRate);
385
411
  return {
386
412
  silkBuffer: Buffer.from(result.data.buffer, result.data.byteOffset, result.data.byteLength),
387
413
  duration: result.duration,
@@ -440,10 +466,11 @@ export async function audioFileToSilkBase64(filePath: string, directUploadFormat
440
466
  }
441
467
 
442
468
  // 1. .slk / .amr 扩展名 → 检测 SILK 魔数,是 SILK 则直传
469
+ const silk = await loadSilkWasm();
443
470
  if ([".slk", ".slac"].includes(ext)) {
444
471
  const stripped = stripAmrHeader(buf);
445
472
  const raw = new Uint8Array(stripped.buffer, stripped.byteOffset, stripped.byteLength);
446
- if (isSilk(raw)) {
473
+ if (silk?.isSilk(raw)) {
447
474
  console.log(`[audio-convert] SILK file, direct use: ${filePath} (${buf.length} bytes)`);
448
475
  return buf.toString("base64");
449
476
  }
@@ -453,7 +480,7 @@ export async function audioFileToSilkBase64(filePath: string, directUploadFormat
453
480
  const rawCheck = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
454
481
  const strippedCheck = stripAmrHeader(buf);
455
482
  const strippedRaw = new Uint8Array(strippedCheck.buffer, strippedCheck.byteOffset, strippedCheck.byteLength);
456
- if (isSilk(rawCheck) || isSilk(strippedRaw)) {
483
+ if (silk?.isSilk(rawCheck) || silk?.isSilk(strippedRaw)) {
457
484
  console.log(`[audio-convert] SILK detected by header: ${filePath} (${buf.length} bytes)`);
458
485
  return buf.toString("base64");
459
486
  }
@@ -544,10 +571,11 @@ export async function audioFileToSilkFile(filePath: string, directUploadFormats?
544
571
  }
545
572
 
546
573
  // 1. 已经是 SILK 编码 → 直接返回原文件
574
+ const silk = await loadSilkWasm();
547
575
  if ([".slk", ".slac"].includes(ext)) {
548
576
  const stripped = stripAmrHeader(buf);
549
577
  const raw = new Uint8Array(stripped.buffer, stripped.byteOffset, stripped.byteLength);
550
- if (isSilk(raw)) {
578
+ if (silk?.isSilk(raw)) {
551
579
  console.log(`[audio-convert] SILK file, direct use: ${filePath} (${buf.length} bytes)`);
552
580
  return filePath;
553
581
  }
@@ -555,7 +583,7 @@ export async function audioFileToSilkFile(filePath: string, directUploadFormats?
555
583
  const rawCheck = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
556
584
  const strippedCheck = stripAmrHeader(buf);
557
585
  const strippedRaw = new Uint8Array(strippedCheck.buffer, strippedCheck.byteOffset, strippedCheck.byteLength);
558
- if (isSilk(rawCheck) || isSilk(strippedRaw)) {
586
+ if (silk?.isSilk(rawCheck) || silk?.isSilk(strippedRaw)) {
559
587
  console.log(`[audio-convert] SILK detected by header: ${filePath} (${buf.length} bytes)`);
560
588
  return filePath;
561
589
  }