@openclaw/qqbot 2026.5.7 → 2026.5.9-beta.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.
- package/dist/api.js +5 -5
- package/dist/{channel-CC2YO9fj.js → channel-nsJeL4qp.js} +128 -58
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.setup-ByCRAe5H.js → channel.setup-DS8qSqfn.js} +1 -1
- package/dist/{config-schema-DFcjQw73.js → config-schema-BUrvFkQH.js} +1 -1
- package/dist/{gateway-Cs3-_on9.js → gateway-Dcs6Ip8b.js} +74 -34
- package/dist/{handler-runtime-Bqm6N0WG.js → handler-runtime-DraqODp_.js} +1 -1
- package/dist/{outbound-BJfhwrPg.js → outbound-CqRmSFJZ.js} +18 -57
- package/dist/{request-context-Cjwx7OwW.js → request-context-DVsN666m.js} +26 -7
- package/dist/{runtime-BJAS3eXW.js → runtime-C3Qpr4Ph.js} +1 -1
- package/dist/runtime-api.js +1 -1
- package/dist/{sender-p-B14eLG.js → sender-CgdXGY2H.js} +241 -268
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{target-parser-Y0prnrXD.js → target-parser-DQ6J3Qe-.js} +1 -1
- package/package.json +5 -6
package/dist/api.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { C as recordMessageReply, E as setOutboundAudioPort, S as getMessageReplyStats, T as OUTBOUND_ERROR_CODES, _ as sendVoice, a as sendText, b as checkMessageReplyLimit, d as buildMediaTarget, f as parseTarget, g as sendVideoMsg, h as sendPhoto, i as sendProactiveMessage, m as sendDocument, n as sendCronMessage, p as resolveOutboundMediaPath, r as sendMedia, v as resolveUserFacingMediaError, w as DEFAULT_MEDIA_SEND_ERROR, x as getMessageReplyConfig, y as MESSAGE_REPLY_LIMIT } from "./outbound-
|
|
1
|
+
import { C as recordMessageReply, E as setOutboundAudioPort, S as getMessageReplyStats, T as OUTBOUND_ERROR_CODES, _ as sendVoice, a as sendText, b as checkMessageReplyLimit, d as buildMediaTarget, f as parseTarget, g as sendVideoMsg, h as sendPhoto, i as sendProactiveMessage, m as sendDocument, n as sendCronMessage, p as resolveOutboundMediaPath, r as sendMedia, v as resolveUserFacingMediaError, w as DEFAULT_MEDIA_SEND_ERROR, x as getMessageReplyConfig, y as MESSAGE_REPLY_LIMIT } from "./outbound-CqRmSFJZ.js";
|
|
2
2
|
import { a as resolveQQBotAccount, i as resolveDefaultQQBotAccountId, n as applyQQBotAccountConfig, r as listQQBotAccountIds, t as DEFAULT_ACCOUNT_ID } from "./config-D6545NkC.js";
|
|
3
|
-
import { t as qqbotPlugin } from "./channel-
|
|
4
|
-
import {
|
|
5
|
-
import { t as qqbotSetupPlugin } from "./channel.setup-
|
|
6
|
-
import { r as getFrameworkCommands, t as getRequestContext } from "./request-context-
|
|
3
|
+
import { t as qqbotPlugin } from "./channel-nsJeL4qp.js";
|
|
4
|
+
import { L as formatErrorMessage, S as debugLog, o as getAccessToken, x as debugError } from "./sender-CgdXGY2H.js";
|
|
5
|
+
import { t as qqbotSetupPlugin } from "./channel.setup-DS8qSqfn.js";
|
|
6
|
+
import { r as getFrameworkCommands, t as getRequestContext } from "./request-context-DVsN666m.js";
|
|
7
7
|
import { callGatewayTool } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
8
8
|
//#region extensions/qqbot/src/engine/tools/channel-api.ts
|
|
9
9
|
/**
|
|
@@ -3,17 +3,20 @@ import { l as ensurePlatformAdapter, u as getBridgeLogger } from "./resolve-D_06
|
|
|
3
3
|
import { a as resolveApprovalTarget } from "./approval-cg0SVahb.js";
|
|
4
4
|
import { a as resolveQQBotAccount, n as applyQQBotAccountConfig, t as DEFAULT_ACCOUNT_ID$1 } from "./config-D6545NkC.js";
|
|
5
5
|
import { a as resolveQQBotExecApprovalConfig, i as matchesQQBotApprovalAccount, n as isQQBotExecApprovalAuthorizedSender, o as shouldHandleQQBotExecApprovalRequest, r as isQQBotExecApprovalClientEnabled, t as isQQBotExecApprovalApprover } from "./exec-approvals-COUsM6wZ.js";
|
|
6
|
-
import { a as qqbotSetupAdapterShared, i as qqbotMeta, n as qqbotSetupWizard, r as qqbotConfigAdapter, t as qqbotChannelConfigSchema } from "./config-schema-
|
|
6
|
+
import { a as qqbotSetupAdapterShared, i as qqbotMeta, n as qqbotSetupWizard, r as qqbotConfigAdapter, t as qqbotChannelConfigSchema } from "./config-schema-BUrvFkQH.js";
|
|
7
7
|
import { n as writeOpenClawConfigThroughRuntime, t as toGatewayAccount } from "./narrowing-BoieBTIU.js";
|
|
8
|
-
import { t as getQQBotRuntime } from "./runtime-
|
|
9
|
-
import { n as normalizeTarget, s as getQQBotDataPath, t as looksLikeQQBotTarget } from "./target-parser-
|
|
8
|
+
import { t as getQQBotRuntime } from "./runtime-C3Qpr4Ph.js";
|
|
9
|
+
import { n as normalizeTarget, s as getQQBotDataPath, t as looksLikeQQBotTarget } from "./target-parser-DQ6J3Qe-.js";
|
|
10
10
|
import { getExecApprovalReplyMetadata } from "openclaw/plugin-sdk/approval-runtime";
|
|
11
|
+
import { createMessageReceiptFromOutboundResults, defineChannelMessageAdapter } from "openclaw/plugin-sdk/channel-message";
|
|
11
12
|
import { createChannelApprovalCapability } from "openclaw/plugin-sdk/approval-delivery-runtime";
|
|
12
13
|
import { createLazyChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
|
|
13
14
|
import { resolveApprovalRequestSessionConversation } from "openclaw/plugin-sdk/approval-native-runtime";
|
|
14
15
|
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
|
15
16
|
import fs from "node:fs";
|
|
17
|
+
import { replaceFileAtomicSync } from "openclaw/plugin-sdk/security-runtime";
|
|
16
18
|
import path from "node:path";
|
|
19
|
+
import { loadJsonFile } from "openclaw/plugin-sdk/json-store";
|
|
17
20
|
//#region extensions/qqbot/src/bridge/approval/capability.ts
|
|
18
21
|
/**
|
|
19
22
|
* QQ Bot Approval Capability — entry point.
|
|
@@ -153,7 +156,7 @@ function createQQBotApprovalCapability() {
|
|
|
153
156
|
},
|
|
154
157
|
load: async () => {
|
|
155
158
|
ensurePlatformAdapter();
|
|
156
|
-
return (await import("./handler-runtime-
|
|
159
|
+
return (await import("./handler-runtime-DraqODp_.js")).qqbotApprovalNativeRuntime;
|
|
157
160
|
}
|
|
158
161
|
})
|
|
159
162
|
});
|
|
@@ -229,16 +232,17 @@ function saveCredentialBackup(accountId, appId, clientSecret) {
|
|
|
229
232
|
if (!appId || !clientSecret) return;
|
|
230
233
|
try {
|
|
231
234
|
const backupPath = getCredentialBackupFile(accountId);
|
|
232
|
-
fs.mkdirSync(path.dirname(backupPath), { recursive: true });
|
|
233
235
|
const data = {
|
|
234
236
|
accountId,
|
|
235
237
|
appId,
|
|
236
238
|
clientSecret,
|
|
237
239
|
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
238
240
|
};
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
241
|
+
replaceFileAtomicSync({
|
|
242
|
+
filePath: backupPath,
|
|
243
|
+
content: `${JSON.stringify(data, null, 2)}\n`,
|
|
244
|
+
tempPrefix: ".qqbot-credential-backup"
|
|
245
|
+
});
|
|
242
246
|
} catch {}
|
|
243
247
|
}
|
|
244
248
|
/**
|
|
@@ -251,23 +255,20 @@ function saveCredentialBackup(accountId, appId, clientSecret) {
|
|
|
251
255
|
function loadCredentialBackup(accountId) {
|
|
252
256
|
try {
|
|
253
257
|
if (accountId) {
|
|
254
|
-
const
|
|
255
|
-
if (
|
|
256
|
-
const data = JSON.parse(fs.readFileSync(newPath, "utf8"));
|
|
257
|
-
if (data?.appId && data.clientSecret) return data;
|
|
258
|
-
}
|
|
258
|
+
const data = loadJsonFile(getCredentialBackupFile(accountId));
|
|
259
|
+
if (data?.appId && data.clientSecret) return data;
|
|
259
260
|
}
|
|
260
261
|
const legacy = getLegacyCredentialBackupFile();
|
|
261
|
-
|
|
262
|
-
|
|
262
|
+
const data = loadJsonFile(legacy);
|
|
263
|
+
if (data) {
|
|
263
264
|
if (!data?.appId || !data?.clientSecret) return null;
|
|
264
265
|
if (accountId && data.accountId !== accountId) return null;
|
|
265
266
|
if (data.accountId) try {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
267
|
+
replaceFileAtomicSync({
|
|
268
|
+
filePath: getCredentialBackupFile(data.accountId),
|
|
269
|
+
content: `${JSON.stringify(data, null, 2)}\n`,
|
|
270
|
+
tempPrefix: ".qqbot-credential-backup"
|
|
271
|
+
});
|
|
271
272
|
fs.unlinkSync(legacy);
|
|
272
273
|
} catch {}
|
|
273
274
|
return data;
|
|
@@ -342,11 +343,99 @@ function clearAccountCredentials(cfg, accountId) {
|
|
|
342
343
|
}
|
|
343
344
|
//#endregion
|
|
344
345
|
//#region extensions/qqbot/src/channel.ts
|
|
345
|
-
let
|
|
346
|
+
let gatewayModulePromise;
|
|
346
347
|
function loadGatewayModule() {
|
|
347
|
-
|
|
348
|
-
return
|
|
348
|
+
gatewayModulePromise ??= import("./gateway-Dcs6Ip8b.js");
|
|
349
|
+
return gatewayModulePromise;
|
|
350
|
+
}
|
|
351
|
+
function createQQBotSendReceipt(params) {
|
|
352
|
+
const messageId = params.messageId?.trim();
|
|
353
|
+
return createMessageReceiptFromOutboundResults({
|
|
354
|
+
results: messageId ? [{
|
|
355
|
+
channel: "qqbot",
|
|
356
|
+
messageId,
|
|
357
|
+
conversationId: params.target
|
|
358
|
+
}] : [],
|
|
359
|
+
threadId: params.target,
|
|
360
|
+
kind: params.kind
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
async function sendQQBotText(params) {
|
|
364
|
+
await loadGatewayModule();
|
|
365
|
+
const account = resolveQQBotAccount(params.cfg, params.accountId);
|
|
366
|
+
const { sendText } = await import("./outbound-CqRmSFJZ.js").then((n) => n.t);
|
|
367
|
+
const result = await sendText({
|
|
368
|
+
to: params.to,
|
|
369
|
+
text: params.text,
|
|
370
|
+
accountId: params.accountId,
|
|
371
|
+
replyToId: params.replyToId,
|
|
372
|
+
account: toGatewayAccount(account)
|
|
373
|
+
});
|
|
374
|
+
return {
|
|
375
|
+
channel: "qqbot",
|
|
376
|
+
messageId: result.messageId ?? "",
|
|
377
|
+
receipt: createQQBotSendReceipt({
|
|
378
|
+
messageId: result.messageId,
|
|
379
|
+
target: params.to,
|
|
380
|
+
kind: "text"
|
|
381
|
+
}),
|
|
382
|
+
meta: result.error ? { error: result.error } : void 0
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
async function sendQQBotMedia(params) {
|
|
386
|
+
await loadGatewayModule();
|
|
387
|
+
const account = resolveQQBotAccount(params.cfg, params.accountId);
|
|
388
|
+
const { sendMedia } = await import("./outbound-CqRmSFJZ.js").then((n) => n.t);
|
|
389
|
+
const result = await sendMedia({
|
|
390
|
+
to: params.to,
|
|
391
|
+
text: params.text ?? "",
|
|
392
|
+
mediaUrl: params.mediaUrl ?? "",
|
|
393
|
+
accountId: params.accountId,
|
|
394
|
+
replyToId: params.replyToId,
|
|
395
|
+
account: toGatewayAccount(account)
|
|
396
|
+
});
|
|
397
|
+
return {
|
|
398
|
+
channel: "qqbot",
|
|
399
|
+
messageId: result.messageId ?? "",
|
|
400
|
+
receipt: createQQBotSendReceipt({
|
|
401
|
+
messageId: result.messageId,
|
|
402
|
+
target: params.to,
|
|
403
|
+
kind: "media"
|
|
404
|
+
}),
|
|
405
|
+
meta: result.error ? { error: result.error } : void 0
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
function toQQBotMessageSendResult(result) {
|
|
409
|
+
return {
|
|
410
|
+
messageId: result.messageId,
|
|
411
|
+
receipt: result.receipt
|
|
412
|
+
};
|
|
349
413
|
}
|
|
414
|
+
const qqbotMessageAdapter = defineChannelMessageAdapter({
|
|
415
|
+
id: "qqbot",
|
|
416
|
+
durableFinal: { capabilities: {
|
|
417
|
+
text: true,
|
|
418
|
+
media: true,
|
|
419
|
+
replyTo: true
|
|
420
|
+
} },
|
|
421
|
+
send: {
|
|
422
|
+
text: async (ctx) => toQQBotMessageSendResult(await sendQQBotText({
|
|
423
|
+
cfg: ctx.cfg,
|
|
424
|
+
to: ctx.to,
|
|
425
|
+
text: ctx.text,
|
|
426
|
+
accountId: ctx.accountId,
|
|
427
|
+
replyToId: ctx.replyToId
|
|
428
|
+
})),
|
|
429
|
+
media: async (ctx) => toQQBotMessageSendResult(await sendQQBotMedia({
|
|
430
|
+
cfg: ctx.cfg,
|
|
431
|
+
to: ctx.to,
|
|
432
|
+
text: ctx.text,
|
|
433
|
+
mediaUrl: ctx.mediaUrl,
|
|
434
|
+
accountId: ctx.accountId,
|
|
435
|
+
replyToId: ctx.replyToId
|
|
436
|
+
}))
|
|
437
|
+
}
|
|
438
|
+
});
|
|
350
439
|
const EXEC_APPROVAL_COMMAND_RE = /\/approve(?:@[^\s]+)?\s+[A-Za-z0-9][A-Za-z0-9._:-]*\s+(?:allow-once|allow-always|always|deny)\b/i;
|
|
351
440
|
function persistAccountCredentialSnapshot(account) {
|
|
352
441
|
if (account.appId && account.clientSecret) saveCredentialBackup(account.accountId, account.appId, account.clientSecret);
|
|
@@ -389,6 +478,7 @@ const qqbotPlugin = {
|
|
|
389
478
|
},
|
|
390
479
|
setup: { ...qqbotSetupAdapterShared },
|
|
391
480
|
approvalCapability: getQQBotApprovalCapability(),
|
|
481
|
+
message: qqbotMessageAdapter,
|
|
392
482
|
messaging: {
|
|
393
483
|
targetPrefixes: ["qqbot"],
|
|
394
484
|
/** Normalize common QQ Bot target formats into the canonical qqbot:... form. */
|
|
@@ -410,41 +500,21 @@ const qqbotPlugin = {
|
|
|
410
500
|
payload,
|
|
411
501
|
hint
|
|
412
502
|
}),
|
|
413
|
-
sendText: async ({ to, text, accountId, replyToId, cfg }) => {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
};
|
|
429
|
-
},
|
|
430
|
-
sendMedia: async ({ to, text, mediaUrl, accountId, replyToId, cfg }) => {
|
|
431
|
-
await loadGatewayModule();
|
|
432
|
-
const account = resolveQQBotAccount(cfg, accountId);
|
|
433
|
-
const { sendMedia } = await import("./outbound-BJfhwrPg.js").then((n) => n.t);
|
|
434
|
-
const result = await sendMedia({
|
|
435
|
-
to,
|
|
436
|
-
text: text ?? "",
|
|
437
|
-
mediaUrl: mediaUrl ?? "",
|
|
438
|
-
accountId,
|
|
439
|
-
replyToId,
|
|
440
|
-
account: toGatewayAccount(account)
|
|
441
|
-
});
|
|
442
|
-
return {
|
|
443
|
-
channel: "qqbot",
|
|
444
|
-
messageId: result.messageId ?? "",
|
|
445
|
-
meta: result.error ? { error: result.error } : void 0
|
|
446
|
-
};
|
|
447
|
-
}
|
|
503
|
+
sendText: async ({ to, text, accountId, replyToId, cfg }) => await sendQQBotText({
|
|
504
|
+
cfg,
|
|
505
|
+
to,
|
|
506
|
+
text,
|
|
507
|
+
accountId,
|
|
508
|
+
replyToId
|
|
509
|
+
}),
|
|
510
|
+
sendMedia: async ({ to, text, mediaUrl, accountId, replyToId, cfg }) => await sendQQBotMedia({
|
|
511
|
+
cfg,
|
|
512
|
+
to,
|
|
513
|
+
text,
|
|
514
|
+
mediaUrl,
|
|
515
|
+
accountId,
|
|
516
|
+
replyToId
|
|
517
|
+
})
|
|
448
518
|
},
|
|
449
519
|
gateway: {
|
|
450
520
|
startAccount: async (ctx) => {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as qqbotPlugin } from "./channel-
|
|
1
|
+
import { t as qqbotPlugin } from "./channel-nsJeL4qp.js";
|
|
2
2
|
export { qqbotPlugin };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "./resolve-D_06fV6-.js";
|
|
2
|
-
import { a as qqbotSetupAdapterShared, i as qqbotMeta, n as qqbotSetupWizard, r as qqbotConfigAdapter, t as qqbotChannelConfigSchema } from "./config-schema-
|
|
2
|
+
import { a as qqbotSetupAdapterShared, i as qqbotMeta, n as qqbotSetupWizard, r as qqbotConfigAdapter, t as qqbotChannelConfigSchema } from "./config-schema-BUrvFkQH.js";
|
|
3
3
|
//#region extensions/qqbot/src/channel.setup.ts
|
|
4
4
|
/**
|
|
5
5
|
* Setup-only QQBot plugin — lightweight subset used during `openclaw onboard`
|
|
@@ -6,7 +6,7 @@ import { applyAccountNameToChannelSection, deleteAccountFromConfigSection, setAc
|
|
|
6
6
|
import { DEFAULT_ACCOUNT_ID, createStandardChannelSetupStatus, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
|
|
7
7
|
import { formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
|
|
8
8
|
import { AllowFromListSchema, buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
|
|
9
|
-
import { z } from "zod";
|
|
9
|
+
import { z } from "openclaw/plugin-sdk/zod";
|
|
10
10
|
//#region extensions/qqbot/src/engine/config/setup-logic.ts
|
|
11
11
|
/**
|
|
12
12
|
* QQBot setup business logic (pure layer).
|
|
@@ -1,20 +1,24 @@
|
|
|
1
|
-
import { E as setOutboundAudioPort, _ as sendVoice, c as isMediaPayload, g as sendVideoMsg, h as sendPhoto, l as parseQQBotPayload, m as sendDocument, o as encodePayloadForCron, r as sendMedia, s as isCronReminderPayload, u as normalizeMediaTags, v as resolveUserFacingMediaError, w as DEFAULT_MEDIA_SEND_ERROR } from "./outbound-
|
|
1
|
+
import { E as setOutboundAudioPort, _ as sendVoice, c as isMediaPayload, g as sendVideoMsg, h as sendPhoto, l as parseQQBotPayload, m as sendDocument, o as encodePayloadForCron, r as sendMedia, s as isCronReminderPayload, u as normalizeMediaTags, v as resolveUserFacingMediaError, w as DEFAULT_MEDIA_SEND_ERROR } from "./outbound-CqRmSFJZ.js";
|
|
2
2
|
import { c as getPlatformAdapter, i as normalizeOptionalString, n as normalizeLowercaseStringOrEmpty, o as readStringField, s as sanitizeFileName, t as asOptionalObjectRecord } from "./string-normalize-Ci6NM5DE.js";
|
|
3
3
|
import { d as setBridgeLogger, l as ensurePlatformAdapter, s as resolveAccountBase } from "./resolve-D_06fV6-.js";
|
|
4
4
|
import { i as parseApprovalButtonData } from "./approval-cg0SVahb.js";
|
|
5
5
|
import { t as toGatewayAccount } from "./narrowing-BoieBTIU.js";
|
|
6
|
-
import { B as StreamInputState, C as
|
|
7
|
-
import { n as getQQBotRuntimeForEngine, t as getQQBotRuntime } from "./runtime-
|
|
8
|
-
import { a as getHomeDir, c as getQQBotMediaDir, d as isLocalPath, f as isWindows, i as checkSilkWasmAvailable, m as resolveQQBotPayloadLocalFilePath, o as getQQBotDataDir, p as normalizePath$1, s as getQQBotDataPath, u as getTempDir } from "./target-parser-
|
|
9
|
-
import { a as getPluginVersion, i as getFrameworkVersion, n as runWithRequestContext, o as initCommands, s as matchSlashCommand } from "./request-context-
|
|
6
|
+
import { B as StreamInputState, C as debugWarn, D as openLocalFile, I as formatDuration, L as formatErrorMessage, N as getImageMimeType, P as getMaxUploadSize, R as StreamContentType, S as debugLog, T as getNextMsgSeq, a as createRawInputNotifyFn, b as withTokenRetry, c as getMessageApi, d as initSender, f as onMessageSent, g as sendText, h as sendMedia$1, i as clearTokenCache, j as formatFileSize, k as downloadFile, l as getPluginUserAgent, m as sendInputNotify, n as acknowledgeInteraction, o as getAccessToken, p as registerAccount, r as buildDeliveryTarget, s as getGatewayUrl, t as accountToCreds, u as initApiConfig, v as startBackgroundTokenRefresh, x as debugError, y as stopBackgroundTokenRefresh, z as StreamInputMode } from "./sender-CgdXGY2H.js";
|
|
7
|
+
import { n as getQQBotRuntimeForEngine, t as getQQBotRuntime } from "./runtime-C3Qpr4Ph.js";
|
|
8
|
+
import { a as getHomeDir, c as getQQBotMediaDir, d as isLocalPath, f as isWindows, i as checkSilkWasmAvailable, m as resolveQQBotPayloadLocalFilePath, o as getQQBotDataDir, p as normalizePath$1, s as getQQBotDataPath, u as getTempDir } from "./target-parser-DQ6J3Qe-.js";
|
|
9
|
+
import { a as getPluginVersion, i as getFrameworkVersion, n as runWithRequestContext, o as initCommands, s as matchSlashCommand } from "./request-context-DVsN666m.js";
|
|
10
10
|
import * as fs$1 from "node:fs";
|
|
11
11
|
import fs from "node:fs";
|
|
12
12
|
import * as os$1 from "node:os";
|
|
13
13
|
import crypto from "node:crypto";
|
|
14
|
+
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
15
|
+
import { appendRegularFileSync, privateFileStoreSync, readRegularFileSync, replaceFileAtomicSync } from "openclaw/plugin-sdk/security-runtime";
|
|
14
16
|
import * as path$1 from "node:path";
|
|
15
17
|
import path from "node:path";
|
|
18
|
+
import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
|
|
16
19
|
import { resolveRuntimeServiceVersion } from "openclaw/plugin-sdk/cli-runtime";
|
|
17
20
|
import WebSocket from "ws";
|
|
21
|
+
import { resolveAmbientNodeProxyAgent } from "openclaw/plugin-sdk/extension-shared";
|
|
18
22
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
19
23
|
import { fileURLToPath } from "node:url";
|
|
20
24
|
import { implicitMentionKindWhen, resolveInboundMentionDecision } from "openclaw/plugin-sdk/channel-mention-gating";
|
|
@@ -258,7 +262,10 @@ function ensureDir$2() {
|
|
|
258
262
|
function appendLine(line) {
|
|
259
263
|
try {
|
|
260
264
|
ensureDir$2();
|
|
261
|
-
|
|
265
|
+
appendRegularFileSync({
|
|
266
|
+
filePath: getRefIndexFile(),
|
|
267
|
+
content: JSON.stringify(line) + "\n"
|
|
268
|
+
});
|
|
262
269
|
totalLinesOnDisk++;
|
|
263
270
|
} catch (err) {
|
|
264
271
|
debugError(`[ref-index-store] Failed to append: ${formatErrorMessage(err)}`);
|
|
@@ -273,7 +280,6 @@ function compactFile() {
|
|
|
273
280
|
try {
|
|
274
281
|
ensureDir$2();
|
|
275
282
|
const refIndexFile = getRefIndexFile();
|
|
276
|
-
const tmpPath = refIndexFile + ".tmp";
|
|
277
283
|
const lines = [];
|
|
278
284
|
for (const [key, entry] of cache) lines.push(JSON.stringify({
|
|
279
285
|
k: key,
|
|
@@ -287,8 +293,11 @@ function compactFile() {
|
|
|
287
293
|
},
|
|
288
294
|
t: entry._createdAt
|
|
289
295
|
}));
|
|
290
|
-
|
|
291
|
-
|
|
296
|
+
replaceFileAtomicSync({
|
|
297
|
+
filePath: refIndexFile,
|
|
298
|
+
content: `${lines.join("\n")}\n`,
|
|
299
|
+
tempPrefix: ".qqbot-ref-index"
|
|
300
|
+
});
|
|
292
301
|
totalLinesOnDisk = cache.size;
|
|
293
302
|
debugLog(`[ref-index-store] Compacted: ${before} lines → ${totalLinesOnDisk} lines`);
|
|
294
303
|
} catch (err) {
|
|
@@ -778,9 +787,8 @@ function loadUsersFromFile() {
|
|
|
778
787
|
usersCache = /* @__PURE__ */ new Map();
|
|
779
788
|
try {
|
|
780
789
|
const knownUsersFile = getKnownUsersFile();
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
const users = JSON.parse(data);
|
|
790
|
+
const users = privateFileStoreSync(path.dirname(knownUsersFile)).readJsonIfExists(path.basename(knownUsersFile));
|
|
791
|
+
if (users) {
|
|
784
792
|
for (const user of users) usersCache.set(makeUserKey(user), user);
|
|
785
793
|
debugLog(`[known-users] Loaded ${usersCache.size} users`);
|
|
786
794
|
}
|
|
@@ -801,7 +809,8 @@ function doSaveUsersToFile() {
|
|
|
801
809
|
if (!usersCache || !isDirty) return;
|
|
802
810
|
try {
|
|
803
811
|
ensureDir$1();
|
|
804
|
-
|
|
812
|
+
const filePath = getKnownUsersFile();
|
|
813
|
+
privateFileStoreSync(path.dirname(filePath)).writeJson(path.basename(filePath), Array.from(usersCache.values()));
|
|
805
814
|
isDirty = false;
|
|
806
815
|
} catch (err) {
|
|
807
816
|
debugError(`[known-users] Failed to save users: ${formatErrorMessage(err)}`);
|
|
@@ -878,13 +887,15 @@ function getCandidateSessionPaths(accountId) {
|
|
|
878
887
|
function loadSession(accountId, expectedAppId) {
|
|
879
888
|
try {
|
|
880
889
|
let filePath = null;
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
890
|
+
let state = null;
|
|
891
|
+
for (const candidatePath of getCandidateSessionPaths(accountId)) {
|
|
892
|
+
state = privateFileStoreSync(path.dirname(candidatePath)).readJsonIfExists(path.basename(candidatePath));
|
|
893
|
+
if (state) {
|
|
894
|
+
filePath = candidatePath;
|
|
895
|
+
break;
|
|
896
|
+
}
|
|
884
897
|
}
|
|
885
|
-
if (!filePath) return null;
|
|
886
|
-
const data = fs.readFileSync(filePath, "utf-8");
|
|
887
|
-
const state = JSON.parse(data);
|
|
898
|
+
if (!filePath || !state) return null;
|
|
888
899
|
const now = Date.now();
|
|
889
900
|
if (now - state.savedAt > SESSION_EXPIRE_TIME) {
|
|
890
901
|
debugLog(`[session-store] Session expired for ${accountId}, age: ${Math.round((now - state.savedAt) / 1e3)}s`);
|
|
@@ -958,7 +969,7 @@ function doSaveSession(state) {
|
|
|
958
969
|
...state,
|
|
959
970
|
savedAt: Date.now()
|
|
960
971
|
};
|
|
961
|
-
|
|
972
|
+
privateFileStoreSync(path.dirname(filePath)).writeJson(path.basename(filePath), stateToSave);
|
|
962
973
|
if (legacyPath !== filePath && fs.existsSync(legacyPath)) fs.unlinkSync(legacyPath);
|
|
963
974
|
debugLog(`[session-store] Saved session for ${state.accountId}: sessionId=${state.sessionId}, lastSeq=${state.lastSeq}`);
|
|
964
975
|
} catch (err) {
|
|
@@ -1740,6 +1751,15 @@ var ReconnectState = class {
|
|
|
1740
1751
|
}
|
|
1741
1752
|
};
|
|
1742
1753
|
//#endregion
|
|
1754
|
+
//#region extensions/qqbot/src/engine/gateway/ws-client.ts
|
|
1755
|
+
async function createQQWSClient(options) {
|
|
1756
|
+
const wsAgent = await resolveAmbientNodeProxyAgent();
|
|
1757
|
+
return new WebSocket(options.gatewayUrl, {
|
|
1758
|
+
headers: { "User-Agent": options.userAgent },
|
|
1759
|
+
...wsAgent ? { agent: wsAgent } : {}
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
//#endregion
|
|
1743
1763
|
//#region extensions/qqbot/src/engine/gateway/gateway-connection.ts
|
|
1744
1764
|
/**
|
|
1745
1765
|
* GatewayConnection — WebSocket lifecycle, heartbeat, reconnect, and session persistence.
|
|
@@ -1852,7 +1872,10 @@ var GatewayConnection = class {
|
|
|
1852
1872
|
log?.info(`✅ Access token obtained successfully`);
|
|
1853
1873
|
const gatewayUrl = await getGatewayUrl(accessToken, account.appId);
|
|
1854
1874
|
log?.info(`Connecting to ${gatewayUrl}`);
|
|
1855
|
-
const ws =
|
|
1875
|
+
const ws = await createQQWSClient({
|
|
1876
|
+
gatewayUrl,
|
|
1877
|
+
userAgent: getPluginUserAgent()
|
|
1878
|
+
});
|
|
1856
1879
|
this.currentWs = ws;
|
|
1857
1880
|
const slashCtx = {
|
|
1858
1881
|
account,
|
|
@@ -2026,20 +2049,28 @@ async function transcribeAudio(audioPath, cfg) {
|
|
|
2026
2049
|
if (!sttCfg) return null;
|
|
2027
2050
|
const fileBuffer = fs$1.readFileSync(audioPath);
|
|
2028
2051
|
const fileName = sanitizeFileName(path.basename(audioPath));
|
|
2029
|
-
const mime =
|
|
2052
|
+
const mime = mimeTypeFromFilePath(fileName) ?? "application/octet-stream";
|
|
2030
2053
|
const form = new FormData();
|
|
2031
2054
|
form.append("file", new Blob([fileBuffer], { type: mime }), fileName);
|
|
2032
2055
|
form.append("model", sttCfg.model);
|
|
2033
|
-
const resp = await
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2056
|
+
const { response: resp, release } = await fetchWithSsrFGuard({
|
|
2057
|
+
url: `${sttCfg.baseUrl}/audio/transcriptions`,
|
|
2058
|
+
auditContext: "qqbot-stt",
|
|
2059
|
+
init: {
|
|
2060
|
+
method: "POST",
|
|
2061
|
+
headers: { Authorization: `Bearer ${sttCfg.apiKey}` },
|
|
2062
|
+
body: form
|
|
2063
|
+
}
|
|
2037
2064
|
});
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2065
|
+
try {
|
|
2066
|
+
if (!resp.ok) {
|
|
2067
|
+
const detail = await resp.text().catch(() => "");
|
|
2068
|
+
throw new Error(`STT failed (HTTP ${resp.status}): ${detail.slice(0, 300)}`);
|
|
2069
|
+
}
|
|
2070
|
+
return normalizeOptionalString((await resp.json()).text) ?? null;
|
|
2071
|
+
} finally {
|
|
2072
|
+
await release();
|
|
2041
2073
|
}
|
|
2042
|
-
return normalizeOptionalString((await resp.json()).text) ?? null;
|
|
2043
2074
|
}
|
|
2044
2075
|
//#endregion
|
|
2045
2076
|
//#region extensions/qqbot/src/engine/gateway/inbound-attachments.ts
|
|
@@ -5552,8 +5583,13 @@ function stripAmrHeader(buf) {
|
|
|
5552
5583
|
}
|
|
5553
5584
|
/** Convert a SILK or AMR voice file to WAV format. */
|
|
5554
5585
|
async function convertSilkToWav(inputPath, outputDir) {
|
|
5555
|
-
|
|
5556
|
-
|
|
5586
|
+
let fileBuf;
|
|
5587
|
+
try {
|
|
5588
|
+
fileBuf = readRegularFileSync({ filePath: inputPath }).buffer;
|
|
5589
|
+
} catch {
|
|
5590
|
+
return null;
|
|
5591
|
+
}
|
|
5592
|
+
const strippedBuf = stripAmrHeader(fileBuf);
|
|
5557
5593
|
const rawData = new Uint8Array(strippedBuf.buffer, strippedBuf.byteOffset, strippedBuf.byteLength);
|
|
5558
5594
|
const silk = await loadSilkWasm();
|
|
5559
5595
|
if (!silk || !silk.isSilk(rawData)) return null;
|
|
@@ -5642,8 +5678,12 @@ function normalizeFormats(formats) {
|
|
|
5642
5678
|
* Attempts conversion via direct QQ-native upload → WASM decoders → null fallback chain.
|
|
5643
5679
|
*/
|
|
5644
5680
|
async function audioFileToSilkBase64(filePath, directUploadFormats) {
|
|
5645
|
-
|
|
5646
|
-
|
|
5681
|
+
let buf;
|
|
5682
|
+
try {
|
|
5683
|
+
buf = readRegularFileSync({ filePath }).buffer;
|
|
5684
|
+
} catch {
|
|
5685
|
+
return null;
|
|
5686
|
+
}
|
|
5647
5687
|
if (buf.length === 0) {
|
|
5648
5688
|
debugError(`[audio-convert] file is empty: ${filePath}`);
|
|
5649
5689
|
return null;
|
|
@@ -2,7 +2,7 @@ import { l as ensurePlatformAdapter, u as getBridgeLogger } from "./resolve-D_06
|
|
|
2
2
|
import { a as resolveApprovalTarget, n as buildExecApprovalText, r as buildPluginApprovalText, t as buildApprovalKeyboard } from "./approval-cg0SVahb.js";
|
|
3
3
|
import { a as resolveQQBotAccount } from "./config-D6545NkC.js";
|
|
4
4
|
import { a as resolveQQBotExecApprovalConfig, i as matchesQQBotApprovalAccount, o as shouldHandleQQBotExecApprovalRequest, r as isQQBotExecApprovalClientEnabled } from "./exec-approvals-COUsM6wZ.js";
|
|
5
|
-
import { c as getMessageApi, t as accountToCreds } from "./sender-
|
|
5
|
+
import { c as getMessageApi, t as accountToCreds } from "./sender-CgdXGY2H.js";
|
|
6
6
|
import { resolveApprovalRequestSessionConversation } from "openclaw/plugin-sdk/approval-native-runtime";
|
|
7
7
|
import { createChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-runtime";
|
|
8
8
|
//#region extensions/qqbot/src/bridge/approval/handler-runtime.ts
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { i as normalizeOptionalString, n as normalizeLowercaseStringOrEmpty, s as sanitizeFileName } from "./string-normalize-Ci6NM5DE.js";
|
|
2
|
-
import { A as fileExistsAsync, C as
|
|
3
|
-
import { c as getQQBotMediaDir, d as isLocalPath, m as resolveQQBotPayloadLocalFilePath, o as getQQBotDataDir, p as normalizePath, r as parseTarget$1 } from "./target-parser-
|
|
4
|
-
import
|
|
2
|
+
import { A as fileExistsAsync, C as debugWarn, E as UPLOAD_PREPARE_FALLBACK_CODE, F as readFileAsync, L as formatErrorMessage, M as getFileTypeName, N as getImageMimeType, O as checkFileSize, P as getMaxUploadSize, S as debugLog, g as sendText$1, h as sendMedia$1, j as formatFileSize, k as downloadFile, t as accountToCreds, u as initApiConfig, w as UploadDailyLimitExceededError, x as debugError } from "./sender-CgdXGY2H.js";
|
|
3
|
+
import { c as getQQBotMediaDir, d as isLocalPath, m as resolveQQBotPayloadLocalFilePath, o as getQQBotDataDir, p as normalizePath, r as parseTarget$1 } from "./target-parser-DQ6J3Qe-.js";
|
|
4
|
+
import { pathExistsSync, resolveLocalPathFromRootsSync } from "openclaw/plugin-sdk/security-runtime";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { getFileExtension } from "openclaw/plugin-sdk/media-mime";
|
|
6
7
|
//#region \0rolldown/runtime.js
|
|
7
8
|
var __defProp = Object.defineProperty;
|
|
8
9
|
var __exportAll = (all, no_symbols) => {
|
|
@@ -239,53 +240,22 @@ const qqBotMediaKindLabel = {
|
|
|
239
240
|
function isHttpOrDataSource(pathValue) {
|
|
240
241
|
return pathValue.startsWith("http://") || pathValue.startsWith("https://") || pathValue.startsWith("data:");
|
|
241
242
|
}
|
|
242
|
-
function isPathWithinRoot(candidate, root) {
|
|
243
|
-
const relative = path.relative(root, candidate);
|
|
244
|
-
return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
245
|
-
}
|
|
246
243
|
function resolveMissingPathWithinMediaRoot(normalizedPath) {
|
|
247
244
|
const resolvedCandidate = path.resolve(normalizedPath);
|
|
248
|
-
if (
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
const missingSegments = [];
|
|
257
|
-
let cursor = resolvedCandidate;
|
|
258
|
-
while (!fs.existsSync(cursor)) {
|
|
259
|
-
const parent = path.dirname(cursor);
|
|
260
|
-
if (parent === cursor) break;
|
|
261
|
-
missingSegments.unshift(path.basename(cursor));
|
|
262
|
-
cursor = parent;
|
|
263
|
-
}
|
|
264
|
-
if (!fs.existsSync(cursor)) return null;
|
|
265
|
-
let canonicalCursor;
|
|
266
|
-
try {
|
|
267
|
-
canonicalCursor = fs.realpathSync(cursor);
|
|
268
|
-
} catch {
|
|
269
|
-
return null;
|
|
270
|
-
}
|
|
271
|
-
const canonicalCandidate = missingSegments.length > 0 ? path.join(canonicalCursor, ...missingSegments) : canonicalCursor;
|
|
272
|
-
return isPathWithinRoot(canonicalCandidate, canonicalAllowedRoot) ? canonicalCandidate : null;
|
|
245
|
+
if (pathExistsSync(resolvedCandidate)) return null;
|
|
246
|
+
return resolveLocalPathFromRootsSync({
|
|
247
|
+
filePath: resolvedCandidate,
|
|
248
|
+
roots: [getQQBotMediaDir()],
|
|
249
|
+
label: "QQ Bot media storage",
|
|
250
|
+
allowMissing: true
|
|
251
|
+
})?.path ?? null;
|
|
273
252
|
}
|
|
274
253
|
function resolveExistingPathWithinRoots(normalizedPath, allowedRoots) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
} catch {
|
|
281
|
-
return null;
|
|
282
|
-
}
|
|
283
|
-
for (const root of allowedRoots) {
|
|
284
|
-
const resolvedRoot = path.resolve(root);
|
|
285
|
-
const canonicalRoot = fs.existsSync(resolvedRoot) ? fs.realpathSync(resolvedRoot) : resolvedRoot;
|
|
286
|
-
if (isPathWithinRoot(canonicalCandidate, canonicalRoot)) return canonicalCandidate;
|
|
287
|
-
}
|
|
288
|
-
return null;
|
|
254
|
+
return resolveLocalPathFromRootsSync({
|
|
255
|
+
filePath: normalizedPath,
|
|
256
|
+
roots: allowedRoots,
|
|
257
|
+
label: "QQ Bot local roots"
|
|
258
|
+
})?.path ?? null;
|
|
289
259
|
}
|
|
290
260
|
function resolveOutboundMediaPath(rawPath, mediaKind, options = {}) {
|
|
291
261
|
const normalizedPath = normalizePath(rawPath);
|
|
@@ -1017,24 +987,15 @@ const VIDEO_EXTENSIONS = new Set([
|
|
|
1017
987
|
".flv",
|
|
1018
988
|
".wmv"
|
|
1019
989
|
]);
|
|
1020
|
-
/**
|
|
1021
|
-
* Extract a lowercase file extension from a path or URL, ignoring query and hash.
|
|
1022
|
-
*/
|
|
1023
|
-
function getCleanExtension(filePath) {
|
|
1024
|
-
const cleanPath = filePath.split("?")[0].split("#")[0];
|
|
1025
|
-
const lastDot = cleanPath.lastIndexOf(".");
|
|
1026
|
-
if (lastDot < 0) return "";
|
|
1027
|
-
return cleanPath.slice(lastDot).toLowerCase();
|
|
1028
|
-
}
|
|
1029
990
|
/** Check whether a file is an image using MIME first and extension as fallback. */
|
|
1030
991
|
function isImageFile$1(filePath, mimeType) {
|
|
1031
992
|
if (mimeType?.startsWith("image/")) return true;
|
|
1032
|
-
return IMAGE_EXTENSIONS.has(
|
|
993
|
+
return IMAGE_EXTENSIONS.has(getFileExtension(filePath) ?? "");
|
|
1033
994
|
}
|
|
1034
995
|
/** Check whether a file is a video using MIME first and extension as fallback. */
|
|
1035
996
|
function isVideoFile$1(filePath, mimeType) {
|
|
1036
997
|
if (mimeType?.startsWith("video/")) return true;
|
|
1037
|
-
return VIDEO_EXTENSIONS.has(
|
|
998
|
+
return VIDEO_EXTENSIONS.has(getFileExtension(filePath) ?? "");
|
|
1038
999
|
}
|
|
1039
1000
|
//#endregion
|
|
1040
1001
|
//#region extensions/qqbot/src/engine/messaging/outbound.ts
|