@openclaw-china/qqbot 2026.3.9 → 2026.3.11
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 +142 -1
- package/dist/index.js +1259 -97
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +5 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as os from 'os';
|
|
2
2
|
import { homedir, tmpdir } from 'os';
|
|
3
3
|
import * as path2 from 'path';
|
|
4
|
-
import { join } from 'path';
|
|
4
|
+
import { join, dirname } from 'path';
|
|
5
5
|
import * as fs3 from 'fs';
|
|
6
|
-
import { existsSync } from 'fs';
|
|
6
|
+
import { existsSync, readFileSync, rmSync, writeFileSync, mkdirSync } 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;
|
|
@@ -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),
|
|
@@ -4245,6 +4248,7 @@ var QQBotAccountSchema = external_exports.object({
|
|
|
4245
4248
|
longTaskNoticeDelayMs: external_exports.number().int().min(0).optional().default(3e4),
|
|
4246
4249
|
maxFileSizeMB: external_exports.number().positive().optional().default(100),
|
|
4247
4250
|
mediaTimeoutMs: external_exports.number().int().positive().optional().default(3e4),
|
|
4251
|
+
autoSendLocalPathMedia: external_exports.boolean().optional().default(true),
|
|
4248
4252
|
inboundMedia: external_exports.object({
|
|
4249
4253
|
dir: external_exports.string().optional(),
|
|
4250
4254
|
keepDays: external_exports.number().optional()
|
|
@@ -4264,6 +4268,9 @@ function resolveInboundMediaKeepDays(config) {
|
|
|
4264
4268
|
const value = config?.inboundMedia?.keepDays;
|
|
4265
4269
|
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : DEFAULT_INBOUND_MEDIA_KEEP_DAYS;
|
|
4266
4270
|
}
|
|
4271
|
+
function resolveQQBotAutoSendLocalPathMedia(config) {
|
|
4272
|
+
return config?.autoSendLocalPathMedia ?? true;
|
|
4273
|
+
}
|
|
4267
4274
|
function resolveInboundMediaTempDir() {
|
|
4268
4275
|
return DEFAULT_INBOUND_MEDIA_TEMP_DIR;
|
|
4269
4276
|
}
|
|
@@ -4316,6 +4323,156 @@ function resolveQQBotASRCredentials(config) {
|
|
|
4316
4323
|
};
|
|
4317
4324
|
}
|
|
4318
4325
|
|
|
4326
|
+
// src/onboarding.ts
|
|
4327
|
+
function isPromptCancelled(value) {
|
|
4328
|
+
return typeof value === "symbol";
|
|
4329
|
+
}
|
|
4330
|
+
function setQQBotCredentials(params) {
|
|
4331
|
+
const existing = params.cfg.channels?.qqbot ?? {};
|
|
4332
|
+
if (params.accountId === DEFAULT_ACCOUNT_ID) {
|
|
4333
|
+
return {
|
|
4334
|
+
...params.cfg,
|
|
4335
|
+
channels: {
|
|
4336
|
+
...params.cfg.channels,
|
|
4337
|
+
qqbot: {
|
|
4338
|
+
...existing,
|
|
4339
|
+
enabled: true,
|
|
4340
|
+
appId: params.appId,
|
|
4341
|
+
clientSecret: params.clientSecret
|
|
4342
|
+
}
|
|
4343
|
+
}
|
|
4344
|
+
};
|
|
4345
|
+
}
|
|
4346
|
+
const accounts = existing.accounts ?? {};
|
|
4347
|
+
return {
|
|
4348
|
+
...params.cfg,
|
|
4349
|
+
channels: {
|
|
4350
|
+
...params.cfg.channels,
|
|
4351
|
+
qqbot: {
|
|
4352
|
+
...existing,
|
|
4353
|
+
enabled: true,
|
|
4354
|
+
accounts: {
|
|
4355
|
+
...accounts,
|
|
4356
|
+
[params.accountId]: {
|
|
4357
|
+
...accounts[params.accountId],
|
|
4358
|
+
enabled: true,
|
|
4359
|
+
appId: params.appId,
|
|
4360
|
+
clientSecret: params.clientSecret
|
|
4361
|
+
}
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
4364
|
+
}
|
|
4365
|
+
};
|
|
4366
|
+
}
|
|
4367
|
+
async function noteQQBotCredentialHelp(prompter) {
|
|
4368
|
+
await prompter.note(
|
|
4369
|
+
[
|
|
4370
|
+
"1) \u6253\u5F00 QQ \u5F00\u653E\u5E73\u53F0 (https://q.qq.com/)",
|
|
4371
|
+
"2) \u521B\u5EFA\u673A\u5668\u4EBA\u5E94\u7528\uFF0C\u83B7\u53D6 AppID \u548C ClientSecret",
|
|
4372
|
+
"3) \u5728\u5F00\u53D1\u8BBE\u7F6E\u4E2D\u914D\u7F6E\u6C99\u7BB1\u6210\u5458\u6216\u6D4B\u8BD5\u7FA4",
|
|
4373
|
+
"4) \u914D\u7F6E\u5B8C\u6210\u540E\u53EF\u4F7F\u7528 openclaw gateway \u542F\u52A8\u8FDE\u63A5",
|
|
4374
|
+
"",
|
|
4375
|
+
'\u547D\u4EE4\u884C\u4E5F\u652F\u6301\uFF1Aopenclaw channels add --channel qqbot --token "AppID:ClientSecret"'
|
|
4376
|
+
].join("\n"),
|
|
4377
|
+
"QQ Bot \u914D\u7F6E"
|
|
4378
|
+
);
|
|
4379
|
+
}
|
|
4380
|
+
function resolveOnboardingAccountId(params) {
|
|
4381
|
+
const override = params.accountOverrides?.qqbot?.trim();
|
|
4382
|
+
if (override) return override;
|
|
4383
|
+
const defaultAccountId = resolveDefaultQQBotAccountId(params.cfg);
|
|
4384
|
+
const accountIds = listQQBotAccountIds(params.cfg);
|
|
4385
|
+
if (!params.shouldPromptAccountIds || accountIds.length <= 1) {
|
|
4386
|
+
return defaultAccountId;
|
|
4387
|
+
}
|
|
4388
|
+
return params.prompter.select({
|
|
4389
|
+
message: "\u9009\u62E9\u8981\u914D\u7F6E\u7684 QQ Bot \u8D26\u6237",
|
|
4390
|
+
options: accountIds.map((accountId) => ({
|
|
4391
|
+
value: accountId,
|
|
4392
|
+
label: accountId === DEFAULT_ACCOUNT_ID ? "\u9ED8\u8BA4\u8D26\u6237" : accountId
|
|
4393
|
+
})),
|
|
4394
|
+
initialValue: defaultAccountId
|
|
4395
|
+
}).then((selected) => isPromptCancelled(selected) ? defaultAccountId : selected);
|
|
4396
|
+
}
|
|
4397
|
+
var qqbotOnboardingAdapter = {
|
|
4398
|
+
channel: "qqbot",
|
|
4399
|
+
getStatus: async (params) => {
|
|
4400
|
+
const accountIds = listQQBotAccountIds(params.cfg);
|
|
4401
|
+
const configuredAccountId = accountIds.find(
|
|
4402
|
+
(accountId) => Boolean(resolveQQBotCredentials(mergeQQBotAccountConfig(params.cfg, accountId)))
|
|
4403
|
+
);
|
|
4404
|
+
const configured = Boolean(configuredAccountId);
|
|
4405
|
+
const defaultAccountId = resolveDefaultQQBotAccountId(params.cfg);
|
|
4406
|
+
const statusLines = configured ? [
|
|
4407
|
+
configuredAccountId && configuredAccountId !== DEFAULT_ACCOUNT_ID ? `QQ Bot: \u5DF2\u914D\u7F6E (${configuredAccountId})` : `QQ Bot: \u5DF2\u914D\u7F6E${defaultAccountId !== DEFAULT_ACCOUNT_ID ? ` (default=${defaultAccountId})` : ""}`
|
|
4408
|
+
] : ["QQ Bot: \u9700\u8981 AppID \u548C ClientSecret"];
|
|
4409
|
+
return {
|
|
4410
|
+
channel: "qqbot",
|
|
4411
|
+
configured,
|
|
4412
|
+
statusLines,
|
|
4413
|
+
selectionHint: configured ? "\u5DF2\u914D\u7F6E" : "\u9700\u8981 AppID \u548C ClientSecret",
|
|
4414
|
+
quickstartScore: configured ? 2 : 0
|
|
4415
|
+
};
|
|
4416
|
+
},
|
|
4417
|
+
configure: async (params) => {
|
|
4418
|
+
const accountId = await resolveOnboardingAccountId(params);
|
|
4419
|
+
const merged = mergeQQBotAccountConfig(params.cfg, accountId);
|
|
4420
|
+
const configured = Boolean(resolveQQBotCredentials(merged));
|
|
4421
|
+
let next = params.cfg;
|
|
4422
|
+
let appId = null;
|
|
4423
|
+
let clientSecret = null;
|
|
4424
|
+
if (!configured) {
|
|
4425
|
+
await noteQQBotCredentialHelp(params.prompter);
|
|
4426
|
+
} else {
|
|
4427
|
+
const keepCurrent = await params.prompter.confirm({
|
|
4428
|
+
message: accountId === DEFAULT_ACCOUNT_ID ? "QQ Bot \u51ED\u8BC1\u5DF2\u914D\u7F6E\uFF0C\u662F\u5426\u4FDD\u7559\u5F53\u524D\u914D\u7F6E\uFF1F" : `\u8D26\u6237 ${accountId} \u7684 QQ Bot \u51ED\u8BC1\u5DF2\u914D\u7F6E\uFF0C\u662F\u5426\u4FDD\u7559\u5F53\u524D\u914D\u7F6E\uFF1F`,
|
|
4429
|
+
initialValue: true
|
|
4430
|
+
});
|
|
4431
|
+
if (keepCurrent) {
|
|
4432
|
+
return { cfg: next, accountId };
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
4435
|
+
const nextAppId = await params.prompter.text({
|
|
4436
|
+
message: "\u8BF7\u8F93\u5165 QQ Bot AppID",
|
|
4437
|
+
placeholder: "\u4F8B\u5982: 102146862",
|
|
4438
|
+
initialValue: typeof merged.appId === "string" ? merged.appId : void 0,
|
|
4439
|
+
validate: (value) => String(value ?? "").trim() ? void 0 : "AppID \u4E0D\u80FD\u4E3A\u7A7A"
|
|
4440
|
+
});
|
|
4441
|
+
if (isPromptCancelled(nextAppId)) {
|
|
4442
|
+
return { cfg: next, accountId };
|
|
4443
|
+
}
|
|
4444
|
+
appId = String(nextAppId).trim();
|
|
4445
|
+
const nextClientSecret = await params.prompter.text({
|
|
4446
|
+
message: "\u8BF7\u8F93\u5165 QQ Bot ClientSecret",
|
|
4447
|
+
placeholder: "\u4F60\u7684 ClientSecret",
|
|
4448
|
+
validate: (value) => String(value ?? "").trim() ? void 0 : "ClientSecret \u4E0D\u80FD\u4E3A\u7A7A"
|
|
4449
|
+
});
|
|
4450
|
+
if (isPromptCancelled(nextClientSecret)) {
|
|
4451
|
+
return { cfg: next, accountId };
|
|
4452
|
+
}
|
|
4453
|
+
clientSecret = String(nextClientSecret).trim();
|
|
4454
|
+
if (appId && clientSecret) {
|
|
4455
|
+
next = setQQBotCredentials({
|
|
4456
|
+
cfg: next,
|
|
4457
|
+
accountId,
|
|
4458
|
+
appId,
|
|
4459
|
+
clientSecret
|
|
4460
|
+
});
|
|
4461
|
+
}
|
|
4462
|
+
return { cfg: next, accountId };
|
|
4463
|
+
},
|
|
4464
|
+
disable: (cfg) => ({
|
|
4465
|
+
...cfg,
|
|
4466
|
+
channels: {
|
|
4467
|
+
...cfg.channels,
|
|
4468
|
+
qqbot: {
|
|
4469
|
+
...cfg.channels?.qqbot ?? {},
|
|
4470
|
+
enabled: false
|
|
4471
|
+
}
|
|
4472
|
+
}
|
|
4473
|
+
})
|
|
4474
|
+
};
|
|
4475
|
+
|
|
4319
4476
|
// ../../packages/shared/src/logger/logger.ts
|
|
4320
4477
|
function createLogger(prefix, opts) {
|
|
4321
4478
|
const logFn = opts?.log ?? console.log;
|
|
@@ -4719,7 +4876,7 @@ function extractMediaFromText(text, options = {}) {
|
|
|
4719
4876
|
const {
|
|
4720
4877
|
removeFromText = true,
|
|
4721
4878
|
checkExists = false,
|
|
4722
|
-
existsSync:
|
|
4879
|
+
existsSync: existsSync6,
|
|
4723
4880
|
parseMediaLines = false,
|
|
4724
4881
|
parseMarkdownImages = true,
|
|
4725
4882
|
parseHtmlImages = true,
|
|
@@ -4734,7 +4891,7 @@ function extractMediaFromText(text, options = {}) {
|
|
|
4734
4891
|
const key = media.localPath || media.source;
|
|
4735
4892
|
if (seenSources.has(key)) return false;
|
|
4736
4893
|
if (checkExists && media.isLocal && media.localPath) {
|
|
4737
|
-
const exists =
|
|
4894
|
+
const exists = existsSync6 ? existsSync6(media.localPath) : fs3.existsSync(media.localPath);
|
|
4738
4895
|
if (!exists) return false;
|
|
4739
4896
|
}
|
|
4740
4897
|
seenSources.add(key);
|
|
@@ -6529,6 +6686,13 @@ function getChannelConfig(cfg, channelId) {
|
|
|
6529
6686
|
const existing = channels[channelId];
|
|
6530
6687
|
return isRecord(existing) ? existing : {};
|
|
6531
6688
|
}
|
|
6689
|
+
function getGatewayAuthToken(cfg) {
|
|
6690
|
+
if (!isRecord(cfg.gateway)) {
|
|
6691
|
+
return void 0;
|
|
6692
|
+
}
|
|
6693
|
+
const auth = isRecord(cfg.gateway.auth) ? cfg.gateway.auth : void 0;
|
|
6694
|
+
return toTrimmedString2(auth?.token);
|
|
6695
|
+
}
|
|
6532
6696
|
function getPreferredAccountConfig(channelCfg) {
|
|
6533
6697
|
const accounts = channelCfg.accounts;
|
|
6534
6698
|
if (!isRecord(accounts)) {
|
|
@@ -6690,11 +6854,22 @@ async function configureDingtalk(prompter, cfg) {
|
|
|
6690
6854
|
"\u542F\u7528 AI Card \u6D41\u5F0F\u56DE\u590D\uFF08\u63A8\u8350\u5173\u95ED\uFF0C\u4F7F\u7528\u975E\u6D41\u5F0F\uFF09",
|
|
6691
6855
|
toBoolean(existing.enableAICard, false)
|
|
6692
6856
|
);
|
|
6693
|
-
|
|
6857
|
+
const patch = {
|
|
6694
6858
|
clientId,
|
|
6695
6859
|
clientSecret,
|
|
6696
6860
|
enableAICard
|
|
6697
|
-
}
|
|
6861
|
+
};
|
|
6862
|
+
if (enableAICard) {
|
|
6863
|
+
const gatewayToken = await prompter.askSecret({
|
|
6864
|
+
label: "OpenClaw Gateway Token\uFF08\u6D41\u5F0F\u8F93\u51FA\u5FC5\u9700\uFF1B\u7559\u7A7A\u5219\u4F7F\u7528\u5168\u5C40 gateway.auth.token\uFF09",
|
|
6865
|
+
existingValue: toTrimmedString2(existing.gatewayToken) ?? getGatewayAuthToken(cfg),
|
|
6866
|
+
required: false
|
|
6867
|
+
});
|
|
6868
|
+
if (gatewayToken.trim()) {
|
|
6869
|
+
patch.gatewayToken = gatewayToken;
|
|
6870
|
+
}
|
|
6871
|
+
}
|
|
6872
|
+
return mergeChannelConfig(cfg, "dingtalk", patch);
|
|
6698
6873
|
}
|
|
6699
6874
|
async function configureFeishu(prompter, cfg) {
|
|
6700
6875
|
section("\u914D\u7F6E Feishu\uFF08\u98DE\u4E66\uFF09");
|
|
@@ -7083,9 +7258,11 @@ function showChinaInstallHint(api) {
|
|
|
7083
7258
|
var API_BASE = "https://api.sgroup.qq.com";
|
|
7084
7259
|
var TOKEN_URL = "https://bots.qq.com/app/getAppAccessToken";
|
|
7085
7260
|
var MSG_SEQ_BASE = 1e6;
|
|
7261
|
+
var MAX_DUPLICATE_MSG_SEQ_RETRIES = 5;
|
|
7086
7262
|
var tokenCacheMap = /* @__PURE__ */ new Map();
|
|
7087
7263
|
var tokenPromiseMap = /* @__PURE__ */ new Map();
|
|
7088
7264
|
var msgSeqMap = /* @__PURE__ */ new Map();
|
|
7265
|
+
var fallbackMsgSeq = 0;
|
|
7089
7266
|
function toTrimmedString3(value) {
|
|
7090
7267
|
if (value === void 0 || value === null) return void 0;
|
|
7091
7268
|
const next = String(value).trim();
|
|
@@ -7105,7 +7282,10 @@ function sanitizeUploadFileName(fileName) {
|
|
|
7105
7282
|
return normalized || "file";
|
|
7106
7283
|
}
|
|
7107
7284
|
function nextMsgSeq(sequenceKey) {
|
|
7108
|
-
if (!sequenceKey)
|
|
7285
|
+
if (!sequenceKey) {
|
|
7286
|
+
fallbackMsgSeq += 1;
|
|
7287
|
+
return MSG_SEQ_BASE + fallbackMsgSeq;
|
|
7288
|
+
}
|
|
7109
7289
|
const current = msgSeqMap.get(sequenceKey) ?? 0;
|
|
7110
7290
|
const next = current + 1;
|
|
7111
7291
|
msgSeqMap.set(sequenceKey, next);
|
|
@@ -7117,6 +7297,46 @@ function nextMsgSeq(sequenceKey) {
|
|
|
7117
7297
|
}
|
|
7118
7298
|
return MSG_SEQ_BASE + next;
|
|
7119
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
|
+
}
|
|
7120
7340
|
function clearTokenCache(appId) {
|
|
7121
7341
|
const normalizedAppId = toTrimmedString3(appId);
|
|
7122
7342
|
if (normalizedAppId) {
|
|
@@ -7185,16 +7405,11 @@ async function getGatewayUrl(accessToken) {
|
|
|
7185
7405
|
return data.url;
|
|
7186
7406
|
}
|
|
7187
7407
|
function buildMessageBody(params) {
|
|
7188
|
-
const
|
|
7189
|
-
const body = params.markdown ? {
|
|
7190
|
-
markdown: { content: params.content },
|
|
7191
|
-
msg_type: 2,
|
|
7192
|
-
msg_seq: msgSeq
|
|
7193
|
-
} : {
|
|
7408
|
+
const body = buildTextMessageBody({
|
|
7194
7409
|
content: params.content,
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7410
|
+
markdown: params.markdown
|
|
7411
|
+
});
|
|
7412
|
+
body.msg_seq = params.msgSeq;
|
|
7198
7413
|
if (params.messageId) {
|
|
7199
7414
|
body.msg_id = params.messageId;
|
|
7200
7415
|
} else if (params.eventId) {
|
|
@@ -7202,22 +7417,63 @@ function buildMessageBody(params) {
|
|
|
7202
7417
|
}
|
|
7203
7418
|
return body;
|
|
7204
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
|
+
}
|
|
7205
7435
|
async function sendC2CMessage(params) {
|
|
7206
|
-
|
|
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({
|
|
7207
7467
|
content: params.content,
|
|
7208
|
-
messageId: params.messageId,
|
|
7209
|
-
eventId: params.eventId,
|
|
7210
7468
|
markdown: params.markdown
|
|
7211
7469
|
});
|
|
7212
7470
|
return apiPost(params.accessToken, `/v2/users/${params.openid}/messages`, body, {
|
|
7213
7471
|
timeout: 15e3
|
|
7214
7472
|
});
|
|
7215
7473
|
}
|
|
7216
|
-
async function
|
|
7217
|
-
const body =
|
|
7474
|
+
async function sendProactiveGroupMessage(params) {
|
|
7475
|
+
const body = buildProactiveMessageBody({
|
|
7218
7476
|
content: params.content,
|
|
7219
|
-
messageId: params.messageId,
|
|
7220
|
-
eventId: params.eventId,
|
|
7221
7477
|
markdown: params.markdown
|
|
7222
7478
|
});
|
|
7223
7479
|
return apiPost(params.accessToken, `/v2/groups/${params.groupOpenid}/messages`, body, {
|
|
@@ -7234,11 +7490,12 @@ async function sendChannelMessage(params) {
|
|
|
7234
7490
|
});
|
|
7235
7491
|
}
|
|
7236
7492
|
async function sendC2CInputNotify(params) {
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
params.
|
|
7240
|
-
|
|
7241
|
-
{
|
|
7493
|
+
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) => ({
|
|
7242
7499
|
msg_type: 6,
|
|
7243
7500
|
input_notify: {
|
|
7244
7501
|
input_type: 1,
|
|
@@ -7246,9 +7503,8 @@ async function sendC2CInputNotify(params) {
|
|
|
7246
7503
|
},
|
|
7247
7504
|
msg_seq: msgSeq,
|
|
7248
7505
|
...params.messageId ? { msg_id: params.messageId } : params.eventId ? { event_id: params.eventId } : {}
|
|
7249
|
-
}
|
|
7250
|
-
|
|
7251
|
-
);
|
|
7506
|
+
})
|
|
7507
|
+
});
|
|
7252
7508
|
}
|
|
7253
7509
|
async function uploadC2CMedia(params) {
|
|
7254
7510
|
const body = {
|
|
@@ -7289,34 +7545,34 @@ async function uploadGroupMedia(params) {
|
|
|
7289
7545
|
});
|
|
7290
7546
|
}
|
|
7291
7547
|
async function sendC2CMediaMessage(params) {
|
|
7292
|
-
|
|
7293
|
-
|
|
7294
|
-
params.
|
|
7295
|
-
|
|
7296
|
-
{
|
|
7548
|
+
return postPassiveMessage({
|
|
7549
|
+
accessToken: params.accessToken,
|
|
7550
|
+
path: `/v2/users/${params.openid}/messages`,
|
|
7551
|
+
sequenceKey: resolveMsgSeqKey(params.messageId, params.eventId),
|
|
7552
|
+
options: { timeout: 15e3 },
|
|
7553
|
+
buildBody: (msgSeq) => ({
|
|
7297
7554
|
msg_type: 7,
|
|
7298
7555
|
media: { file_info: params.fileInfo },
|
|
7299
7556
|
msg_seq: msgSeq,
|
|
7300
7557
|
...params.content ? { content: params.content } : {},
|
|
7301
7558
|
...params.messageId ? { msg_id: params.messageId } : params.eventId ? { event_id: params.eventId } : {}
|
|
7302
|
-
}
|
|
7303
|
-
|
|
7304
|
-
);
|
|
7559
|
+
})
|
|
7560
|
+
});
|
|
7305
7561
|
}
|
|
7306
7562
|
async function sendGroupMediaMessage(params) {
|
|
7307
|
-
|
|
7308
|
-
|
|
7309
|
-
params.
|
|
7310
|
-
|
|
7311
|
-
{
|
|
7563
|
+
return postPassiveMessage({
|
|
7564
|
+
accessToken: params.accessToken,
|
|
7565
|
+
path: `/v2/groups/${params.groupOpenid}/messages`,
|
|
7566
|
+
sequenceKey: resolveMsgSeqKey(params.messageId, params.eventId),
|
|
7567
|
+
options: { timeout: 15e3 },
|
|
7568
|
+
buildBody: (msgSeq) => ({
|
|
7312
7569
|
msg_type: 7,
|
|
7313
7570
|
media: { file_info: params.fileInfo },
|
|
7314
7571
|
msg_seq: msgSeq,
|
|
7315
7572
|
...params.content ? { content: params.content } : {},
|
|
7316
7573
|
...params.messageId ? { msg_id: params.messageId } : params.eventId ? { event_id: params.eventId } : {}
|
|
7317
|
-
}
|
|
7318
|
-
|
|
7319
|
-
);
|
|
7574
|
+
})
|
|
7575
|
+
});
|
|
7320
7576
|
}
|
|
7321
7577
|
var require2 = createRequire(import.meta.url);
|
|
7322
7578
|
function resolveQQBotMediaFileType(fileName) {
|
|
@@ -7569,6 +7825,14 @@ function logEventIdFallback(params) {
|
|
|
7569
7825
|
}
|
|
7570
7826
|
console.info(detail);
|
|
7571
7827
|
}
|
|
7828
|
+
function logQQBotOutboundDispatch(params) {
|
|
7829
|
+
const accountLabel = params.accountId?.trim() || DEFAULT_ACCOUNT_ID;
|
|
7830
|
+
const textLength = typeof params.text === "string" ? params.text.length : 0;
|
|
7831
|
+
const mediaLabel = params.mediaUrl ? ` media=${shortId(params.mediaUrl)}` : "";
|
|
7832
|
+
console.info(
|
|
7833
|
+
`[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}`
|
|
7834
|
+
);
|
|
7835
|
+
}
|
|
7572
7836
|
function shouldRetryWithEventId(err) {
|
|
7573
7837
|
const status = err instanceof HttpError ? err.status : void 0;
|
|
7574
7838
|
let body = "";
|
|
@@ -7589,6 +7853,15 @@ function shouldRetryWithEventId(err) {
|
|
|
7589
7853
|
function shouldSendTextAsFollowupForMedia(mediaUrl) {
|
|
7590
7854
|
return detectMediaType(stripTitleFromUrl(mediaUrl)) === "file";
|
|
7591
7855
|
}
|
|
7856
|
+
function buildPassiveReplyRefs(params) {
|
|
7857
|
+
if (params.replyToId) {
|
|
7858
|
+
return { messageId: params.replyToId };
|
|
7859
|
+
}
|
|
7860
|
+
if (params.replyEventId) {
|
|
7861
|
+
return { eventId: params.replyEventId };
|
|
7862
|
+
}
|
|
7863
|
+
return {};
|
|
7864
|
+
}
|
|
7592
7865
|
var qqbotOutbound = {
|
|
7593
7866
|
deliveryMode: "direct",
|
|
7594
7867
|
textChunkLimit: 1500,
|
|
@@ -7606,13 +7879,42 @@ var qqbotOutbound = {
|
|
|
7606
7879
|
const groupMarkdown = false;
|
|
7607
7880
|
try {
|
|
7608
7881
|
if (target.kind === "group") {
|
|
7882
|
+
if (!replyToId && !replyEventId) {
|
|
7883
|
+
logQQBotOutboundDispatch({
|
|
7884
|
+
action: "text",
|
|
7885
|
+
api: "sendProactiveGroupMessage",
|
|
7886
|
+
accountId,
|
|
7887
|
+
targetKind: target.kind,
|
|
7888
|
+
targetId: target.id,
|
|
7889
|
+
markdown,
|
|
7890
|
+
text
|
|
7891
|
+
});
|
|
7892
|
+
const result3 = await sendProactiveGroupMessage({
|
|
7893
|
+
accessToken,
|
|
7894
|
+
groupOpenid: target.id,
|
|
7895
|
+
content: text,
|
|
7896
|
+
markdown
|
|
7897
|
+
});
|
|
7898
|
+
return { channel: "qqbot", messageId: result3.id, timestamp: result3.timestamp };
|
|
7899
|
+
}
|
|
7609
7900
|
let result2;
|
|
7610
7901
|
try {
|
|
7902
|
+
logQQBotOutboundDispatch({
|
|
7903
|
+
action: "text",
|
|
7904
|
+
api: "sendGroupMessage",
|
|
7905
|
+
accountId,
|
|
7906
|
+
targetKind: target.kind,
|
|
7907
|
+
targetId: target.id,
|
|
7908
|
+
markdown: groupMarkdown,
|
|
7909
|
+
replyToId,
|
|
7910
|
+
replyEventId,
|
|
7911
|
+
text
|
|
7912
|
+
});
|
|
7611
7913
|
result2 = await sendGroupMessage({
|
|
7612
7914
|
accessToken,
|
|
7613
7915
|
groupOpenid: target.id,
|
|
7614
7916
|
content: text,
|
|
7615
|
-
|
|
7917
|
+
...buildPassiveReplyRefs({ replyToId, replyEventId }),
|
|
7616
7918
|
markdown: groupMarkdown
|
|
7617
7919
|
});
|
|
7618
7920
|
} catch (err) {
|
|
@@ -7663,6 +7965,15 @@ var qqbotOutbound = {
|
|
|
7663
7965
|
return { channel: "qqbot", messageId: result2.id, timestamp: result2.timestamp };
|
|
7664
7966
|
}
|
|
7665
7967
|
if (target.kind === "channel") {
|
|
7968
|
+
logQQBotOutboundDispatch({
|
|
7969
|
+
action: "text",
|
|
7970
|
+
api: "sendChannelMessage",
|
|
7971
|
+
accountId,
|
|
7972
|
+
targetKind: target.kind,
|
|
7973
|
+
targetId: target.id,
|
|
7974
|
+
replyToId,
|
|
7975
|
+
text
|
|
7976
|
+
});
|
|
7666
7977
|
const result2 = await sendChannelMessage({
|
|
7667
7978
|
accessToken,
|
|
7668
7979
|
channelId: target.id,
|
|
@@ -7671,13 +7982,42 @@ var qqbotOutbound = {
|
|
|
7671
7982
|
});
|
|
7672
7983
|
return { channel: "qqbot", messageId: result2.id, timestamp: result2.timestamp };
|
|
7673
7984
|
}
|
|
7985
|
+
if (!replyToId && !replyEventId) {
|
|
7986
|
+
logQQBotOutboundDispatch({
|
|
7987
|
+
action: "text",
|
|
7988
|
+
api: "sendProactiveC2CMessage",
|
|
7989
|
+
accountId,
|
|
7990
|
+
targetKind: target.kind,
|
|
7991
|
+
targetId: target.id,
|
|
7992
|
+
markdown,
|
|
7993
|
+
text
|
|
7994
|
+
});
|
|
7995
|
+
const result2 = await sendProactiveC2CMessage({
|
|
7996
|
+
accessToken,
|
|
7997
|
+
openid: target.id,
|
|
7998
|
+
content: text,
|
|
7999
|
+
markdown
|
|
8000
|
+
});
|
|
8001
|
+
return { channel: "qqbot", messageId: result2.id, timestamp: result2.timestamp };
|
|
8002
|
+
}
|
|
7674
8003
|
let result;
|
|
7675
8004
|
try {
|
|
8005
|
+
logQQBotOutboundDispatch({
|
|
8006
|
+
action: "text",
|
|
8007
|
+
api: "sendC2CMessage",
|
|
8008
|
+
accountId,
|
|
8009
|
+
targetKind: target.kind,
|
|
8010
|
+
targetId: target.id,
|
|
8011
|
+
markdown,
|
|
8012
|
+
replyToId,
|
|
8013
|
+
replyEventId,
|
|
8014
|
+
text
|
|
8015
|
+
});
|
|
7676
8016
|
result = await sendC2CMessage({
|
|
7677
8017
|
accessToken,
|
|
7678
8018
|
openid: target.id,
|
|
7679
8019
|
content: text,
|
|
7680
|
-
|
|
8020
|
+
...buildPassiveReplyRefs({ replyToId, replyEventId }),
|
|
7681
8021
|
markdown
|
|
7682
8022
|
});
|
|
7683
8023
|
} catch (err) {
|
|
@@ -7755,6 +8095,17 @@ ${mediaUrl}` : mediaUrl;
|
|
|
7755
8095
|
try {
|
|
7756
8096
|
let result;
|
|
7757
8097
|
try {
|
|
8098
|
+
logQQBotOutboundDispatch({
|
|
8099
|
+
action: "media",
|
|
8100
|
+
api: "sendFileQQBot",
|
|
8101
|
+
accountId,
|
|
8102
|
+
targetKind: target.kind,
|
|
8103
|
+
targetId: target.id,
|
|
8104
|
+
replyToId,
|
|
8105
|
+
replyEventId,
|
|
8106
|
+
text: sendTextAsFollowup ? void 0 : trimmedText,
|
|
8107
|
+
mediaUrl
|
|
8108
|
+
});
|
|
7758
8109
|
result = await sendFileQQBot({
|
|
7759
8110
|
cfg: qqCfg,
|
|
7760
8111
|
target: { kind: target.kind, id: target.id },
|
|
@@ -7904,6 +8255,451 @@ ${mediaUrl}` : mediaUrl;
|
|
|
7904
8255
|
|
|
7905
8256
|
// src/logger.ts
|
|
7906
8257
|
createLogger("qqbot");
|
|
8258
|
+
var DEFAULT_QQBOT_MARKDOWN_IMAGE_SIZE = {
|
|
8259
|
+
width: 512,
|
|
8260
|
+
height: 512
|
|
8261
|
+
};
|
|
8262
|
+
var MARKDOWN_IMAGE_RE2 = /!\[([^\]]*)\]\(([^)\n]+)\)/g;
|
|
8263
|
+
var BARE_HTTP_IMAGE_URL_RE = /(?<![(\["'<])(https?:\/\/[^\s)"'<>]+\.(?:png|jpe?g|gif|webp)(?:\?[^\s)"'<>]*)?)/gi;
|
|
8264
|
+
var FENCED_CODE_BLOCK_RE = /(^|\n)(`{3,}|~{3,})[^\n]*\n[\s\S]*?\n\2(?=\n|$)/g;
|
|
8265
|
+
function parsePngSize(buffer) {
|
|
8266
|
+
if (buffer.length < 24) return null;
|
|
8267
|
+
if (buffer[0] !== 137 || buffer[1] !== 80 || buffer[2] !== 78 || buffer[3] !== 71) {
|
|
8268
|
+
return null;
|
|
8269
|
+
}
|
|
8270
|
+
return {
|
|
8271
|
+
width: buffer.readUInt32BE(16),
|
|
8272
|
+
height: buffer.readUInt32BE(20)
|
|
8273
|
+
};
|
|
8274
|
+
}
|
|
8275
|
+
function parseJpegSize(buffer) {
|
|
8276
|
+
if (buffer.length < 4 || buffer[0] !== 255 || buffer[1] !== 216) {
|
|
8277
|
+
return null;
|
|
8278
|
+
}
|
|
8279
|
+
let offset = 2;
|
|
8280
|
+
while (offset < buffer.length - 9) {
|
|
8281
|
+
if (buffer[offset] !== 255) {
|
|
8282
|
+
offset += 1;
|
|
8283
|
+
continue;
|
|
8284
|
+
}
|
|
8285
|
+
const marker = buffer[offset + 1];
|
|
8286
|
+
if (marker === 192 || marker === 194) {
|
|
8287
|
+
return {
|
|
8288
|
+
height: buffer.readUInt16BE(offset + 5),
|
|
8289
|
+
width: buffer.readUInt16BE(offset + 7)
|
|
8290
|
+
};
|
|
8291
|
+
}
|
|
8292
|
+
if (offset + 3 >= buffer.length) {
|
|
8293
|
+
break;
|
|
8294
|
+
}
|
|
8295
|
+
const blockLength = buffer.readUInt16BE(offset + 2);
|
|
8296
|
+
offset += 2 + blockLength;
|
|
8297
|
+
}
|
|
8298
|
+
return null;
|
|
8299
|
+
}
|
|
8300
|
+
function parseGifSize(buffer) {
|
|
8301
|
+
if (buffer.length < 10) return null;
|
|
8302
|
+
const signature = buffer.toString("ascii", 0, 6);
|
|
8303
|
+
if (signature !== "GIF87a" && signature !== "GIF89a") {
|
|
8304
|
+
return null;
|
|
8305
|
+
}
|
|
8306
|
+
return {
|
|
8307
|
+
width: buffer.readUInt16LE(6),
|
|
8308
|
+
height: buffer.readUInt16LE(8)
|
|
8309
|
+
};
|
|
8310
|
+
}
|
|
8311
|
+
function parseWebpSize(buffer) {
|
|
8312
|
+
if (buffer.length < 30) return null;
|
|
8313
|
+
if (buffer.toString("ascii", 0, 4) !== "RIFF" || buffer.toString("ascii", 8, 12) !== "WEBP") {
|
|
8314
|
+
return null;
|
|
8315
|
+
}
|
|
8316
|
+
const chunkType = buffer.toString("ascii", 12, 16);
|
|
8317
|
+
if (chunkType === "VP8 " && buffer[23] === 157 && buffer[24] === 1 && buffer[25] === 42) {
|
|
8318
|
+
return {
|
|
8319
|
+
width: buffer.readUInt16LE(26) & 16383,
|
|
8320
|
+
height: buffer.readUInt16LE(28) & 16383
|
|
8321
|
+
};
|
|
8322
|
+
}
|
|
8323
|
+
if (chunkType === "VP8L" && buffer[20] === 47) {
|
|
8324
|
+
const bits = buffer.readUInt32LE(21);
|
|
8325
|
+
return {
|
|
8326
|
+
width: (bits & 16383) + 1,
|
|
8327
|
+
height: (bits >> 14 & 16383) + 1
|
|
8328
|
+
};
|
|
8329
|
+
}
|
|
8330
|
+
if (chunkType === "VP8X") {
|
|
8331
|
+
return {
|
|
8332
|
+
width: (buffer[24] | buffer[25] << 8 | buffer[26] << 16) + 1,
|
|
8333
|
+
height: (buffer[27] | buffer[28] << 8 | buffer[29] << 16) + 1
|
|
8334
|
+
};
|
|
8335
|
+
}
|
|
8336
|
+
return null;
|
|
8337
|
+
}
|
|
8338
|
+
function parseImageSize(buffer) {
|
|
8339
|
+
return parsePngSize(buffer) ?? parseJpegSize(buffer) ?? parseGifSize(buffer) ?? parseWebpSize(buffer);
|
|
8340
|
+
}
|
|
8341
|
+
function normalizeImageSize(size) {
|
|
8342
|
+
if (!size) return null;
|
|
8343
|
+
if (!Number.isFinite(size.width) || !Number.isFinite(size.height)) return null;
|
|
8344
|
+
if (size.width <= 0 || size.height <= 0) return null;
|
|
8345
|
+
return {
|
|
8346
|
+
width: Math.round(size.width),
|
|
8347
|
+
height: Math.round(size.height)
|
|
8348
|
+
};
|
|
8349
|
+
}
|
|
8350
|
+
function splitMarkdownImageDestination(rawDestination) {
|
|
8351
|
+
let next = rawDestination.trim();
|
|
8352
|
+
const whitespaceIndex = next.search(/\s/);
|
|
8353
|
+
if (whitespaceIndex >= 0) {
|
|
8354
|
+
next = next.slice(0, whitespaceIndex);
|
|
8355
|
+
}
|
|
8356
|
+
if (next.startsWith("<") && next.endsWith(">")) {
|
|
8357
|
+
next = next.slice(1, -1).trim();
|
|
8358
|
+
}
|
|
8359
|
+
return next;
|
|
8360
|
+
}
|
|
8361
|
+
function splitFencedCodeBlocks(text) {
|
|
8362
|
+
const segments = [];
|
|
8363
|
+
const re = new RegExp(FENCED_CODE_BLOCK_RE.source, FENCED_CODE_BLOCK_RE.flags);
|
|
8364
|
+
let lastIndex = 0;
|
|
8365
|
+
let match;
|
|
8366
|
+
while ((match = re.exec(text)) !== null) {
|
|
8367
|
+
const leading = match[1] ?? "";
|
|
8368
|
+
const codeStart = match.index + leading.length;
|
|
8369
|
+
if (codeStart > lastIndex) {
|
|
8370
|
+
segments.push({ kind: "text", value: text.slice(lastIndex, codeStart) });
|
|
8371
|
+
}
|
|
8372
|
+
segments.push({ kind: "code", value: text.slice(codeStart, re.lastIndex) });
|
|
8373
|
+
lastIndex = re.lastIndex;
|
|
8374
|
+
}
|
|
8375
|
+
if (lastIndex < text.length) {
|
|
8376
|
+
segments.push({ kind: "text", value: text.slice(lastIndex) });
|
|
8377
|
+
}
|
|
8378
|
+
return segments;
|
|
8379
|
+
}
|
|
8380
|
+
async function replaceAsync(input2, pattern, replacer) {
|
|
8381
|
+
const re = new RegExp(pattern.source, pattern.flags);
|
|
8382
|
+
let result = "";
|
|
8383
|
+
let lastIndex = 0;
|
|
8384
|
+
let match;
|
|
8385
|
+
while ((match = re.exec(input2)) !== null) {
|
|
8386
|
+
result += input2.slice(lastIndex, match.index);
|
|
8387
|
+
result += await replacer(match);
|
|
8388
|
+
lastIndex = match.index + match[0].length;
|
|
8389
|
+
if (match[0].length === 0) {
|
|
8390
|
+
re.lastIndex += 1;
|
|
8391
|
+
}
|
|
8392
|
+
}
|
|
8393
|
+
result += input2.slice(lastIndex);
|
|
8394
|
+
return result;
|
|
8395
|
+
}
|
|
8396
|
+
async function getResolvedImageSize(params) {
|
|
8397
|
+
const { url, cache, resolveImageSize } = params;
|
|
8398
|
+
const existing = cache.get(url);
|
|
8399
|
+
if (existing) {
|
|
8400
|
+
return existing;
|
|
8401
|
+
}
|
|
8402
|
+
const pending = resolveImageSize(url).then((size) => normalizeImageSize(size) ?? DEFAULT_QQBOT_MARKDOWN_IMAGE_SIZE).catch(() => DEFAULT_QQBOT_MARKDOWN_IMAGE_SIZE);
|
|
8403
|
+
cache.set(url, pending);
|
|
8404
|
+
return pending;
|
|
8405
|
+
}
|
|
8406
|
+
async function normalizeTextSegment(params) {
|
|
8407
|
+
const { text, seenImageUrls, imageSizeCache, resolveImageSize } = params;
|
|
8408
|
+
const withMarkdownImages = await replaceAsync(text, MARKDOWN_IMAGE_RE2, async (match) => {
|
|
8409
|
+
const fullMatch = match[0];
|
|
8410
|
+
const destination = splitMarkdownImageDestination(match[2] ?? "");
|
|
8411
|
+
if (!isQQBotHttpImageUrl(destination)) {
|
|
8412
|
+
return fullMatch;
|
|
8413
|
+
}
|
|
8414
|
+
seenImageUrls.add(destination);
|
|
8415
|
+
if (hasQQBotMarkdownImageSize(fullMatch)) {
|
|
8416
|
+
return fullMatch;
|
|
8417
|
+
}
|
|
8418
|
+
const size = await getResolvedImageSize({
|
|
8419
|
+
url: destination,
|
|
8420
|
+
cache: imageSizeCache,
|
|
8421
|
+
resolveImageSize
|
|
8422
|
+
});
|
|
8423
|
+
return formatQQBotMarkdownImage(destination, size);
|
|
8424
|
+
});
|
|
8425
|
+
return replaceAsync(withMarkdownImages, BARE_HTTP_IMAGE_URL_RE, async (match) => {
|
|
8426
|
+
const url = match[1] ?? match[0];
|
|
8427
|
+
if (!isQQBotHttpImageUrl(url)) {
|
|
8428
|
+
return match[0];
|
|
8429
|
+
}
|
|
8430
|
+
seenImageUrls.add(url);
|
|
8431
|
+
const size = await getResolvedImageSize({
|
|
8432
|
+
url,
|
|
8433
|
+
cache: imageSizeCache,
|
|
8434
|
+
resolveImageSize
|
|
8435
|
+
});
|
|
8436
|
+
return formatQQBotMarkdownImage(url, size);
|
|
8437
|
+
});
|
|
8438
|
+
}
|
|
8439
|
+
function isQQBotHttpImageUrl(url) {
|
|
8440
|
+
const trimmed = url.trim();
|
|
8441
|
+
if (!/^https?:\/\//i.test(trimmed)) {
|
|
8442
|
+
return false;
|
|
8443
|
+
}
|
|
8444
|
+
try {
|
|
8445
|
+
const parsed = new URL(trimmed);
|
|
8446
|
+
return /\.(?:png|jpe?g|gif|webp)$/i.test(parsed.pathname);
|
|
8447
|
+
} catch {
|
|
8448
|
+
return /\.(?:png|jpe?g|gif|webp)(?:\?[^\s)"'<>]*)?$/i.test(trimmed);
|
|
8449
|
+
}
|
|
8450
|
+
}
|
|
8451
|
+
async function getQQBotHttpImageSize(url, timeoutMs = 5e3) {
|
|
8452
|
+
if (!isQQBotHttpImageUrl(url)) {
|
|
8453
|
+
return null;
|
|
8454
|
+
}
|
|
8455
|
+
const controller = new AbortController();
|
|
8456
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
8457
|
+
try {
|
|
8458
|
+
const response = await fetch(url, {
|
|
8459
|
+
signal: controller.signal,
|
|
8460
|
+
headers: {
|
|
8461
|
+
Range: "bytes=0-65535",
|
|
8462
|
+
"User-Agent": "OpenClaw-QQBot-ImageSize/1.0"
|
|
8463
|
+
}
|
|
8464
|
+
});
|
|
8465
|
+
if (!(response.ok || response.status === 206)) {
|
|
8466
|
+
return null;
|
|
8467
|
+
}
|
|
8468
|
+
const buffer = Buffer$1.from(await response.arrayBuffer());
|
|
8469
|
+
return normalizeImageSize(parseImageSize(buffer));
|
|
8470
|
+
} catch {
|
|
8471
|
+
return null;
|
|
8472
|
+
} finally {
|
|
8473
|
+
clearTimeout(timeoutId);
|
|
8474
|
+
}
|
|
8475
|
+
}
|
|
8476
|
+
function formatQQBotMarkdownImage(url, size) {
|
|
8477
|
+
const resolved = normalizeImageSize(size) ?? DEFAULT_QQBOT_MARKDOWN_IMAGE_SIZE;
|
|
8478
|
+
return ``;
|
|
8479
|
+
}
|
|
8480
|
+
function hasQQBotMarkdownImageSize(markdownImage) {
|
|
8481
|
+
return /!\[#\d+px\s+#\d+px\]\([^)]+\)/.test(markdownImage);
|
|
8482
|
+
}
|
|
8483
|
+
async function normalizeQQBotMarkdownImages(params) {
|
|
8484
|
+
const text = params.text ?? "";
|
|
8485
|
+
const appendImageUrls = params.appendImageUrls ?? [];
|
|
8486
|
+
const resolveImageSize = params.resolveImageSize ?? ((url) => getQQBotHttpImageSize(url, params.timeoutMs));
|
|
8487
|
+
const imageSizeCache = /* @__PURE__ */ new Map();
|
|
8488
|
+
const seenImageUrls = /* @__PURE__ */ new Set();
|
|
8489
|
+
const segments = splitFencedCodeBlocks(text);
|
|
8490
|
+
const normalizedSegments = [];
|
|
8491
|
+
for (const segment of segments) {
|
|
8492
|
+
if (segment.kind === "code") {
|
|
8493
|
+
normalizedSegments.push(segment.value);
|
|
8494
|
+
continue;
|
|
8495
|
+
}
|
|
8496
|
+
normalizedSegments.push(
|
|
8497
|
+
await normalizeTextSegment({
|
|
8498
|
+
text: segment.value,
|
|
8499
|
+
seenImageUrls,
|
|
8500
|
+
imageSizeCache,
|
|
8501
|
+
resolveImageSize
|
|
8502
|
+
})
|
|
8503
|
+
);
|
|
8504
|
+
}
|
|
8505
|
+
const appendedImages = [];
|
|
8506
|
+
for (const rawUrl of appendImageUrls) {
|
|
8507
|
+
const url = rawUrl.trim();
|
|
8508
|
+
if (!isQQBotHttpImageUrl(url) || seenImageUrls.has(url)) {
|
|
8509
|
+
continue;
|
|
8510
|
+
}
|
|
8511
|
+
seenImageUrls.add(url);
|
|
8512
|
+
const size = await getResolvedImageSize({
|
|
8513
|
+
url,
|
|
8514
|
+
cache: imageSizeCache,
|
|
8515
|
+
resolveImageSize
|
|
8516
|
+
});
|
|
8517
|
+
appendedImages.push(formatQQBotMarkdownImage(url, size));
|
|
8518
|
+
}
|
|
8519
|
+
const body = normalizedSegments.join("").trim();
|
|
8520
|
+
if (appendedImages.length === 0) {
|
|
8521
|
+
return body;
|
|
8522
|
+
}
|
|
8523
|
+
if (!body) {
|
|
8524
|
+
return appendedImages.join("\n");
|
|
8525
|
+
}
|
|
8526
|
+
return `${body}
|
|
8527
|
+
|
|
8528
|
+
${appendedImages.join("\n")}`;
|
|
8529
|
+
}
|
|
8530
|
+
var DEFAULT_KNOWN_TARGETS_PATH = join(homedir(), ".openclaw", "data", "qqbot", "known-targets.json");
|
|
8531
|
+
function resolveKnownTargetsFilePath(options) {
|
|
8532
|
+
return options?.filePath?.trim() || DEFAULT_KNOWN_TARGETS_PATH;
|
|
8533
|
+
}
|
|
8534
|
+
function ensureKnownTargetsDir(filePath) {
|
|
8535
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
8536
|
+
}
|
|
8537
|
+
function compareTargetsByLastSeenDesc(a, b) {
|
|
8538
|
+
if (b.lastSeenAt !== a.lastSeenAt) {
|
|
8539
|
+
return b.lastSeenAt - a.lastSeenAt;
|
|
8540
|
+
}
|
|
8541
|
+
return a.target.localeCompare(b.target);
|
|
8542
|
+
}
|
|
8543
|
+
function normalizeKnownQQBotTarget(target) {
|
|
8544
|
+
const accountId = target.accountId.trim() || DEFAULT_ACCOUNT_ID;
|
|
8545
|
+
const normalized = {
|
|
8546
|
+
accountId,
|
|
8547
|
+
kind: target.kind,
|
|
8548
|
+
target: target.target.trim(),
|
|
8549
|
+
sourceChatType: target.sourceChatType,
|
|
8550
|
+
firstSeenAt: Math.trunc(target.firstSeenAt),
|
|
8551
|
+
lastSeenAt: Math.trunc(target.lastSeenAt)
|
|
8552
|
+
};
|
|
8553
|
+
const displayName = target.displayName?.trim();
|
|
8554
|
+
if (displayName) {
|
|
8555
|
+
normalized.displayName = displayName;
|
|
8556
|
+
}
|
|
8557
|
+
return normalized;
|
|
8558
|
+
}
|
|
8559
|
+
function parseKnownTargets(raw, filePath) {
|
|
8560
|
+
const parsed = JSON.parse(raw);
|
|
8561
|
+
if (!Array.isArray(parsed)) {
|
|
8562
|
+
throw new Error(`Invalid known QQBot targets file: ${filePath}`);
|
|
8563
|
+
}
|
|
8564
|
+
return parsed.filter((entry) => {
|
|
8565
|
+
if (!entry || typeof entry !== "object") return false;
|
|
8566
|
+
const candidate = entry;
|
|
8567
|
+
return typeof candidate.accountId === "string" && typeof candidate.kind === "string" && typeof candidate.target === "string" && typeof candidate.sourceChatType === "string" && typeof candidate.firstSeenAt === "number" && typeof candidate.lastSeenAt === "number";
|
|
8568
|
+
}).map((entry) => normalizeKnownQQBotTarget(entry)).filter((entry) => entry.target.length > 0);
|
|
8569
|
+
}
|
|
8570
|
+
function readKnownTargets(options) {
|
|
8571
|
+
const filePath = resolveKnownTargetsFilePath(options);
|
|
8572
|
+
if (!existsSync(filePath)) {
|
|
8573
|
+
return [];
|
|
8574
|
+
}
|
|
8575
|
+
const raw = readFileSync(filePath, "utf8");
|
|
8576
|
+
if (!raw.trim()) {
|
|
8577
|
+
return [];
|
|
8578
|
+
}
|
|
8579
|
+
return parseKnownTargets(raw, filePath).sort(compareTargetsByLastSeenDesc);
|
|
8580
|
+
}
|
|
8581
|
+
function writeKnownTargets(targets, options) {
|
|
8582
|
+
const filePath = resolveKnownTargetsFilePath(options);
|
|
8583
|
+
if (targets.length === 0) {
|
|
8584
|
+
if (existsSync(filePath)) {
|
|
8585
|
+
rmSync(filePath, { force: true });
|
|
8586
|
+
}
|
|
8587
|
+
return;
|
|
8588
|
+
}
|
|
8589
|
+
ensureKnownTargetsDir(filePath);
|
|
8590
|
+
writeFileSync(filePath, `${JSON.stringify(targets, null, 2)}
|
|
8591
|
+
`, "utf8");
|
|
8592
|
+
}
|
|
8593
|
+
function upsertKnownQQBotTarget(params) {
|
|
8594
|
+
const next = normalizeKnownQQBotTarget(params.target);
|
|
8595
|
+
if (!next.target) {
|
|
8596
|
+
throw new Error("Known QQBot target requires a non-empty target");
|
|
8597
|
+
}
|
|
8598
|
+
const targets = readKnownTargets(params);
|
|
8599
|
+
const index = targets.findIndex(
|
|
8600
|
+
(entry) => entry.accountId === next.accountId && entry.target === next.target
|
|
8601
|
+
);
|
|
8602
|
+
if (index >= 0) {
|
|
8603
|
+
const existing = targets[index];
|
|
8604
|
+
targets[index] = {
|
|
8605
|
+
...existing,
|
|
8606
|
+
kind: next.kind,
|
|
8607
|
+
sourceChatType: next.sourceChatType,
|
|
8608
|
+
displayName: next.displayName ?? existing.displayName,
|
|
8609
|
+
lastSeenAt: next.lastSeenAt
|
|
8610
|
+
};
|
|
8611
|
+
} else {
|
|
8612
|
+
targets.push(next);
|
|
8613
|
+
}
|
|
8614
|
+
targets.sort(compareTargetsByLastSeenDesc);
|
|
8615
|
+
writeKnownTargets(targets, params);
|
|
8616
|
+
return index >= 0 ? targets.find((entry) => entry.accountId === next.accountId && entry.target === next.target) : next;
|
|
8617
|
+
}
|
|
8618
|
+
function listKnownQQBotTargets(params = {}) {
|
|
8619
|
+
let targets = readKnownTargets(params);
|
|
8620
|
+
if (params.accountId?.trim()) {
|
|
8621
|
+
targets = targets.filter((entry) => entry.accountId === params.accountId?.trim());
|
|
8622
|
+
}
|
|
8623
|
+
if (params.kind) {
|
|
8624
|
+
targets = targets.filter((entry) => entry.kind === params.kind);
|
|
8625
|
+
}
|
|
8626
|
+
if (typeof params.limit === "number" && params.limit > 0) {
|
|
8627
|
+
targets = targets.slice(0, params.limit);
|
|
8628
|
+
}
|
|
8629
|
+
return targets;
|
|
8630
|
+
}
|
|
8631
|
+
function getKnownQQBotTarget(params) {
|
|
8632
|
+
const target = params.target.trim();
|
|
8633
|
+
if (!target) return void 0;
|
|
8634
|
+
const matches = readKnownTargets(params).filter((entry) => {
|
|
8635
|
+
if (entry.target !== target) return false;
|
|
8636
|
+
if (params.accountId?.trim()) {
|
|
8637
|
+
return entry.accountId === params.accountId.trim();
|
|
8638
|
+
}
|
|
8639
|
+
return true;
|
|
8640
|
+
});
|
|
8641
|
+
return matches[0];
|
|
8642
|
+
}
|
|
8643
|
+
function removeKnownQQBotTarget(params) {
|
|
8644
|
+
const target = params.target.trim();
|
|
8645
|
+
if (!target) return false;
|
|
8646
|
+
const before = readKnownTargets(params);
|
|
8647
|
+
const filtered = before.filter((entry) => {
|
|
8648
|
+
if (entry.target !== target) return true;
|
|
8649
|
+
if (params.accountId?.trim()) {
|
|
8650
|
+
return entry.accountId !== params.accountId.trim();
|
|
8651
|
+
}
|
|
8652
|
+
return false;
|
|
8653
|
+
});
|
|
8654
|
+
if (filtered.length === before.length) {
|
|
8655
|
+
return false;
|
|
8656
|
+
}
|
|
8657
|
+
writeKnownTargets(filtered, params);
|
|
8658
|
+
return true;
|
|
8659
|
+
}
|
|
8660
|
+
function clearKnownQQBotTargets(params = {}) {
|
|
8661
|
+
const before = readKnownTargets(params);
|
|
8662
|
+
const filtered = before.filter((entry) => {
|
|
8663
|
+
if (params.accountId?.trim() && entry.accountId !== params.accountId.trim()) {
|
|
8664
|
+
return true;
|
|
8665
|
+
}
|
|
8666
|
+
if (params.kind && entry.kind !== params.kind) {
|
|
8667
|
+
return true;
|
|
8668
|
+
}
|
|
8669
|
+
return false;
|
|
8670
|
+
});
|
|
8671
|
+
const removed = before.length - filtered.length;
|
|
8672
|
+
if (removed === 0) {
|
|
8673
|
+
return 0;
|
|
8674
|
+
}
|
|
8675
|
+
writeKnownTargets(filtered, params);
|
|
8676
|
+
return removed;
|
|
8677
|
+
}
|
|
8678
|
+
async function sendProactiveQQBotMessage(params) {
|
|
8679
|
+
const to = params.to.trim();
|
|
8680
|
+
if (!to) {
|
|
8681
|
+
return { channel: "qqbot", error: "to is required for proactive send" };
|
|
8682
|
+
}
|
|
8683
|
+
if (params.mediaUrl?.trim()) {
|
|
8684
|
+
return qqbotOutbound.sendMedia({
|
|
8685
|
+
cfg: params.cfg,
|
|
8686
|
+
to,
|
|
8687
|
+
mediaUrl: params.mediaUrl.trim(),
|
|
8688
|
+
text: params.text,
|
|
8689
|
+
accountId: params.accountId
|
|
8690
|
+
});
|
|
8691
|
+
}
|
|
8692
|
+
const text = params.text?.trim();
|
|
8693
|
+
if (!text) {
|
|
8694
|
+
return { channel: "qqbot", error: "text or mediaUrl is required for proactive send" };
|
|
8695
|
+
}
|
|
8696
|
+
return qqbotOutbound.sendText({
|
|
8697
|
+
cfg: params.cfg,
|
|
8698
|
+
to,
|
|
8699
|
+
text,
|
|
8700
|
+
accountId: params.accountId
|
|
8701
|
+
});
|
|
8702
|
+
}
|
|
7907
8703
|
|
|
7908
8704
|
// src/runtime.ts
|
|
7909
8705
|
var runtime = null;
|
|
@@ -7916,6 +8712,24 @@ function getQQBotRuntime() {
|
|
|
7916
8712
|
}
|
|
7917
8713
|
return runtime;
|
|
7918
8714
|
}
|
|
8715
|
+
var sessionDispatchQueue = /* @__PURE__ */ new Map();
|
|
8716
|
+
function buildSessionDispatchQueueKey(route) {
|
|
8717
|
+
const accountId = route.accountId?.trim() || DEFAULT_ACCOUNT_ID;
|
|
8718
|
+
return `${accountId}:${route.sessionKey}`;
|
|
8719
|
+
}
|
|
8720
|
+
async function runSerializedSessionDispatch(queueKey, task) {
|
|
8721
|
+
const previous = sessionDispatchQueue.get(queueKey) ?? Promise.resolve();
|
|
8722
|
+
const run = previous.catch(() => void 0).then(task);
|
|
8723
|
+
const cleanup = run.then(() => void 0, () => void 0);
|
|
8724
|
+
sessionDispatchQueue.set(queueKey, cleanup);
|
|
8725
|
+
try {
|
|
8726
|
+
return await run;
|
|
8727
|
+
} finally {
|
|
8728
|
+
if (sessionDispatchQueue.get(queueKey) === cleanup) {
|
|
8729
|
+
sessionDispatchQueue.delete(queueKey);
|
|
8730
|
+
}
|
|
8731
|
+
}
|
|
8732
|
+
}
|
|
7919
8733
|
function toString(value) {
|
|
7920
8734
|
if (typeof value === "string" && value.trim()) return value;
|
|
7921
8735
|
return void 0;
|
|
@@ -8341,26 +9155,100 @@ function resolveEnvelopeFrom(event) {
|
|
|
8341
9155
|
}
|
|
8342
9156
|
return event.senderName?.trim() || event.senderId;
|
|
8343
9157
|
}
|
|
9158
|
+
function resolveKnownQQBotTargetFromInbound(params) {
|
|
9159
|
+
const { inbound, accountId } = params;
|
|
9160
|
+
if (inbound.type === "direct") {
|
|
9161
|
+
if (!inbound.c2cOpenid?.trim()) {
|
|
9162
|
+
return void 0;
|
|
9163
|
+
}
|
|
9164
|
+
return {
|
|
9165
|
+
accountId,
|
|
9166
|
+
kind: "user",
|
|
9167
|
+
target: `user:${inbound.c2cOpenid}`,
|
|
9168
|
+
displayName: inbound.senderName,
|
|
9169
|
+
sourceChatType: "direct",
|
|
9170
|
+
firstSeenAt: inbound.timestamp,
|
|
9171
|
+
lastSeenAt: inbound.timestamp
|
|
9172
|
+
};
|
|
9173
|
+
}
|
|
9174
|
+
if (inbound.type === "group" && inbound.groupOpenid?.trim()) {
|
|
9175
|
+
return {
|
|
9176
|
+
accountId,
|
|
9177
|
+
kind: "group",
|
|
9178
|
+
target: `group:${inbound.groupOpenid}`,
|
|
9179
|
+
displayName: inbound.senderName,
|
|
9180
|
+
sourceChatType: "group",
|
|
9181
|
+
firstSeenAt: inbound.timestamp,
|
|
9182
|
+
lastSeenAt: inbound.timestamp
|
|
9183
|
+
};
|
|
9184
|
+
}
|
|
9185
|
+
if (inbound.type === "channel" && inbound.channelId?.trim()) {
|
|
9186
|
+
return {
|
|
9187
|
+
accountId,
|
|
9188
|
+
kind: "channel",
|
|
9189
|
+
target: `channel:${inbound.channelId}`,
|
|
9190
|
+
displayName: inbound.senderName,
|
|
9191
|
+
sourceChatType: "channel",
|
|
9192
|
+
firstSeenAt: inbound.timestamp,
|
|
9193
|
+
lastSeenAt: inbound.timestamp
|
|
9194
|
+
};
|
|
9195
|
+
}
|
|
9196
|
+
return void 0;
|
|
9197
|
+
}
|
|
8344
9198
|
function extractLocalMediaFromText(params) {
|
|
8345
9199
|
const { text, logger } = params;
|
|
8346
|
-
const
|
|
8347
|
-
|
|
8348
|
-
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
8355
|
-
|
|
8356
|
-
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
|
|
8360
|
-
|
|
9200
|
+
const mediaUrls = [];
|
|
9201
|
+
const seenMedia = /* @__PURE__ */ new Set();
|
|
9202
|
+
let nextText = text;
|
|
9203
|
+
const MARKDOWN_LINKED_IMAGE_RE2 = /\[!\[([^\]]*)\]\(([^)]+)\)\]\(([^)]+)\)/g;
|
|
9204
|
+
const MARKDOWN_IMAGE_RE3 = /!\[([^\]]*)\]\(([^)]+)\)/g;
|
|
9205
|
+
const MARKDOWN_LINK_RE2 = /\[([^\]]*)\]\(([^)]+)\)/g;
|
|
9206
|
+
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;
|
|
9207
|
+
const collectLocalRichMedia = (rawValue, allowedTypes) => {
|
|
9208
|
+
const candidate = stripTitleFromUrl(rawValue.trim());
|
|
9209
|
+
if (!candidate || !isLocalReference(candidate)) {
|
|
9210
|
+
return void 0;
|
|
9211
|
+
}
|
|
9212
|
+
if (!fs3.existsSync(candidate)) {
|
|
9213
|
+
logger?.warn?.(`[media] local file not found: ${candidate}`);
|
|
9214
|
+
return void 0;
|
|
9215
|
+
}
|
|
9216
|
+
const mediaType = detectMediaType(candidate);
|
|
9217
|
+
if (mediaType === "file") {
|
|
9218
|
+
return void 0;
|
|
9219
|
+
}
|
|
9220
|
+
if (allowedTypes && !allowedTypes.has(mediaType)) {
|
|
9221
|
+
return void 0;
|
|
9222
|
+
}
|
|
9223
|
+
if (seenMedia.has(candidate)) {
|
|
9224
|
+
return candidate;
|
|
9225
|
+
}
|
|
9226
|
+
seenMedia.add(candidate);
|
|
9227
|
+
mediaUrls.push(candidate);
|
|
9228
|
+
return candidate;
|
|
9229
|
+
};
|
|
9230
|
+
nextText = nextText.replace(MARKDOWN_LINKED_IMAGE_RE2, (fullMatch, _alt, rawPath) => {
|
|
9231
|
+
return collectLocalRichMedia(rawPath) ? "" : fullMatch;
|
|
8361
9232
|
});
|
|
8362
|
-
|
|
8363
|
-
|
|
9233
|
+
nextText = nextText.replace(MARKDOWN_IMAGE_RE3, (fullMatch, _alt, rawPath) => {
|
|
9234
|
+
return collectLocalRichMedia(rawPath) ? "" : fullMatch;
|
|
9235
|
+
});
|
|
9236
|
+
nextText = nextText.replace(MARKDOWN_LINK_RE2, (fullMatch, _label, rawPath) => {
|
|
9237
|
+
const mediaPath = collectLocalRichMedia(rawPath, /* @__PURE__ */ new Set(["audio", "video"]));
|
|
9238
|
+
if (!mediaPath) {
|
|
9239
|
+
return fullMatch;
|
|
9240
|
+
}
|
|
9241
|
+
return "";
|
|
9242
|
+
});
|
|
9243
|
+
nextText = nextText.replace(BARE_LOCAL_MEDIA_PATH_RE, (fullMatch, rawPath) => {
|
|
9244
|
+
return collectLocalRichMedia(rawPath) ? "" : fullMatch;
|
|
9245
|
+
});
|
|
9246
|
+
nextText = nextText.replace(/[ \t]+\n/g, "\n");
|
|
9247
|
+
nextText = nextText.replace(/\n{3,}/g, "\n\n");
|
|
9248
|
+
return {
|
|
9249
|
+
text: nextText.trim(),
|
|
9250
|
+
mediaUrls
|
|
9251
|
+
};
|
|
8364
9252
|
}
|
|
8365
9253
|
function extractMediaLinesFromText(params) {
|
|
8366
9254
|
const { text, logger } = params;
|
|
@@ -8383,6 +9271,23 @@ function extractMediaLinesFromText(params) {
|
|
|
8383
9271
|
const mediaUrls = result.all.map((m) => m.isLocal ? m.localPath ?? m.source : m.source).filter((m) => typeof m === "string" && m.trim().length > 0);
|
|
8384
9272
|
return { text: result.text, mediaUrls };
|
|
8385
9273
|
}
|
|
9274
|
+
function extractQQBotReplyMedia(params) {
|
|
9275
|
+
const mediaLineResult = extractMediaLinesFromText({
|
|
9276
|
+
text: params.text,
|
|
9277
|
+
logger: params.logger
|
|
9278
|
+
});
|
|
9279
|
+
if (!params.autoSendLocalPathMedia) {
|
|
9280
|
+
return mediaLineResult;
|
|
9281
|
+
}
|
|
9282
|
+
const localMediaResult = extractLocalMediaFromText({
|
|
9283
|
+
text: mediaLineResult.text,
|
|
9284
|
+
logger: params.logger
|
|
9285
|
+
});
|
|
9286
|
+
return {
|
|
9287
|
+
text: localMediaResult.text,
|
|
9288
|
+
mediaUrls: [.../* @__PURE__ */ new Set([...mediaLineResult.mediaUrls, ...localMediaResult.mediaUrls])]
|
|
9289
|
+
};
|
|
9290
|
+
}
|
|
8386
9291
|
function buildMediaFallbackText(mediaUrl) {
|
|
8387
9292
|
if (!/^https?:\/\//i.test(mediaUrl)) {
|
|
8388
9293
|
return void 0;
|
|
@@ -8396,6 +9301,9 @@ var FILE_PLACEHOLDER_RE = /\[文件:\s*[^\]\n]+\]/g;
|
|
|
8396
9301
|
var DIRECTIVE_TAG_RE = /\[\[\s*(?:reply_to_current|reply_to\s*:[^\]]+|audio_as_voice|tts(?::text)?|\/tts(?::text)?)\s*\]\]/gi;
|
|
8397
9302
|
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;
|
|
8398
9303
|
var TTS_LIKE_RAW_TEXT_RE = /\[\[\s*(?:tts(?::text)?|\/tts(?::text)?|audio_as_voice|reply_to_current|reply_to\s*:)/i;
|
|
9304
|
+
var MARKDOWN_TABLE_SEPARATOR_RE = /^\|?(?:\s*:?-{3,}:?\s*\|)+(?:\s*:?-{3,}:?)?\|?$/;
|
|
9305
|
+
var EXPLICIT_MARKDOWN_FENCE_RE = /(^|\n)(`{3,}|~{3,})\s*(?:markdown|md)\s*\n([\s\S]*?)\n\2(?=\n|$)/gi;
|
|
9306
|
+
var GENERIC_MARKDOWN_FENCE_RE = /(^|\n)(`{3,}|~{3,})\s*\n([\s\S]*?)\n\2(?=\n|$)/g;
|
|
8399
9307
|
function extractFinalBlocks(text) {
|
|
8400
9308
|
const matches = Array.from(text.matchAll(FINAL_BLOCK_RE));
|
|
8401
9309
|
if (matches.length === 0) return void 0;
|
|
@@ -8420,6 +9328,14 @@ function sanitizeQQBotOutboundText(rawText) {
|
|
|
8420
9328
|
if (/^NO_REPLY$/i.test(next)) return "";
|
|
8421
9329
|
return next;
|
|
8422
9330
|
}
|
|
9331
|
+
function formatQQBotOutboundPreview(text, maxLength = 240) {
|
|
9332
|
+
const normalized = text.replace(/\r\n/g, "\n").trim();
|
|
9333
|
+
if (!normalized) {
|
|
9334
|
+
return '""';
|
|
9335
|
+
}
|
|
9336
|
+
const preview = normalized.length > maxLength ? `${normalized.slice(0, Math.max(0, maxLength - 3))}...` : normalized;
|
|
9337
|
+
return JSON.stringify(preview);
|
|
9338
|
+
}
|
|
8423
9339
|
function shouldSuppressQQBotTextWhenMediaPresent(rawText, sanitizedText) {
|
|
8424
9340
|
const raw = rawText.trim();
|
|
8425
9341
|
if (!raw) return false;
|
|
@@ -8452,6 +9368,111 @@ function evaluateReplyFinalOnlyDelivery(params) {
|
|
|
8452
9368
|
}
|
|
8453
9369
|
return { skipDelivery: true, suppressText: false };
|
|
8454
9370
|
}
|
|
9371
|
+
function isQQBotC2CTarget(to) {
|
|
9372
|
+
const trimmed = to.trim();
|
|
9373
|
+
const raw = trimmed.startsWith("qqbot:") ? trimmed.slice("qqbot:".length) : trimmed;
|
|
9374
|
+
return !raw.startsWith("group:") && !raw.startsWith("channel:");
|
|
9375
|
+
}
|
|
9376
|
+
function splitQQBotMarkdownTransportMediaUrls(mediaUrls) {
|
|
9377
|
+
const markdownImageUrls = [];
|
|
9378
|
+
const mediaQueue = [];
|
|
9379
|
+
const seenMarkdownImages = /* @__PURE__ */ new Set();
|
|
9380
|
+
const seenMedia = /* @__PURE__ */ new Set();
|
|
9381
|
+
for (const rawUrl of mediaUrls) {
|
|
9382
|
+
const next = rawUrl.trim();
|
|
9383
|
+
if (!next) continue;
|
|
9384
|
+
if (isQQBotHttpImageUrl(next)) {
|
|
9385
|
+
if (seenMarkdownImages.has(next)) continue;
|
|
9386
|
+
seenMarkdownImages.add(next);
|
|
9387
|
+
markdownImageUrls.push(next);
|
|
9388
|
+
continue;
|
|
9389
|
+
}
|
|
9390
|
+
if (seenMedia.has(next)) continue;
|
|
9391
|
+
seenMedia.add(next);
|
|
9392
|
+
mediaQueue.push(next);
|
|
9393
|
+
}
|
|
9394
|
+
return { markdownImageUrls, mediaQueue };
|
|
9395
|
+
}
|
|
9396
|
+
function hasQQBotMarkdownTable(text) {
|
|
9397
|
+
const lines = text.replace(/\r\n/g, "\n").split("\n");
|
|
9398
|
+
for (let index = 0; index < lines.length - 1; index += 1) {
|
|
9399
|
+
const header = lines[index]?.trim() ?? "";
|
|
9400
|
+
const separator = lines[index + 1]?.trim() ?? "";
|
|
9401
|
+
if (!header.includes("|") || !MARKDOWN_TABLE_SEPARATOR_RE.test(separator)) {
|
|
9402
|
+
continue;
|
|
9403
|
+
}
|
|
9404
|
+
const headerColumns = header.split("|").filter((column) => column.trim()).length;
|
|
9405
|
+
const separatorColumns = separator.split("|").filter((column) => column.trim()).length;
|
|
9406
|
+
if (headerColumns >= 2 && separatorColumns >= 2) {
|
|
9407
|
+
return true;
|
|
9408
|
+
}
|
|
9409
|
+
}
|
|
9410
|
+
return false;
|
|
9411
|
+
}
|
|
9412
|
+
function resolveQQBotTextReplyRefs(params) {
|
|
9413
|
+
const mode = params.c2cMarkdownDeliveryMode ?? "proactive-table-only";
|
|
9414
|
+
const forceProactive = params.markdownSupport && isQQBotC2CTarget(params.to) && (mode === "proactive-all" || mode === "proactive-table-only" && hasQQBotMarkdownTable(params.text));
|
|
9415
|
+
if (!forceProactive) {
|
|
9416
|
+
return {
|
|
9417
|
+
forceProactive: false,
|
|
9418
|
+
replyToId: params.replyToId,
|
|
9419
|
+
replyEventId: params.replyEventId
|
|
9420
|
+
};
|
|
9421
|
+
}
|
|
9422
|
+
return {
|
|
9423
|
+
forceProactive: true,
|
|
9424
|
+
replyToId: void 0,
|
|
9425
|
+
replyEventId: void 0
|
|
9426
|
+
};
|
|
9427
|
+
}
|
|
9428
|
+
function appendQQBotBufferedText(bufferedTexts, nextText) {
|
|
9429
|
+
const normalized = nextText.trim();
|
|
9430
|
+
if (!normalized) return bufferedTexts;
|
|
9431
|
+
if (bufferedTexts.length === 0) return [normalized];
|
|
9432
|
+
const currentCombined = bufferedTexts.join("\n\n");
|
|
9433
|
+
if (currentCombined === normalized || currentCombined.includes(normalized)) {
|
|
9434
|
+
return bufferedTexts;
|
|
9435
|
+
}
|
|
9436
|
+
if (normalized.includes(currentCombined)) {
|
|
9437
|
+
return [normalized];
|
|
9438
|
+
}
|
|
9439
|
+
const last = bufferedTexts[bufferedTexts.length - 1];
|
|
9440
|
+
if (last === normalized) {
|
|
9441
|
+
return bufferedTexts;
|
|
9442
|
+
}
|
|
9443
|
+
return [...bufferedTexts, normalized];
|
|
9444
|
+
}
|
|
9445
|
+
function normalizeQQBotRenderedMarkdown(text) {
|
|
9446
|
+
if (!text.trim()) return "";
|
|
9447
|
+
let next = text.trim();
|
|
9448
|
+
let changed = false;
|
|
9449
|
+
next = next.replace(
|
|
9450
|
+
EXPLICIT_MARKDOWN_FENCE_RE,
|
|
9451
|
+
(block, leadingLineBreak, _fence, inner) => {
|
|
9452
|
+
const normalizedInner = inner.trim();
|
|
9453
|
+
if (!normalizedInner) {
|
|
9454
|
+
return block;
|
|
9455
|
+
}
|
|
9456
|
+
changed = true;
|
|
9457
|
+
return `${leadingLineBreak}${normalizedInner}`;
|
|
9458
|
+
}
|
|
9459
|
+
);
|
|
9460
|
+
next = next.replace(
|
|
9461
|
+
GENERIC_MARKDOWN_FENCE_RE,
|
|
9462
|
+
(block, leadingLineBreak, _fence, inner) => {
|
|
9463
|
+
const normalizedInner = inner.trim();
|
|
9464
|
+
if (!normalizedInner) {
|
|
9465
|
+
return block;
|
|
9466
|
+
}
|
|
9467
|
+
if (!hasQQBotMarkdownTable(normalizedInner)) {
|
|
9468
|
+
return block;
|
|
9469
|
+
}
|
|
9470
|
+
changed = true;
|
|
9471
|
+
return `${leadingLineBreak}${normalizedInner}`;
|
|
9472
|
+
}
|
|
9473
|
+
);
|
|
9474
|
+
return changed ? next.trim() : text.trim();
|
|
9475
|
+
}
|
|
8455
9476
|
async function sendQQBotMediaWithFallback(params) {
|
|
8456
9477
|
const { qqCfg, to, mediaQueue, replyToId, replyEventId, logger, onDelivered, onError } = params;
|
|
8457
9478
|
const outbound = params.outbound ?? qqbotOutbound;
|
|
@@ -8518,13 +9539,8 @@ function buildInboundContext(params) {
|
|
|
8518
9539
|
};
|
|
8519
9540
|
}
|
|
8520
9541
|
async function dispatchToAgent(params) {
|
|
8521
|
-
const { inbound, cfg, qqCfg, accountId, logger } = params;
|
|
9542
|
+
const { inbound, cfg, qqCfg, accountId, logger, route } = params;
|
|
8522
9543
|
const runtime2 = getQQBotRuntime();
|
|
8523
|
-
const routing = runtime2.channel?.routing?.resolveAgentRoute;
|
|
8524
|
-
if (!routing) {
|
|
8525
|
-
logger.warn("routing API not available");
|
|
8526
|
-
return;
|
|
8527
|
-
}
|
|
8528
9544
|
const target = resolveChatTarget(inbound);
|
|
8529
9545
|
if (inbound.c2cOpenid) {
|
|
8530
9546
|
const typing = await qqbotOutbound.sendTyping({
|
|
@@ -8538,12 +9554,6 @@ async function dispatchToAgent(params) {
|
|
|
8538
9554
|
logger.warn(`sendTyping failed: ${typing.error}`);
|
|
8539
9555
|
}
|
|
8540
9556
|
}
|
|
8541
|
-
const route = routing({
|
|
8542
|
-
cfg,
|
|
8543
|
-
channel: "qqbot",
|
|
8544
|
-
accountId,
|
|
8545
|
-
peer: { kind: target.peerKind, id: target.peerId }
|
|
8546
|
-
});
|
|
8547
9557
|
const replyApi = runtime2.channel?.reply;
|
|
8548
9558
|
if (!replyApi) {
|
|
8549
9559
|
logger.warn("reply API not available");
|
|
@@ -8714,17 +9724,99 @@ async function dispatchToAgent(params) {
|
|
|
8714
9724
|
return [text];
|
|
8715
9725
|
};
|
|
8716
9726
|
const replyFinalOnly = qqCfg.replyFinalOnly ?? false;
|
|
9727
|
+
const markdownSupport = qqCfg.markdownSupport ?? true;
|
|
9728
|
+
const c2cMarkdownDeliveryMode = qqCfg.c2cMarkdownDeliveryMode ?? "proactive-table-only";
|
|
9729
|
+
const useC2CMarkdownTransport = markdownSupport && isQQBotC2CTarget(target.to);
|
|
9730
|
+
let bufferedC2CMarkdownTexts = [];
|
|
9731
|
+
let bufferedC2CMarkdownMediaUrls = [];
|
|
9732
|
+
const bufferedC2CMarkdownMediaSeen = /* @__PURE__ */ new Set();
|
|
9733
|
+
const bufferC2CMarkdownMedia = (url) => {
|
|
9734
|
+
const next = url?.trim();
|
|
9735
|
+
if (!next || bufferedC2CMarkdownMediaSeen.has(next)) return;
|
|
9736
|
+
bufferedC2CMarkdownMediaSeen.add(next);
|
|
9737
|
+
bufferedC2CMarkdownMediaUrls.push(next);
|
|
9738
|
+
};
|
|
9739
|
+
const flushBufferedC2CMarkdownReply = async () => {
|
|
9740
|
+
if (!useC2CMarkdownTransport || bufferedC2CMarkdownTexts.length === 0 && bufferedC2CMarkdownMediaUrls.length === 0) {
|
|
9741
|
+
bufferedC2CMarkdownTexts = [];
|
|
9742
|
+
bufferedC2CMarkdownMediaUrls = [];
|
|
9743
|
+
bufferedC2CMarkdownMediaSeen.clear();
|
|
9744
|
+
return;
|
|
9745
|
+
}
|
|
9746
|
+
const combinedText = bufferedC2CMarkdownTexts.join("\n\n").trim();
|
|
9747
|
+
const combinedMediaUrls = [...bufferedC2CMarkdownMediaUrls];
|
|
9748
|
+
bufferedC2CMarkdownTexts = [];
|
|
9749
|
+
bufferedC2CMarkdownMediaUrls = [];
|
|
9750
|
+
bufferedC2CMarkdownMediaSeen.clear();
|
|
9751
|
+
const normalizedCombinedText = normalizeQQBotRenderedMarkdown(combinedText);
|
|
9752
|
+
const { markdownImageUrls, mediaQueue } = splitQQBotMarkdownTransportMediaUrls(combinedMediaUrls);
|
|
9753
|
+
const finalMarkdownText = await normalizeQQBotMarkdownImages({
|
|
9754
|
+
text: normalizedCombinedText,
|
|
9755
|
+
appendImageUrls: markdownImageUrls
|
|
9756
|
+
});
|
|
9757
|
+
const textReplyRefs = resolveQQBotTextReplyRefs({
|
|
9758
|
+
to: target.to,
|
|
9759
|
+
text: finalMarkdownText || normalizedCombinedText,
|
|
9760
|
+
markdownSupport,
|
|
9761
|
+
c2cMarkdownDeliveryMode,
|
|
9762
|
+
replyToId: inbound.messageId,
|
|
9763
|
+
replyEventId: inbound.eventId
|
|
9764
|
+
});
|
|
9765
|
+
const textSegments = finalMarkdownText ? [finalMarkdownText] : [];
|
|
9766
|
+
const deliveryLabel = textReplyRefs.forceProactive ? "c2c-markdown-proactive" : "c2c-markdown-passive";
|
|
9767
|
+
logger.info(
|
|
9768
|
+
`delivery=${deliveryLabel} to=${target.to} segments=${textSegments.length} media=${mediaQueue.length} replyToId=${textReplyRefs.replyToId ? "yes" : "no"} replyEventId=${textReplyRefs.replyEventId ? "yes" : "no"} tableMode=${String(resolvedTableMode)} chunkMode=${String(chunkMode ?? "default")}`
|
|
9769
|
+
);
|
|
9770
|
+
await sendQQBotMediaWithFallback({
|
|
9771
|
+
qqCfg,
|
|
9772
|
+
to: target.to,
|
|
9773
|
+
mediaQueue,
|
|
9774
|
+
replyToId: textReplyRefs.replyToId,
|
|
9775
|
+
replyEventId: textReplyRefs.replyEventId,
|
|
9776
|
+
logger,
|
|
9777
|
+
onDelivered: () => {
|
|
9778
|
+
markReplyDelivered();
|
|
9779
|
+
},
|
|
9780
|
+
onError: (error) => {
|
|
9781
|
+
markGroupMessageInterfaceBlocked(error);
|
|
9782
|
+
}
|
|
9783
|
+
});
|
|
9784
|
+
if (!finalMarkdownText) {
|
|
9785
|
+
return;
|
|
9786
|
+
}
|
|
9787
|
+
for (let segmentIndex = 0; segmentIndex < textSegments.length; segmentIndex += 1) {
|
|
9788
|
+
const segment = textSegments[segmentIndex] ?? "";
|
|
9789
|
+
const chunks = chunkText(segment);
|
|
9790
|
+
for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex += 1) {
|
|
9791
|
+
const chunk = chunks[chunkIndex] ?? "";
|
|
9792
|
+
logger.info(
|
|
9793
|
+
`delivery=${deliveryLabel} segment=${segmentIndex + 1}/${textSegments.length} chunk=${chunkIndex + 1}/${chunks.length} preview=${formatQQBotOutboundPreview(chunk)}`
|
|
9794
|
+
);
|
|
9795
|
+
const result = await qqbotOutbound.sendText({
|
|
9796
|
+
cfg: { channels: { qqbot: qqCfg } },
|
|
9797
|
+
to: target.to,
|
|
9798
|
+
text: chunk,
|
|
9799
|
+
replyToId: textReplyRefs.replyToId,
|
|
9800
|
+
replyEventId: textReplyRefs.replyEventId
|
|
9801
|
+
});
|
|
9802
|
+
if (result.error) {
|
|
9803
|
+
logger.error(`send buffered QQ markdown reply failed: ${result.error}`);
|
|
9804
|
+
markGroupMessageInterfaceBlocked(result.error);
|
|
9805
|
+
} else {
|
|
9806
|
+
logger.info(`sent buffered QQ markdown reply (len=${chunk.length})`);
|
|
9807
|
+
markReplyDelivered();
|
|
9808
|
+
}
|
|
9809
|
+
}
|
|
9810
|
+
}
|
|
9811
|
+
};
|
|
8717
9812
|
const deliver = async (payload, info) => {
|
|
8718
9813
|
const typed = payload;
|
|
8719
|
-
const
|
|
9814
|
+
const extractedTextMedia = extractQQBotReplyMedia({
|
|
8720
9815
|
text: typed?.text ?? "",
|
|
8721
|
-
logger
|
|
8722
|
-
|
|
8723
|
-
const localMediaResult = extractLocalMediaFromText({
|
|
8724
|
-
text: mediaLineResult.text,
|
|
8725
|
-
logger
|
|
9816
|
+
logger,
|
|
9817
|
+
autoSendLocalPathMedia: resolveQQBotAutoSendLocalPathMedia(qqCfg)
|
|
8726
9818
|
});
|
|
8727
|
-
const cleanedText = sanitizeQQBotOutboundText(
|
|
9819
|
+
const cleanedText = sanitizeQQBotOutboundText(extractedTextMedia.text);
|
|
8728
9820
|
const payloadMediaUrls = Array.isArray(typed?.mediaUrls) ? typed?.mediaUrls : typed?.mediaUrl ? [typed.mediaUrl] : [];
|
|
8729
9821
|
const mediaQueue = [];
|
|
8730
9822
|
const seenMedia = /* @__PURE__ */ new Set();
|
|
@@ -8736,8 +9828,7 @@ async function dispatchToAgent(params) {
|
|
|
8736
9828
|
mediaQueue.push(next);
|
|
8737
9829
|
};
|
|
8738
9830
|
for (const url of payloadMediaUrls) addMedia(url);
|
|
8739
|
-
for (const url of
|
|
8740
|
-
for (const url of localMediaResult.mediaUrls) addMedia(url);
|
|
9831
|
+
for (const url of extractedTextMedia.mediaUrls) addMedia(url);
|
|
8741
9832
|
const deliveryDecision = evaluateReplyFinalOnlyDelivery({
|
|
8742
9833
|
replyFinalOnly,
|
|
8743
9834
|
kind: info?.kind,
|
|
@@ -8745,19 +9836,36 @@ async function dispatchToAgent(params) {
|
|
|
8745
9836
|
sanitizedText: cleanedText
|
|
8746
9837
|
});
|
|
8747
9838
|
if (deliveryDecision.skipDelivery) return;
|
|
8748
|
-
const suppressEchoText = mediaQueue.length > 0 && shouldSuppressQQBotTextWhenMediaPresent(
|
|
9839
|
+
const suppressEchoText = mediaQueue.length > 0 && shouldSuppressQQBotTextWhenMediaPresent(extractedTextMedia.text, cleanedText);
|
|
8749
9840
|
const suppressText = deliveryDecision.suppressText || suppressEchoText;
|
|
8750
9841
|
const textToSend = suppressText ? "" : cleanedText;
|
|
9842
|
+
if (useC2CMarkdownTransport) {
|
|
9843
|
+
if (textToSend) {
|
|
9844
|
+
bufferedC2CMarkdownTexts = appendQQBotBufferedText(bufferedC2CMarkdownTexts, textToSend);
|
|
9845
|
+
}
|
|
9846
|
+
for (const url of mediaQueue) {
|
|
9847
|
+
bufferC2CMarkdownMedia(url);
|
|
9848
|
+
}
|
|
9849
|
+
return;
|
|
9850
|
+
}
|
|
8751
9851
|
if (textToSend) {
|
|
8752
9852
|
const converted = textApi?.convertMarkdownTables ? textApi.convertMarkdownTables(textToSend, resolvedTableMode) : textToSend;
|
|
9853
|
+
const textReplyRefs = resolveQQBotTextReplyRefs({
|
|
9854
|
+
to: target.to,
|
|
9855
|
+
text: converted,
|
|
9856
|
+
markdownSupport,
|
|
9857
|
+
c2cMarkdownDeliveryMode,
|
|
9858
|
+
replyToId: inbound.messageId,
|
|
9859
|
+
replyEventId: inbound.eventId
|
|
9860
|
+
});
|
|
8753
9861
|
const chunks = chunkText(converted);
|
|
8754
9862
|
for (const chunk of chunks) {
|
|
8755
9863
|
const result = await qqbotOutbound.sendText({
|
|
8756
9864
|
cfg: { channels: { qqbot: qqCfg } },
|
|
8757
9865
|
to: target.to,
|
|
8758
9866
|
text: chunk,
|
|
8759
|
-
replyToId:
|
|
8760
|
-
replyEventId:
|
|
9867
|
+
replyToId: textReplyRefs.replyToId,
|
|
9868
|
+
replyEventId: textReplyRefs.replyEventId
|
|
8761
9869
|
});
|
|
8762
9870
|
if (result.error) {
|
|
8763
9871
|
logger.error(`sendText failed: ${result.error}`);
|
|
@@ -8801,6 +9909,7 @@ async function dispatchToAgent(params) {
|
|
|
8801
9909
|
}
|
|
8802
9910
|
}
|
|
8803
9911
|
});
|
|
9912
|
+
await flushBufferedC2CMarkdownReply();
|
|
8804
9913
|
} else {
|
|
8805
9914
|
const dispatcherResult = replyApi.createReplyDispatcherWithTyping ? replyApi.createReplyDispatcherWithTyping({
|
|
8806
9915
|
deliver,
|
|
@@ -8830,6 +9939,7 @@ async function dispatchToAgent(params) {
|
|
|
8830
9939
|
replyOptions: dispatcherResult.replyOptions
|
|
8831
9940
|
});
|
|
8832
9941
|
dispatcherResult.markDispatchIdle?.();
|
|
9942
|
+
await flushBufferedC2CMarkdownReply();
|
|
8833
9943
|
}
|
|
8834
9944
|
const noReplyFallback = resolveQQBotNoReplyFallback({
|
|
8835
9945
|
inbound,
|
|
@@ -8919,6 +10029,14 @@ async function handleQQBotDispatch(params) {
|
|
|
8919
10029
|
if (!shouldHandleMessage(inbound, qqCfg, logger)) {
|
|
8920
10030
|
return;
|
|
8921
10031
|
}
|
|
10032
|
+
const knownTarget = resolveKnownQQBotTargetFromInbound({ inbound, accountId });
|
|
10033
|
+
if (knownTarget) {
|
|
10034
|
+
try {
|
|
10035
|
+
upsertKnownQQBotTarget({ target: knownTarget });
|
|
10036
|
+
} catch (err) {
|
|
10037
|
+
logger.warn(`failed to record known qqbot target: ${String(err)}`);
|
|
10038
|
+
}
|
|
10039
|
+
}
|
|
8922
10040
|
const attachmentCount = inbound.attachments?.length ?? 0;
|
|
8923
10041
|
if (attachmentCount > 0) {
|
|
8924
10042
|
logger.info(`inbound message includes ${attachmentCount} attachment(s)`);
|
|
@@ -8926,13 +10044,34 @@ async function handleQQBotDispatch(params) {
|
|
|
8926
10044
|
if (!content && attachmentCount === 0) {
|
|
8927
10045
|
return;
|
|
8928
10046
|
}
|
|
8929
|
-
|
|
8930
|
-
|
|
10047
|
+
const runtime2 = getQQBotRuntime();
|
|
10048
|
+
const routing = runtime2.channel?.routing?.resolveAgentRoute;
|
|
10049
|
+
if (!routing) {
|
|
10050
|
+
logger.warn("routing API not available");
|
|
10051
|
+
return;
|
|
10052
|
+
}
|
|
10053
|
+
const target = resolveChatTarget(inbound);
|
|
10054
|
+
const route = routing({
|
|
8931
10055
|
cfg: params.cfg,
|
|
8932
|
-
|
|
10056
|
+
channel: "qqbot",
|
|
8933
10057
|
accountId,
|
|
8934
|
-
|
|
10058
|
+
peer: { kind: target.peerKind, id: target.peerId }
|
|
8935
10059
|
});
|
|
10060
|
+
const queueKey = buildSessionDispatchQueueKey(route);
|
|
10061
|
+
if (sessionDispatchQueue.has(queueKey)) {
|
|
10062
|
+
logger.info(`session busy; queueing inbound dispatch sessionKey=${route.sessionKey}`);
|
|
10063
|
+
}
|
|
10064
|
+
await runSerializedSessionDispatch(
|
|
10065
|
+
queueKey,
|
|
10066
|
+
async () => dispatchToAgent({
|
|
10067
|
+
inbound: { ...inbound, content },
|
|
10068
|
+
cfg: params.cfg,
|
|
10069
|
+
qqCfg,
|
|
10070
|
+
accountId,
|
|
10071
|
+
logger,
|
|
10072
|
+
route
|
|
10073
|
+
})
|
|
10074
|
+
);
|
|
8936
10075
|
}
|
|
8937
10076
|
|
|
8938
10077
|
// src/monitor.ts
|
|
@@ -9232,7 +10371,8 @@ function resolveQQBotAccount(params) {
|
|
|
9232
10371
|
enabled,
|
|
9233
10372
|
configured,
|
|
9234
10373
|
appId: credentials?.appId,
|
|
9235
|
-
markdownSupport: merged.markdownSupport ?? true
|
|
10374
|
+
markdownSupport: merged.markdownSupport ?? true,
|
|
10375
|
+
c2cMarkdownDeliveryMode: merged.c2cMarkdownDeliveryMode ?? "proactive-table-only"
|
|
9236
10376
|
};
|
|
9237
10377
|
}
|
|
9238
10378
|
var qqbotPlugin = {
|
|
@@ -9248,7 +10388,8 @@ var qqbotPlugin = {
|
|
|
9248
10388
|
edit: false,
|
|
9249
10389
|
reply: true,
|
|
9250
10390
|
polls: false,
|
|
9251
|
-
blockStreaming: false
|
|
10391
|
+
blockStreaming: false,
|
|
10392
|
+
activeSend: true
|
|
9252
10393
|
},
|
|
9253
10394
|
messaging: {
|
|
9254
10395
|
normalizeTarget: (raw) => {
|
|
@@ -9320,6 +10461,10 @@ var qqbotPlugin = {
|
|
|
9320
10461
|
}
|
|
9321
10462
|
},
|
|
9322
10463
|
markdownSupport: { type: "boolean" },
|
|
10464
|
+
c2cMarkdownDeliveryMode: {
|
|
10465
|
+
type: "string",
|
|
10466
|
+
enum: ["passive", "proactive-table-only", "proactive-all"]
|
|
10467
|
+
},
|
|
9323
10468
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
9324
10469
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
9325
10470
|
requireMention: { type: "boolean" },
|
|
@@ -9331,6 +10476,7 @@ var qqbotPlugin = {
|
|
|
9331
10476
|
longTaskNoticeDelayMs: { type: "integer", minimum: 0 },
|
|
9332
10477
|
maxFileSizeMB: { type: "number" },
|
|
9333
10478
|
mediaTimeoutMs: { type: "number" },
|
|
10479
|
+
autoSendLocalPathMedia: { type: "boolean" },
|
|
9334
10480
|
inboundMedia: {
|
|
9335
10481
|
type: "object",
|
|
9336
10482
|
additionalProperties: false,
|
|
@@ -9360,6 +10506,10 @@ var qqbotPlugin = {
|
|
|
9360
10506
|
}
|
|
9361
10507
|
},
|
|
9362
10508
|
markdownSupport: { type: "boolean" },
|
|
10509
|
+
c2cMarkdownDeliveryMode: {
|
|
10510
|
+
type: "string",
|
|
10511
|
+
enum: ["passive", "proactive-table-only", "proactive-all"]
|
|
10512
|
+
},
|
|
9363
10513
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
9364
10514
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
9365
10515
|
requireMention: { type: "boolean" },
|
|
@@ -9371,6 +10521,7 @@ var qqbotPlugin = {
|
|
|
9371
10521
|
longTaskNoticeDelayMs: { type: "integer", minimum: 0 },
|
|
9372
10522
|
maxFileSizeMB: { type: "number" },
|
|
9373
10523
|
mediaTimeoutMs: { type: "number" },
|
|
10524
|
+
autoSendLocalPathMedia: { type: "boolean" },
|
|
9374
10525
|
inboundMedia: {
|
|
9375
10526
|
type: "object",
|
|
9376
10527
|
additionalProperties: false,
|
|
@@ -9386,6 +10537,7 @@ var qqbotPlugin = {
|
|
|
9386
10537
|
}
|
|
9387
10538
|
},
|
|
9388
10539
|
reload: { configPrefixes: ["channels.qqbot"] },
|
|
10540
|
+
onboarding: qqbotOnboardingAdapter,
|
|
9389
10541
|
config: {
|
|
9390
10542
|
listAccountIds: (cfg) => listQQBotAccountIds(cfg),
|
|
9391
10543
|
resolveAccount: (cfg, accountId) => resolveQQBotAccount({ cfg, accountId }),
|
|
@@ -9556,6 +10708,10 @@ var plugin = {
|
|
|
9556
10708
|
}
|
|
9557
10709
|
},
|
|
9558
10710
|
markdownSupport: { type: "boolean" },
|
|
10711
|
+
c2cMarkdownDeliveryMode: {
|
|
10712
|
+
type: "string",
|
|
10713
|
+
enum: ["passive", "proactive-table-only", "proactive-all"]
|
|
10714
|
+
},
|
|
9559
10715
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
9560
10716
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
9561
10717
|
requireMention: { type: "boolean" },
|
|
@@ -9567,6 +10723,7 @@ var plugin = {
|
|
|
9567
10723
|
longTaskNoticeDelayMs: { type: "integer", minimum: 0 },
|
|
9568
10724
|
maxFileSizeMB: { type: "number" },
|
|
9569
10725
|
mediaTimeoutMs: { type: "number" },
|
|
10726
|
+
autoSendLocalPathMedia: { type: "boolean" },
|
|
9570
10727
|
inboundMedia: {
|
|
9571
10728
|
type: "object",
|
|
9572
10729
|
additionalProperties: false,
|
|
@@ -9596,6 +10753,10 @@ var plugin = {
|
|
|
9596
10753
|
}
|
|
9597
10754
|
},
|
|
9598
10755
|
markdownSupport: { type: "boolean" },
|
|
10756
|
+
c2cMarkdownDeliveryMode: {
|
|
10757
|
+
type: "string",
|
|
10758
|
+
enum: ["passive", "proactive-table-only", "proactive-all"]
|
|
10759
|
+
},
|
|
9599
10760
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
9600
10761
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
9601
10762
|
requireMention: { type: "boolean" },
|
|
@@ -9607,6 +10768,7 @@ var plugin = {
|
|
|
9607
10768
|
longTaskNoticeDelayMs: { type: "integer", minimum: 0 },
|
|
9608
10769
|
maxFileSizeMB: { type: "number" },
|
|
9609
10770
|
mediaTimeoutMs: { type: "number" },
|
|
10771
|
+
autoSendLocalPathMedia: { type: "boolean" },
|
|
9610
10772
|
inboundMedia: {
|
|
9611
10773
|
type: "object",
|
|
9612
10774
|
additionalProperties: false,
|
|
@@ -9631,6 +10793,6 @@ var plugin = {
|
|
|
9631
10793
|
};
|
|
9632
10794
|
var index_default = plugin;
|
|
9633
10795
|
|
|
9634
|
-
export { DEFAULT_ACCOUNT_ID, index_default as default, getQQBotRuntime, qqbotPlugin, setQQBotRuntime };
|
|
10796
|
+
export { DEFAULT_ACCOUNT_ID, clearKnownQQBotTargets, index_default as default, getKnownQQBotTarget, getQQBotRuntime, listKnownQQBotTargets, qqbotPlugin, removeKnownQQBotTarget, sendProactiveQQBotMessage, setQQBotRuntime };
|
|
9635
10797
|
//# sourceMappingURL=index.js.map
|
|
9636
10798
|
//# sourceMappingURL=index.js.map
|