@ryantest/openclaw-qqbot 0.0.3 → 1.6.6-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.
- package/README.md +2 -15
- package/README.zh.md +3 -16
- package/dist/src/admin-resolver.d.ts +12 -6
- package/dist/src/admin-resolver.js +69 -34
- package/dist/src/api.d.ts +105 -1
- package/dist/src/api.js +164 -15
- package/dist/src/channel.js +13 -0
- package/dist/src/config.js +3 -10
- package/dist/src/deliver-debounce.d.ts +74 -0
- package/dist/src/deliver-debounce.js +174 -0
- package/dist/src/gateway.js +450 -248
- package/dist/src/image-server.d.ts +27 -8
- package/dist/src/image-server.js +179 -71
- package/dist/src/inbound-attachments.d.ts +3 -1
- package/dist/src/inbound-attachments.js +28 -14
- package/dist/src/outbound-deliver.js +77 -148
- package/dist/src/outbound.d.ts +6 -4
- package/dist/src/outbound.js +266 -442
- package/dist/src/reply-dispatcher.js +4 -4
- package/dist/src/request-context.d.ts +18 -0
- package/dist/src/request-context.js +30 -0
- package/dist/src/slash-commands.js +277 -32
- package/dist/src/startup-greeting.d.ts +5 -5
- package/dist/src/startup-greeting.js +32 -13
- package/dist/src/streaming.d.ts +244 -0
- package/dist/src/streaming.js +907 -0
- package/dist/src/tools/remind.js +11 -10
- package/dist/src/types.d.ts +101 -0
- package/dist/src/types.js +17 -1
- package/dist/src/update-checker.js +2 -8
- package/dist/src/utils/audio-convert.d.ts +9 -0
- package/dist/src/utils/audio-convert.js +51 -0
- package/dist/src/utils/chunked-upload.d.ts +59 -0
- package/dist/src/utils/chunked-upload.js +289 -0
- package/dist/src/utils/file-utils.d.ts +7 -1
- package/dist/src/utils/file-utils.js +24 -2
- package/dist/src/utils/media-send.d.ts +147 -0
- package/dist/src/utils/media-send.js +434 -0
- package/dist/src/utils/pkg-version.d.ts +5 -0
- package/dist/src/utils/pkg-version.js +51 -0
- package/dist/src/utils/ssrf-guard.d.ts +25 -0
- package/dist/src/utils/ssrf-guard.js +91 -0
- package/node_modules/ws/index.js +15 -6
- package/node_modules/ws/lib/permessage-deflate.js +6 -6
- package/node_modules/ws/lib/websocket-server.js +5 -5
- package/node_modules/ws/lib/websocket.js +6 -6
- package/node_modules/ws/package.json +4 -3
- package/node_modules/ws/wrapper.mjs +14 -1
- package/openclaw.plugin.json +1 -0
- package/package.json +11 -22
- package/scripts/postinstall-link-sdk.js +113 -0
- package/scripts/upgrade-via-npm.ps1 +161 -6
- package/scripts/upgrade-via-npm.sh +311 -104
- package/scripts/upgrade-via-source.sh +117 -0
- package/skills/qqbot-media/SKILL.md +9 -5
- package/skills/qqbot-remind/SKILL.md +3 -3
- package/src/admin-resolver.ts +76 -35
- package/src/api.ts +284 -12
- package/src/channel.ts +12 -0
- package/src/config.ts +3 -10
- package/src/deliver-debounce.ts +229 -0
- package/src/gateway.ts +277 -67
- package/src/image-server.ts +213 -77
- package/src/inbound-attachments.ts +32 -15
- package/src/outbound-deliver.ts +77 -157
- package/src/outbound.ts +304 -451
- package/src/reply-dispatcher.ts +4 -4
- package/src/request-context.ts +39 -0
- package/src/slash-commands.ts +303 -33
- package/src/startup-greeting.ts +35 -13
- package/src/streaming.ts +1096 -0
- package/src/tools/remind.ts +15 -11
- package/src/types.ts +111 -0
- package/src/update-checker.ts +2 -7
- package/src/utils/audio-convert.ts +56 -0
- package/src/utils/chunked-upload.ts +419 -0
- package/src/utils/file-utils.ts +28 -2
- package/src/utils/media-send.ts +563 -0
- package/src/utils/pkg-version.ts +54 -0
- package/src/utils/ssrf-guard.ts +102 -0
- package/clawdbot.plugin.json +0 -16
- package/dist/src/user-messages.d.ts +0 -8
- package/dist/src/user-messages.js +0 -8
- package/moltbot.plugin.json +0 -16
- package/scripts/upgrade-via-alt-pkg.sh +0 -307
- package/src/bot-logs-2026-03-21T11-21-47(2).txt +0 -46
- package/src/gateway.log +0 -43
- package/src/openclaw-2026-03-21.log +0 -3729
- package/src/user-messages.ts +0 -7
package/src/tools/remind.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
|
+
import { getRequestTarget } from "../request-context.js";
|
|
2
3
|
|
|
3
4
|
// ========== 类型定义 ==========
|
|
4
5
|
|
|
@@ -6,7 +7,10 @@ interface RemindParams {
|
|
|
6
7
|
action: "add" | "list" | "remove";
|
|
7
8
|
/** 提醒内容(action=add 时必填) */
|
|
8
9
|
content?: string;
|
|
9
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* 投递目标地址(可选,系统会自动从当前会话上下文获取)。
|
|
12
|
+
* 仅在需要手动指定时填写。
|
|
13
|
+
*/
|
|
10
14
|
to?: string;
|
|
11
15
|
/**
|
|
12
16
|
* 时间描述(action=add 时必填)
|
|
@@ -40,8 +44,8 @@ const RemindSchema = {
|
|
|
40
44
|
to: {
|
|
41
45
|
type: "string",
|
|
42
46
|
description:
|
|
43
|
-
"
|
|
44
|
-
"私聊格式:user_openid,群聊格式:group:group_openid。
|
|
47
|
+
"投递目标地址(可选)。系统会自动从当前会话获取,通常无需手动填写。" +
|
|
48
|
+
"私聊格式:qqbot:c2c:user_openid,群聊格式:qqbot:group:group_openid。",
|
|
45
49
|
},
|
|
46
50
|
time: {
|
|
47
51
|
type: "string",
|
|
@@ -130,9 +134,8 @@ function generateJobName(content: string): string {
|
|
|
130
134
|
/**
|
|
131
135
|
* 构建一次性提醒的 cron 工具参数
|
|
132
136
|
*/
|
|
133
|
-
function buildOnceJob(params: RemindParams, delayMs: number) {
|
|
137
|
+
function buildOnceJob(params: RemindParams, delayMs: number, to: string) {
|
|
134
138
|
const atMs = Date.now() + delayMs;
|
|
135
|
-
const to = params.to!;
|
|
136
139
|
const content = params.content!;
|
|
137
140
|
const name = params.name || generateJobName(content);
|
|
138
141
|
|
|
@@ -158,8 +161,7 @@ function buildOnceJob(params: RemindParams, delayMs: number) {
|
|
|
158
161
|
/**
|
|
159
162
|
* 构建周期提醒的 cron 工具参数
|
|
160
163
|
*/
|
|
161
|
-
function buildCronJob(params: RemindParams) {
|
|
162
|
-
const to = params.to!;
|
|
164
|
+
function buildCronJob(params: RemindParams, to: string) {
|
|
163
165
|
const content = params.content!;
|
|
164
166
|
const name = params.name || generateJobName(content);
|
|
165
167
|
const tz = params.timezone || "Asia/Shanghai";
|
|
@@ -249,8 +251,10 @@ export function registerRemindTool(api: OpenClawPluginApi): void {
|
|
|
249
251
|
if (!p.content) {
|
|
250
252
|
return json({ error: "action=add 时 content(提醒内容)为必填参数" });
|
|
251
253
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
+
// 优先使用 AI 传入的 to,否则自动从请求级上下文获取(AsyncLocalStorage)
|
|
255
|
+
const resolvedTo = p.to || getRequestTarget();
|
|
256
|
+
if (!resolvedTo) {
|
|
257
|
+
return json({ error: "action=add 时无法确定投递目标地址。请稍后重试。" });
|
|
254
258
|
}
|
|
255
259
|
if (!p.time) {
|
|
256
260
|
return json({ error: "action=add 时 time(时间)为必填参数。示例:\"5m\"、\"1h30m\"、\"0 8 * * *\"" });
|
|
@@ -259,7 +263,7 @@ export function registerRemindTool(api: OpenClawPluginApi): void {
|
|
|
259
263
|
// 判断是 cron 表达式还是相对时间
|
|
260
264
|
if (isCronExpression(p.time)) {
|
|
261
265
|
// 周期提醒
|
|
262
|
-
const cronJob = buildCronJob(p);
|
|
266
|
+
const cronJob = buildCronJob(p, resolvedTo);
|
|
263
267
|
return json({
|
|
264
268
|
_instruction: "请立即使用 cron 工具执行以下参数,然后回复用户提醒已设置",
|
|
265
269
|
cronParams: cronJob,
|
|
@@ -281,7 +285,7 @@ export function registerRemindTool(api: OpenClawPluginApi): void {
|
|
|
281
285
|
return json({ error: "提醒时间不能少于 30 秒" });
|
|
282
286
|
}
|
|
283
287
|
|
|
284
|
-
const onceJob = buildOnceJob(p, delayMs);
|
|
288
|
+
const onceJob = buildOnceJob(p, delayMs, resolvedTo);
|
|
285
289
|
return json({
|
|
286
290
|
_instruction: "请立即使用 cron 工具执行以下参数,然后回复用户提醒已设置",
|
|
287
291
|
cronParams: onceJob,
|
package/src/types.ts
CHANGED
|
@@ -70,6 +70,46 @@ export interface QQBotAccountConfig {
|
|
|
70
70
|
* - "hot-reload":检测到新版本时直接执行 npm 升级脚本进行热更新
|
|
71
71
|
*/
|
|
72
72
|
upgradeMode?: "doc" | "hot-reload";
|
|
73
|
+
/**
|
|
74
|
+
* 出站消息合并回复(debounce)配置
|
|
75
|
+
* 当短时间内收到多次 deliver 时,将文本合并为一条消息发送,避免消息轰炸
|
|
76
|
+
*/
|
|
77
|
+
deliverDebounce?: DeliverDebounceConfig;
|
|
78
|
+
/**
|
|
79
|
+
* 是否启用流式消息(默认 false)
|
|
80
|
+
* 启用后,AI 的回复会以流式形式逐步显示在 QQ 聊天中,
|
|
81
|
+
* 用户可以看到文字逐字出现的打字机效果。
|
|
82
|
+
* 设置为 true 可开启流式消息。
|
|
83
|
+
*
|
|
84
|
+
* 注意:仅 C2C(私聊)支持流式消息 API。
|
|
85
|
+
*/
|
|
86
|
+
streaming?: boolean;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 出站消息合并回复配置
|
|
91
|
+
*/
|
|
92
|
+
export interface DeliverDebounceConfig {
|
|
93
|
+
/**
|
|
94
|
+
* 是否启用合并回复(默认 true)
|
|
95
|
+
*/
|
|
96
|
+
enabled?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* 合并窗口时长(毫秒),在此时间内的连续 deliver 会被合并
|
|
99
|
+
* 默认 1500ms
|
|
100
|
+
*/
|
|
101
|
+
windowMs?: number;
|
|
102
|
+
/**
|
|
103
|
+
* 最大等待时长(毫秒),从第一条 deliver 开始计算,超过此时间强制发送
|
|
104
|
+
* 防止持续有新 deliver 导致一直不发送
|
|
105
|
+
* 默认 8000ms
|
|
106
|
+
*/
|
|
107
|
+
maxWaitMs?: number;
|
|
108
|
+
/**
|
|
109
|
+
* 合并文本之间的分隔符
|
|
110
|
+
* 默认 "\n\n---\n\n"
|
|
111
|
+
*/
|
|
112
|
+
separator?: string;
|
|
73
113
|
}
|
|
74
114
|
|
|
75
115
|
/**
|
|
@@ -181,3 +221,74 @@ export interface WSPayload {
|
|
|
181
221
|
s?: number;
|
|
182
222
|
t?: string;
|
|
183
223
|
}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
// ---- 流式消息常量 ----
|
|
228
|
+
|
|
229
|
+
/** 流式消息输入模式 */
|
|
230
|
+
export const StreamInputMode = {
|
|
231
|
+
/** 每次发送的 content_raw 替换整条消息内容 */
|
|
232
|
+
REPLACE: "replace",
|
|
233
|
+
} as const;
|
|
234
|
+
export type StreamInputMode = (typeof StreamInputMode)[keyof typeof StreamInputMode];
|
|
235
|
+
|
|
236
|
+
/** 流式消息输入状态 */
|
|
237
|
+
export const StreamInputState = {
|
|
238
|
+
/** 正文生成中 */
|
|
239
|
+
GENERATING: 1,
|
|
240
|
+
/** 正文生成结束(终结状态) */
|
|
241
|
+
DONE: 10,
|
|
242
|
+
} as const;
|
|
243
|
+
export type StreamInputState = (typeof StreamInputState)[keyof typeof StreamInputState];
|
|
244
|
+
|
|
245
|
+
/** 流式消息内容类型 */
|
|
246
|
+
export const StreamContentType = {
|
|
247
|
+
MARKDOWN: "markdown",
|
|
248
|
+
} as const;
|
|
249
|
+
export type StreamContentType = (typeof StreamContentType)[keyof typeof StreamContentType];
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 流式消息请求体
|
|
253
|
+
* 对应 StreamReq proto
|
|
254
|
+
*/
|
|
255
|
+
export interface StreamMessageRequest {
|
|
256
|
+
/** 输入模式 */
|
|
257
|
+
input_mode: StreamInputMode;
|
|
258
|
+
/** 输入状态 */
|
|
259
|
+
input_state: StreamInputState;
|
|
260
|
+
/** 内容类型 */
|
|
261
|
+
content_type: StreamContentType;
|
|
262
|
+
/** markdown 内容 */
|
|
263
|
+
content_raw: string;
|
|
264
|
+
/** 事件 ID */
|
|
265
|
+
event_id: string;
|
|
266
|
+
/** 原始消息 ID */
|
|
267
|
+
msg_id: string;
|
|
268
|
+
/** 流式消息 ID,首次发送后返回,后续分片需携带 */
|
|
269
|
+
stream_msg_id?: string;
|
|
270
|
+
/** 递增序号 */
|
|
271
|
+
msg_seq: number;
|
|
272
|
+
/** 同一条流式会话内的发送索引,从 0 开始,每次发送前递增;新流式会话重新从 0 开始 */
|
|
273
|
+
index: number;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* 流式消息响应体
|
|
278
|
+
* 对应 StreamRsp proto
|
|
279
|
+
*
|
|
280
|
+
* 成功时返回:{ id, timestamp, extInfo }(无 code/message)
|
|
281
|
+
* 失败时返回:{ code, message }(code > 0)
|
|
282
|
+
*/
|
|
283
|
+
export interface StreamMessageResponse {
|
|
284
|
+
/** 错误码,仅失败时存在(> 0 表示失败);成功时不存在 */
|
|
285
|
+
code?: number;
|
|
286
|
+
/** 错误信息,仅失败时存在 */
|
|
287
|
+
message?: string;
|
|
288
|
+
/** 流式消息 ID */
|
|
289
|
+
id?: string;
|
|
290
|
+
/** 时间戳 */
|
|
291
|
+
timestamp?: string;
|
|
292
|
+
/** 扩展信息 */
|
|
293
|
+
extInfo?: Record<string, unknown>;
|
|
294
|
+
}
|
package/src/update-checker.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { createRequire } from "node:module";
|
|
12
12
|
import https from "node:https";
|
|
13
|
+
import { getPackageVersion } from "./utils/pkg-version.js";
|
|
13
14
|
|
|
14
15
|
const require = createRequire(import.meta.url);
|
|
15
16
|
|
|
@@ -21,13 +22,7 @@ const REGISTRIES = [
|
|
|
21
22
|
`https://registry.npmmirror.com/${ENCODED_PKG}`,
|
|
22
23
|
];
|
|
23
24
|
|
|
24
|
-
let CURRENT_VERSION =
|
|
25
|
-
try {
|
|
26
|
-
const pkg = require("../package.json");
|
|
27
|
-
CURRENT_VERSION = pkg.version ?? "unknown";
|
|
28
|
-
} catch {
|
|
29
|
-
// fallback
|
|
30
|
-
}
|
|
25
|
+
let CURRENT_VERSION = getPackageVersion(import.meta.url);
|
|
31
26
|
|
|
32
27
|
export interface UpdateInfo {
|
|
33
28
|
current: string;
|
|
@@ -517,6 +517,62 @@ export async function audioFileToSilkBase64(filePath: string, directUploadFormat
|
|
|
517
517
|
return null;
|
|
518
518
|
}
|
|
519
519
|
|
|
520
|
+
/**
|
|
521
|
+
* 将音频文件转码为 SILK,**输出到临时文件**(供分片上传使用)。
|
|
522
|
+
*
|
|
523
|
+
* 如果文件已经是 QQ 原生格式(WAV/MP3/SILK)或已经是 SILK 编码,
|
|
524
|
+
* 则直接返回原文件路径(不需要转码)。
|
|
525
|
+
*
|
|
526
|
+
* @returns 转码后的文件路径,或 null 表示转码失败
|
|
527
|
+
*/
|
|
528
|
+
export async function audioFileToSilkFile(filePath: string, directUploadFormats?: string[]): Promise<string | null> {
|
|
529
|
+
if (!fs.existsSync(filePath)) return null;
|
|
530
|
+
|
|
531
|
+
const buf = fs.readFileSync(filePath);
|
|
532
|
+
if (buf.length === 0) {
|
|
533
|
+
console.error(`[audio-convert] file is empty: ${filePath}`);
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
538
|
+
|
|
539
|
+
// 0. 直传格式 → 直接返回原文件
|
|
540
|
+
const uploadFormats = directUploadFormats ? normalizeFormats(directUploadFormats) : QQ_NATIVE_UPLOAD_FORMATS;
|
|
541
|
+
if (uploadFormats.includes(ext)) {
|
|
542
|
+
console.log(`[audio-convert] direct upload (QQ native format): ${ext} (${buf.length} bytes)`);
|
|
543
|
+
return filePath;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// 1. 已经是 SILK 编码 → 直接返回原文件
|
|
547
|
+
if ([".slk", ".slac"].includes(ext)) {
|
|
548
|
+
const stripped = stripAmrHeader(buf);
|
|
549
|
+
const raw = new Uint8Array(stripped.buffer, stripped.byteOffset, stripped.byteLength);
|
|
550
|
+
if (isSilk(raw)) {
|
|
551
|
+
console.log(`[audio-convert] SILK file, direct use: ${filePath} (${buf.length} bytes)`);
|
|
552
|
+
return filePath;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
const rawCheck = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
556
|
+
const strippedCheck = stripAmrHeader(buf);
|
|
557
|
+
const strippedRaw = new Uint8Array(strippedCheck.buffer, strippedCheck.byteOffset, strippedCheck.byteLength);
|
|
558
|
+
if (isSilk(rawCheck) || isSilk(strippedRaw)) {
|
|
559
|
+
console.log(`[audio-convert] SILK detected by header: ${filePath} (${buf.length} bytes)`);
|
|
560
|
+
return filePath;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// 需要转码 → 调用 audioFileToSilkBase64 获取结果,写入临时文件
|
|
564
|
+
const silkBase64 = await audioFileToSilkBase64(filePath, directUploadFormats);
|
|
565
|
+
if (!silkBase64) return null;
|
|
566
|
+
|
|
567
|
+
const silkBuffer = Buffer.from(silkBase64, "base64");
|
|
568
|
+
const os = await import("node:os");
|
|
569
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "voice-silk-"));
|
|
570
|
+
const tmpFile = path.join(tmpDir, `voice${Date.now()}.silk`);
|
|
571
|
+
fs.writeFileSync(tmpFile, silkBuffer);
|
|
572
|
+
console.log(`[audio-convert] SILK written to temp file: ${tmpFile} (${silkBuffer.length} bytes)`);
|
|
573
|
+
return tmpFile;
|
|
574
|
+
}
|
|
575
|
+
|
|
520
576
|
/**
|
|
521
577
|
* 等待文件就绪(轮询直到文件出现且大小稳定)
|
|
522
578
|
* 用于 TTS 生成后等待文件写入完成
|