@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.
- package/README.md +216 -49
- package/README.zh.md +216 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/src/api.d.ts +6 -0
- package/dist/src/api.js +33 -4
- package/dist/src/approval-handler.d.ts +47 -0
- package/dist/src/approval-handler.js +372 -0
- package/dist/src/channel.js +72 -0
- package/dist/src/config.d.ts +5 -1
- package/dist/src/config.js +12 -2
- package/dist/src/gateway.js +175 -170
- package/dist/src/slash-commands.d.ts +7 -2
- package/dist/src/slash-commands.js +354 -3
- package/dist/src/tools/channel.js +1 -4
- package/dist/src/tools/remind.js +0 -1
- package/dist/src/transport/index.d.ts +10 -0
- package/dist/src/transport/index.js +9 -0
- package/dist/src/transport/webhook-transport.d.ts +67 -0
- package/dist/src/transport/webhook-transport.js +245 -0
- package/dist/src/transport/webhook-verify.d.ts +48 -0
- package/dist/src/transport/webhook-verify.js +98 -0
- package/dist/src/types.d.ts +85 -0
- package/dist/src/utils/audio-convert.js +37 -9
- package/index.ts +1 -0
- package/package.json +1 -1
- package/scripts/postinstall-link-sdk.js +44 -0
- package/scripts/upgrade-via-npm.sh +358 -62
- package/scripts/upgrade-via-source.sh +122 -85
- package/src/api.ts +50 -5
- package/src/approval-handler.ts +505 -0
- package/src/channel.ts +76 -0
- package/src/config.ts +15 -2
- package/src/gateway.ts +181 -169
- package/src/onboarding.ts +8 -0
- package/src/openclaw-plugin-sdk.d.ts +127 -2
- package/src/slash-commands.ts +390 -5
- package/src/tools/channel.ts +1 -7
- package/src/tools/remind.ts +0 -2
- package/src/transport/index.ts +11 -0
- package/src/transport/webhook-transport.ts +332 -0
- package/src/transport/webhook-verify.ts +119 -0
- package/src/types.ts +100 -1
- package/src/typings/openclaw-webhook-ingress.d.ts +66 -0
- 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
|
|
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
|
-
|
|
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
|
}
|