@openclaw-china/qqbot 2026.3.10 → 2026.3.12
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/index.d.ts +32 -1
- package/dist/index.js +1480 -129
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +4 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { homedir, tmpdir } from 'os';
|
|
|
3
3
|
import * as path2 from 'path';
|
|
4
4
|
import { join, dirname } from 'path';
|
|
5
5
|
import * as fs3 from 'fs';
|
|
6
|
-
import { existsSync, readFileSync, rmSync, writeFileSync, mkdirSync } from 'fs';
|
|
6
|
+
import { existsSync, readFileSync, rmSync, writeFileSync, renameSync, copyFileSync, mkdirSync, appendFileSync } from 'fs';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import * as fsPromises from 'fs/promises';
|
|
9
9
|
import { createHmac } from 'crypto';
|
|
@@ -14,6 +14,7 @@ import 'util';
|
|
|
14
14
|
import { createRequire } from 'module';
|
|
15
15
|
import { execFileSync } from 'child_process';
|
|
16
16
|
import WebSocket from 'ws';
|
|
17
|
+
import { Buffer as Buffer$1 } from 'buffer';
|
|
17
18
|
|
|
18
19
|
var __create = Object.create;
|
|
19
20
|
var __defProp = Object.defineProperty;
|
|
@@ -650,8 +651,8 @@ function getErrorMap() {
|
|
|
650
651
|
|
|
651
652
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
|
|
652
653
|
var makeIssue = (params) => {
|
|
653
|
-
const { data, path:
|
|
654
|
-
const fullPath = [...
|
|
654
|
+
const { data, path: path5, errorMaps, issueData } = params;
|
|
655
|
+
const fullPath = [...path5, ...issueData.path || []];
|
|
655
656
|
const fullIssue = {
|
|
656
657
|
...issueData,
|
|
657
658
|
path: fullPath
|
|
@@ -767,11 +768,11 @@ var errorUtil;
|
|
|
767
768
|
|
|
768
769
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
|
|
769
770
|
var ParseInputLazyPath = class {
|
|
770
|
-
constructor(parent, value,
|
|
771
|
+
constructor(parent, value, path5, key) {
|
|
771
772
|
this._cachedPath = [];
|
|
772
773
|
this.parent = parent;
|
|
773
774
|
this.data = value;
|
|
774
|
-
this._path =
|
|
775
|
+
this._path = path5;
|
|
775
776
|
this._key = key;
|
|
776
777
|
}
|
|
777
778
|
get path() {
|
|
@@ -4222,6 +4223,7 @@ var optionalCoercedString = external_exports.preprocess(
|
|
|
4222
4223
|
(value) => toTrimmedString(value),
|
|
4223
4224
|
external_exports.string().min(1).optional()
|
|
4224
4225
|
);
|
|
4226
|
+
var QQBotC2CMarkdownDeliveryModeSchema = external_exports.enum(["passive", "proactive-table-only", "proactive-all"]).optional().default("proactive-table-only");
|
|
4225
4227
|
var QQBotAccountSchema = external_exports.object({
|
|
4226
4228
|
name: external_exports.string().optional(),
|
|
4227
4229
|
enabled: external_exports.boolean().optional(),
|
|
@@ -4234,6 +4236,7 @@ var QQBotAccountSchema = external_exports.object({
|
|
|
4234
4236
|
secretKey: optionalCoercedString
|
|
4235
4237
|
}).optional(),
|
|
4236
4238
|
markdownSupport: external_exports.boolean().optional().default(true),
|
|
4239
|
+
c2cMarkdownDeliveryMode: QQBotC2CMarkdownDeliveryModeSchema,
|
|
4237
4240
|
dmPolicy: external_exports.enum(["open", "pairing", "allowlist"]).optional().default("open"),
|
|
4238
4241
|
groupPolicy: external_exports.enum(["open", "allowlist", "disabled"]).optional().default("open"),
|
|
4239
4242
|
requireMention: external_exports.boolean().optional().default(true),
|
|
@@ -4873,7 +4876,7 @@ function extractMediaFromText(text, options = {}) {
|
|
|
4873
4876
|
const {
|
|
4874
4877
|
removeFromText = true,
|
|
4875
4878
|
checkExists = false,
|
|
4876
|
-
existsSync:
|
|
4879
|
+
existsSync: existsSync7,
|
|
4877
4880
|
parseMediaLines = false,
|
|
4878
4881
|
parseMarkdownImages = true,
|
|
4879
4882
|
parseHtmlImages = true,
|
|
@@ -4888,7 +4891,7 @@ function extractMediaFromText(text, options = {}) {
|
|
|
4888
4891
|
const key = media.localPath || media.source;
|
|
4889
4892
|
if (seenSources.has(key)) return false;
|
|
4890
4893
|
if (checkExists && media.isLocal && media.localPath) {
|
|
4891
|
-
const exists =
|
|
4894
|
+
const exists = existsSync7 ? existsSync7(media.localPath) : fs3.existsSync(media.localPath);
|
|
4892
4895
|
if (!exists) return false;
|
|
4893
4896
|
}
|
|
4894
4897
|
seenSources.add(key);
|
|
@@ -7255,9 +7258,11 @@ function showChinaInstallHint(api) {
|
|
|
7255
7258
|
var API_BASE = "https://api.sgroup.qq.com";
|
|
7256
7259
|
var TOKEN_URL = "https://bots.qq.com/app/getAppAccessToken";
|
|
7257
7260
|
var MSG_SEQ_BASE = 1e6;
|
|
7261
|
+
var MAX_DUPLICATE_MSG_SEQ_RETRIES = 5;
|
|
7258
7262
|
var tokenCacheMap = /* @__PURE__ */ new Map();
|
|
7259
7263
|
var tokenPromiseMap = /* @__PURE__ */ new Map();
|
|
7260
7264
|
var msgSeqMap = /* @__PURE__ */ new Map();
|
|
7265
|
+
var fallbackMsgSeq = 0;
|
|
7261
7266
|
function toTrimmedString3(value) {
|
|
7262
7267
|
if (value === void 0 || value === null) return void 0;
|
|
7263
7268
|
const next = String(value).trim();
|
|
@@ -7277,7 +7282,10 @@ function sanitizeUploadFileName(fileName) {
|
|
|
7277
7282
|
return normalized || "file";
|
|
7278
7283
|
}
|
|
7279
7284
|
function nextMsgSeq(sequenceKey) {
|
|
7280
|
-
if (!sequenceKey)
|
|
7285
|
+
if (!sequenceKey) {
|
|
7286
|
+
fallbackMsgSeq += 1;
|
|
7287
|
+
return MSG_SEQ_BASE + fallbackMsgSeq;
|
|
7288
|
+
}
|
|
7281
7289
|
const current = msgSeqMap.get(sequenceKey) ?? 0;
|
|
7282
7290
|
const next = current + 1;
|
|
7283
7291
|
msgSeqMap.set(sequenceKey, next);
|
|
@@ -7289,6 +7297,46 @@ function nextMsgSeq(sequenceKey) {
|
|
|
7289
7297
|
}
|
|
7290
7298
|
return MSG_SEQ_BASE + next;
|
|
7291
7299
|
}
|
|
7300
|
+
function resolveMsgSeqKey(messageId, eventId) {
|
|
7301
|
+
if (messageId) return `msg:${messageId}`;
|
|
7302
|
+
if (eventId) return `event:${eventId}`;
|
|
7303
|
+
return void 0;
|
|
7304
|
+
}
|
|
7305
|
+
function isDuplicateMsgSeqError(err) {
|
|
7306
|
+
if (!(err instanceof HttpError) || err.status !== 400) {
|
|
7307
|
+
return false;
|
|
7308
|
+
}
|
|
7309
|
+
const body = err.body?.trim();
|
|
7310
|
+
if (!body) {
|
|
7311
|
+
return false;
|
|
7312
|
+
}
|
|
7313
|
+
try {
|
|
7314
|
+
const parsed = JSON.parse(body);
|
|
7315
|
+
if (parsed.code === 40054005 || parsed.err_code === 40054005) {
|
|
7316
|
+
return true;
|
|
7317
|
+
}
|
|
7318
|
+
const message = typeof parsed.message === "string" ? parsed.message.toLowerCase() : "";
|
|
7319
|
+
return message.includes("msgseq") && (message.includes("\u53BB\u91CD") || message.includes("duplicate"));
|
|
7320
|
+
} catch {
|
|
7321
|
+
const lowered = body.toLowerCase();
|
|
7322
|
+
return lowered.includes("msgseq") && (lowered.includes("\u53BB\u91CD") || lowered.includes("duplicate"));
|
|
7323
|
+
}
|
|
7324
|
+
}
|
|
7325
|
+
async function postPassiveMessage(params) {
|
|
7326
|
+
let lastError;
|
|
7327
|
+
for (let attempt = 0; attempt <= MAX_DUPLICATE_MSG_SEQ_RETRIES; attempt += 1) {
|
|
7328
|
+
const msgSeq = nextMsgSeq(params.sequenceKey);
|
|
7329
|
+
try {
|
|
7330
|
+
return await apiPost(params.accessToken, params.path, params.buildBody(msgSeq), params.options);
|
|
7331
|
+
} catch (err) {
|
|
7332
|
+
lastError = err;
|
|
7333
|
+
if (!isDuplicateMsgSeqError(err) || attempt === MAX_DUPLICATE_MSG_SEQ_RETRIES) {
|
|
7334
|
+
throw err;
|
|
7335
|
+
}
|
|
7336
|
+
}
|
|
7337
|
+
}
|
|
7338
|
+
throw lastError;
|
|
7339
|
+
}
|
|
7292
7340
|
function clearTokenCache(appId) {
|
|
7293
7341
|
const normalizedAppId = toTrimmedString3(appId);
|
|
7294
7342
|
if (normalizedAppId) {
|
|
@@ -7332,8 +7380,8 @@ async function getAccessToken(appId, clientSecret, options) {
|
|
|
7332
7380
|
tokenPromiseMap.set(normalizedAppId, promise);
|
|
7333
7381
|
return promise;
|
|
7334
7382
|
}
|
|
7335
|
-
async function apiGet(accessToken,
|
|
7336
|
-
const url = `${API_BASE}${
|
|
7383
|
+
async function apiGet(accessToken, path5, options) {
|
|
7384
|
+
const url = `${API_BASE}${path5}`;
|
|
7337
7385
|
return httpGet(url, {
|
|
7338
7386
|
...options,
|
|
7339
7387
|
headers: {
|
|
@@ -7342,8 +7390,8 @@ async function apiGet(accessToken, path4, options) {
|
|
|
7342
7390
|
}
|
|
7343
7391
|
});
|
|
7344
7392
|
}
|
|
7345
|
-
async function apiPost(accessToken,
|
|
7346
|
-
const url = `${API_BASE}${
|
|
7393
|
+
async function apiPost(accessToken, path5, body, options) {
|
|
7394
|
+
const url = `${API_BASE}${path5}`;
|
|
7347
7395
|
return httpPost(url, body, {
|
|
7348
7396
|
...options,
|
|
7349
7397
|
headers: {
|
|
@@ -7357,16 +7405,11 @@ async function getGatewayUrl(accessToken) {
|
|
|
7357
7405
|
return data.url;
|
|
7358
7406
|
}
|
|
7359
7407
|
function buildMessageBody(params) {
|
|
7360
|
-
const
|
|
7361
|
-
const body = params.markdown ? {
|
|
7362
|
-
markdown: { content: params.content },
|
|
7363
|
-
msg_type: 2,
|
|
7364
|
-
msg_seq: msgSeq
|
|
7365
|
-
} : {
|
|
7408
|
+
const body = buildTextMessageBody({
|
|
7366
7409
|
content: params.content,
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
|
|
7410
|
+
markdown: params.markdown
|
|
7411
|
+
});
|
|
7412
|
+
body.msg_seq = params.msgSeq;
|
|
7370
7413
|
if (params.messageId) {
|
|
7371
7414
|
body.msg_id = params.messageId;
|
|
7372
7415
|
} else if (params.eventId) {
|
|
@@ -7374,22 +7417,63 @@ function buildMessageBody(params) {
|
|
|
7374
7417
|
}
|
|
7375
7418
|
return body;
|
|
7376
7419
|
}
|
|
7420
|
+
function buildTextMessageBody(params) {
|
|
7421
|
+
return params.markdown ? {
|
|
7422
|
+
markdown: { content: params.content },
|
|
7423
|
+
msg_type: 2
|
|
7424
|
+
} : {
|
|
7425
|
+
content: params.content,
|
|
7426
|
+
msg_type: 0
|
|
7427
|
+
};
|
|
7428
|
+
}
|
|
7429
|
+
function buildProactiveMessageBody(params) {
|
|
7430
|
+
if (!params.content.trim()) {
|
|
7431
|
+
throw new Error("QQBot proactive message content is empty");
|
|
7432
|
+
}
|
|
7433
|
+
return buildTextMessageBody(params);
|
|
7434
|
+
}
|
|
7377
7435
|
async function sendC2CMessage(params) {
|
|
7378
|
-
|
|
7436
|
+
return postPassiveMessage({
|
|
7437
|
+
accessToken: params.accessToken,
|
|
7438
|
+
path: `/v2/users/${params.openid}/messages`,
|
|
7439
|
+
sequenceKey: resolveMsgSeqKey(params.messageId, params.eventId),
|
|
7440
|
+
options: { timeout: 15e3 },
|
|
7441
|
+
buildBody: (msgSeq) => buildMessageBody({
|
|
7442
|
+
content: params.content,
|
|
7443
|
+
messageId: params.messageId,
|
|
7444
|
+
eventId: params.eventId,
|
|
7445
|
+
markdown: params.markdown,
|
|
7446
|
+
msgSeq
|
|
7447
|
+
})
|
|
7448
|
+
});
|
|
7449
|
+
}
|
|
7450
|
+
async function sendGroupMessage(params) {
|
|
7451
|
+
return postPassiveMessage({
|
|
7452
|
+
accessToken: params.accessToken,
|
|
7453
|
+
path: `/v2/groups/${params.groupOpenid}/messages`,
|
|
7454
|
+
sequenceKey: resolveMsgSeqKey(params.messageId, params.eventId),
|
|
7455
|
+
options: { timeout: 15e3 },
|
|
7456
|
+
buildBody: (msgSeq) => buildMessageBody({
|
|
7457
|
+
content: params.content,
|
|
7458
|
+
messageId: params.messageId,
|
|
7459
|
+
eventId: params.eventId,
|
|
7460
|
+
markdown: params.markdown,
|
|
7461
|
+
msgSeq
|
|
7462
|
+
})
|
|
7463
|
+
});
|
|
7464
|
+
}
|
|
7465
|
+
async function sendProactiveC2CMessage(params) {
|
|
7466
|
+
const body = buildProactiveMessageBody({
|
|
7379
7467
|
content: params.content,
|
|
7380
|
-
messageId: params.messageId,
|
|
7381
|
-
eventId: params.eventId,
|
|
7382
7468
|
markdown: params.markdown
|
|
7383
7469
|
});
|
|
7384
7470
|
return apiPost(params.accessToken, `/v2/users/${params.openid}/messages`, body, {
|
|
7385
7471
|
timeout: 15e3
|
|
7386
7472
|
});
|
|
7387
7473
|
}
|
|
7388
|
-
async function
|
|
7389
|
-
const body =
|
|
7474
|
+
async function sendProactiveGroupMessage(params) {
|
|
7475
|
+
const body = buildProactiveMessageBody({
|
|
7390
7476
|
content: params.content,
|
|
7391
|
-
messageId: params.messageId,
|
|
7392
|
-
eventId: params.eventId,
|
|
7393
7477
|
markdown: params.markdown
|
|
7394
7478
|
});
|
|
7395
7479
|
return apiPost(params.accessToken, `/v2/groups/${params.groupOpenid}/messages`, body, {
|
|
@@ -7406,11 +7490,12 @@ async function sendChannelMessage(params) {
|
|
|
7406
7490
|
});
|
|
7407
7491
|
}
|
|
7408
7492
|
async function sendC2CInputNotify(params) {
|
|
7409
|
-
const
|
|
7410
|
-
|
|
7411
|
-
params.
|
|
7412
|
-
|
|
7413
|
-
{
|
|
7493
|
+
const response = await postPassiveMessage({
|
|
7494
|
+
accessToken: params.accessToken,
|
|
7495
|
+
path: `/v2/users/${params.openid}/messages`,
|
|
7496
|
+
sequenceKey: resolveMsgSeqKey(params.messageId, params.eventId),
|
|
7497
|
+
options: { timeout: 15e3 },
|
|
7498
|
+
buildBody: (msgSeq) => ({
|
|
7414
7499
|
msg_type: 6,
|
|
7415
7500
|
input_notify: {
|
|
7416
7501
|
input_type: 1,
|
|
@@ -7418,9 +7503,10 @@ async function sendC2CInputNotify(params) {
|
|
|
7418
7503
|
},
|
|
7419
7504
|
msg_seq: msgSeq,
|
|
7420
7505
|
...params.messageId ? { msg_id: params.messageId } : params.eventId ? { event_id: params.eventId } : {}
|
|
7421
|
-
}
|
|
7422
|
-
|
|
7423
|
-
);
|
|
7506
|
+
})
|
|
7507
|
+
});
|
|
7508
|
+
const refIdx = response.ext_info?.ref_idx?.trim();
|
|
7509
|
+
return refIdx ? { refIdx } : {};
|
|
7424
7510
|
}
|
|
7425
7511
|
async function uploadC2CMedia(params) {
|
|
7426
7512
|
const body = {
|
|
@@ -7461,34 +7547,261 @@ async function uploadGroupMedia(params) {
|
|
|
7461
7547
|
});
|
|
7462
7548
|
}
|
|
7463
7549
|
async function sendC2CMediaMessage(params) {
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
params.
|
|
7467
|
-
|
|
7468
|
-
{
|
|
7550
|
+
return postPassiveMessage({
|
|
7551
|
+
accessToken: params.accessToken,
|
|
7552
|
+
path: `/v2/users/${params.openid}/messages`,
|
|
7553
|
+
sequenceKey: resolveMsgSeqKey(params.messageId, params.eventId),
|
|
7554
|
+
options: { timeout: 15e3 },
|
|
7555
|
+
buildBody: (msgSeq) => ({
|
|
7469
7556
|
msg_type: 7,
|
|
7470
7557
|
media: { file_info: params.fileInfo },
|
|
7471
7558
|
msg_seq: msgSeq,
|
|
7472
7559
|
...params.content ? { content: params.content } : {},
|
|
7473
7560
|
...params.messageId ? { msg_id: params.messageId } : params.eventId ? { event_id: params.eventId } : {}
|
|
7474
|
-
}
|
|
7475
|
-
|
|
7476
|
-
);
|
|
7561
|
+
})
|
|
7562
|
+
});
|
|
7477
7563
|
}
|
|
7478
7564
|
async function sendGroupMediaMessage(params) {
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
params.
|
|
7482
|
-
|
|
7483
|
-
{
|
|
7565
|
+
return postPassiveMessage({
|
|
7566
|
+
accessToken: params.accessToken,
|
|
7567
|
+
path: `/v2/groups/${params.groupOpenid}/messages`,
|
|
7568
|
+
sequenceKey: resolveMsgSeqKey(params.messageId, params.eventId),
|
|
7569
|
+
options: { timeout: 15e3 },
|
|
7570
|
+
buildBody: (msgSeq) => ({
|
|
7484
7571
|
msg_type: 7,
|
|
7485
7572
|
media: { file_info: params.fileInfo },
|
|
7486
7573
|
msg_seq: msgSeq,
|
|
7487
7574
|
...params.content ? { content: params.content } : {},
|
|
7488
7575
|
...params.messageId ? { msg_id: params.messageId } : params.eventId ? { event_id: params.eventId } : {}
|
|
7489
|
-
}
|
|
7490
|
-
|
|
7491
|
-
|
|
7576
|
+
})
|
|
7577
|
+
});
|
|
7578
|
+
}
|
|
7579
|
+
var REF_INDEX_FILE = join(homedir(), ".openclaw", "qqbot", "data", "ref-index.jsonl");
|
|
7580
|
+
var MAX_CONTENT_LENGTH = 500;
|
|
7581
|
+
var MAX_ENTRIES = 5e4;
|
|
7582
|
+
var TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
7583
|
+
var COMPACT_THRESHOLD_RATIO = 2;
|
|
7584
|
+
var cache = null;
|
|
7585
|
+
var totalLinesOnDisk = 0;
|
|
7586
|
+
function normalizeRefIdx(refIdx) {
|
|
7587
|
+
const next = refIdx.trim();
|
|
7588
|
+
return next ? next : void 0;
|
|
7589
|
+
}
|
|
7590
|
+
function ensureStorageDir() {
|
|
7591
|
+
mkdirSync(dirname(REF_INDEX_FILE), { recursive: true });
|
|
7592
|
+
}
|
|
7593
|
+
function truncateContent(content) {
|
|
7594
|
+
return content.trim().slice(0, MAX_CONTENT_LENGTH);
|
|
7595
|
+
}
|
|
7596
|
+
function sanitizeAttachmentSummary(attachment) {
|
|
7597
|
+
const type = attachment.type;
|
|
7598
|
+
const filename = attachment.filename?.trim();
|
|
7599
|
+
const contentType = attachment.contentType?.trim();
|
|
7600
|
+
const localPath = attachment.localPath?.trim();
|
|
7601
|
+
const url = attachment.url?.trim();
|
|
7602
|
+
const transcript = attachment.transcript?.trim();
|
|
7603
|
+
if (!filename && !contentType && !localPath && !url && !transcript && type === "unknown") {
|
|
7604
|
+
return void 0;
|
|
7605
|
+
}
|
|
7606
|
+
return {
|
|
7607
|
+
type,
|
|
7608
|
+
...filename ? { filename } : {},
|
|
7609
|
+
...contentType ? { contentType } : {},
|
|
7610
|
+
...localPath ? { localPath } : {},
|
|
7611
|
+
...url ? { url } : {},
|
|
7612
|
+
...transcript ? { transcript } : {},
|
|
7613
|
+
...transcript && attachment.transcriptSource ? { transcriptSource: attachment.transcriptSource } : {}
|
|
7614
|
+
};
|
|
7615
|
+
}
|
|
7616
|
+
function sanitizeEntry(entry) {
|
|
7617
|
+
const senderId = entry.senderId.trim() || "unknown";
|
|
7618
|
+
const senderName = entry.senderName?.trim();
|
|
7619
|
+
const timestamp = Number.isFinite(entry.timestamp) ? Math.trunc(entry.timestamp) : Date.now();
|
|
7620
|
+
const attachments = entry.attachments?.map((attachment) => sanitizeAttachmentSummary(attachment)).filter((attachment) => Boolean(attachment));
|
|
7621
|
+
return {
|
|
7622
|
+
content: truncateContent(entry.content),
|
|
7623
|
+
senderId,
|
|
7624
|
+
...senderName ? { senderName } : {},
|
|
7625
|
+
timestamp,
|
|
7626
|
+
...entry.isBot ? { isBot: true } : {},
|
|
7627
|
+
...attachments && attachments.length > 0 ? { attachments } : {}
|
|
7628
|
+
};
|
|
7629
|
+
}
|
|
7630
|
+
function shouldCompact() {
|
|
7631
|
+
if (!cache) return false;
|
|
7632
|
+
return totalLinesOnDisk > cache.size * COMPACT_THRESHOLD_RATIO && totalLinesOnDisk > 1e3;
|
|
7633
|
+
}
|
|
7634
|
+
function compactFile() {
|
|
7635
|
+
if (!cache) return;
|
|
7636
|
+
try {
|
|
7637
|
+
ensureStorageDir();
|
|
7638
|
+
const tempPath = `${REF_INDEX_FILE}.tmp`;
|
|
7639
|
+
const lines = [];
|
|
7640
|
+
for (const [key, entry] of cache.entries()) {
|
|
7641
|
+
lines.push(
|
|
7642
|
+
JSON.stringify({
|
|
7643
|
+
k: key,
|
|
7644
|
+
v: sanitizeEntry(entry),
|
|
7645
|
+
t: entry._createdAt
|
|
7646
|
+
})
|
|
7647
|
+
);
|
|
7648
|
+
}
|
|
7649
|
+
writeFileSync(tempPath, lines.length > 0 ? `${lines.join("\n")}
|
|
7650
|
+
` : "", "utf8");
|
|
7651
|
+
renameSync(tempPath, REF_INDEX_FILE);
|
|
7652
|
+
totalLinesOnDisk = cache.size;
|
|
7653
|
+
} catch {
|
|
7654
|
+
}
|
|
7655
|
+
}
|
|
7656
|
+
function evictIfNeeded() {
|
|
7657
|
+
if (!cache || cache.size < MAX_ENTRIES) return;
|
|
7658
|
+
const now = Date.now();
|
|
7659
|
+
for (const [key, entry] of cache.entries()) {
|
|
7660
|
+
if (now - entry._createdAt > TTL_MS) {
|
|
7661
|
+
cache.delete(key);
|
|
7662
|
+
}
|
|
7663
|
+
}
|
|
7664
|
+
if (cache.size < MAX_ENTRIES) {
|
|
7665
|
+
return;
|
|
7666
|
+
}
|
|
7667
|
+
const sorted = [...cache.entries()].sort((left, right) => left[1]._createdAt - right[1]._createdAt);
|
|
7668
|
+
const removeCount = cache.size - MAX_ENTRIES + 1;
|
|
7669
|
+
for (let index = 0; index < removeCount; index += 1) {
|
|
7670
|
+
const key = sorted[index]?.[0];
|
|
7671
|
+
if (key) {
|
|
7672
|
+
cache.delete(key);
|
|
7673
|
+
}
|
|
7674
|
+
}
|
|
7675
|
+
}
|
|
7676
|
+
function loadCache() {
|
|
7677
|
+
if (cache) {
|
|
7678
|
+
return cache;
|
|
7679
|
+
}
|
|
7680
|
+
cache = /* @__PURE__ */ new Map();
|
|
7681
|
+
totalLinesOnDisk = 0;
|
|
7682
|
+
try {
|
|
7683
|
+
if (!existsSync(REF_INDEX_FILE)) {
|
|
7684
|
+
return cache;
|
|
7685
|
+
}
|
|
7686
|
+
const now = Date.now();
|
|
7687
|
+
const raw = readFileSync(REF_INDEX_FILE, "utf8");
|
|
7688
|
+
const lines = raw.split(/\r?\n/);
|
|
7689
|
+
for (const line of lines) {
|
|
7690
|
+
const trimmed = line.trim();
|
|
7691
|
+
if (!trimmed) continue;
|
|
7692
|
+
totalLinesOnDisk += 1;
|
|
7693
|
+
try {
|
|
7694
|
+
const parsed = JSON.parse(trimmed);
|
|
7695
|
+
const key = typeof parsed.k === "string" ? normalizeRefIdx(parsed.k) : void 0;
|
|
7696
|
+
const createdAt = typeof parsed.t === "number" && Number.isFinite(parsed.t) ? parsed.t : void 0;
|
|
7697
|
+
if (!key || createdAt === void 0 || !parsed.v || typeof parsed.v !== "object") {
|
|
7698
|
+
continue;
|
|
7699
|
+
}
|
|
7700
|
+
if (now - createdAt > TTL_MS) {
|
|
7701
|
+
continue;
|
|
7702
|
+
}
|
|
7703
|
+
const entry = sanitizeEntry(parsed.v);
|
|
7704
|
+
cache.set(key, {
|
|
7705
|
+
...entry,
|
|
7706
|
+
_createdAt: createdAt
|
|
7707
|
+
});
|
|
7708
|
+
} catch {
|
|
7709
|
+
}
|
|
7710
|
+
}
|
|
7711
|
+
if (shouldCompact()) {
|
|
7712
|
+
compactFile();
|
|
7713
|
+
}
|
|
7714
|
+
} catch {
|
|
7715
|
+
cache = /* @__PURE__ */ new Map();
|
|
7716
|
+
totalLinesOnDisk = 0;
|
|
7717
|
+
}
|
|
7718
|
+
return cache;
|
|
7719
|
+
}
|
|
7720
|
+
function appendLine(line) {
|
|
7721
|
+
try {
|
|
7722
|
+
ensureStorageDir();
|
|
7723
|
+
appendFileSync(REF_INDEX_FILE, `${JSON.stringify(line)}
|
|
7724
|
+
`, "utf8");
|
|
7725
|
+
totalLinesOnDisk += 1;
|
|
7726
|
+
} catch {
|
|
7727
|
+
}
|
|
7728
|
+
}
|
|
7729
|
+
function restoreEntry(entry) {
|
|
7730
|
+
const sanitized = sanitizeEntry(entry);
|
|
7731
|
+
return {
|
|
7732
|
+
content: sanitized.content,
|
|
7733
|
+
senderId: sanitized.senderId,
|
|
7734
|
+
...sanitized.senderName ? { senderName: sanitized.senderName } : {},
|
|
7735
|
+
timestamp: sanitized.timestamp,
|
|
7736
|
+
...sanitized.isBot ? { isBot: true } : {},
|
|
7737
|
+
...sanitized.attachments ? { attachments: sanitized.attachments } : {}
|
|
7738
|
+
};
|
|
7739
|
+
}
|
|
7740
|
+
function formatAttachmentSummary(attachment) {
|
|
7741
|
+
const sourceParts = [
|
|
7742
|
+
attachment.localPath?.trim() ? `\u672C\u5730: ${attachment.localPath.trim()}` : void 0,
|
|
7743
|
+
attachment.url?.trim() ? `\u94FE\u63A5: ${attachment.url.trim()}` : void 0
|
|
7744
|
+
].filter((value) => Boolean(value));
|
|
7745
|
+
const sourceSuffix = sourceParts.length > 0 ? ` (${sourceParts.join(" | ")})` : "";
|
|
7746
|
+
if (attachment.type === "image") {
|
|
7747
|
+
return `[\u56FE\u7247${attachment.filename?.trim() ? `: ${attachment.filename.trim()}` : ""}]${sourceSuffix}`;
|
|
7748
|
+
}
|
|
7749
|
+
if (attachment.type === "voice") {
|
|
7750
|
+
if (attachment.transcript?.trim()) {
|
|
7751
|
+
const sourceLabel = attachment.transcriptSource === "asr" ? "\u5B98\u65B9\u8BC6\u522B" : attachment.transcriptSource === "stt" ? "\u672C\u5730\u8BC6\u522B" : attachment.transcriptSource === "tts" ? "TTS \u539F\u6587" : attachment.transcriptSource === "fallback" ? "\u515C\u5E95\u6587\u672C" : void 0;
|
|
7752
|
+
return `[\u8BED\u97F3\u6D88\u606F: ${attachment.transcript.trim()}${sourceLabel ? ` (${sourceLabel})` : ""}]${sourceSuffix}`;
|
|
7753
|
+
}
|
|
7754
|
+
return `[\u8BED\u97F3\u6D88\u606F]${sourceSuffix}`;
|
|
7755
|
+
}
|
|
7756
|
+
if (attachment.type === "video") {
|
|
7757
|
+
return `[\u89C6\u9891${attachment.filename?.trim() ? `: ${attachment.filename.trim()}` : ""}]${sourceSuffix}`;
|
|
7758
|
+
}
|
|
7759
|
+
if (attachment.type === "file") {
|
|
7760
|
+
return `[\u6587\u4EF6${attachment.filename?.trim() ? `: ${attachment.filename.trim()}` : ""}]${sourceSuffix}`;
|
|
7761
|
+
}
|
|
7762
|
+
return `[\u9644\u4EF6${attachment.filename?.trim() ? `: ${attachment.filename.trim()}` : ""}]${sourceSuffix}`;
|
|
7763
|
+
}
|
|
7764
|
+
function setRefIndex(refIdx, entry) {
|
|
7765
|
+
const key = normalizeRefIdx(refIdx);
|
|
7766
|
+
if (!key) return;
|
|
7767
|
+
const store = loadCache();
|
|
7768
|
+
evictIfNeeded();
|
|
7769
|
+
const nextEntry = sanitizeEntry(entry);
|
|
7770
|
+
const createdAt = Date.now();
|
|
7771
|
+
store.set(key, {
|
|
7772
|
+
...nextEntry,
|
|
7773
|
+
_createdAt: createdAt
|
|
7774
|
+
});
|
|
7775
|
+
appendLine({
|
|
7776
|
+
k: key,
|
|
7777
|
+
v: nextEntry,
|
|
7778
|
+
t: createdAt
|
|
7779
|
+
});
|
|
7780
|
+
if (shouldCompact()) {
|
|
7781
|
+
compactFile();
|
|
7782
|
+
}
|
|
7783
|
+
}
|
|
7784
|
+
function getRefIndex(refIdx) {
|
|
7785
|
+
const key = normalizeRefIdx(refIdx);
|
|
7786
|
+
if (!key) return null;
|
|
7787
|
+
const store = loadCache();
|
|
7788
|
+
const entry = store.get(key);
|
|
7789
|
+
if (!entry) {
|
|
7790
|
+
return null;
|
|
7791
|
+
}
|
|
7792
|
+
if (Date.now() - entry._createdAt > TTL_MS) {
|
|
7793
|
+
store.delete(key);
|
|
7794
|
+
return null;
|
|
7795
|
+
}
|
|
7796
|
+
return restoreEntry(entry);
|
|
7797
|
+
}
|
|
7798
|
+
function formatRefEntryForAgent(entry) {
|
|
7799
|
+
const content = entry.content.trim();
|
|
7800
|
+
const parts = content ? [content] : [];
|
|
7801
|
+
for (const attachment of entry.attachments ?? []) {
|
|
7802
|
+
parts.push(formatAttachmentSummary(attachment));
|
|
7803
|
+
}
|
|
7804
|
+
return parts.join("\n") || "[\u7A7A\u6D88\u606F]";
|
|
7492
7805
|
}
|
|
7493
7806
|
var require2 = createRequire(import.meta.url);
|
|
7494
7807
|
function resolveQQBotMediaFileType(fileName) {
|
|
@@ -7645,7 +7958,7 @@ async function sendFileQQBot(params) {
|
|
|
7645
7958
|
}
|
|
7646
7959
|
}
|
|
7647
7960
|
try {
|
|
7648
|
-
|
|
7961
|
+
const result = await sendC2CMediaMessage({
|
|
7649
7962
|
accessToken,
|
|
7650
7963
|
openid: target.id,
|
|
7651
7964
|
fileInfo,
|
|
@@ -7653,6 +7966,12 @@ async function sendFileQQBot(params) {
|
|
|
7653
7966
|
...messageId ? { messageId } : {},
|
|
7654
7967
|
...eventId ? { eventId } : {}
|
|
7655
7968
|
});
|
|
7969
|
+
const refIdx = result.ext_info?.ref_idx?.trim();
|
|
7970
|
+
return {
|
|
7971
|
+
id: result.id,
|
|
7972
|
+
timestamp: result.timestamp,
|
|
7973
|
+
...refIdx ? { refIdx } : {}
|
|
7974
|
+
};
|
|
7656
7975
|
} catch (err) {
|
|
7657
7976
|
const message = formatQQBotError(err);
|
|
7658
7977
|
throw new Error(`QQBot C2C media send failed: ${message}`);
|
|
@@ -7741,6 +8060,14 @@ function logEventIdFallback(params) {
|
|
|
7741
8060
|
}
|
|
7742
8061
|
console.info(detail);
|
|
7743
8062
|
}
|
|
8063
|
+
function logQQBotOutboundDispatch(params) {
|
|
8064
|
+
const accountLabel = params.accountId?.trim() || DEFAULT_ACCOUNT_ID;
|
|
8065
|
+
const textLength = typeof params.text === "string" ? params.text.length : 0;
|
|
8066
|
+
const mediaLabel = params.mediaUrl ? ` media=${shortId(params.mediaUrl)}` : "";
|
|
8067
|
+
console.info(
|
|
8068
|
+
`[qqbot] outbound action=${params.action} api=${params.api} accountId=${accountLabel} target=${params.targetKind}:${shortId(params.targetId)} markdown=${params.markdown ? "yes" : "no"} replyToId=${params.replyToId ? "yes" : "no"} replyEventId=${params.replyEventId ? "yes" : "no"} textLen=${textLength}${mediaLabel}`
|
|
8069
|
+
);
|
|
8070
|
+
}
|
|
7744
8071
|
function shouldRetryWithEventId(err) {
|
|
7745
8072
|
const status = err instanceof HttpError ? err.status : void 0;
|
|
7746
8073
|
let body = "";
|
|
@@ -7761,6 +8088,94 @@ function shouldRetryWithEventId(err) {
|
|
|
7761
8088
|
function shouldSendTextAsFollowupForMedia(mediaUrl) {
|
|
7762
8089
|
return detectMediaType(stripTitleFromUrl(mediaUrl)) === "file";
|
|
7763
8090
|
}
|
|
8091
|
+
function isHttpUrl2(value) {
|
|
8092
|
+
return /^https?:\/\//i.test(value);
|
|
8093
|
+
}
|
|
8094
|
+
function resolveResponseRefIdx(response) {
|
|
8095
|
+
if (!response || typeof response !== "object") {
|
|
8096
|
+
return void 0;
|
|
8097
|
+
}
|
|
8098
|
+
const direct = response.refIdx;
|
|
8099
|
+
if (typeof direct === "string" && direct.trim()) {
|
|
8100
|
+
return direct.trim();
|
|
8101
|
+
}
|
|
8102
|
+
const extInfo = response.ext_info;
|
|
8103
|
+
if (typeof extInfo?.ref_idx === "string" && extInfo.ref_idx.trim()) {
|
|
8104
|
+
return extInfo.ref_idx.trim();
|
|
8105
|
+
}
|
|
8106
|
+
return void 0;
|
|
8107
|
+
}
|
|
8108
|
+
function resolveOutboundAttachmentType(mediaUrl) {
|
|
8109
|
+
const detected = detectMediaType(stripTitleFromUrl(mediaUrl));
|
|
8110
|
+
if (detected === "image") return "image";
|
|
8111
|
+
if (detected === "video") return "video";
|
|
8112
|
+
if (detected === "audio") return "voice";
|
|
8113
|
+
if (detected === "file") return "file";
|
|
8114
|
+
return "unknown";
|
|
8115
|
+
}
|
|
8116
|
+
function resolveOutboundAttachmentFileName(mediaUrl) {
|
|
8117
|
+
const source = stripTitleFromUrl(mediaUrl).trim();
|
|
8118
|
+
if (!source) return void 0;
|
|
8119
|
+
if (isHttpUrl2(source)) {
|
|
8120
|
+
try {
|
|
8121
|
+
const base2 = path2.posix.basename(new URL(source).pathname);
|
|
8122
|
+
return base2 && base2 !== "/" ? base2 : void 0;
|
|
8123
|
+
} catch {
|
|
8124
|
+
return void 0;
|
|
8125
|
+
}
|
|
8126
|
+
}
|
|
8127
|
+
const base = path2.basename(source);
|
|
8128
|
+
return base || void 0;
|
|
8129
|
+
}
|
|
8130
|
+
function buildOutboundAttachmentSummary(params) {
|
|
8131
|
+
const source = stripTitleFromUrl(params.mediaUrl).trim();
|
|
8132
|
+
const type = resolveOutboundAttachmentType(source);
|
|
8133
|
+
const filename = resolveOutboundAttachmentFileName(source);
|
|
8134
|
+
const text = params.text?.trim();
|
|
8135
|
+
return {
|
|
8136
|
+
type,
|
|
8137
|
+
...filename ? { filename } : {},
|
|
8138
|
+
...isHttpUrl2(source) ? { url: source } : { localPath: source },
|
|
8139
|
+
...type === "voice" && text ? {
|
|
8140
|
+
transcript: text,
|
|
8141
|
+
transcriptSource: "tts"
|
|
8142
|
+
} : {}
|
|
8143
|
+
};
|
|
8144
|
+
}
|
|
8145
|
+
function recordOutboundC2CRefIndex(params) {
|
|
8146
|
+
const refIdx = params.refIdx?.trim();
|
|
8147
|
+
if (!refIdx) return;
|
|
8148
|
+
const text = params.text?.trim() ?? "";
|
|
8149
|
+
const attachments = params.mediaUrl?.trim() ? [buildOutboundAttachmentSummary({ mediaUrl: params.mediaUrl, text })] : void 0;
|
|
8150
|
+
if (!text && !attachments) {
|
|
8151
|
+
return;
|
|
8152
|
+
}
|
|
8153
|
+
try {
|
|
8154
|
+
const accountLabel = params.accountId?.trim() || DEFAULT_ACCOUNT_ID;
|
|
8155
|
+
setRefIndex(refIdx, {
|
|
8156
|
+
content: text,
|
|
8157
|
+
senderId: accountLabel,
|
|
8158
|
+
senderName: accountLabel,
|
|
8159
|
+
timestamp: Date.now(),
|
|
8160
|
+
isBot: true,
|
|
8161
|
+
...attachments ? { attachments } : {}
|
|
8162
|
+
});
|
|
8163
|
+
console.info(
|
|
8164
|
+
`[qqbot] cached outbound ref_idx=${refIdx} accountId=${accountLabel} textLen=${text.length} media=${params.mediaUrl?.trim() ? "yes" : "no"}`
|
|
8165
|
+
);
|
|
8166
|
+
} catch (err) {
|
|
8167
|
+
console.warn(`[qqbot] failed to cache outbound ref_idx=${refIdx}: ${String(err)}`);
|
|
8168
|
+
}
|
|
8169
|
+
}
|
|
8170
|
+
function buildPassiveReplyRefs(params) {
|
|
8171
|
+
if (params.replyToId) {
|
|
8172
|
+
return { messageId: params.replyToId };
|
|
8173
|
+
}
|
|
8174
|
+
if (params.replyEventId) {
|
|
8175
|
+
return { eventId: params.replyEventId };
|
|
8176
|
+
}
|
|
8177
|
+
return {};
|
|
8178
|
+
}
|
|
7764
8179
|
var qqbotOutbound = {
|
|
7765
8180
|
deliveryMode: "direct",
|
|
7766
8181
|
textChunkLimit: 1500,
|
|
@@ -7778,13 +8193,42 @@ var qqbotOutbound = {
|
|
|
7778
8193
|
const groupMarkdown = false;
|
|
7779
8194
|
try {
|
|
7780
8195
|
if (target.kind === "group") {
|
|
8196
|
+
if (!replyToId && !replyEventId) {
|
|
8197
|
+
logQQBotOutboundDispatch({
|
|
8198
|
+
action: "text",
|
|
8199
|
+
api: "sendProactiveGroupMessage",
|
|
8200
|
+
accountId,
|
|
8201
|
+
targetKind: target.kind,
|
|
8202
|
+
targetId: target.id,
|
|
8203
|
+
markdown,
|
|
8204
|
+
text
|
|
8205
|
+
});
|
|
8206
|
+
const result3 = await sendProactiveGroupMessage({
|
|
8207
|
+
accessToken,
|
|
8208
|
+
groupOpenid: target.id,
|
|
8209
|
+
content: text,
|
|
8210
|
+
markdown
|
|
8211
|
+
});
|
|
8212
|
+
return { channel: "qqbot", messageId: result3.id, timestamp: result3.timestamp };
|
|
8213
|
+
}
|
|
7781
8214
|
let result2;
|
|
7782
8215
|
try {
|
|
8216
|
+
logQQBotOutboundDispatch({
|
|
8217
|
+
action: "text",
|
|
8218
|
+
api: "sendGroupMessage",
|
|
8219
|
+
accountId,
|
|
8220
|
+
targetKind: target.kind,
|
|
8221
|
+
targetId: target.id,
|
|
8222
|
+
markdown: groupMarkdown,
|
|
8223
|
+
replyToId,
|
|
8224
|
+
replyEventId,
|
|
8225
|
+
text
|
|
8226
|
+
});
|
|
7783
8227
|
result2 = await sendGroupMessage({
|
|
7784
8228
|
accessToken,
|
|
7785
8229
|
groupOpenid: target.id,
|
|
7786
8230
|
content: text,
|
|
7787
|
-
|
|
8231
|
+
...buildPassiveReplyRefs({ replyToId, replyEventId }),
|
|
7788
8232
|
markdown: groupMarkdown
|
|
7789
8233
|
});
|
|
7790
8234
|
} catch (err) {
|
|
@@ -7835,6 +8279,15 @@ var qqbotOutbound = {
|
|
|
7835
8279
|
return { channel: "qqbot", messageId: result2.id, timestamp: result2.timestamp };
|
|
7836
8280
|
}
|
|
7837
8281
|
if (target.kind === "channel") {
|
|
8282
|
+
logQQBotOutboundDispatch({
|
|
8283
|
+
action: "text",
|
|
8284
|
+
api: "sendChannelMessage",
|
|
8285
|
+
accountId,
|
|
8286
|
+
targetKind: target.kind,
|
|
8287
|
+
targetId: target.id,
|
|
8288
|
+
replyToId,
|
|
8289
|
+
text
|
|
8290
|
+
});
|
|
7838
8291
|
const result2 = await sendChannelMessage({
|
|
7839
8292
|
accessToken,
|
|
7840
8293
|
channelId: target.id,
|
|
@@ -7843,13 +8296,49 @@ var qqbotOutbound = {
|
|
|
7843
8296
|
});
|
|
7844
8297
|
return { channel: "qqbot", messageId: result2.id, timestamp: result2.timestamp };
|
|
7845
8298
|
}
|
|
8299
|
+
if (!replyToId && !replyEventId) {
|
|
8300
|
+
logQQBotOutboundDispatch({
|
|
8301
|
+
action: "text",
|
|
8302
|
+
api: "sendProactiveC2CMessage",
|
|
8303
|
+
accountId,
|
|
8304
|
+
targetKind: target.kind,
|
|
8305
|
+
targetId: target.id,
|
|
8306
|
+
markdown,
|
|
8307
|
+
text
|
|
8308
|
+
});
|
|
8309
|
+
const result2 = await sendProactiveC2CMessage({
|
|
8310
|
+
accessToken,
|
|
8311
|
+
openid: target.id,
|
|
8312
|
+
content: text,
|
|
8313
|
+
markdown
|
|
8314
|
+
});
|
|
8315
|
+
const refIdx2 = resolveResponseRefIdx(result2);
|
|
8316
|
+
recordOutboundC2CRefIndex({ refIdx: refIdx2, accountId, text });
|
|
8317
|
+
return {
|
|
8318
|
+
channel: "qqbot",
|
|
8319
|
+
messageId: result2.id,
|
|
8320
|
+
timestamp: result2.timestamp,
|
|
8321
|
+
...refIdx2 ? { refIdx: refIdx2 } : {}
|
|
8322
|
+
};
|
|
8323
|
+
}
|
|
7846
8324
|
let result;
|
|
7847
8325
|
try {
|
|
8326
|
+
logQQBotOutboundDispatch({
|
|
8327
|
+
action: "text",
|
|
8328
|
+
api: "sendC2CMessage",
|
|
8329
|
+
accountId,
|
|
8330
|
+
targetKind: target.kind,
|
|
8331
|
+
targetId: target.id,
|
|
8332
|
+
markdown,
|
|
8333
|
+
replyToId,
|
|
8334
|
+
replyEventId,
|
|
8335
|
+
text
|
|
8336
|
+
});
|
|
7848
8337
|
result = await sendC2CMessage({
|
|
7849
8338
|
accessToken,
|
|
7850
8339
|
openid: target.id,
|
|
7851
8340
|
content: text,
|
|
7852
|
-
|
|
8341
|
+
...buildPassiveReplyRefs({ replyToId, replyEventId }),
|
|
7853
8342
|
markdown
|
|
7854
8343
|
});
|
|
7855
8344
|
} catch (err) {
|
|
@@ -7897,7 +8386,14 @@ var qqbotOutbound = {
|
|
|
7897
8386
|
throw retryErr;
|
|
7898
8387
|
}
|
|
7899
8388
|
}
|
|
7900
|
-
|
|
8389
|
+
const refIdx = resolveResponseRefIdx(result);
|
|
8390
|
+
recordOutboundC2CRefIndex({ refIdx, accountId, text });
|
|
8391
|
+
return {
|
|
8392
|
+
channel: "qqbot",
|
|
8393
|
+
messageId: result.id,
|
|
8394
|
+
timestamp: result.timestamp,
|
|
8395
|
+
...refIdx ? { refIdx } : {}
|
|
8396
|
+
};
|
|
7901
8397
|
} catch (err) {
|
|
7902
8398
|
const message = summarizeError(err);
|
|
7903
8399
|
return { channel: "qqbot", error: message };
|
|
@@ -7927,6 +8423,17 @@ ${mediaUrl}` : mediaUrl;
|
|
|
7927
8423
|
try {
|
|
7928
8424
|
let result;
|
|
7929
8425
|
try {
|
|
8426
|
+
logQQBotOutboundDispatch({
|
|
8427
|
+
action: "media",
|
|
8428
|
+
api: "sendFileQQBot",
|
|
8429
|
+
accountId,
|
|
8430
|
+
targetKind: target.kind,
|
|
8431
|
+
targetId: target.id,
|
|
8432
|
+
replyToId,
|
|
8433
|
+
replyEventId,
|
|
8434
|
+
text: sendTextAsFollowup ? void 0 : trimmedText,
|
|
8435
|
+
mediaUrl
|
|
8436
|
+
});
|
|
7930
8437
|
result = await sendFileQQBot({
|
|
7931
8438
|
cfg: qqCfg,
|
|
7932
8439
|
target: { kind: target.kind, id: target.id },
|
|
@@ -7995,7 +8502,21 @@ ${mediaUrl}` : mediaUrl;
|
|
|
7995
8502
|
};
|
|
7996
8503
|
}
|
|
7997
8504
|
}
|
|
7998
|
-
|
|
8505
|
+
const refIdx = target.kind === "c2c" ? resolveResponseRefIdx(result) : void 0;
|
|
8506
|
+
if (target.kind === "c2c") {
|
|
8507
|
+
recordOutboundC2CRefIndex({
|
|
8508
|
+
refIdx,
|
|
8509
|
+
accountId,
|
|
8510
|
+
text: sendTextAsFollowup ? void 0 : trimmedText,
|
|
8511
|
+
mediaUrl
|
|
8512
|
+
});
|
|
8513
|
+
}
|
|
8514
|
+
return {
|
|
8515
|
+
channel: "qqbot",
|
|
8516
|
+
messageId: result.id,
|
|
8517
|
+
timestamp: result.timestamp,
|
|
8518
|
+
...refIdx ? { refIdx } : {}
|
|
8519
|
+
};
|
|
7999
8520
|
} catch (err) {
|
|
8000
8521
|
const message = summarizeError(err);
|
|
8001
8522
|
return { channel: "qqbot", error: message };
|
|
@@ -8014,8 +8535,9 @@ ${mediaUrl}` : mediaUrl;
|
|
|
8014
8535
|
}
|
|
8015
8536
|
try {
|
|
8016
8537
|
const accessToken = await getAccessToken(credentials.appId, credentials.clientSecret);
|
|
8538
|
+
let typingResult;
|
|
8017
8539
|
try {
|
|
8018
|
-
await sendC2CInputNotify({
|
|
8540
|
+
typingResult = await sendC2CInputNotify({
|
|
8019
8541
|
accessToken,
|
|
8020
8542
|
openid: target.id,
|
|
8021
8543
|
messageId: replyToId,
|
|
@@ -8037,7 +8559,7 @@ ${mediaUrl}` : mediaUrl;
|
|
|
8037
8559
|
reason: summarizeError(err)
|
|
8038
8560
|
});
|
|
8039
8561
|
try {
|
|
8040
|
-
await sendC2CInputNotify({
|
|
8562
|
+
typingResult = await sendC2CInputNotify({
|
|
8041
8563
|
accessToken,
|
|
8042
8564
|
openid: target.id,
|
|
8043
8565
|
eventId: replyEventId,
|
|
@@ -8066,7 +8588,10 @@ ${mediaUrl}` : mediaUrl;
|
|
|
8066
8588
|
throw retryErr;
|
|
8067
8589
|
}
|
|
8068
8590
|
}
|
|
8069
|
-
return {
|
|
8591
|
+
return {
|
|
8592
|
+
channel: "qqbot",
|
|
8593
|
+
...typingResult?.refIdx ? { refIdx: typingResult.refIdx } : {}
|
|
8594
|
+
};
|
|
8070
8595
|
} catch (err) {
|
|
8071
8596
|
const message = summarizeError(err);
|
|
8072
8597
|
return { channel: "qqbot", error: message };
|
|
@@ -8076,13 +8601,301 @@ ${mediaUrl}` : mediaUrl;
|
|
|
8076
8601
|
|
|
8077
8602
|
// src/logger.ts
|
|
8078
8603
|
createLogger("qqbot");
|
|
8079
|
-
var
|
|
8604
|
+
var DEFAULT_QQBOT_MARKDOWN_IMAGE_SIZE = {
|
|
8605
|
+
width: 512,
|
|
8606
|
+
height: 512
|
|
8607
|
+
};
|
|
8608
|
+
var MARKDOWN_IMAGE_RE2 = /!\[([^\]]*)\]\(([^)\n]+)\)/g;
|
|
8609
|
+
var BARE_HTTP_IMAGE_URL_RE = /(?<![(\["'<])(https?:\/\/[^\s)"'<>]+\.(?:png|jpe?g|gif|webp)(?:\?[^\s)"'<>]*)?)/gi;
|
|
8610
|
+
var FENCED_CODE_BLOCK_RE = /(^|\n)(`{3,}|~{3,})[^\n]*\n[\s\S]*?\n\2(?=\n|$)/g;
|
|
8611
|
+
function parsePngSize(buffer) {
|
|
8612
|
+
if (buffer.length < 24) return null;
|
|
8613
|
+
if (buffer[0] !== 137 || buffer[1] !== 80 || buffer[2] !== 78 || buffer[3] !== 71) {
|
|
8614
|
+
return null;
|
|
8615
|
+
}
|
|
8616
|
+
return {
|
|
8617
|
+
width: buffer.readUInt32BE(16),
|
|
8618
|
+
height: buffer.readUInt32BE(20)
|
|
8619
|
+
};
|
|
8620
|
+
}
|
|
8621
|
+
function parseJpegSize(buffer) {
|
|
8622
|
+
if (buffer.length < 4 || buffer[0] !== 255 || buffer[1] !== 216) {
|
|
8623
|
+
return null;
|
|
8624
|
+
}
|
|
8625
|
+
let offset = 2;
|
|
8626
|
+
while (offset < buffer.length - 9) {
|
|
8627
|
+
if (buffer[offset] !== 255) {
|
|
8628
|
+
offset += 1;
|
|
8629
|
+
continue;
|
|
8630
|
+
}
|
|
8631
|
+
const marker = buffer[offset + 1];
|
|
8632
|
+
if (marker === 192 || marker === 194) {
|
|
8633
|
+
return {
|
|
8634
|
+
height: buffer.readUInt16BE(offset + 5),
|
|
8635
|
+
width: buffer.readUInt16BE(offset + 7)
|
|
8636
|
+
};
|
|
8637
|
+
}
|
|
8638
|
+
if (offset + 3 >= buffer.length) {
|
|
8639
|
+
break;
|
|
8640
|
+
}
|
|
8641
|
+
const blockLength = buffer.readUInt16BE(offset + 2);
|
|
8642
|
+
offset += 2 + blockLength;
|
|
8643
|
+
}
|
|
8644
|
+
return null;
|
|
8645
|
+
}
|
|
8646
|
+
function parseGifSize(buffer) {
|
|
8647
|
+
if (buffer.length < 10) return null;
|
|
8648
|
+
const signature = buffer.toString("ascii", 0, 6);
|
|
8649
|
+
if (signature !== "GIF87a" && signature !== "GIF89a") {
|
|
8650
|
+
return null;
|
|
8651
|
+
}
|
|
8652
|
+
return {
|
|
8653
|
+
width: buffer.readUInt16LE(6),
|
|
8654
|
+
height: buffer.readUInt16LE(8)
|
|
8655
|
+
};
|
|
8656
|
+
}
|
|
8657
|
+
function parseWebpSize(buffer) {
|
|
8658
|
+
if (buffer.length < 30) return null;
|
|
8659
|
+
if (buffer.toString("ascii", 0, 4) !== "RIFF" || buffer.toString("ascii", 8, 12) !== "WEBP") {
|
|
8660
|
+
return null;
|
|
8661
|
+
}
|
|
8662
|
+
const chunkType = buffer.toString("ascii", 12, 16);
|
|
8663
|
+
if (chunkType === "VP8 " && buffer[23] === 157 && buffer[24] === 1 && buffer[25] === 42) {
|
|
8664
|
+
return {
|
|
8665
|
+
width: buffer.readUInt16LE(26) & 16383,
|
|
8666
|
+
height: buffer.readUInt16LE(28) & 16383
|
|
8667
|
+
};
|
|
8668
|
+
}
|
|
8669
|
+
if (chunkType === "VP8L" && buffer[20] === 47) {
|
|
8670
|
+
const bits = buffer.readUInt32LE(21);
|
|
8671
|
+
return {
|
|
8672
|
+
width: (bits & 16383) + 1,
|
|
8673
|
+
height: (bits >> 14 & 16383) + 1
|
|
8674
|
+
};
|
|
8675
|
+
}
|
|
8676
|
+
if (chunkType === "VP8X") {
|
|
8677
|
+
return {
|
|
8678
|
+
width: (buffer[24] | buffer[25] << 8 | buffer[26] << 16) + 1,
|
|
8679
|
+
height: (buffer[27] | buffer[28] << 8 | buffer[29] << 16) + 1
|
|
8680
|
+
};
|
|
8681
|
+
}
|
|
8682
|
+
return null;
|
|
8683
|
+
}
|
|
8684
|
+
function parseImageSize(buffer) {
|
|
8685
|
+
return parsePngSize(buffer) ?? parseJpegSize(buffer) ?? parseGifSize(buffer) ?? parseWebpSize(buffer);
|
|
8686
|
+
}
|
|
8687
|
+
function normalizeImageSize(size) {
|
|
8688
|
+
if (!size) return null;
|
|
8689
|
+
if (!Number.isFinite(size.width) || !Number.isFinite(size.height)) return null;
|
|
8690
|
+
if (size.width <= 0 || size.height <= 0) return null;
|
|
8691
|
+
return {
|
|
8692
|
+
width: Math.round(size.width),
|
|
8693
|
+
height: Math.round(size.height)
|
|
8694
|
+
};
|
|
8695
|
+
}
|
|
8696
|
+
function splitMarkdownImageDestination(rawDestination) {
|
|
8697
|
+
let next = rawDestination.trim();
|
|
8698
|
+
const whitespaceIndex = next.search(/\s/);
|
|
8699
|
+
if (whitespaceIndex >= 0) {
|
|
8700
|
+
next = next.slice(0, whitespaceIndex);
|
|
8701
|
+
}
|
|
8702
|
+
if (next.startsWith("<") && next.endsWith(">")) {
|
|
8703
|
+
next = next.slice(1, -1).trim();
|
|
8704
|
+
}
|
|
8705
|
+
return next;
|
|
8706
|
+
}
|
|
8707
|
+
function splitFencedCodeBlocks(text) {
|
|
8708
|
+
const segments = [];
|
|
8709
|
+
const re = new RegExp(FENCED_CODE_BLOCK_RE.source, FENCED_CODE_BLOCK_RE.flags);
|
|
8710
|
+
let lastIndex = 0;
|
|
8711
|
+
let match;
|
|
8712
|
+
while ((match = re.exec(text)) !== null) {
|
|
8713
|
+
const leading = match[1] ?? "";
|
|
8714
|
+
const codeStart = match.index + leading.length;
|
|
8715
|
+
if (codeStart > lastIndex) {
|
|
8716
|
+
segments.push({ kind: "text", value: text.slice(lastIndex, codeStart) });
|
|
8717
|
+
}
|
|
8718
|
+
segments.push({ kind: "code", value: text.slice(codeStart, re.lastIndex) });
|
|
8719
|
+
lastIndex = re.lastIndex;
|
|
8720
|
+
}
|
|
8721
|
+
if (lastIndex < text.length) {
|
|
8722
|
+
segments.push({ kind: "text", value: text.slice(lastIndex) });
|
|
8723
|
+
}
|
|
8724
|
+
return segments;
|
|
8725
|
+
}
|
|
8726
|
+
async function replaceAsync(input2, pattern, replacer) {
|
|
8727
|
+
const re = new RegExp(pattern.source, pattern.flags);
|
|
8728
|
+
let result = "";
|
|
8729
|
+
let lastIndex = 0;
|
|
8730
|
+
let match;
|
|
8731
|
+
while ((match = re.exec(input2)) !== null) {
|
|
8732
|
+
result += input2.slice(lastIndex, match.index);
|
|
8733
|
+
result += await replacer(match);
|
|
8734
|
+
lastIndex = match.index + match[0].length;
|
|
8735
|
+
if (match[0].length === 0) {
|
|
8736
|
+
re.lastIndex += 1;
|
|
8737
|
+
}
|
|
8738
|
+
}
|
|
8739
|
+
result += input2.slice(lastIndex);
|
|
8740
|
+
return result;
|
|
8741
|
+
}
|
|
8742
|
+
async function getResolvedImageSize(params) {
|
|
8743
|
+
const { url, cache: cache2, resolveImageSize } = params;
|
|
8744
|
+
const existing = cache2.get(url);
|
|
8745
|
+
if (existing) {
|
|
8746
|
+
return existing;
|
|
8747
|
+
}
|
|
8748
|
+
const pending = resolveImageSize(url).then((size) => normalizeImageSize(size) ?? DEFAULT_QQBOT_MARKDOWN_IMAGE_SIZE).catch(() => DEFAULT_QQBOT_MARKDOWN_IMAGE_SIZE);
|
|
8749
|
+
cache2.set(url, pending);
|
|
8750
|
+
return pending;
|
|
8751
|
+
}
|
|
8752
|
+
async function normalizeTextSegment(params) {
|
|
8753
|
+
const { text, seenImageUrls, imageSizeCache, resolveImageSize } = params;
|
|
8754
|
+
const withMarkdownImages = await replaceAsync(text, MARKDOWN_IMAGE_RE2, async (match) => {
|
|
8755
|
+
const fullMatch = match[0];
|
|
8756
|
+
const destination = splitMarkdownImageDestination(match[2] ?? "");
|
|
8757
|
+
if (!isQQBotHttpImageUrl(destination)) {
|
|
8758
|
+
return fullMatch;
|
|
8759
|
+
}
|
|
8760
|
+
seenImageUrls.add(destination);
|
|
8761
|
+
if (hasQQBotMarkdownImageSize(fullMatch)) {
|
|
8762
|
+
return fullMatch;
|
|
8763
|
+
}
|
|
8764
|
+
const size = await getResolvedImageSize({
|
|
8765
|
+
url: destination,
|
|
8766
|
+
cache: imageSizeCache,
|
|
8767
|
+
resolveImageSize
|
|
8768
|
+
});
|
|
8769
|
+
return formatQQBotMarkdownImage(destination, size);
|
|
8770
|
+
});
|
|
8771
|
+
return replaceAsync(withMarkdownImages, BARE_HTTP_IMAGE_URL_RE, async (match) => {
|
|
8772
|
+
const url = match[1] ?? match[0];
|
|
8773
|
+
if (!isQQBotHttpImageUrl(url)) {
|
|
8774
|
+
return match[0];
|
|
8775
|
+
}
|
|
8776
|
+
seenImageUrls.add(url);
|
|
8777
|
+
const size = await getResolvedImageSize({
|
|
8778
|
+
url,
|
|
8779
|
+
cache: imageSizeCache,
|
|
8780
|
+
resolveImageSize
|
|
8781
|
+
});
|
|
8782
|
+
return formatQQBotMarkdownImage(url, size);
|
|
8783
|
+
});
|
|
8784
|
+
}
|
|
8785
|
+
function isQQBotHttpImageUrl(url) {
|
|
8786
|
+
const trimmed = url.trim();
|
|
8787
|
+
if (!/^https?:\/\//i.test(trimmed)) {
|
|
8788
|
+
return false;
|
|
8789
|
+
}
|
|
8790
|
+
try {
|
|
8791
|
+
const parsed = new URL(trimmed);
|
|
8792
|
+
return /\.(?:png|jpe?g|gif|webp)$/i.test(parsed.pathname);
|
|
8793
|
+
} catch {
|
|
8794
|
+
return /\.(?:png|jpe?g|gif|webp)(?:\?[^\s)"'<>]*)?$/i.test(trimmed);
|
|
8795
|
+
}
|
|
8796
|
+
}
|
|
8797
|
+
async function getQQBotHttpImageSize(url, timeoutMs = 5e3) {
|
|
8798
|
+
if (!isQQBotHttpImageUrl(url)) {
|
|
8799
|
+
return null;
|
|
8800
|
+
}
|
|
8801
|
+
const controller = new AbortController();
|
|
8802
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
8803
|
+
try {
|
|
8804
|
+
const response = await fetch(url, {
|
|
8805
|
+
signal: controller.signal,
|
|
8806
|
+
headers: {
|
|
8807
|
+
Range: "bytes=0-65535",
|
|
8808
|
+
"User-Agent": "OpenClaw-QQBot-ImageSize/1.0"
|
|
8809
|
+
}
|
|
8810
|
+
});
|
|
8811
|
+
if (!(response.ok || response.status === 206)) {
|
|
8812
|
+
return null;
|
|
8813
|
+
}
|
|
8814
|
+
const buffer = Buffer$1.from(await response.arrayBuffer());
|
|
8815
|
+
return normalizeImageSize(parseImageSize(buffer));
|
|
8816
|
+
} catch {
|
|
8817
|
+
return null;
|
|
8818
|
+
} finally {
|
|
8819
|
+
clearTimeout(timeoutId);
|
|
8820
|
+
}
|
|
8821
|
+
}
|
|
8822
|
+
function formatQQBotMarkdownImage(url, size) {
|
|
8823
|
+
const resolved = normalizeImageSize(size) ?? DEFAULT_QQBOT_MARKDOWN_IMAGE_SIZE;
|
|
8824
|
+
return ``;
|
|
8825
|
+
}
|
|
8826
|
+
function hasQQBotMarkdownImageSize(markdownImage) {
|
|
8827
|
+
return /!\[#\d+px\s+#\d+px\]\([^)]+\)/.test(markdownImage);
|
|
8828
|
+
}
|
|
8829
|
+
async function normalizeQQBotMarkdownImages(params) {
|
|
8830
|
+
const text = params.text ?? "";
|
|
8831
|
+
const appendImageUrls = params.appendImageUrls ?? [];
|
|
8832
|
+
const resolveImageSize = params.resolveImageSize ?? ((url) => getQQBotHttpImageSize(url, params.timeoutMs));
|
|
8833
|
+
const imageSizeCache = /* @__PURE__ */ new Map();
|
|
8834
|
+
const seenImageUrls = /* @__PURE__ */ new Set();
|
|
8835
|
+
const segments = splitFencedCodeBlocks(text);
|
|
8836
|
+
const normalizedSegments = [];
|
|
8837
|
+
for (const segment of segments) {
|
|
8838
|
+
if (segment.kind === "code") {
|
|
8839
|
+
normalizedSegments.push(segment.value);
|
|
8840
|
+
continue;
|
|
8841
|
+
}
|
|
8842
|
+
normalizedSegments.push(
|
|
8843
|
+
await normalizeTextSegment({
|
|
8844
|
+
text: segment.value,
|
|
8845
|
+
seenImageUrls,
|
|
8846
|
+
imageSizeCache,
|
|
8847
|
+
resolveImageSize
|
|
8848
|
+
})
|
|
8849
|
+
);
|
|
8850
|
+
}
|
|
8851
|
+
const appendedImages = [];
|
|
8852
|
+
for (const rawUrl of appendImageUrls) {
|
|
8853
|
+
const url = rawUrl.trim();
|
|
8854
|
+
if (!isQQBotHttpImageUrl(url) || seenImageUrls.has(url)) {
|
|
8855
|
+
continue;
|
|
8856
|
+
}
|
|
8857
|
+
seenImageUrls.add(url);
|
|
8858
|
+
const size = await getResolvedImageSize({
|
|
8859
|
+
url,
|
|
8860
|
+
cache: imageSizeCache,
|
|
8861
|
+
resolveImageSize
|
|
8862
|
+
});
|
|
8863
|
+
appendedImages.push(formatQQBotMarkdownImage(url, size));
|
|
8864
|
+
}
|
|
8865
|
+
const body = normalizedSegments.join("").trim();
|
|
8866
|
+
if (appendedImages.length === 0) {
|
|
8867
|
+
return body;
|
|
8868
|
+
}
|
|
8869
|
+
if (!body) {
|
|
8870
|
+
return appendedImages.join("\n");
|
|
8871
|
+
}
|
|
8872
|
+
return `${body}
|
|
8873
|
+
|
|
8874
|
+
${appendedImages.join("\n")}`;
|
|
8875
|
+
}
|
|
8876
|
+
var DEFAULT_KNOWN_TARGETS_PATH = join(homedir(), ".openclaw", "qqbot", "data", "known-targets.json");
|
|
8877
|
+
var LEGACY_KNOWN_TARGETS_PATH = join(homedir(), ".openclaw", "data", "qqbot", "known-targets.json");
|
|
8080
8878
|
function resolveKnownTargetsFilePath(options) {
|
|
8081
8879
|
return options?.filePath?.trim() || DEFAULT_KNOWN_TARGETS_PATH;
|
|
8082
8880
|
}
|
|
8083
8881
|
function ensureKnownTargetsDir(filePath) {
|
|
8084
8882
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
8085
8883
|
}
|
|
8884
|
+
function migrateLegacyKnownTargets(filePath) {
|
|
8885
|
+
if (filePath !== DEFAULT_KNOWN_TARGETS_PATH) {
|
|
8886
|
+
return;
|
|
8887
|
+
}
|
|
8888
|
+
if (existsSync(filePath) || !existsSync(LEGACY_KNOWN_TARGETS_PATH)) {
|
|
8889
|
+
return;
|
|
8890
|
+
}
|
|
8891
|
+
ensureKnownTargetsDir(filePath);
|
|
8892
|
+
try {
|
|
8893
|
+
renameSync(LEGACY_KNOWN_TARGETS_PATH, filePath);
|
|
8894
|
+
} catch {
|
|
8895
|
+
copyFileSync(LEGACY_KNOWN_TARGETS_PATH, filePath);
|
|
8896
|
+
rmSync(LEGACY_KNOWN_TARGETS_PATH, { force: true });
|
|
8897
|
+
}
|
|
8898
|
+
}
|
|
8086
8899
|
function compareTargetsByLastSeenDesc(a, b) {
|
|
8087
8900
|
if (b.lastSeenAt !== a.lastSeenAt) {
|
|
8088
8901
|
return b.lastSeenAt - a.lastSeenAt;
|
|
@@ -8118,6 +8931,7 @@ function parseKnownTargets(raw, filePath) {
|
|
|
8118
8931
|
}
|
|
8119
8932
|
function readKnownTargets(options) {
|
|
8120
8933
|
const filePath = resolveKnownTargetsFilePath(options);
|
|
8934
|
+
migrateLegacyKnownTargets(filePath);
|
|
8121
8935
|
if (!existsSync(filePath)) {
|
|
8122
8936
|
return [];
|
|
8123
8937
|
}
|
|
@@ -8129,6 +8943,7 @@ function readKnownTargets(options) {
|
|
|
8129
8943
|
}
|
|
8130
8944
|
function writeKnownTargets(targets, options) {
|
|
8131
8945
|
const filePath = resolveKnownTargetsFilePath(options);
|
|
8946
|
+
migrateLegacyKnownTargets(filePath);
|
|
8132
8947
|
if (targets.length === 0) {
|
|
8133
8948
|
if (existsSync(filePath)) {
|
|
8134
8949
|
rmSync(filePath, { force: true });
|
|
@@ -8261,6 +9076,65 @@ function getQQBotRuntime() {
|
|
|
8261
9076
|
}
|
|
8262
9077
|
return runtime;
|
|
8263
9078
|
}
|
|
9079
|
+
var sessionDispatchQueue = /* @__PURE__ */ new Map();
|
|
9080
|
+
function resolveQQBotRouteSessionKey(route) {
|
|
9081
|
+
const effectiveSessionKey = route.effectiveSessionKey?.trim();
|
|
9082
|
+
if (effectiveSessionKey) {
|
|
9083
|
+
return effectiveSessionKey;
|
|
9084
|
+
}
|
|
9085
|
+
return route.sessionKey;
|
|
9086
|
+
}
|
|
9087
|
+
function buildSessionDispatchQueueKey(route) {
|
|
9088
|
+
const accountId = route.accountId?.trim() || DEFAULT_ACCOUNT_ID;
|
|
9089
|
+
return `${accountId}:${resolveQQBotRouteSessionKey(route)}`;
|
|
9090
|
+
}
|
|
9091
|
+
function normalizeQQBotSessionKeyPart(value) {
|
|
9092
|
+
const trimmed = value.trim();
|
|
9093
|
+
return trimmed ? trimmed.toLowerCase() : "unknown";
|
|
9094
|
+
}
|
|
9095
|
+
function buildQQBotDirectSessionKey(params) {
|
|
9096
|
+
const normalizedAccountId = normalizeQQBotSessionKeyPart(params.accountId);
|
|
9097
|
+
const normalizedSenderId = normalizeQQBotSessionKeyPart(params.senderStableId);
|
|
9098
|
+
const trimmedRouteSessionKey = params.routeSessionKey.trim();
|
|
9099
|
+
if (!trimmedRouteSessionKey) {
|
|
9100
|
+
return `agent:main:qqbot:dm:${normalizedAccountId}:${normalizedSenderId}`;
|
|
9101
|
+
}
|
|
9102
|
+
const qqAgentRouteMatch = trimmedRouteSessionKey.match(/^(agent:[^:]+:qqbot:)(?:direct|dm):.+$/i);
|
|
9103
|
+
if (qqAgentRouteMatch?.[1]) {
|
|
9104
|
+
return `${qqAgentRouteMatch[1]}dm:${normalizedAccountId}:${normalizedSenderId}`;
|
|
9105
|
+
}
|
|
9106
|
+
return `${trimmedRouteSessionKey}:dm:${normalizedAccountId}:${normalizedSenderId}`;
|
|
9107
|
+
}
|
|
9108
|
+
function normalizeQQBotReplyTarget(value) {
|
|
9109
|
+
if (typeof value !== "string") {
|
|
9110
|
+
return void 0;
|
|
9111
|
+
}
|
|
9112
|
+
let trimmed = value.trim();
|
|
9113
|
+
if (!trimmed) {
|
|
9114
|
+
return void 0;
|
|
9115
|
+
}
|
|
9116
|
+
if (/^qqbot:/i.test(trimmed)) {
|
|
9117
|
+
trimmed = trimmed.slice("qqbot:".length).trim();
|
|
9118
|
+
}
|
|
9119
|
+
if (/^c2c:/i.test(trimmed)) {
|
|
9120
|
+
const openid = trimmed.slice("c2c:".length).trim();
|
|
9121
|
+
return openid ? `user:${openid}` : void 0;
|
|
9122
|
+
}
|
|
9123
|
+
return /^(user|group|channel):/i.test(trimmed) ? trimmed : void 0;
|
|
9124
|
+
}
|
|
9125
|
+
async function runSerializedSessionDispatch(queueKey, task) {
|
|
9126
|
+
const previous = sessionDispatchQueue.get(queueKey) ?? Promise.resolve();
|
|
9127
|
+
const run = previous.catch(() => void 0).then(task);
|
|
9128
|
+
const cleanup = run.then(() => void 0, () => void 0);
|
|
9129
|
+
sessionDispatchQueue.set(queueKey, cleanup);
|
|
9130
|
+
try {
|
|
9131
|
+
return await run;
|
|
9132
|
+
} finally {
|
|
9133
|
+
if (sessionDispatchQueue.get(queueKey) === cleanup) {
|
|
9134
|
+
sessionDispatchQueue.delete(queueKey);
|
|
9135
|
+
}
|
|
9136
|
+
}
|
|
9137
|
+
}
|
|
8264
9138
|
function toString(value) {
|
|
8265
9139
|
if (typeof value === "string" && value.trim()) return value;
|
|
8266
9140
|
return void 0;
|
|
@@ -8311,6 +9185,33 @@ function parseTextWithAttachments(payload) {
|
|
|
8311
9185
|
attachments
|
|
8312
9186
|
};
|
|
8313
9187
|
}
|
|
9188
|
+
function parseQQBotRefIndices(payload) {
|
|
9189
|
+
const scene = payload.message_scene;
|
|
9190
|
+
if (!scene || typeof scene !== "object") {
|
|
9191
|
+
return {};
|
|
9192
|
+
}
|
|
9193
|
+
const ext = scene.ext;
|
|
9194
|
+
if (!Array.isArray(ext)) {
|
|
9195
|
+
return {};
|
|
9196
|
+
}
|
|
9197
|
+
let refMsgIdx;
|
|
9198
|
+
let msgIdx;
|
|
9199
|
+
for (const value of ext) {
|
|
9200
|
+
const item = toString(value);
|
|
9201
|
+
if (!item) continue;
|
|
9202
|
+
if (item.startsWith("ref_msg_idx=")) {
|
|
9203
|
+
refMsgIdx = toString(item.slice("ref_msg_idx=".length));
|
|
9204
|
+
continue;
|
|
9205
|
+
}
|
|
9206
|
+
if (item.startsWith("msg_idx=")) {
|
|
9207
|
+
msgIdx = toString(item.slice("msg_idx=".length));
|
|
9208
|
+
}
|
|
9209
|
+
}
|
|
9210
|
+
return {
|
|
9211
|
+
...refMsgIdx ? { refMsgIdx } : {},
|
|
9212
|
+
...msgIdx ? { msgIdx } : {}
|
|
9213
|
+
};
|
|
9214
|
+
}
|
|
8314
9215
|
function resolveEventId(payload, fallbackEventId) {
|
|
8315
9216
|
return toString(payload.event_id) ?? toString(payload.eventId) ?? toString(fallbackEventId);
|
|
8316
9217
|
}
|
|
@@ -8320,6 +9221,7 @@ var VOICE_ASR_ERROR_MAX_LENGTH = 500;
|
|
|
8320
9221
|
var LONG_TASK_NOTICE_TEXT = "\u4EFB\u52A1\u5904\u7406\u65F6\u95F4\u8F83\u957F\uFF0C\u8BF7\u7A0D\u7B49\uFF0C\u6211\u8FD8\u5728\u7EE7\u7EED\u5904\u7406\u3002";
|
|
8321
9222
|
var DEFAULT_LONG_TASK_NOTICE_DELAY_MS = 3e4;
|
|
8322
9223
|
var QQ_GROUP_NO_REPLY_FALLBACK_TEXT = "\u6211\u5728\u3002\u4F60\u53EF\u4EE5\u76F4\u63A5\u8BF4\u5177\u4F53\u4E00\u70B9\u3002";
|
|
9224
|
+
var QQ_QUOTE_BODY_UNAVAILABLE_TEXT = "\u539F\u59CB\u5185\u5BB9\u4E0D\u53EF\u7528";
|
|
8323
9225
|
function startLongTaskNoticeTimer(params) {
|
|
8324
9226
|
const { delayMs, logger, sendNotice } = params;
|
|
8325
9227
|
let completed = false;
|
|
@@ -8354,7 +9256,7 @@ function startLongTaskNoticeTimer(params) {
|
|
|
8354
9256
|
}
|
|
8355
9257
|
};
|
|
8356
9258
|
}
|
|
8357
|
-
function
|
|
9259
|
+
function isHttpUrl3(value) {
|
|
8358
9260
|
return /^https?:\/\//i.test(value);
|
|
8359
9261
|
}
|
|
8360
9262
|
function isImageAttachment(att) {
|
|
@@ -8427,7 +9329,7 @@ async function resolveInboundAttachmentsForAgent(params) {
|
|
|
8427
9329
|
let asrErrorMessage;
|
|
8428
9330
|
for (const att of list) {
|
|
8429
9331
|
const next = { attachment: att };
|
|
8430
|
-
if (isImageAttachment(att) &&
|
|
9332
|
+
if (isImageAttachment(att) && isHttpUrl3(att.url)) {
|
|
8431
9333
|
try {
|
|
8432
9334
|
const downloaded = await downloadToTempFile(att.url, {
|
|
8433
9335
|
timeout,
|
|
@@ -8456,7 +9358,7 @@ async function resolveInboundAttachmentsForAgent(params) {
|
|
|
8456
9358
|
logger.info("voice attachment received but ASR is disabled");
|
|
8457
9359
|
} else if (!asrCredentials) {
|
|
8458
9360
|
logger.warn("voice ASR enabled but credentials are missing or invalid");
|
|
8459
|
-
} else if (!
|
|
9361
|
+
} else if (!isHttpUrl3(att.url)) {
|
|
8460
9362
|
logger.warn("voice ASR skipped: attachment URL is not an HTTP URL");
|
|
8461
9363
|
} else {
|
|
8462
9364
|
try {
|
|
@@ -8527,6 +9429,74 @@ function buildInboundContentWithAttachments(params) {
|
|
|
8527
9429
|
parts.push(block);
|
|
8528
9430
|
return parts.join("\n\n");
|
|
8529
9431
|
}
|
|
9432
|
+
function resolveRefAttachmentType(attachment) {
|
|
9433
|
+
const contentType = attachment.contentType?.trim().toLowerCase() ?? "";
|
|
9434
|
+
if (contentType.startsWith("image/") || isImageAttachment(attachment)) {
|
|
9435
|
+
return "image";
|
|
9436
|
+
}
|
|
9437
|
+
if (contentType === "voice" || contentType.startsWith("audio/") || isVoiceAttachment(attachment)) {
|
|
9438
|
+
return "voice";
|
|
9439
|
+
}
|
|
9440
|
+
if (contentType.startsWith("video/")) {
|
|
9441
|
+
return "video";
|
|
9442
|
+
}
|
|
9443
|
+
const mediaType = detectMediaType(attachment.filename?.trim() || attachment.url);
|
|
9444
|
+
if (mediaType === "image") return "image";
|
|
9445
|
+
if (mediaType === "audio") return "voice";
|
|
9446
|
+
if (mediaType === "video") return "video";
|
|
9447
|
+
if (mediaType === "file") return "file";
|
|
9448
|
+
return "unknown";
|
|
9449
|
+
}
|
|
9450
|
+
function buildInboundRefAttachmentSummaries(attachments) {
|
|
9451
|
+
if (attachments.length === 0) {
|
|
9452
|
+
return void 0;
|
|
9453
|
+
}
|
|
9454
|
+
return attachments.map((item) => ({
|
|
9455
|
+
type: resolveRefAttachmentType(item.attachment),
|
|
9456
|
+
...item.attachment.filename?.trim() ? { filename: item.attachment.filename.trim() } : {},
|
|
9457
|
+
...item.attachment.contentType?.trim() ? { contentType: item.attachment.contentType.trim() } : {},
|
|
9458
|
+
...item.localImagePath?.trim() ? { localPath: item.localImagePath.trim() } : {},
|
|
9459
|
+
...item.attachment.url?.trim() ? { url: item.attachment.url.trim() } : {},
|
|
9460
|
+
...item.voiceTranscript?.trim() ? {
|
|
9461
|
+
transcript: item.voiceTranscript.trim(),
|
|
9462
|
+
transcriptSource: "asr"
|
|
9463
|
+
} : {}
|
|
9464
|
+
}));
|
|
9465
|
+
}
|
|
9466
|
+
function buildQuotedAgentBody(params) {
|
|
9467
|
+
const quoteBlock = `[\u5F15\u7528\u6D88\u606F\u5F00\u59CB]
|
|
9468
|
+
${params.replyToBody}
|
|
9469
|
+
[\u5F15\u7528\u6D88\u606F\u7ED3\u675F]`;
|
|
9470
|
+
return params.baseBody ? `${quoteBlock}
|
|
9471
|
+
|
|
9472
|
+
${params.baseBody}` : quoteBlock;
|
|
9473
|
+
}
|
|
9474
|
+
function resolveAgentBodyBase(ctx) {
|
|
9475
|
+
if (typeof ctx.BodyForAgent === "string" && ctx.BodyForAgent.trim()) {
|
|
9476
|
+
return ctx.BodyForAgent;
|
|
9477
|
+
}
|
|
9478
|
+
if (typeof ctx.RawBody === "string" && ctx.RawBody.trim()) {
|
|
9479
|
+
return ctx.RawBody;
|
|
9480
|
+
}
|
|
9481
|
+
if (typeof ctx.Body === "string" && ctx.Body.trim()) {
|
|
9482
|
+
return ctx.Body;
|
|
9483
|
+
}
|
|
9484
|
+
if (typeof ctx.CommandBody === "string" && ctx.CommandBody.trim()) {
|
|
9485
|
+
return ctx.CommandBody;
|
|
9486
|
+
}
|
|
9487
|
+
return "";
|
|
9488
|
+
}
|
|
9489
|
+
function uniqueRefIndexKeys(...values) {
|
|
9490
|
+
const keys = [];
|
|
9491
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9492
|
+
for (const value of values) {
|
|
9493
|
+
const next = value?.trim();
|
|
9494
|
+
if (!next || seen.has(next)) continue;
|
|
9495
|
+
seen.add(next);
|
|
9496
|
+
keys.push(next);
|
|
9497
|
+
}
|
|
9498
|
+
return keys;
|
|
9499
|
+
}
|
|
8530
9500
|
function resolveInboundLogContent(params) {
|
|
8531
9501
|
const text = params.content.trim();
|
|
8532
9502
|
if (text) return text;
|
|
@@ -8548,6 +9518,7 @@ function sanitizeInboundLogText(text) {
|
|
|
8548
9518
|
function parseC2CMessage(data, fallbackEventId) {
|
|
8549
9519
|
const payload = data;
|
|
8550
9520
|
const { text, attachments } = parseTextWithAttachments(payload);
|
|
9521
|
+
const refIndices = parseQQBotRefIndices(payload);
|
|
8551
9522
|
const id = toString(payload.id);
|
|
8552
9523
|
const eventId = resolveEventId(payload, fallbackEventId);
|
|
8553
9524
|
const timestamp = toNumber2(payload.timestamp) ?? Date.now();
|
|
@@ -8564,6 +9535,7 @@ function parseC2CMessage(data, fallbackEventId) {
|
|
|
8564
9535
|
messageId: id,
|
|
8565
9536
|
eventId,
|
|
8566
9537
|
timestamp,
|
|
9538
|
+
...refIndices,
|
|
8567
9539
|
mentionedBot: false
|
|
8568
9540
|
};
|
|
8569
9541
|
}
|
|
@@ -8677,6 +9649,22 @@ function resolveChatTarget(event) {
|
|
|
8677
9649
|
peerKind: "dm"
|
|
8678
9650
|
};
|
|
8679
9651
|
}
|
|
9652
|
+
function resolveQQBotEffectiveSessionKey(params) {
|
|
9653
|
+
const { inbound, route, accountId } = params;
|
|
9654
|
+
if (inbound.type !== "direct") {
|
|
9655
|
+
return route.sessionKey;
|
|
9656
|
+
}
|
|
9657
|
+
const senderStableId = inbound.c2cOpenid?.trim() || inbound.senderId?.trim();
|
|
9658
|
+
if (!senderStableId) {
|
|
9659
|
+
return route.sessionKey;
|
|
9660
|
+
}
|
|
9661
|
+
const resolvedAccountId = route.accountId?.trim() || accountId.trim() || DEFAULT_ACCOUNT_ID;
|
|
9662
|
+
return buildQQBotDirectSessionKey({
|
|
9663
|
+
routeSessionKey: route.sessionKey,
|
|
9664
|
+
accountId: resolvedAccountId,
|
|
9665
|
+
senderStableId
|
|
9666
|
+
});
|
|
9667
|
+
}
|
|
8680
9668
|
function resolveEnvelopeFrom(event) {
|
|
8681
9669
|
if (event.type === "group") {
|
|
8682
9670
|
return `group:${(event.groupOpenid ?? "unknown").toLowerCase()}`;
|
|
@@ -8728,24 +9716,58 @@ function resolveKnownQQBotTargetFromInbound(params) {
|
|
|
8728
9716
|
}
|
|
8729
9717
|
function extractLocalMediaFromText(params) {
|
|
8730
9718
|
const { text, logger } = params;
|
|
8731
|
-
const
|
|
8732
|
-
|
|
8733
|
-
|
|
8734
|
-
|
|
8735
|
-
|
|
8736
|
-
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
|
|
8740
|
-
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
8744
|
-
|
|
8745
|
-
|
|
9719
|
+
const mediaUrls = [];
|
|
9720
|
+
const seenMedia = /* @__PURE__ */ new Set();
|
|
9721
|
+
let nextText = text;
|
|
9722
|
+
const MARKDOWN_LINKED_IMAGE_RE2 = /\[!\[([^\]]*)\]\(([^)]+)\)\]\(([^)]+)\)/g;
|
|
9723
|
+
const MARKDOWN_IMAGE_RE3 = /!\[([^\]]*)\]\(([^)]+)\)/g;
|
|
9724
|
+
const MARKDOWN_LINK_RE2 = /\[([^\]]*)\]\(([^)]+)\)/g;
|
|
9725
|
+
const BARE_LOCAL_MEDIA_PATH_RE = /`?((?:\/(?:tmp|var|private|Users|home|root)\/[^\s`'",)]+|[A-Za-z]:[\\/][^\s`'",)]+)\.(?:png|jpg|jpeg|gif|bmp|webp|svg|ico|mp3|wav|ogg|m4a|amr|flac|aac|wma|mp4|mov|avi|mkv|webm|flv|wmv|m4v))`?/gi;
|
|
9726
|
+
const collectLocalRichMedia = (rawValue, allowedTypes) => {
|
|
9727
|
+
const candidate = stripTitleFromUrl(rawValue.trim());
|
|
9728
|
+
if (!candidate || !isLocalReference(candidate)) {
|
|
9729
|
+
return void 0;
|
|
9730
|
+
}
|
|
9731
|
+
if (!fs3.existsSync(candidate)) {
|
|
9732
|
+
logger?.warn?.(`[media] local file not found: ${candidate}`);
|
|
9733
|
+
return void 0;
|
|
9734
|
+
}
|
|
9735
|
+
const mediaType = detectMediaType(candidate);
|
|
9736
|
+
if (mediaType === "file") {
|
|
9737
|
+
return void 0;
|
|
9738
|
+
}
|
|
9739
|
+
if (allowedTypes && !allowedTypes.has(mediaType)) {
|
|
9740
|
+
return void 0;
|
|
9741
|
+
}
|
|
9742
|
+
if (seenMedia.has(candidate)) {
|
|
9743
|
+
return candidate;
|
|
9744
|
+
}
|
|
9745
|
+
seenMedia.add(candidate);
|
|
9746
|
+
mediaUrls.push(candidate);
|
|
9747
|
+
return candidate;
|
|
9748
|
+
};
|
|
9749
|
+
nextText = nextText.replace(MARKDOWN_LINKED_IMAGE_RE2, (fullMatch, _alt, rawPath) => {
|
|
9750
|
+
return collectLocalRichMedia(rawPath) ? "" : fullMatch;
|
|
8746
9751
|
});
|
|
8747
|
-
|
|
8748
|
-
|
|
9752
|
+
nextText = nextText.replace(MARKDOWN_IMAGE_RE3, (fullMatch, _alt, rawPath) => {
|
|
9753
|
+
return collectLocalRichMedia(rawPath) ? "" : fullMatch;
|
|
9754
|
+
});
|
|
9755
|
+
nextText = nextText.replace(MARKDOWN_LINK_RE2, (fullMatch, _label, rawPath) => {
|
|
9756
|
+
const mediaPath = collectLocalRichMedia(rawPath, /* @__PURE__ */ new Set(["audio", "video"]));
|
|
9757
|
+
if (!mediaPath) {
|
|
9758
|
+
return fullMatch;
|
|
9759
|
+
}
|
|
9760
|
+
return "";
|
|
9761
|
+
});
|
|
9762
|
+
nextText = nextText.replace(BARE_LOCAL_MEDIA_PATH_RE, (fullMatch, rawPath) => {
|
|
9763
|
+
return collectLocalRichMedia(rawPath) ? "" : fullMatch;
|
|
9764
|
+
});
|
|
9765
|
+
nextText = nextText.replace(/[ \t]+\n/g, "\n");
|
|
9766
|
+
nextText = nextText.replace(/\n{3,}/g, "\n\n");
|
|
9767
|
+
return {
|
|
9768
|
+
text: nextText.trim(),
|
|
9769
|
+
mediaUrls
|
|
9770
|
+
};
|
|
8749
9771
|
}
|
|
8750
9772
|
function extractMediaLinesFromText(params) {
|
|
8751
9773
|
const { text, logger } = params;
|
|
@@ -8798,6 +9820,9 @@ var FILE_PLACEHOLDER_RE = /\[文件:\s*[^\]\n]+\]/g;
|
|
|
8798
9820
|
var DIRECTIVE_TAG_RE = /\[\[\s*(?:reply_to_current|reply_to\s*:[^\]]+|audio_as_voice|tts(?::text)?|\/tts(?::text)?)\s*\]\]/gi;
|
|
8799
9821
|
var VOICE_EMOTION_TAG_RE = /\[(?:happy|excited|calm|sad|angry|frustrated|softly|whispers|loudly|cheerfully|deadpan|sarcastically|laughs|sighs|chuckles|gasps|pause|slowly|rushed|hesitates|playfully|warmly|gently)\]/gi;
|
|
8800
9822
|
var TTS_LIKE_RAW_TEXT_RE = /\[\[\s*(?:tts(?::text)?|\/tts(?::text)?|audio_as_voice|reply_to_current|reply_to\s*:)/i;
|
|
9823
|
+
var MARKDOWN_TABLE_SEPARATOR_RE = /^\|?(?:\s*:?-{3,}:?\s*\|)+(?:\s*:?-{3,}:?)?\|?$/;
|
|
9824
|
+
var EXPLICIT_MARKDOWN_FENCE_RE = /(^|\n)(`{3,}|~{3,})\s*(?:markdown|md)\s*\n([\s\S]*?)\n\2(?=\n|$)/gi;
|
|
9825
|
+
var GENERIC_MARKDOWN_FENCE_RE = /(^|\n)(`{3,}|~{3,})\s*\n([\s\S]*?)\n\2(?=\n|$)/g;
|
|
8801
9826
|
function extractFinalBlocks(text) {
|
|
8802
9827
|
const matches = Array.from(text.matchAll(FINAL_BLOCK_RE));
|
|
8803
9828
|
if (matches.length === 0) return void 0;
|
|
@@ -8822,6 +9847,14 @@ function sanitizeQQBotOutboundText(rawText) {
|
|
|
8822
9847
|
if (/^NO_REPLY$/i.test(next)) return "";
|
|
8823
9848
|
return next;
|
|
8824
9849
|
}
|
|
9850
|
+
function formatQQBotOutboundPreview(text, maxLength = 240) {
|
|
9851
|
+
const normalized = text.replace(/\r\n/g, "\n").trim();
|
|
9852
|
+
if (!normalized) {
|
|
9853
|
+
return '""';
|
|
9854
|
+
}
|
|
9855
|
+
const preview = normalized.length > maxLength ? `${normalized.slice(0, Math.max(0, maxLength - 3))}...` : normalized;
|
|
9856
|
+
return JSON.stringify(preview);
|
|
9857
|
+
}
|
|
8825
9858
|
function shouldSuppressQQBotTextWhenMediaPresent(rawText, sanitizedText) {
|
|
8826
9859
|
const raw = rawText.trim();
|
|
8827
9860
|
if (!raw) return false;
|
|
@@ -8854,8 +9887,113 @@ function evaluateReplyFinalOnlyDelivery(params) {
|
|
|
8854
9887
|
}
|
|
8855
9888
|
return { skipDelivery: true, suppressText: false };
|
|
8856
9889
|
}
|
|
9890
|
+
function isQQBotC2CTarget(to) {
|
|
9891
|
+
const trimmed = to.trim();
|
|
9892
|
+
const raw = trimmed.startsWith("qqbot:") ? trimmed.slice("qqbot:".length) : trimmed;
|
|
9893
|
+
return !raw.startsWith("group:") && !raw.startsWith("channel:");
|
|
9894
|
+
}
|
|
9895
|
+
function splitQQBotMarkdownTransportMediaUrls(mediaUrls) {
|
|
9896
|
+
const markdownImageUrls = [];
|
|
9897
|
+
const mediaQueue = [];
|
|
9898
|
+
const seenMarkdownImages = /* @__PURE__ */ new Set();
|
|
9899
|
+
const seenMedia = /* @__PURE__ */ new Set();
|
|
9900
|
+
for (const rawUrl of mediaUrls) {
|
|
9901
|
+
const next = rawUrl.trim();
|
|
9902
|
+
if (!next) continue;
|
|
9903
|
+
if (isQQBotHttpImageUrl(next)) {
|
|
9904
|
+
if (seenMarkdownImages.has(next)) continue;
|
|
9905
|
+
seenMarkdownImages.add(next);
|
|
9906
|
+
markdownImageUrls.push(next);
|
|
9907
|
+
continue;
|
|
9908
|
+
}
|
|
9909
|
+
if (seenMedia.has(next)) continue;
|
|
9910
|
+
seenMedia.add(next);
|
|
9911
|
+
mediaQueue.push(next);
|
|
9912
|
+
}
|
|
9913
|
+
return { markdownImageUrls, mediaQueue };
|
|
9914
|
+
}
|
|
9915
|
+
function hasQQBotMarkdownTable(text) {
|
|
9916
|
+
const lines = text.replace(/\r\n/g, "\n").split("\n");
|
|
9917
|
+
for (let index = 0; index < lines.length - 1; index += 1) {
|
|
9918
|
+
const header = lines[index]?.trim() ?? "";
|
|
9919
|
+
const separator = lines[index + 1]?.trim() ?? "";
|
|
9920
|
+
if (!header.includes("|") || !MARKDOWN_TABLE_SEPARATOR_RE.test(separator)) {
|
|
9921
|
+
continue;
|
|
9922
|
+
}
|
|
9923
|
+
const headerColumns = header.split("|").filter((column) => column.trim()).length;
|
|
9924
|
+
const separatorColumns = separator.split("|").filter((column) => column.trim()).length;
|
|
9925
|
+
if (headerColumns >= 2 && separatorColumns >= 2) {
|
|
9926
|
+
return true;
|
|
9927
|
+
}
|
|
9928
|
+
}
|
|
9929
|
+
return false;
|
|
9930
|
+
}
|
|
9931
|
+
function resolveQQBotTextReplyRefs(params) {
|
|
9932
|
+
const mode = params.c2cMarkdownDeliveryMode ?? "proactive-table-only";
|
|
9933
|
+
const forceProactive = params.markdownSupport && isQQBotC2CTarget(params.to) && (mode === "proactive-all" || mode === "proactive-table-only" && hasQQBotMarkdownTable(params.text));
|
|
9934
|
+
if (!forceProactive) {
|
|
9935
|
+
return {
|
|
9936
|
+
forceProactive: false,
|
|
9937
|
+
replyToId: params.replyToId,
|
|
9938
|
+
replyEventId: params.replyEventId
|
|
9939
|
+
};
|
|
9940
|
+
}
|
|
9941
|
+
return {
|
|
9942
|
+
forceProactive: true,
|
|
9943
|
+
replyToId: void 0,
|
|
9944
|
+
replyEventId: void 0
|
|
9945
|
+
};
|
|
9946
|
+
}
|
|
9947
|
+
function appendQQBotBufferedText(bufferedTexts, nextText) {
|
|
9948
|
+
const normalized = nextText.trim();
|
|
9949
|
+
if (!normalized) return bufferedTexts;
|
|
9950
|
+
if (bufferedTexts.length === 0) return [normalized];
|
|
9951
|
+
const currentCombined = bufferedTexts.join("\n\n");
|
|
9952
|
+
if (currentCombined === normalized || currentCombined.includes(normalized)) {
|
|
9953
|
+
return bufferedTexts;
|
|
9954
|
+
}
|
|
9955
|
+
if (normalized.includes(currentCombined)) {
|
|
9956
|
+
return [normalized];
|
|
9957
|
+
}
|
|
9958
|
+
const last = bufferedTexts[bufferedTexts.length - 1];
|
|
9959
|
+
if (last === normalized) {
|
|
9960
|
+
return bufferedTexts;
|
|
9961
|
+
}
|
|
9962
|
+
return [...bufferedTexts, normalized];
|
|
9963
|
+
}
|
|
9964
|
+
function normalizeQQBotRenderedMarkdown(text) {
|
|
9965
|
+
if (!text.trim()) return "";
|
|
9966
|
+
let next = text.trim();
|
|
9967
|
+
let changed = false;
|
|
9968
|
+
next = next.replace(
|
|
9969
|
+
EXPLICIT_MARKDOWN_FENCE_RE,
|
|
9970
|
+
(block, leadingLineBreak, _fence, inner) => {
|
|
9971
|
+
const normalizedInner = inner.trim();
|
|
9972
|
+
if (!normalizedInner) {
|
|
9973
|
+
return block;
|
|
9974
|
+
}
|
|
9975
|
+
changed = true;
|
|
9976
|
+
return `${leadingLineBreak}${normalizedInner}`;
|
|
9977
|
+
}
|
|
9978
|
+
);
|
|
9979
|
+
next = next.replace(
|
|
9980
|
+
GENERIC_MARKDOWN_FENCE_RE,
|
|
9981
|
+
(block, leadingLineBreak, _fence, inner) => {
|
|
9982
|
+
const normalizedInner = inner.trim();
|
|
9983
|
+
if (!normalizedInner) {
|
|
9984
|
+
return block;
|
|
9985
|
+
}
|
|
9986
|
+
if (!hasQQBotMarkdownTable(normalizedInner)) {
|
|
9987
|
+
return block;
|
|
9988
|
+
}
|
|
9989
|
+
changed = true;
|
|
9990
|
+
return `${leadingLineBreak}${normalizedInner}`;
|
|
9991
|
+
}
|
|
9992
|
+
);
|
|
9993
|
+
return changed ? next.trim() : text.trim();
|
|
9994
|
+
}
|
|
8857
9995
|
async function sendQQBotMediaWithFallback(params) {
|
|
8858
|
-
const { qqCfg, to, mediaQueue, replyToId, replyEventId, logger, onDelivered, onError } = params;
|
|
9996
|
+
const { qqCfg, to, mediaQueue, replyToId, replyEventId, accountId, logger, onDelivered, onError } = params;
|
|
8859
9997
|
const outbound = params.outbound ?? qqbotOutbound;
|
|
8860
9998
|
for (const mediaUrl of mediaQueue) {
|
|
8861
9999
|
const result = await outbound.sendMedia({
|
|
@@ -8863,7 +10001,8 @@ async function sendQQBotMediaWithFallback(params) {
|
|
|
8863
10001
|
to,
|
|
8864
10002
|
mediaUrl,
|
|
8865
10003
|
replyToId,
|
|
8866
|
-
replyEventId
|
|
10004
|
+
replyEventId,
|
|
10005
|
+
accountId
|
|
8867
10006
|
});
|
|
8868
10007
|
if (result.error) {
|
|
8869
10008
|
logger.error(`sendMedia failed: ${result.error}`);
|
|
@@ -8877,7 +10016,8 @@ async function sendQQBotMediaWithFallback(params) {
|
|
|
8877
10016
|
to,
|
|
8878
10017
|
text: fallback,
|
|
8879
10018
|
replyToId,
|
|
8880
|
-
replyEventId
|
|
10019
|
+
replyEventId,
|
|
10020
|
+
accountId
|
|
8881
10021
|
});
|
|
8882
10022
|
if (fallbackResult.error) {
|
|
8883
10023
|
logger.error(`sendText fallback failed: ${fallbackResult.error}`);
|
|
@@ -8920,32 +10060,27 @@ function buildInboundContext(params) {
|
|
|
8920
10060
|
};
|
|
8921
10061
|
}
|
|
8922
10062
|
async function dispatchToAgent(params) {
|
|
8923
|
-
const { inbound, cfg, qqCfg, accountId, logger } = params;
|
|
10063
|
+
const { inbound, cfg, qqCfg, accountId, logger, route } = params;
|
|
8924
10064
|
const runtime2 = getQQBotRuntime();
|
|
8925
|
-
const
|
|
8926
|
-
if (!routing) {
|
|
8927
|
-
logger.warn("routing API not available");
|
|
8928
|
-
return;
|
|
8929
|
-
}
|
|
10065
|
+
const routeSessionKey = resolveQQBotRouteSessionKey(route);
|
|
8930
10066
|
const target = resolveChatTarget(inbound);
|
|
10067
|
+
const outboundAccountId = route.accountId ?? accountId;
|
|
10068
|
+
let typingRefIdx;
|
|
8931
10069
|
if (inbound.c2cOpenid) {
|
|
8932
10070
|
const typing = await qqbotOutbound.sendTyping({
|
|
8933
10071
|
cfg: { channels: { qqbot: qqCfg } },
|
|
8934
10072
|
to: `user:${inbound.c2cOpenid}`,
|
|
8935
10073
|
replyToId: inbound.messageId,
|
|
8936
10074
|
replyEventId: inbound.eventId,
|
|
8937
|
-
inputSecond: 60
|
|
10075
|
+
inputSecond: 60,
|
|
10076
|
+
accountId: outboundAccountId
|
|
8938
10077
|
});
|
|
8939
10078
|
if (typing.error) {
|
|
8940
10079
|
logger.warn(`sendTyping failed: ${typing.error}`);
|
|
10080
|
+
} else {
|
|
10081
|
+
typingRefIdx = typing.refIdx;
|
|
8941
10082
|
}
|
|
8942
10083
|
}
|
|
8943
|
-
const route = routing({
|
|
8944
|
-
cfg,
|
|
8945
|
-
channel: "qqbot",
|
|
8946
|
-
accountId,
|
|
8947
|
-
peer: { kind: target.peerKind, id: target.peerId }
|
|
8948
|
-
});
|
|
8949
10084
|
const replyApi = runtime2.channel?.reply;
|
|
8950
10085
|
if (!replyApi) {
|
|
8951
10086
|
logger.warn("reply API not available");
|
|
@@ -8974,7 +10109,8 @@ async function dispatchToAgent(params) {
|
|
|
8974
10109
|
to: target.to,
|
|
8975
10110
|
text: LONG_TASK_NOTICE_TEXT,
|
|
8976
10111
|
replyToId: inbound.messageId,
|
|
8977
|
-
replyEventId: inbound.eventId
|
|
10112
|
+
replyEventId: inbound.eventId,
|
|
10113
|
+
accountId: outboundAccountId
|
|
8978
10114
|
});
|
|
8979
10115
|
if (result.error) {
|
|
8980
10116
|
logger.warn(`send long-task notice failed: ${result.error}`);
|
|
@@ -8994,7 +10130,7 @@ async function dispatchToAgent(params) {
|
|
|
8994
10130
|
{ agentId: route.agentId }
|
|
8995
10131
|
);
|
|
8996
10132
|
const envelopeOptions = replyApi.resolveEnvelopeFormatOptions?.(cfg);
|
|
8997
|
-
const previousTimestamp = storePath && sessionApi?.readSessionUpdatedAt ? sessionApi.readSessionUpdatedAt({ storePath, sessionKey:
|
|
10133
|
+
const previousTimestamp = storePath && sessionApi?.readSessionUpdatedAt ? sessionApi.readSessionUpdatedAt({ storePath, sessionKey: routeSessionKey }) : null;
|
|
8998
10134
|
const resolvedAttachmentResult = await resolveInboundAttachmentsForAgent({
|
|
8999
10135
|
attachments: inbound.attachments,
|
|
9000
10136
|
qqCfg,
|
|
@@ -9006,7 +10142,8 @@ async function dispatchToAgent(params) {
|
|
|
9006
10142
|
to: target.to,
|
|
9007
10143
|
text: buildVoiceASRFallbackReply(resolvedAttachmentResult.asrErrorMessage),
|
|
9008
10144
|
replyToId: inbound.messageId,
|
|
9009
|
-
replyEventId: inbound.eventId
|
|
10145
|
+
replyEventId: inbound.eventId,
|
|
10146
|
+
accountId: outboundAccountId
|
|
9010
10147
|
});
|
|
9011
10148
|
if (fallback.error) {
|
|
9012
10149
|
logger.error(`sendText ASR fallback failed: ${fallback.error}`);
|
|
@@ -9021,6 +10158,39 @@ async function dispatchToAgent(params) {
|
|
|
9021
10158
|
if (localImageCount > 0) {
|
|
9022
10159
|
logger.info(`prepared ${localImageCount} local image attachment(s) for agent`);
|
|
9023
10160
|
}
|
|
10161
|
+
let replyToId;
|
|
10162
|
+
let replyToBody;
|
|
10163
|
+
let replyToSender;
|
|
10164
|
+
let replyToIsQuote = false;
|
|
10165
|
+
if (inbound.c2cOpenid && inbound.refMsgIdx) {
|
|
10166
|
+
replyToId = inbound.refMsgIdx;
|
|
10167
|
+
replyToIsQuote = true;
|
|
10168
|
+
const refEntry = getRefIndex(inbound.refMsgIdx);
|
|
10169
|
+
if (refEntry) {
|
|
10170
|
+
replyToBody = formatRefEntryForAgent(refEntry);
|
|
10171
|
+
replyToSender = refEntry.senderName ?? refEntry.senderId;
|
|
10172
|
+
logger.info(`quote context resolved refMsgIdx=${inbound.refMsgIdx}`);
|
|
10173
|
+
} else {
|
|
10174
|
+
replyToBody = QQ_QUOTE_BODY_UNAVAILABLE_TEXT;
|
|
10175
|
+
logger.warn(`quote context missing refMsgIdx=${inbound.refMsgIdx}`);
|
|
10176
|
+
}
|
|
10177
|
+
}
|
|
10178
|
+
const refAttachmentSummaries = buildInboundRefAttachmentSummaries(resolvedAttachments);
|
|
10179
|
+
const currentRefIndexKeys = inbound.c2cOpenid ? uniqueRefIndexKeys(inbound.msgIdx, typingRefIdx) : [];
|
|
10180
|
+
if (currentRefIndexKeys.length > 0) {
|
|
10181
|
+
for (const currentRefIndexKey of currentRefIndexKeys) {
|
|
10182
|
+
setRefIndex(currentRefIndexKey, {
|
|
10183
|
+
content: inbound.content,
|
|
10184
|
+
senderId: inbound.senderId,
|
|
10185
|
+
...inbound.senderName ? { senderName: inbound.senderName } : {},
|
|
10186
|
+
timestamp: inbound.timestamp,
|
|
10187
|
+
...refAttachmentSummaries ? { attachments: refAttachmentSummaries } : {}
|
|
10188
|
+
});
|
|
10189
|
+
}
|
|
10190
|
+
logger.info(
|
|
10191
|
+
`cached inbound ref_idx keys=${currentRefIndexKeys.join(",")} msgIdx=${inbound.msgIdx ?? "-"} typingRefIdx=${typingRefIdx ?? "-"}`
|
|
10192
|
+
);
|
|
10193
|
+
}
|
|
9024
10194
|
const rawBody = buildInboundContentWithAttachments({
|
|
9025
10195
|
content: inbound.content,
|
|
9026
10196
|
attachments: resolvedAttachments
|
|
@@ -9046,40 +10216,48 @@ async function dispatchToAgent(params) {
|
|
|
9046
10216
|
}) : rawBody;
|
|
9047
10217
|
const inboundCtx = buildInboundContext({
|
|
9048
10218
|
event: inbound,
|
|
9049
|
-
sessionKey:
|
|
9050
|
-
accountId:
|
|
10219
|
+
sessionKey: routeSessionKey,
|
|
10220
|
+
accountId: outboundAccountId,
|
|
9051
10221
|
body: inboundBody,
|
|
9052
10222
|
rawBody,
|
|
9053
10223
|
commandBody: rawBody
|
|
9054
10224
|
});
|
|
9055
10225
|
const finalizeInboundContext = replyApi?.finalizeInboundContext;
|
|
9056
10226
|
const finalCtx = finalizeInboundContext ? finalizeInboundContext(inboundCtx) : inboundCtx;
|
|
9057
|
-
|
|
9058
|
-
|
|
9059
|
-
|
|
9060
|
-
|
|
9061
|
-
|
|
9062
|
-
|
|
9063
|
-
|
|
9064
|
-
|
|
9065
|
-
|
|
9066
|
-
|
|
9067
|
-
|
|
9068
|
-
|
|
10227
|
+
const ctxTo = normalizeQQBotReplyTarget(finalCtx.To);
|
|
10228
|
+
const ctxOriginatingTo = normalizeQQBotReplyTarget(finalCtx.OriginatingTo);
|
|
10229
|
+
const stableTo = ctxOriginatingTo ?? ctxTo ?? target.to;
|
|
10230
|
+
finalCtx.To = stableTo;
|
|
10231
|
+
finalCtx.OriginatingTo = stableTo;
|
|
10232
|
+
if (replyToId) {
|
|
10233
|
+
finalCtx.ReplyToId = replyToId;
|
|
10234
|
+
finalCtx.ReplyToBody = replyToBody;
|
|
10235
|
+
finalCtx.ReplyToSender = replyToSender;
|
|
10236
|
+
finalCtx.ReplyToIsQuote = replyToIsQuote;
|
|
10237
|
+
}
|
|
10238
|
+
const isSlashCommand = typeof finalCtx.CommandBody === "string" ? finalCtx.CommandBody.trim().startsWith("/") : typeof finalCtx.RawBody === "string" ? finalCtx.RawBody.trim().startsWith("/") : false;
|
|
10239
|
+
if (!isSlashCommand) {
|
|
10240
|
+
let agentBody = resolveAgentBodyBase(finalCtx);
|
|
10241
|
+
if (replyToIsQuote && replyToBody && replyToBody !== QQ_QUOTE_BODY_UNAVAILABLE_TEXT) {
|
|
10242
|
+
agentBody = buildQuotedAgentBody({
|
|
10243
|
+
baseBody: agentBody,
|
|
10244
|
+
replyToBody
|
|
10245
|
+
});
|
|
9069
10246
|
}
|
|
10247
|
+
finalCtx.BodyForAgent = appendCronHiddenPrompt(agentBody);
|
|
9070
10248
|
}
|
|
9071
10249
|
if (storePath && sessionApi?.recordInboundSession) {
|
|
9072
10250
|
try {
|
|
9073
|
-
const mainSessionKeyRaw = route
|
|
9074
|
-
const mainSessionKey = typeof mainSessionKeyRaw === "string" && mainSessionKeyRaw.trim() ? mainSessionKeyRaw : void 0;
|
|
10251
|
+
const mainSessionKeyRaw = route.mainSessionKey;
|
|
10252
|
+
const mainSessionKey = typeof mainSessionKeyRaw === "string" && mainSessionKeyRaw.trim() ? mainSessionKeyRaw.trim() : void 0;
|
|
9075
10253
|
const isGroup = inbound.type === "group" || inbound.type === "channel";
|
|
9076
10254
|
const updateLastRoute = !isGroup ? {
|
|
9077
10255
|
sessionKey: mainSessionKey ?? route.sessionKey,
|
|
9078
10256
|
channel: "qqbot",
|
|
9079
|
-
to:
|
|
9080
|
-
accountId:
|
|
10257
|
+
to: stableTo,
|
|
10258
|
+
accountId: outboundAccountId
|
|
9081
10259
|
} : void 0;
|
|
9082
|
-
const recordSessionKey = typeof finalCtx.SessionKey === "string" && finalCtx.SessionKey.trim() ? finalCtx.SessionKey :
|
|
10260
|
+
const recordSessionKey = typeof finalCtx.SessionKey === "string" && finalCtx.SessionKey.trim() ? finalCtx.SessionKey : routeSessionKey;
|
|
9083
10261
|
await sessionApi.recordInboundSession({
|
|
9084
10262
|
storePath,
|
|
9085
10263
|
sessionKey: recordSessionKey,
|
|
@@ -9103,7 +10281,7 @@ async function dispatchToAgent(params) {
|
|
|
9103
10281
|
const tableMode = textApi?.resolveMarkdownTableMode?.({
|
|
9104
10282
|
cfg,
|
|
9105
10283
|
channel: "qqbot",
|
|
9106
|
-
accountId:
|
|
10284
|
+
accountId: outboundAccountId
|
|
9107
10285
|
});
|
|
9108
10286
|
const resolvedTableMode = tableMode ?? "bullets";
|
|
9109
10287
|
const chunkText = (text) => {
|
|
@@ -9116,6 +10294,100 @@ async function dispatchToAgent(params) {
|
|
|
9116
10294
|
return [text];
|
|
9117
10295
|
};
|
|
9118
10296
|
const replyFinalOnly = qqCfg.replyFinalOnly ?? false;
|
|
10297
|
+
const markdownSupport = qqCfg.markdownSupport ?? true;
|
|
10298
|
+
const c2cMarkdownDeliveryMode = qqCfg.c2cMarkdownDeliveryMode ?? "proactive-table-only";
|
|
10299
|
+
const useC2CMarkdownTransport = markdownSupport && isQQBotC2CTarget(target.to);
|
|
10300
|
+
let bufferedC2CMarkdownTexts = [];
|
|
10301
|
+
let bufferedC2CMarkdownMediaUrls = [];
|
|
10302
|
+
const bufferedC2CMarkdownMediaSeen = /* @__PURE__ */ new Set();
|
|
10303
|
+
const bufferC2CMarkdownMedia = (url) => {
|
|
10304
|
+
const next = url?.trim();
|
|
10305
|
+
if (!next || bufferedC2CMarkdownMediaSeen.has(next)) return;
|
|
10306
|
+
bufferedC2CMarkdownMediaSeen.add(next);
|
|
10307
|
+
bufferedC2CMarkdownMediaUrls.push(next);
|
|
10308
|
+
};
|
|
10309
|
+
const sendC2CMarkdownTransportPayload = async (params2) => {
|
|
10310
|
+
const normalizedText = normalizeQQBotRenderedMarkdown(params2.text);
|
|
10311
|
+
const { markdownImageUrls, mediaQueue } = splitQQBotMarkdownTransportMediaUrls(params2.mediaUrls);
|
|
10312
|
+
const finalMarkdownText = await normalizeQQBotMarkdownImages({
|
|
10313
|
+
text: normalizedText,
|
|
10314
|
+
appendImageUrls: markdownImageUrls
|
|
10315
|
+
});
|
|
10316
|
+
const textReplyRefs = resolveQQBotTextReplyRefs({
|
|
10317
|
+
to: target.to,
|
|
10318
|
+
text: finalMarkdownText || normalizedText,
|
|
10319
|
+
markdownSupport,
|
|
10320
|
+
c2cMarkdownDeliveryMode,
|
|
10321
|
+
replyToId: inbound.messageId,
|
|
10322
|
+
replyEventId: inbound.eventId
|
|
10323
|
+
});
|
|
10324
|
+
const textSegments = finalMarkdownText ? [finalMarkdownText] : [];
|
|
10325
|
+
const deliveryLabel = textReplyRefs.forceProactive ? "c2c-markdown-proactive" : "c2c-markdown-passive";
|
|
10326
|
+
logger.info(
|
|
10327
|
+
`delivery=${deliveryLabel} to=${target.to} segments=${textSegments.length} media=${mediaQueue.length} replyToId=${textReplyRefs.replyToId ? "yes" : "no"} replyEventId=${textReplyRefs.replyEventId ? "yes" : "no"} phase=${params2.phase} tableMode=${String(resolvedTableMode)} chunkMode=${String(chunkMode ?? "default")}`
|
|
10328
|
+
);
|
|
10329
|
+
await sendQQBotMediaWithFallback({
|
|
10330
|
+
qqCfg,
|
|
10331
|
+
to: target.to,
|
|
10332
|
+
mediaQueue,
|
|
10333
|
+
replyToId: textReplyRefs.replyToId,
|
|
10334
|
+
replyEventId: textReplyRefs.replyEventId,
|
|
10335
|
+
accountId: outboundAccountId,
|
|
10336
|
+
logger,
|
|
10337
|
+
onDelivered: () => {
|
|
10338
|
+
markReplyDelivered();
|
|
10339
|
+
},
|
|
10340
|
+
onError: (error) => {
|
|
10341
|
+
markGroupMessageInterfaceBlocked(error);
|
|
10342
|
+
}
|
|
10343
|
+
});
|
|
10344
|
+
if (!finalMarkdownText) {
|
|
10345
|
+
return;
|
|
10346
|
+
}
|
|
10347
|
+
for (let segmentIndex = 0; segmentIndex < textSegments.length; segmentIndex += 1) {
|
|
10348
|
+
const segment = textSegments[segmentIndex] ?? "";
|
|
10349
|
+
const chunks = chunkText(segment);
|
|
10350
|
+
for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex += 1) {
|
|
10351
|
+
const chunk = chunks[chunkIndex] ?? "";
|
|
10352
|
+
logger.info(
|
|
10353
|
+
`delivery=${deliveryLabel} segment=${segmentIndex + 1}/${textSegments.length} chunk=${chunkIndex + 1}/${chunks.length} phase=${params2.phase} preview=${formatQQBotOutboundPreview(chunk)}`
|
|
10354
|
+
);
|
|
10355
|
+
const result = await qqbotOutbound.sendText({
|
|
10356
|
+
cfg: { channels: { qqbot: qqCfg } },
|
|
10357
|
+
to: target.to,
|
|
10358
|
+
text: chunk,
|
|
10359
|
+
replyToId: textReplyRefs.replyToId,
|
|
10360
|
+
replyEventId: textReplyRefs.replyEventId,
|
|
10361
|
+
accountId: outboundAccountId
|
|
10362
|
+
});
|
|
10363
|
+
if (result.error) {
|
|
10364
|
+
logger.error(`send QQ markdown reply failed: ${result.error}`);
|
|
10365
|
+
markGroupMessageInterfaceBlocked(result.error);
|
|
10366
|
+
} else {
|
|
10367
|
+
logger.info(`sent QQ markdown reply (phase=${params2.phase}, len=${chunk.length})`);
|
|
10368
|
+
markReplyDelivered();
|
|
10369
|
+
}
|
|
10370
|
+
}
|
|
10371
|
+
}
|
|
10372
|
+
};
|
|
10373
|
+
const flushBufferedC2CMarkdownReply = async () => {
|
|
10374
|
+
if (!useC2CMarkdownTransport || bufferedC2CMarkdownTexts.length === 0 && bufferedC2CMarkdownMediaUrls.length === 0) {
|
|
10375
|
+
bufferedC2CMarkdownTexts = [];
|
|
10376
|
+
bufferedC2CMarkdownMediaUrls = [];
|
|
10377
|
+
bufferedC2CMarkdownMediaSeen.clear();
|
|
10378
|
+
return;
|
|
10379
|
+
}
|
|
10380
|
+
const combinedText = bufferedC2CMarkdownTexts.join("\n\n").trim();
|
|
10381
|
+
const combinedMediaUrls = [...bufferedC2CMarkdownMediaUrls];
|
|
10382
|
+
bufferedC2CMarkdownTexts = [];
|
|
10383
|
+
bufferedC2CMarkdownMediaUrls = [];
|
|
10384
|
+
bufferedC2CMarkdownMediaSeen.clear();
|
|
10385
|
+
await sendC2CMarkdownTransportPayload({
|
|
10386
|
+
text: combinedText,
|
|
10387
|
+
mediaUrls: combinedMediaUrls,
|
|
10388
|
+
phase: "buffered"
|
|
10389
|
+
});
|
|
10390
|
+
};
|
|
9119
10391
|
const deliver = async (payload, info) => {
|
|
9120
10392
|
const typed = payload;
|
|
9121
10393
|
const extractedTextMedia = extractQQBotReplyMedia({
|
|
@@ -9146,16 +10418,43 @@ async function dispatchToAgent(params) {
|
|
|
9146
10418
|
const suppressEchoText = mediaQueue.length > 0 && shouldSuppressQQBotTextWhenMediaPresent(extractedTextMedia.text, cleanedText);
|
|
9147
10419
|
const suppressText = deliveryDecision.suppressText || suppressEchoText;
|
|
9148
10420
|
const textToSend = suppressText ? "" : cleanedText;
|
|
10421
|
+
if (useC2CMarkdownTransport) {
|
|
10422
|
+
const shouldBufferFinalOnlyPayload = replyFinalOnly && (!info?.kind || info.kind === "final");
|
|
10423
|
+
if (shouldBufferFinalOnlyPayload) {
|
|
10424
|
+
if (textToSend) {
|
|
10425
|
+
bufferedC2CMarkdownTexts = appendQQBotBufferedText(bufferedC2CMarkdownTexts, textToSend);
|
|
10426
|
+
}
|
|
10427
|
+
for (const url of mediaQueue) {
|
|
10428
|
+
bufferC2CMarkdownMedia(url);
|
|
10429
|
+
}
|
|
10430
|
+
return;
|
|
10431
|
+
}
|
|
10432
|
+
await sendC2CMarkdownTransportPayload({
|
|
10433
|
+
text: textToSend,
|
|
10434
|
+
mediaUrls: mediaQueue,
|
|
10435
|
+
phase: "immediate"
|
|
10436
|
+
});
|
|
10437
|
+
return;
|
|
10438
|
+
}
|
|
9149
10439
|
if (textToSend) {
|
|
9150
10440
|
const converted = textApi?.convertMarkdownTables ? textApi.convertMarkdownTables(textToSend, resolvedTableMode) : textToSend;
|
|
10441
|
+
const textReplyRefs = resolveQQBotTextReplyRefs({
|
|
10442
|
+
to: target.to,
|
|
10443
|
+
text: converted,
|
|
10444
|
+
markdownSupport,
|
|
10445
|
+
c2cMarkdownDeliveryMode,
|
|
10446
|
+
replyToId: inbound.messageId,
|
|
10447
|
+
replyEventId: inbound.eventId
|
|
10448
|
+
});
|
|
9151
10449
|
const chunks = chunkText(converted);
|
|
9152
10450
|
for (const chunk of chunks) {
|
|
9153
10451
|
const result = await qqbotOutbound.sendText({
|
|
9154
10452
|
cfg: { channels: { qqbot: qqCfg } },
|
|
9155
10453
|
to: target.to,
|
|
9156
10454
|
text: chunk,
|
|
9157
|
-
replyToId:
|
|
9158
|
-
replyEventId:
|
|
10455
|
+
replyToId: textReplyRefs.replyToId,
|
|
10456
|
+
replyEventId: textReplyRefs.replyEventId,
|
|
10457
|
+
accountId: outboundAccountId
|
|
9159
10458
|
});
|
|
9160
10459
|
if (result.error) {
|
|
9161
10460
|
logger.error(`sendText failed: ${result.error}`);
|
|
@@ -9171,6 +10470,7 @@ async function dispatchToAgent(params) {
|
|
|
9171
10470
|
mediaQueue,
|
|
9172
10471
|
replyToId: inbound.messageId,
|
|
9173
10472
|
replyEventId: inbound.eventId,
|
|
10473
|
+
accountId: outboundAccountId,
|
|
9174
10474
|
logger,
|
|
9175
10475
|
onDelivered: () => {
|
|
9176
10476
|
markReplyDelivered();
|
|
@@ -9199,6 +10499,7 @@ async function dispatchToAgent(params) {
|
|
|
9199
10499
|
}
|
|
9200
10500
|
}
|
|
9201
10501
|
});
|
|
10502
|
+
await flushBufferedC2CMarkdownReply();
|
|
9202
10503
|
} else {
|
|
9203
10504
|
const dispatcherResult = replyApi.createReplyDispatcherWithTyping ? replyApi.createReplyDispatcherWithTyping({
|
|
9204
10505
|
deliver,
|
|
@@ -9228,6 +10529,7 @@ async function dispatchToAgent(params) {
|
|
|
9228
10529
|
replyOptions: dispatcherResult.replyOptions
|
|
9229
10530
|
});
|
|
9230
10531
|
dispatcherResult.markDispatchIdle?.();
|
|
10532
|
+
await flushBufferedC2CMarkdownReply();
|
|
9231
10533
|
}
|
|
9232
10534
|
const noReplyFallback = resolveQQBotNoReplyFallback({
|
|
9233
10535
|
inbound,
|
|
@@ -9240,7 +10542,8 @@ async function dispatchToAgent(params) {
|
|
|
9240
10542
|
to: target.to,
|
|
9241
10543
|
text: noReplyFallback,
|
|
9242
10544
|
replyToId: inbound.messageId,
|
|
9243
|
-
replyEventId: inbound.eventId
|
|
10545
|
+
replyEventId: inbound.eventId,
|
|
10546
|
+
accountId: outboundAccountId
|
|
9244
10547
|
});
|
|
9245
10548
|
if (fallbackResult.error) {
|
|
9246
10549
|
logger.error(`sendText no-reply fallback failed: ${fallbackResult.error}`);
|
|
@@ -9332,13 +10635,44 @@ async function handleQQBotDispatch(params) {
|
|
|
9332
10635
|
if (!content && attachmentCount === 0) {
|
|
9333
10636
|
return;
|
|
9334
10637
|
}
|
|
9335
|
-
|
|
9336
|
-
|
|
10638
|
+
const runtime2 = getQQBotRuntime();
|
|
10639
|
+
const routing = runtime2.channel?.routing?.resolveAgentRoute;
|
|
10640
|
+
if (!routing) {
|
|
10641
|
+
logger.warn("routing API not available");
|
|
10642
|
+
return;
|
|
10643
|
+
}
|
|
10644
|
+
const target = resolveChatTarget(inbound);
|
|
10645
|
+
const route = routing({
|
|
9337
10646
|
cfg: params.cfg,
|
|
9338
|
-
|
|
10647
|
+
channel: "qqbot",
|
|
9339
10648
|
accountId,
|
|
9340
|
-
|
|
10649
|
+
peer: { kind: target.peerKind, id: target.peerId }
|
|
10650
|
+
});
|
|
10651
|
+
const effectiveSessionKey = resolveQQBotEffectiveSessionKey({
|
|
10652
|
+
inbound,
|
|
10653
|
+
route,
|
|
10654
|
+
accountId
|
|
9341
10655
|
});
|
|
10656
|
+
const resolvedRoute = effectiveSessionKey === route.sessionKey ? route : {
|
|
10657
|
+
...route,
|
|
10658
|
+
mainSessionKey: route.mainSessionKey?.trim() || route.sessionKey,
|
|
10659
|
+
effectiveSessionKey
|
|
10660
|
+
};
|
|
10661
|
+
const queueKey = buildSessionDispatchQueueKey(resolvedRoute);
|
|
10662
|
+
if (sessionDispatchQueue.has(queueKey)) {
|
|
10663
|
+
logger.info(`session busy; queueing inbound dispatch sessionKey=${resolveQQBotRouteSessionKey(resolvedRoute)}`);
|
|
10664
|
+
}
|
|
10665
|
+
await runSerializedSessionDispatch(
|
|
10666
|
+
queueKey,
|
|
10667
|
+
async () => dispatchToAgent({
|
|
10668
|
+
inbound: { ...inbound, content },
|
|
10669
|
+
cfg: params.cfg,
|
|
10670
|
+
qqCfg,
|
|
10671
|
+
accountId,
|
|
10672
|
+
logger,
|
|
10673
|
+
route: resolvedRoute
|
|
10674
|
+
})
|
|
10675
|
+
);
|
|
9342
10676
|
}
|
|
9343
10677
|
|
|
9344
10678
|
// src/monitor.ts
|
|
@@ -9638,7 +10972,8 @@ function resolveQQBotAccount(params) {
|
|
|
9638
10972
|
enabled,
|
|
9639
10973
|
configured,
|
|
9640
10974
|
appId: credentials?.appId,
|
|
9641
|
-
markdownSupport: merged.markdownSupport ?? true
|
|
10975
|
+
markdownSupport: merged.markdownSupport ?? true,
|
|
10976
|
+
c2cMarkdownDeliveryMode: merged.c2cMarkdownDeliveryMode ?? "proactive-table-only"
|
|
9642
10977
|
};
|
|
9643
10978
|
}
|
|
9644
10979
|
var qqbotPlugin = {
|
|
@@ -9647,7 +10982,7 @@ var qqbotPlugin = {
|
|
|
9647
10982
|
...meta
|
|
9648
10983
|
},
|
|
9649
10984
|
capabilities: {
|
|
9650
|
-
chatTypes: ["direct", "channel"],
|
|
10985
|
+
chatTypes: ["direct", "group", "channel"],
|
|
9651
10986
|
media: true,
|
|
9652
10987
|
reactions: false,
|
|
9653
10988
|
threads: false,
|
|
@@ -9727,6 +11062,10 @@ var qqbotPlugin = {
|
|
|
9727
11062
|
}
|
|
9728
11063
|
},
|
|
9729
11064
|
markdownSupport: { type: "boolean" },
|
|
11065
|
+
c2cMarkdownDeliveryMode: {
|
|
11066
|
+
type: "string",
|
|
11067
|
+
enum: ["passive", "proactive-table-only", "proactive-all"]
|
|
11068
|
+
},
|
|
9730
11069
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
9731
11070
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
9732
11071
|
requireMention: { type: "boolean" },
|
|
@@ -9768,6 +11107,10 @@ var qqbotPlugin = {
|
|
|
9768
11107
|
}
|
|
9769
11108
|
},
|
|
9770
11109
|
markdownSupport: { type: "boolean" },
|
|
11110
|
+
c2cMarkdownDeliveryMode: {
|
|
11111
|
+
type: "string",
|
|
11112
|
+
enum: ["passive", "proactive-table-only", "proactive-all"]
|
|
11113
|
+
},
|
|
9771
11114
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
9772
11115
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
9773
11116
|
requireMention: { type: "boolean" },
|
|
@@ -9966,6 +11309,10 @@ var plugin = {
|
|
|
9966
11309
|
}
|
|
9967
11310
|
},
|
|
9968
11311
|
markdownSupport: { type: "boolean" },
|
|
11312
|
+
c2cMarkdownDeliveryMode: {
|
|
11313
|
+
type: "string",
|
|
11314
|
+
enum: ["passive", "proactive-table-only", "proactive-all"]
|
|
11315
|
+
},
|
|
9969
11316
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
9970
11317
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
9971
11318
|
requireMention: { type: "boolean" },
|
|
@@ -10007,6 +11354,10 @@ var plugin = {
|
|
|
10007
11354
|
}
|
|
10008
11355
|
},
|
|
10009
11356
|
markdownSupport: { type: "boolean" },
|
|
11357
|
+
c2cMarkdownDeliveryMode: {
|
|
11358
|
+
type: "string",
|
|
11359
|
+
enum: ["passive", "proactive-table-only", "proactive-all"]
|
|
11360
|
+
},
|
|
10010
11361
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
10011
11362
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
10012
11363
|
requireMention: { type: "boolean" },
|