@openclaw-china/qqbot 0.1.13 → 2026.3.3
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 +423 -26
- package/dist/index.js +468 -211
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -11,6 +11,8 @@ import N2, { stdout, stdin } from 'process';
|
|
|
11
11
|
import ot from 'readline';
|
|
12
12
|
import 'tty';
|
|
13
13
|
import 'util';
|
|
14
|
+
import { createRequire } from 'module';
|
|
15
|
+
import { execFileSync } from 'child_process';
|
|
14
16
|
import WebSocket from 'ws';
|
|
15
17
|
|
|
16
18
|
var __create = Object.create;
|
|
@@ -648,8 +650,8 @@ function getErrorMap() {
|
|
|
648
650
|
|
|
649
651
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
|
|
650
652
|
var makeIssue = (params) => {
|
|
651
|
-
const { data, path:
|
|
652
|
-
const fullPath = [...
|
|
653
|
+
const { data, path: path4, errorMaps, issueData } = params;
|
|
654
|
+
const fullPath = [...path4, ...issueData.path || []];
|
|
653
655
|
const fullIssue = {
|
|
654
656
|
...issueData,
|
|
655
657
|
path: fullPath
|
|
@@ -765,11 +767,11 @@ var errorUtil;
|
|
|
765
767
|
|
|
766
768
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
|
|
767
769
|
var ParseInputLazyPath = class {
|
|
768
|
-
constructor(parent, value,
|
|
770
|
+
constructor(parent, value, path4, key) {
|
|
769
771
|
this._cachedPath = [];
|
|
770
772
|
this.parent = parent;
|
|
771
773
|
this.data = value;
|
|
772
|
-
this._path =
|
|
774
|
+
this._path = path4;
|
|
773
775
|
this._key = key;
|
|
774
776
|
}
|
|
775
777
|
get path() {
|
|
@@ -4219,8 +4221,9 @@ var optionalCoercedString = external_exports.preprocess(
|
|
|
4219
4221
|
},
|
|
4220
4222
|
external_exports.string().min(1).optional()
|
|
4221
4223
|
);
|
|
4222
|
-
var
|
|
4223
|
-
|
|
4224
|
+
var QQBotAccountSchema = external_exports.object({
|
|
4225
|
+
name: external_exports.string().optional(),
|
|
4226
|
+
enabled: external_exports.boolean().optional(),
|
|
4224
4227
|
appId: optionalCoercedString,
|
|
4225
4228
|
clientSecret: optionalCoercedString,
|
|
4226
4229
|
asr: external_exports.object({
|
|
@@ -4241,8 +4244,38 @@ var QQBotConfigSchema = external_exports.object({
|
|
|
4241
4244
|
maxFileSizeMB: external_exports.number().positive().optional().default(100),
|
|
4242
4245
|
mediaTimeoutMs: external_exports.number().int().positive().optional().default(3e4)
|
|
4243
4246
|
});
|
|
4244
|
-
|
|
4245
|
-
|
|
4247
|
+
QQBotAccountSchema.extend({
|
|
4248
|
+
defaultAccount: external_exports.string().optional(),
|
|
4249
|
+
accounts: external_exports.record(QQBotAccountSchema).optional()
|
|
4250
|
+
});
|
|
4251
|
+
var DEFAULT_ACCOUNT_ID = "default";
|
|
4252
|
+
function listConfiguredAccountIds(cfg) {
|
|
4253
|
+
const accounts = cfg.channels?.qqbot?.accounts;
|
|
4254
|
+
if (!accounts || typeof accounts !== "object") return [];
|
|
4255
|
+
return Object.keys(accounts).filter(Boolean);
|
|
4256
|
+
}
|
|
4257
|
+
function listQQBotAccountIds(cfg) {
|
|
4258
|
+
const ids = listConfiguredAccountIds(cfg);
|
|
4259
|
+
if (ids.length === 0) return [DEFAULT_ACCOUNT_ID];
|
|
4260
|
+
return ids.sort((a, b) => a.localeCompare(b));
|
|
4261
|
+
}
|
|
4262
|
+
function resolveDefaultQQBotAccountId(cfg) {
|
|
4263
|
+
const qqbotConfig = cfg.channels?.qqbot;
|
|
4264
|
+
if (qqbotConfig?.defaultAccount?.trim()) return qqbotConfig.defaultAccount.trim();
|
|
4265
|
+
const ids = listQQBotAccountIds(cfg);
|
|
4266
|
+
if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID;
|
|
4267
|
+
return ids[0] ?? DEFAULT_ACCOUNT_ID;
|
|
4268
|
+
}
|
|
4269
|
+
function resolveAccountConfig(cfg, accountId) {
|
|
4270
|
+
const accounts = cfg.channels?.qqbot?.accounts;
|
|
4271
|
+
if (!accounts || typeof accounts !== "object") return void 0;
|
|
4272
|
+
return accounts[accountId];
|
|
4273
|
+
}
|
|
4274
|
+
function mergeQQBotAccountConfig(cfg, accountId) {
|
|
4275
|
+
const base = cfg.channels?.qqbot ?? {};
|
|
4276
|
+
const { accounts: _ignored, defaultAccount: _ignored2, ...baseConfig } = base;
|
|
4277
|
+
const account = resolveAccountConfig(cfg, accountId) ?? {};
|
|
4278
|
+
return { ...baseConfig, ...account };
|
|
4246
4279
|
}
|
|
4247
4280
|
function resolveQQBotCredentials(config) {
|
|
4248
4281
|
if (!config?.appId || !config?.clientSecret) return void 0;
|
|
@@ -6920,9 +6953,9 @@ function showChinaInstallHint(api) {
|
|
|
6920
6953
|
// src/client.ts
|
|
6921
6954
|
var API_BASE = "https://api.sgroup.qq.com";
|
|
6922
6955
|
var TOKEN_URL = "https://bots.qq.com/app/getAppAccessToken";
|
|
6923
|
-
var
|
|
6924
|
-
var
|
|
6925
|
-
var
|
|
6956
|
+
var MSG_SEQ_BASE = 1e6;
|
|
6957
|
+
var tokenCacheMap = /* @__PURE__ */ new Map();
|
|
6958
|
+
var tokenPromiseMap = /* @__PURE__ */ new Map();
|
|
6926
6959
|
var msgSeqMap = /* @__PURE__ */ new Map();
|
|
6927
6960
|
function nextMsgSeq(messageId) {
|
|
6928
6961
|
if (!messageId) return MSG_SEQ_BASE + 1;
|
|
@@ -6937,17 +6970,25 @@ function nextMsgSeq(messageId) {
|
|
|
6937
6970
|
}
|
|
6938
6971
|
return MSG_SEQ_BASE + next;
|
|
6939
6972
|
}
|
|
6940
|
-
function clearTokenCache() {
|
|
6941
|
-
|
|
6973
|
+
function clearTokenCache(appId) {
|
|
6974
|
+
if (appId) {
|
|
6975
|
+
tokenCacheMap.delete(appId);
|
|
6976
|
+
tokenPromiseMap.delete(appId);
|
|
6977
|
+
} else {
|
|
6978
|
+
tokenCacheMap.clear();
|
|
6979
|
+
tokenPromiseMap.clear();
|
|
6980
|
+
}
|
|
6942
6981
|
}
|
|
6943
6982
|
async function getAccessToken(appId, clientSecret, options) {
|
|
6944
|
-
|
|
6945
|
-
|
|
6983
|
+
const cached = tokenCacheMap.get(appId);
|
|
6984
|
+
if (cached && Date.now() < cached.expiresAt - 5 * 60 * 1e3) {
|
|
6985
|
+
return cached.token;
|
|
6946
6986
|
}
|
|
6947
|
-
|
|
6948
|
-
|
|
6987
|
+
const existingPromise = tokenPromiseMap.get(appId);
|
|
6988
|
+
if (existingPromise) {
|
|
6989
|
+
return existingPromise;
|
|
6949
6990
|
}
|
|
6950
|
-
|
|
6991
|
+
const promise = (async () => {
|
|
6951
6992
|
try {
|
|
6952
6993
|
const data = await httpPost(
|
|
6953
6994
|
TOKEN_URL,
|
|
@@ -6957,19 +6998,20 @@ async function getAccessToken(appId, clientSecret, options) {
|
|
|
6957
6998
|
if (!data.access_token) {
|
|
6958
6999
|
throw new Error("access_token missing from QQ response");
|
|
6959
7000
|
}
|
|
6960
|
-
|
|
7001
|
+
tokenCacheMap.set(appId, {
|
|
6961
7002
|
token: data.access_token,
|
|
6962
7003
|
expiresAt: Date.now() + (data.expires_in ?? 7200) * 1e3
|
|
6963
|
-
};
|
|
6964
|
-
return
|
|
7004
|
+
});
|
|
7005
|
+
return data.access_token;
|
|
6965
7006
|
} finally {
|
|
6966
|
-
|
|
7007
|
+
tokenPromiseMap.delete(appId);
|
|
6967
7008
|
}
|
|
6968
7009
|
})();
|
|
6969
|
-
|
|
7010
|
+
tokenPromiseMap.set(appId, promise);
|
|
7011
|
+
return promise;
|
|
6970
7012
|
}
|
|
6971
|
-
async function apiGet(accessToken,
|
|
6972
|
-
const url = `${API_BASE}${
|
|
7013
|
+
async function apiGet(accessToken, path4, options) {
|
|
7014
|
+
const url = `${API_BASE}${path4}`;
|
|
6973
7015
|
return httpGet(url, {
|
|
6974
7016
|
...options,
|
|
6975
7017
|
headers: {
|
|
@@ -6978,8 +7020,8 @@ async function apiGet(accessToken, path3, options) {
|
|
|
6978
7020
|
}
|
|
6979
7021
|
});
|
|
6980
7022
|
}
|
|
6981
|
-
async function apiPost(accessToken,
|
|
6982
|
-
const url = `${API_BASE}${
|
|
7023
|
+
async function apiPost(accessToken, path4, body, options) {
|
|
7024
|
+
const url = `${API_BASE}${path4}`;
|
|
6983
7025
|
return httpPost(url, body, {
|
|
6984
7026
|
...options,
|
|
6985
7027
|
headers: {
|
|
@@ -7116,9 +7158,8 @@ async function sendGroupMediaMessage(params) {
|
|
|
7116
7158
|
{ timeout: 15e3 }
|
|
7117
7159
|
);
|
|
7118
7160
|
}
|
|
7119
|
-
|
|
7120
|
-
// src/send.ts
|
|
7121
7161
|
var QQBOT_UNSUPPORTED_FILE_TYPE_MESSAGE = "QQ official C2C/group media API does not support generic files (file_type=4, e.g. PDF). Images and other supported media types are unaffected.";
|
|
7162
|
+
var require2 = createRequire(import.meta.url);
|
|
7122
7163
|
function resolveQQBotMediaFileType(fileName) {
|
|
7123
7164
|
const mediaType = detectMediaType(fileName);
|
|
7124
7165
|
switch (mediaType) {
|
|
@@ -7153,6 +7194,30 @@ async function uploadQQBotFile(params) {
|
|
|
7153
7194
|
}
|
|
7154
7195
|
return upload.file_info;
|
|
7155
7196
|
}
|
|
7197
|
+
async function convertAudioToSilk(audioPath) {
|
|
7198
|
+
const ffmpegPath = require2("ffmpeg-static");
|
|
7199
|
+
if (!ffmpegPath) {
|
|
7200
|
+
throw new Error("ffmpeg-static not found");
|
|
7201
|
+
}
|
|
7202
|
+
const silkWasm = require2("silk-wasm");
|
|
7203
|
+
const tmpDir = fs3.mkdtempSync(path.join(os.tmpdir(), "qqbot-silk-"));
|
|
7204
|
+
const pcmPath = path.join(tmpDir, "audio.pcm");
|
|
7205
|
+
try {
|
|
7206
|
+
execFileSync(
|
|
7207
|
+
ffmpegPath,
|
|
7208
|
+
["-y", "-i", audioPath, "-f", "s16le", "-ar", "24000", "-ac", "1", pcmPath],
|
|
7209
|
+
{ timeout: 3e4, stdio: "pipe" }
|
|
7210
|
+
);
|
|
7211
|
+
const pcmBuffer = fs3.readFileSync(pcmPath);
|
|
7212
|
+
const result = await silkWasm.encode(pcmBuffer, 24e3);
|
|
7213
|
+
return result.data;
|
|
7214
|
+
} finally {
|
|
7215
|
+
try {
|
|
7216
|
+
fs3.rmSync(tmpDir, { recursive: true, force: true });
|
|
7217
|
+
} catch {
|
|
7218
|
+
}
|
|
7219
|
+
}
|
|
7220
|
+
}
|
|
7156
7221
|
async function sendFileQQBot(params) {
|
|
7157
7222
|
const { cfg, target, mediaUrl, messageId } = params;
|
|
7158
7223
|
if (!cfg.appId || !cfg.clientSecret) {
|
|
@@ -7178,10 +7243,25 @@ async function sendFileQQBot(params) {
|
|
|
7178
7243
|
url: src
|
|
7179
7244
|
});
|
|
7180
7245
|
} else {
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7246
|
+
let buffer;
|
|
7247
|
+
if (fileType === 3 /* VOICE */) {
|
|
7248
|
+
try {
|
|
7249
|
+
const silkData = await convertAudioToSilk(src);
|
|
7250
|
+
buffer = Buffer.from(silkData);
|
|
7251
|
+
} catch {
|
|
7252
|
+
const local = await readMediaWithConfig(src, {
|
|
7253
|
+
timeout: mediaTimeoutMs,
|
|
7254
|
+
maxSize: maxSizeBytes
|
|
7255
|
+
});
|
|
7256
|
+
buffer = local.buffer;
|
|
7257
|
+
}
|
|
7258
|
+
} else {
|
|
7259
|
+
const local = await readMediaWithConfig(src, {
|
|
7260
|
+
timeout: mediaTimeoutMs,
|
|
7261
|
+
maxSize: maxSizeBytes
|
|
7262
|
+
});
|
|
7263
|
+
buffer = local.buffer;
|
|
7264
|
+
}
|
|
7185
7265
|
fileInfo = await uploadQQBotFile({
|
|
7186
7266
|
accessToken,
|
|
7187
7267
|
target,
|
|
@@ -7280,13 +7360,8 @@ var qqbotOutbound = {
|
|
|
7280
7360
|
textChunkLimit: 1500,
|
|
7281
7361
|
chunkerMode: "markdown",
|
|
7282
7362
|
sendText: async (params) => {
|
|
7283
|
-
const { cfg, to, text, replyToId } = params;
|
|
7284
|
-
const
|
|
7285
|
-
const parsed = rawCfg ? QQBotConfigSchema.safeParse(rawCfg) : null;
|
|
7286
|
-
const qqCfg = parsed?.success ? parsed.data : rawCfg;
|
|
7287
|
-
if (!qqCfg) {
|
|
7288
|
-
return { channel: "qqbot", error: "QQBot channel not configured" };
|
|
7289
|
-
}
|
|
7363
|
+
const { cfg, to, text, replyToId, accountId } = params;
|
|
7364
|
+
const qqCfg = mergeQQBotAccountConfig(cfg, accountId ?? DEFAULT_ACCOUNT_ID);
|
|
7290
7365
|
if (!qqCfg.appId || !qqCfg.clientSecret) {
|
|
7291
7366
|
return { channel: "qqbot", error: "QQBot not configured (missing appId/clientSecret)" };
|
|
7292
7367
|
}
|
|
@@ -7327,20 +7402,15 @@ var qqbotOutbound = {
|
|
|
7327
7402
|
}
|
|
7328
7403
|
},
|
|
7329
7404
|
sendMedia: async (params) => {
|
|
7330
|
-
const { cfg, to, mediaUrl, text, replyToId } = params;
|
|
7405
|
+
const { cfg, to, mediaUrl, text, replyToId, accountId } = params;
|
|
7331
7406
|
if (!mediaUrl) {
|
|
7332
7407
|
const fallbackText = text?.trim() ?? "";
|
|
7333
7408
|
if (!fallbackText) {
|
|
7334
7409
|
return { channel: "qqbot", error: "mediaUrl is required for sendMedia" };
|
|
7335
7410
|
}
|
|
7336
|
-
return qqbotOutbound.sendText({ cfg, to, text: fallbackText, replyToId });
|
|
7337
|
-
}
|
|
7338
|
-
const rawCfg = cfg.channels?.qqbot;
|
|
7339
|
-
const parsed = rawCfg ? QQBotConfigSchema.safeParse(rawCfg) : null;
|
|
7340
|
-
const qqCfg = parsed?.success ? parsed.data : rawCfg;
|
|
7341
|
-
if (!qqCfg) {
|
|
7342
|
-
return { channel: "qqbot", error: "QQBot channel not configured" };
|
|
7411
|
+
return qqbotOutbound.sendText({ cfg, to, text: fallbackText, replyToId, accountId });
|
|
7343
7412
|
}
|
|
7413
|
+
const qqCfg = mergeQQBotAccountConfig(cfg, accountId ?? DEFAULT_ACCOUNT_ID);
|
|
7344
7414
|
if (!qqCfg.appId || !qqCfg.clientSecret) {
|
|
7345
7415
|
return { channel: "qqbot", error: "QQBot not configured (missing appId/clientSecret)" };
|
|
7346
7416
|
}
|
|
@@ -7364,13 +7434,8 @@ ${mediaUrl}` : mediaUrl;
|
|
|
7364
7434
|
}
|
|
7365
7435
|
},
|
|
7366
7436
|
sendTyping: async (params) => {
|
|
7367
|
-
const { cfg, to, replyToId, inputSecond } = params;
|
|
7368
|
-
const
|
|
7369
|
-
const parsed = rawCfg ? QQBotConfigSchema.safeParse(rawCfg) : null;
|
|
7370
|
-
const qqCfg = parsed?.success ? parsed.data : rawCfg;
|
|
7371
|
-
if (!qqCfg) {
|
|
7372
|
-
return { channel: "qqbot", error: "QQBot channel not configured" };
|
|
7373
|
-
}
|
|
7437
|
+
const { cfg, to, replyToId, inputSecond, accountId } = params;
|
|
7438
|
+
const qqCfg = mergeQQBotAccountConfig(cfg, accountId ?? DEFAULT_ACCOUNT_ID);
|
|
7374
7439
|
if (!qqCfg.appId || !qqCfg.clientSecret) {
|
|
7375
7440
|
return { channel: "qqbot", error: "QQBot not configured (missing appId/clientSecret)" };
|
|
7376
7441
|
}
|
|
@@ -7830,6 +7895,78 @@ function buildMediaFallbackText(mediaUrl, errorMessage) {
|
|
|
7830
7895
|
}
|
|
7831
7896
|
return `\u{1F4CE} ${mediaUrl}`;
|
|
7832
7897
|
}
|
|
7898
|
+
var THINK_BLOCK_RE = /<think\b[^>]*>[\s\S]*?<\/think>/gi;
|
|
7899
|
+
var FINAL_BLOCK_RE = /<final\b[^>]*>([\s\S]*?)<\/final>/gi;
|
|
7900
|
+
var RAW_THINK_OR_FINAL_TAG_RE = /<\/?(?:think|final)\b[^>]*>/gi;
|
|
7901
|
+
var DIRECTIVE_TAG_RE = /\[\[\s*(?:reply_to_current|reply_to\s*:[^\]]+|audio_as_voice|tts(?::text)?|\/tts(?::text)?)\s*\]\]/gi;
|
|
7902
|
+
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;
|
|
7903
|
+
var TTS_LIKE_RAW_TEXT_RE = /\[\[\s*(?:tts(?::text)?|\/tts(?::text)?|audio_as_voice|reply_to_current|reply_to\s*:)/i;
|
|
7904
|
+
function extractFinalBlocks(text) {
|
|
7905
|
+
const matches = Array.from(text.matchAll(FINAL_BLOCK_RE));
|
|
7906
|
+
if (matches.length === 0) return void 0;
|
|
7907
|
+
return matches.map((match) => (match[1] ?? "").trim()).filter(Boolean).join("\n");
|
|
7908
|
+
}
|
|
7909
|
+
function sanitizeQQBotOutboundText(rawText) {
|
|
7910
|
+
if (!rawText) return "";
|
|
7911
|
+
let next = rawText.replace(/\r\n/g, "\n");
|
|
7912
|
+
const finalOnly = extractFinalBlocks(next);
|
|
7913
|
+
if (typeof finalOnly === "string") {
|
|
7914
|
+
next = finalOnly;
|
|
7915
|
+
}
|
|
7916
|
+
next = next.replace(THINK_BLOCK_RE, "");
|
|
7917
|
+
next = next.replace(RAW_THINK_OR_FINAL_TAG_RE, "");
|
|
7918
|
+
next = next.replace(DIRECTIVE_TAG_RE, " ");
|
|
7919
|
+
next = next.replace(VOICE_EMOTION_TAG_RE, " ");
|
|
7920
|
+
next = next.replace(/[ \t]+\n/g, "\n");
|
|
7921
|
+
next = next.replace(/\n{3,}/g, "\n\n");
|
|
7922
|
+
next = next.trim();
|
|
7923
|
+
if (!next) return "";
|
|
7924
|
+
if (/^NO_REPLY$/i.test(next)) return "";
|
|
7925
|
+
return next;
|
|
7926
|
+
}
|
|
7927
|
+
function shouldSuppressQQBotTextWhenMediaPresent(rawText, sanitizedText) {
|
|
7928
|
+
const raw = rawText.trim();
|
|
7929
|
+
if (!raw) return false;
|
|
7930
|
+
if (TTS_LIKE_RAW_TEXT_RE.test(raw)) return true;
|
|
7931
|
+
if (/<(?:think|final)\b/i.test(raw)) return true;
|
|
7932
|
+
if (!sanitizedText) return true;
|
|
7933
|
+
return !/[A-Za-z0-9\u4e00-\u9fff]/.test(sanitizedText);
|
|
7934
|
+
}
|
|
7935
|
+
function evaluateReplyFinalOnlyDelivery(params) {
|
|
7936
|
+
const { replyFinalOnly, kind, hasMedia } = params;
|
|
7937
|
+
if (!replyFinalOnly || !kind || kind === "final") {
|
|
7938
|
+
return { skipDelivery: false, suppressText: false };
|
|
7939
|
+
}
|
|
7940
|
+
if (hasMedia) {
|
|
7941
|
+
return { skipDelivery: false, suppressText: true };
|
|
7942
|
+
}
|
|
7943
|
+
return { skipDelivery: true, suppressText: false };
|
|
7944
|
+
}
|
|
7945
|
+
async function sendQQBotMediaWithFallback(params) {
|
|
7946
|
+
const { qqCfg, to, mediaQueue, replyToId, logger } = params;
|
|
7947
|
+
const outbound = params.outbound ?? qqbotOutbound;
|
|
7948
|
+
for (const mediaUrl of mediaQueue) {
|
|
7949
|
+
const result = await outbound.sendMedia({
|
|
7950
|
+
cfg: { channels: { qqbot: qqCfg } },
|
|
7951
|
+
to,
|
|
7952
|
+
mediaUrl,
|
|
7953
|
+
replyToId
|
|
7954
|
+
});
|
|
7955
|
+
if (result.error) {
|
|
7956
|
+
logger.error(`sendMedia failed: ${result.error}`);
|
|
7957
|
+
const fallback = buildMediaFallbackText(mediaUrl, result.error);
|
|
7958
|
+
const fallbackResult = await outbound.sendText({
|
|
7959
|
+
cfg: { channels: { qqbot: qqCfg } },
|
|
7960
|
+
to,
|
|
7961
|
+
text: fallback,
|
|
7962
|
+
replyToId
|
|
7963
|
+
});
|
|
7964
|
+
if (fallbackResult.error) {
|
|
7965
|
+
logger.error(`sendText fallback failed: ${fallbackResult.error}`);
|
|
7966
|
+
}
|
|
7967
|
+
}
|
|
7968
|
+
}
|
|
7969
|
+
}
|
|
7833
7970
|
function buildInboundContext(params) {
|
|
7834
7971
|
const { event, sessionKey, accountId } = params;
|
|
7835
7972
|
const body = params.body ?? event.content;
|
|
@@ -8015,18 +8152,16 @@ async function dispatchToAgent(params) {
|
|
|
8015
8152
|
};
|
|
8016
8153
|
const replyFinalOnly = qqCfg.replyFinalOnly ?? false;
|
|
8017
8154
|
const deliver = async (payload, info) => {
|
|
8018
|
-
if (replyFinalOnly && info?.kind && info.kind !== "final") return;
|
|
8019
8155
|
const typed = payload;
|
|
8020
|
-
const rawText = typed?.text ?? "";
|
|
8021
8156
|
const mediaLineResult = extractMediaLinesFromText({
|
|
8022
|
-
text:
|
|
8157
|
+
text: typed?.text ?? "",
|
|
8023
8158
|
logger
|
|
8024
8159
|
});
|
|
8025
8160
|
const localMediaResult = extractLocalMediaFromText({
|
|
8026
8161
|
text: mediaLineResult.text,
|
|
8027
8162
|
logger
|
|
8028
8163
|
});
|
|
8029
|
-
const
|
|
8164
|
+
const cleanedText = sanitizeQQBotOutboundText(localMediaResult.text);
|
|
8030
8165
|
const payloadMediaUrls = Array.isArray(typed?.mediaUrls) ? typed?.mediaUrls : typed?.mediaUrl ? [typed.mediaUrl] : [];
|
|
8031
8166
|
const mediaQueue = [];
|
|
8032
8167
|
const seenMedia = /* @__PURE__ */ new Set();
|
|
@@ -8040,8 +8175,16 @@ async function dispatchToAgent(params) {
|
|
|
8040
8175
|
for (const url of payloadMediaUrls) addMedia(url);
|
|
8041
8176
|
for (const url of mediaLineResult.mediaUrls) addMedia(url);
|
|
8042
8177
|
for (const url of localMediaResult.mediaUrls) addMedia(url);
|
|
8043
|
-
|
|
8044
|
-
|
|
8178
|
+
const deliveryDecision = evaluateReplyFinalOnlyDelivery({
|
|
8179
|
+
replyFinalOnly,
|
|
8180
|
+
kind: info?.kind,
|
|
8181
|
+
hasMedia: mediaQueue.length > 0});
|
|
8182
|
+
if (deliveryDecision.skipDelivery) return;
|
|
8183
|
+
const suppressEchoText = mediaQueue.length > 0 && shouldSuppressQQBotTextWhenMediaPresent(localMediaResult.text, cleanedText);
|
|
8184
|
+
const suppressText = deliveryDecision.suppressText || suppressEchoText;
|
|
8185
|
+
const textToSend = suppressText ? "" : cleanedText;
|
|
8186
|
+
if (textToSend) {
|
|
8187
|
+
const converted = textApi?.convertMarkdownTables ? textApi.convertMarkdownTables(textToSend, resolvedTableMode) : textToSend;
|
|
8045
8188
|
const chunks = chunkText(converted);
|
|
8046
8189
|
for (const chunk of chunks) {
|
|
8047
8190
|
const result = await qqbotOutbound.sendText({
|
|
@@ -8055,27 +8198,13 @@ async function dispatchToAgent(params) {
|
|
|
8055
8198
|
}
|
|
8056
8199
|
}
|
|
8057
8200
|
}
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
if (result.error) {
|
|
8066
|
-
logger.error(`sendMedia failed: ${result.error}`);
|
|
8067
|
-
const fallback = buildMediaFallbackText(mediaUrl, result.error);
|
|
8068
|
-
const fallbackResult = await qqbotOutbound.sendText({
|
|
8069
|
-
cfg: { channels: { qqbot: qqCfg } },
|
|
8070
|
-
to: target.to,
|
|
8071
|
-
text: fallback,
|
|
8072
|
-
replyToId: inbound.messageId
|
|
8073
|
-
});
|
|
8074
|
-
if (fallbackResult.error) {
|
|
8075
|
-
logger.error(`sendText fallback failed: ${fallbackResult.error}`);
|
|
8076
|
-
}
|
|
8077
|
-
}
|
|
8078
|
-
}
|
|
8201
|
+
await sendQQBotMediaWithFallback({
|
|
8202
|
+
qqCfg,
|
|
8203
|
+
to: target.to,
|
|
8204
|
+
mediaQueue,
|
|
8205
|
+
replyToId: inbound.messageId,
|
|
8206
|
+
logger
|
|
8207
|
+
});
|
|
8079
8208
|
};
|
|
8080
8209
|
const humanDelay = replyApi.resolveHumanDelayConfig?.(cfg, route.agentId);
|
|
8081
8210
|
const dispatchBuffered = replyApi.dispatchReplyWithBufferedBlockDispatcher;
|
|
@@ -8162,14 +8291,13 @@ async function handleQQBotDispatch(params) {
|
|
|
8162
8291
|
if (!inbound) {
|
|
8163
8292
|
return;
|
|
8164
8293
|
}
|
|
8165
|
-
const
|
|
8166
|
-
const
|
|
8167
|
-
const qqCfg = parsedCfg?.success ? parsedCfg.data : rawCfg;
|
|
8294
|
+
const accountId = params.accountId ?? DEFAULT_ACCOUNT_ID;
|
|
8295
|
+
const qqCfg = params.cfg ? mergeQQBotAccountConfig(params.cfg, accountId) : void 0;
|
|
8168
8296
|
if (!qqCfg) {
|
|
8169
8297
|
logger.warn("qqbot config missing, ignoring inbound message");
|
|
8170
8298
|
return;
|
|
8171
8299
|
}
|
|
8172
|
-
if (
|
|
8300
|
+
if (qqCfg.enabled === false) {
|
|
8173
8301
|
logger.info("qqbot disabled, ignoring inbound message");
|
|
8174
8302
|
return;
|
|
8175
8303
|
}
|
|
@@ -8180,7 +8308,7 @@ async function handleQQBotDispatch(params) {
|
|
|
8180
8308
|
attachments: inbound.attachments
|
|
8181
8309
|
})
|
|
8182
8310
|
);
|
|
8183
|
-
logger.info(`[inbound-user] senderId=${inbound.senderId} content=${inboundLogContent}`);
|
|
8311
|
+
logger.info(`[inbound-user] accountId=${accountId} senderId=${inbound.senderId} content=${inboundLogContent}`);
|
|
8184
8312
|
if (!shouldHandleMessage(inbound, qqCfg, logger)) {
|
|
8185
8313
|
return;
|
|
8186
8314
|
}
|
|
@@ -8195,7 +8323,7 @@ async function handleQQBotDispatch(params) {
|
|
|
8195
8323
|
inbound: { ...inbound, content },
|
|
8196
8324
|
cfg: params.cfg,
|
|
8197
8325
|
qqCfg,
|
|
8198
|
-
accountId
|
|
8326
|
+
accountId,
|
|
8199
8327
|
logger
|
|
8200
8328
|
});
|
|
8201
8329
|
}
|
|
@@ -8208,72 +8336,80 @@ var INTENTS = {
|
|
|
8208
8336
|
};
|
|
8209
8337
|
var DEFAULT_INTENTS = INTENTS.GUILD_MESSAGES | INTENTS.DIRECT_MESSAGE | INTENTS.GROUP_AND_C2C;
|
|
8210
8338
|
var RECONNECT_DELAYS_MS = [1e3, 2e3, 5e3, 1e4, 2e4, 3e4];
|
|
8211
|
-
var
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8339
|
+
var activeConnections = /* @__PURE__ */ new Map();
|
|
8340
|
+
function getOrCreateConnection(accountId) {
|
|
8341
|
+
let conn = activeConnections.get(accountId);
|
|
8342
|
+
if (!conn) {
|
|
8343
|
+
conn = {
|
|
8344
|
+
socket: null,
|
|
8345
|
+
promise: null,
|
|
8346
|
+
stop: null,
|
|
8347
|
+
sessionId: null,
|
|
8348
|
+
lastSeq: null,
|
|
8349
|
+
heartbeatTimer: null,
|
|
8350
|
+
reconnectTimer: null,
|
|
8351
|
+
reconnectAttempt: 0,
|
|
8352
|
+
connecting: false
|
|
8353
|
+
};
|
|
8354
|
+
activeConnections.set(accountId, conn);
|
|
8355
|
+
}
|
|
8356
|
+
return conn;
|
|
8357
|
+
}
|
|
8358
|
+
function clearTimers(conn) {
|
|
8359
|
+
if (conn.heartbeatTimer) {
|
|
8360
|
+
clearInterval(conn.heartbeatTimer);
|
|
8361
|
+
conn.heartbeatTimer = null;
|
|
8362
|
+
}
|
|
8363
|
+
if (conn.reconnectTimer) {
|
|
8364
|
+
clearTimeout(conn.reconnectTimer);
|
|
8365
|
+
conn.reconnectTimer = null;
|
|
8366
|
+
}
|
|
8367
|
+
}
|
|
8368
|
+
function cleanupSocket(conn) {
|
|
8369
|
+
clearTimers(conn);
|
|
8370
|
+
if (conn.socket) {
|
|
8371
|
+
try {
|
|
8372
|
+
if (conn.socket.readyState === WebSocket.OPEN) {
|
|
8373
|
+
conn.socket.close();
|
|
8374
|
+
}
|
|
8375
|
+
} catch {
|
|
8376
|
+
}
|
|
8377
|
+
conn.socket = null;
|
|
8378
|
+
}
|
|
8379
|
+
}
|
|
8215
8380
|
async function monitorQQBotProvider(opts = {}) {
|
|
8216
|
-
const { config, runtime: runtime2, abortSignal, accountId =
|
|
8381
|
+
const { config, runtime: runtime2, abortSignal, accountId = DEFAULT_ACCOUNT_ID } = opts;
|
|
8217
8382
|
const logger = createLogger("qqbot", {
|
|
8218
8383
|
log: runtime2?.log,
|
|
8219
8384
|
error: runtime2?.error
|
|
8220
8385
|
});
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
if (activePromise) {
|
|
8226
|
-
return activePromise;
|
|
8386
|
+
const conn = getOrCreateConnection(accountId);
|
|
8387
|
+
if (conn.socket) {
|
|
8388
|
+
if (conn.promise) {
|
|
8389
|
+
return conn.promise;
|
|
8227
8390
|
}
|
|
8228
|
-
throw new Error(
|
|
8391
|
+
throw new Error(`QQBot monitor state invalid for account ${accountId}: active socket without promise`);
|
|
8229
8392
|
}
|
|
8230
|
-
const
|
|
8231
|
-
const parsed = rawCfg ? QQBotConfigSchema.safeParse(rawCfg) : null;
|
|
8232
|
-
const qqCfg = parsed?.success ? parsed.data : rawCfg;
|
|
8393
|
+
const qqCfg = config ? mergeQQBotAccountConfig(config, accountId) : void 0;
|
|
8233
8394
|
if (!qqCfg) {
|
|
8234
8395
|
throw new Error("QQBot configuration not found");
|
|
8235
8396
|
}
|
|
8236
8397
|
if (!qqCfg.appId || !qqCfg.clientSecret) {
|
|
8237
|
-
throw new Error(
|
|
8398
|
+
throw new Error(`QQBot not configured for account ${accountId} (missing appId or clientSecret)`);
|
|
8238
8399
|
}
|
|
8239
|
-
|
|
8400
|
+
conn.promise = new Promise((resolve3, reject) => {
|
|
8240
8401
|
let stopped = false;
|
|
8241
|
-
let reconnectAttempt = 0;
|
|
8242
|
-
let heartbeatTimer = null;
|
|
8243
|
-
let reconnectTimer = null;
|
|
8244
|
-
let sessionId = null;
|
|
8245
|
-
let lastSeq = null;
|
|
8246
|
-
let connecting = false;
|
|
8247
|
-
const clearTimers = () => {
|
|
8248
|
-
if (heartbeatTimer) {
|
|
8249
|
-
clearInterval(heartbeatTimer);
|
|
8250
|
-
heartbeatTimer = null;
|
|
8251
|
-
}
|
|
8252
|
-
if (reconnectTimer) {
|
|
8253
|
-
clearTimeout(reconnectTimer);
|
|
8254
|
-
reconnectTimer = null;
|
|
8255
|
-
}
|
|
8256
|
-
};
|
|
8257
|
-
const cleanupSocket = () => {
|
|
8258
|
-
clearTimers();
|
|
8259
|
-
if (activeSocket) {
|
|
8260
|
-
try {
|
|
8261
|
-
if (activeSocket.readyState === WebSocket.OPEN) {
|
|
8262
|
-
activeSocket.close();
|
|
8263
|
-
}
|
|
8264
|
-
} catch {
|
|
8265
|
-
}
|
|
8266
|
-
}
|
|
8267
|
-
activeSocket = null;
|
|
8268
|
-
};
|
|
8269
8402
|
const finish = (err) => {
|
|
8270
8403
|
if (stopped) return;
|
|
8271
8404
|
stopped = true;
|
|
8272
8405
|
abortSignal?.removeEventListener("abort", onAbort);
|
|
8273
|
-
cleanupSocket();
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
|
|
8406
|
+
cleanupSocket(conn);
|
|
8407
|
+
conn.sessionId = null;
|
|
8408
|
+
conn.lastSeq = null;
|
|
8409
|
+
conn.promise = null;
|
|
8410
|
+
conn.stop = null;
|
|
8411
|
+
conn.reconnectAttempt = 0;
|
|
8412
|
+
activeConnections.delete(accountId);
|
|
8277
8413
|
{
|
|
8278
8414
|
resolve3();
|
|
8279
8415
|
}
|
|
@@ -8282,33 +8418,33 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
8282
8418
|
logger.info("abort signal received, stopping gateway");
|
|
8283
8419
|
finish();
|
|
8284
8420
|
};
|
|
8285
|
-
|
|
8421
|
+
conn.stop = () => {
|
|
8286
8422
|
logger.info("stop requested");
|
|
8287
8423
|
finish();
|
|
8288
8424
|
};
|
|
8289
8425
|
const scheduleReconnect = (reason) => {
|
|
8290
8426
|
if (stopped) return;
|
|
8291
|
-
if (reconnectTimer) return;
|
|
8292
|
-
const delay = RECONNECT_DELAYS_MS[Math.min(reconnectAttempt, RECONNECT_DELAYS_MS.length - 1)];
|
|
8293
|
-
reconnectAttempt += 1;
|
|
8427
|
+
if (conn.reconnectTimer) return;
|
|
8428
|
+
const delay = RECONNECT_DELAYS_MS[Math.min(conn.reconnectAttempt, RECONNECT_DELAYS_MS.length - 1)];
|
|
8429
|
+
conn.reconnectAttempt += 1;
|
|
8294
8430
|
logger.warn(`[reconnect] ${reason}; retry in ${delay}ms`);
|
|
8295
|
-
reconnectTimer = setTimeout(() => {
|
|
8296
|
-
reconnectTimer = null;
|
|
8431
|
+
conn.reconnectTimer = setTimeout(() => {
|
|
8432
|
+
conn.reconnectTimer = null;
|
|
8297
8433
|
void connect();
|
|
8298
8434
|
}, delay);
|
|
8299
8435
|
};
|
|
8300
8436
|
const startHeartbeat = (intervalMs) => {
|
|
8301
|
-
if (heartbeatTimer) {
|
|
8302
|
-
clearInterval(heartbeatTimer);
|
|
8437
|
+
if (conn.heartbeatTimer) {
|
|
8438
|
+
clearInterval(conn.heartbeatTimer);
|
|
8303
8439
|
}
|
|
8304
|
-
heartbeatTimer = setInterval(() => {
|
|
8305
|
-
if (!
|
|
8306
|
-
const payload = JSON.stringify({ op: 1, d: lastSeq });
|
|
8307
|
-
|
|
8440
|
+
conn.heartbeatTimer = setInterval(() => {
|
|
8441
|
+
if (!conn.socket || conn.socket.readyState !== WebSocket.OPEN) return;
|
|
8442
|
+
const payload = JSON.stringify({ op: 1, d: conn.lastSeq });
|
|
8443
|
+
conn.socket.send(payload);
|
|
8308
8444
|
}, intervalMs);
|
|
8309
8445
|
};
|
|
8310
8446
|
const sendIdentify = (token) => {
|
|
8311
|
-
if (!
|
|
8447
|
+
if (!conn.socket || conn.socket.readyState !== WebSocket.OPEN) return;
|
|
8312
8448
|
const payload = {
|
|
8313
8449
|
op: 2,
|
|
8314
8450
|
d: {
|
|
@@ -8317,10 +8453,10 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
8317
8453
|
shard: [0, 1]
|
|
8318
8454
|
}
|
|
8319
8455
|
};
|
|
8320
|
-
|
|
8456
|
+
conn.socket.send(JSON.stringify(payload));
|
|
8321
8457
|
};
|
|
8322
8458
|
const sendResume = (token, session, seq) => {
|
|
8323
|
-
if (!
|
|
8459
|
+
if (!conn.socket || conn.socket.readyState !== WebSocket.OPEN) return;
|
|
8324
8460
|
const payload = {
|
|
8325
8461
|
op: 6,
|
|
8326
8462
|
d: {
|
|
@@ -8329,11 +8465,11 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
8329
8465
|
seq
|
|
8330
8466
|
}
|
|
8331
8467
|
};
|
|
8332
|
-
|
|
8468
|
+
conn.socket.send(JSON.stringify(payload));
|
|
8333
8469
|
};
|
|
8334
8470
|
const handleGatewayPayload = async (payload) => {
|
|
8335
8471
|
if (typeof payload.s === "number") {
|
|
8336
|
-
lastSeq = payload.s;
|
|
8472
|
+
conn.lastSeq = payload.s;
|
|
8337
8473
|
}
|
|
8338
8474
|
switch (payload.op) {
|
|
8339
8475
|
case 10: {
|
|
@@ -8341,8 +8477,8 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
8341
8477
|
const interval = hello?.heartbeat_interval ?? 3e4;
|
|
8342
8478
|
startHeartbeat(interval);
|
|
8343
8479
|
const token = await getAccessToken(qqCfg.appId, qqCfg.clientSecret);
|
|
8344
|
-
if (sessionId && typeof lastSeq === "number") {
|
|
8345
|
-
sendResume(token, sessionId, lastSeq);
|
|
8480
|
+
if (conn.sessionId && typeof conn.lastSeq === "number") {
|
|
8481
|
+
sendResume(token, conn.sessionId, conn.lastSeq);
|
|
8346
8482
|
} else {
|
|
8347
8483
|
sendIdentify(token);
|
|
8348
8484
|
}
|
|
@@ -8351,14 +8487,14 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
8351
8487
|
case 11:
|
|
8352
8488
|
return;
|
|
8353
8489
|
case 7:
|
|
8354
|
-
cleanupSocket();
|
|
8490
|
+
cleanupSocket(conn);
|
|
8355
8491
|
scheduleReconnect("server requested reconnect");
|
|
8356
8492
|
return;
|
|
8357
8493
|
case 9:
|
|
8358
|
-
sessionId = null;
|
|
8359
|
-
lastSeq = null;
|
|
8360
|
-
clearTokenCache();
|
|
8361
|
-
cleanupSocket();
|
|
8494
|
+
conn.sessionId = null;
|
|
8495
|
+
conn.lastSeq = null;
|
|
8496
|
+
clearTokenCache(qqCfg.appId);
|
|
8497
|
+
cleanupSocket(conn);
|
|
8362
8498
|
scheduleReconnect("invalid session");
|
|
8363
8499
|
return;
|
|
8364
8500
|
case 0: {
|
|
@@ -8366,14 +8502,14 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
8366
8502
|
if (eventType === "READY") {
|
|
8367
8503
|
const ready = payload.d;
|
|
8368
8504
|
if (ready?.session_id) {
|
|
8369
|
-
sessionId = ready.session_id;
|
|
8505
|
+
conn.sessionId = ready.session_id;
|
|
8370
8506
|
}
|
|
8371
|
-
reconnectAttempt = 0;
|
|
8507
|
+
conn.reconnectAttempt = 0;
|
|
8372
8508
|
logger.info("gateway ready");
|
|
8373
8509
|
return;
|
|
8374
8510
|
}
|
|
8375
8511
|
if (eventType === "RESUMED") {
|
|
8376
|
-
reconnectAttempt = 0;
|
|
8512
|
+
conn.reconnectAttempt = 0;
|
|
8377
8513
|
logger.info("gateway resumed");
|
|
8378
8514
|
return;
|
|
8379
8515
|
}
|
|
@@ -8393,16 +8529,15 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
8393
8529
|
}
|
|
8394
8530
|
};
|
|
8395
8531
|
const connect = async () => {
|
|
8396
|
-
if (stopped || connecting) return;
|
|
8397
|
-
connecting = true;
|
|
8532
|
+
if (stopped || conn.connecting) return;
|
|
8533
|
+
conn.connecting = true;
|
|
8398
8534
|
try {
|
|
8399
|
-
cleanupSocket();
|
|
8535
|
+
cleanupSocket(conn);
|
|
8400
8536
|
const token = await getAccessToken(qqCfg.appId, qqCfg.clientSecret);
|
|
8401
8537
|
const gatewayUrl = await getGatewayUrl(token);
|
|
8402
8538
|
logger.info(`connecting gateway: ${gatewayUrl}`);
|
|
8403
8539
|
const ws = new WebSocket(gatewayUrl);
|
|
8404
|
-
|
|
8405
|
-
activeAccountId = accountId;
|
|
8540
|
+
conn.socket = ws;
|
|
8406
8541
|
ws.on("open", () => {
|
|
8407
8542
|
logger.info("gateway socket opened");
|
|
8408
8543
|
});
|
|
@@ -8421,7 +8556,7 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
8421
8556
|
});
|
|
8422
8557
|
ws.on("close", (code, reason) => {
|
|
8423
8558
|
logger.warn(`gateway socket closed (${code}) ${String(reason)}`);
|
|
8424
|
-
cleanupSocket();
|
|
8559
|
+
cleanupSocket(conn);
|
|
8425
8560
|
scheduleReconnect("socket closed");
|
|
8426
8561
|
});
|
|
8427
8562
|
ws.on("error", (err) => {
|
|
@@ -8429,10 +8564,10 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
8429
8564
|
});
|
|
8430
8565
|
} catch (err) {
|
|
8431
8566
|
logger.error(`gateway connect failed: ${String(err)}`);
|
|
8432
|
-
cleanupSocket();
|
|
8567
|
+
cleanupSocket(conn);
|
|
8433
8568
|
scheduleReconnect("connect failed");
|
|
8434
8569
|
} finally {
|
|
8435
|
-
connecting = false;
|
|
8570
|
+
conn.connecting = false;
|
|
8436
8571
|
}
|
|
8437
8572
|
};
|
|
8438
8573
|
if (abortSignal?.aborted) {
|
|
@@ -8442,27 +8577,20 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
8442
8577
|
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
8443
8578
|
void connect();
|
|
8444
8579
|
});
|
|
8445
|
-
return
|
|
8580
|
+
return conn.promise;
|
|
8446
8581
|
}
|
|
8447
|
-
function
|
|
8448
|
-
|
|
8449
|
-
|
|
8582
|
+
function stopQQBotMonitorForAccount(accountId = DEFAULT_ACCOUNT_ID) {
|
|
8583
|
+
const conn = activeConnections.get(accountId);
|
|
8584
|
+
if (!conn) return;
|
|
8585
|
+
if (conn.stop) {
|
|
8586
|
+
conn.stop();
|
|
8450
8587
|
return;
|
|
8451
8588
|
}
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
activeSocket.close();
|
|
8455
|
-
} catch {
|
|
8456
|
-
}
|
|
8457
|
-
activeSocket = null;
|
|
8458
|
-
activeAccountId = null;
|
|
8459
|
-
activePromise = null;
|
|
8460
|
-
activeStop = null;
|
|
8461
|
-
}
|
|
8589
|
+
cleanupSocket(conn);
|
|
8590
|
+
activeConnections.delete(accountId);
|
|
8462
8591
|
}
|
|
8463
8592
|
|
|
8464
8593
|
// src/channel.ts
|
|
8465
|
-
var DEFAULT_ACCOUNT_ID = "default";
|
|
8466
8594
|
var meta = {
|
|
8467
8595
|
id: "qqbot",
|
|
8468
8596
|
label: "QQ Bot",
|
|
@@ -8475,17 +8603,17 @@ var meta = {
|
|
|
8475
8603
|
};
|
|
8476
8604
|
function resolveQQBotAccount(params) {
|
|
8477
8605
|
const { cfg, accountId = DEFAULT_ACCOUNT_ID } = params;
|
|
8478
|
-
const
|
|
8479
|
-
const
|
|
8480
|
-
const
|
|
8481
|
-
const credentials = resolveQQBotCredentials(
|
|
8606
|
+
const merged = mergeQQBotAccountConfig(cfg, accountId);
|
|
8607
|
+
const baseEnabled = cfg.channels?.qqbot?.enabled !== false;
|
|
8608
|
+
const enabled = baseEnabled && merged.enabled !== false;
|
|
8609
|
+
const credentials = resolveQQBotCredentials(merged);
|
|
8482
8610
|
const configured = Boolean(credentials);
|
|
8483
8611
|
return {
|
|
8484
8612
|
accountId,
|
|
8485
|
-
enabled
|
|
8613
|
+
enabled,
|
|
8486
8614
|
configured,
|
|
8487
8615
|
appId: credentials?.appId,
|
|
8488
|
-
markdownSupport:
|
|
8616
|
+
markdownSupport: merged.markdownSupport ?? true
|
|
8489
8617
|
};
|
|
8490
8618
|
}
|
|
8491
8619
|
var qqbotPlugin = {
|
|
@@ -8558,6 +8686,8 @@ var qqbotPlugin = {
|
|
|
8558
8686
|
additionalProperties: false,
|
|
8559
8687
|
properties: {
|
|
8560
8688
|
enabled: { type: "boolean" },
|
|
8689
|
+
name: { type: "string" },
|
|
8690
|
+
defaultAccount: { type: "string" },
|
|
8561
8691
|
appId: { type: "string" },
|
|
8562
8692
|
clientSecret: { type: "string" },
|
|
8563
8693
|
asr: {
|
|
@@ -8578,46 +8708,121 @@ var qqbotPlugin = {
|
|
|
8578
8708
|
groupAllowFrom: { type: "array", items: { type: "string" } },
|
|
8579
8709
|
historyLimit: { type: "integer", minimum: 0 },
|
|
8580
8710
|
textChunkLimit: { type: "integer", minimum: 1 },
|
|
8581
|
-
replyFinalOnly: { type: "boolean" }
|
|
8711
|
+
replyFinalOnly: { type: "boolean" },
|
|
8712
|
+
maxFileSizeMB: { type: "number" },
|
|
8713
|
+
mediaTimeoutMs: { type: "number" },
|
|
8714
|
+
accounts: {
|
|
8715
|
+
type: "object",
|
|
8716
|
+
additionalProperties: {
|
|
8717
|
+
type: "object",
|
|
8718
|
+
additionalProperties: false,
|
|
8719
|
+
properties: {
|
|
8720
|
+
name: { type: "string" },
|
|
8721
|
+
enabled: { type: "boolean" },
|
|
8722
|
+
appId: { type: "string" },
|
|
8723
|
+
clientSecret: { type: "string" },
|
|
8724
|
+
asr: {
|
|
8725
|
+
type: "object",
|
|
8726
|
+
additionalProperties: false,
|
|
8727
|
+
properties: {
|
|
8728
|
+
enabled: { type: "boolean" },
|
|
8729
|
+
appId: { type: "string" },
|
|
8730
|
+
secretId: { type: "string" },
|
|
8731
|
+
secretKey: { type: "string" }
|
|
8732
|
+
}
|
|
8733
|
+
},
|
|
8734
|
+
markdownSupport: { type: "boolean" },
|
|
8735
|
+
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
8736
|
+
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
8737
|
+
requireMention: { type: "boolean" },
|
|
8738
|
+
allowFrom: { type: "array", items: { type: "string" } },
|
|
8739
|
+
groupAllowFrom: { type: "array", items: { type: "string" } },
|
|
8740
|
+
historyLimit: { type: "integer", minimum: 0 },
|
|
8741
|
+
textChunkLimit: { type: "integer", minimum: 1 },
|
|
8742
|
+
replyFinalOnly: { type: "boolean" },
|
|
8743
|
+
maxFileSizeMB: { type: "number" },
|
|
8744
|
+
mediaTimeoutMs: { type: "number" }
|
|
8745
|
+
}
|
|
8746
|
+
}
|
|
8747
|
+
}
|
|
8582
8748
|
}
|
|
8583
8749
|
}
|
|
8584
8750
|
},
|
|
8585
8751
|
reload: { configPrefixes: ["channels.qqbot"] },
|
|
8586
8752
|
config: {
|
|
8587
|
-
listAccountIds: (
|
|
8753
|
+
listAccountIds: (cfg) => listQQBotAccountIds(cfg),
|
|
8588
8754
|
resolveAccount: (cfg, accountId) => resolveQQBotAccount({ cfg, accountId }),
|
|
8589
8755
|
defaultAccountId: () => DEFAULT_ACCOUNT_ID,
|
|
8590
8756
|
setAccountEnabled: (params) => {
|
|
8757
|
+
const accountId = params.accountId ?? DEFAULT_ACCOUNT_ID;
|
|
8591
8758
|
const existing = params.cfg.channels?.qqbot ?? {};
|
|
8759
|
+
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
8760
|
+
return {
|
|
8761
|
+
...params.cfg,
|
|
8762
|
+
channels: {
|
|
8763
|
+
...params.cfg.channels,
|
|
8764
|
+
qqbot: { ...existing, enabled: params.enabled }
|
|
8765
|
+
}
|
|
8766
|
+
};
|
|
8767
|
+
}
|
|
8768
|
+
const accounts = existing.accounts ?? {};
|
|
8769
|
+
const account = accounts[accountId] ?? {};
|
|
8592
8770
|
return {
|
|
8593
8771
|
...params.cfg,
|
|
8594
8772
|
channels: {
|
|
8595
8773
|
...params.cfg.channels,
|
|
8596
8774
|
qqbot: {
|
|
8597
8775
|
...existing,
|
|
8598
|
-
|
|
8776
|
+
accounts: {
|
|
8777
|
+
...accounts,
|
|
8778
|
+
[accountId]: { ...account, enabled: params.enabled }
|
|
8779
|
+
}
|
|
8599
8780
|
}
|
|
8600
8781
|
}
|
|
8601
8782
|
};
|
|
8602
8783
|
},
|
|
8603
8784
|
deleteAccount: (params) => {
|
|
8604
|
-
const
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
|
|
8785
|
+
const accountId = params.accountId ?? DEFAULT_ACCOUNT_ID;
|
|
8786
|
+
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
8787
|
+
const next = { ...params.cfg };
|
|
8788
|
+
const nextChannels = { ...params.cfg.channels };
|
|
8789
|
+
delete nextChannels.qqbot;
|
|
8790
|
+
if (Object.keys(nextChannels).length > 0) {
|
|
8791
|
+
next.channels = nextChannels;
|
|
8792
|
+
} else {
|
|
8793
|
+
delete next.channels;
|
|
8794
|
+
}
|
|
8795
|
+
return next;
|
|
8611
8796
|
}
|
|
8612
|
-
|
|
8797
|
+
const existing = params.cfg.channels?.qqbot;
|
|
8798
|
+
if (!existing?.accounts?.[accountId]) return params.cfg;
|
|
8799
|
+
const { [accountId]: _removed, ...remainingAccounts } = existing.accounts;
|
|
8800
|
+
return {
|
|
8801
|
+
...params.cfg,
|
|
8802
|
+
channels: {
|
|
8803
|
+
...params.cfg.channels,
|
|
8804
|
+
qqbot: {
|
|
8805
|
+
...existing,
|
|
8806
|
+
accounts: Object.keys(remainingAccounts).length > 0 ? remainingAccounts : void 0
|
|
8807
|
+
}
|
|
8808
|
+
}
|
|
8809
|
+
};
|
|
8810
|
+
},
|
|
8811
|
+
isConfigured: (_account, cfg, accountId) => {
|
|
8812
|
+
const id = accountId ?? _account.accountId;
|
|
8813
|
+
const merged = mergeQQBotAccountConfig(cfg, id);
|
|
8814
|
+
return Boolean(merged.appId && merged.clientSecret);
|
|
8613
8815
|
},
|
|
8614
|
-
isConfigured: (_account, cfg) => isConfigured(cfg.channels?.qqbot),
|
|
8615
8816
|
describeAccount: (account) => ({
|
|
8616
8817
|
accountId: account.accountId,
|
|
8617
8818
|
enabled: account.enabled,
|
|
8618
8819
|
configured: account.configured
|
|
8619
8820
|
}),
|
|
8620
|
-
resolveAllowFrom: (params) =>
|
|
8821
|
+
resolveAllowFrom: (params) => {
|
|
8822
|
+
const accountId = params.accountId ?? DEFAULT_ACCOUNT_ID;
|
|
8823
|
+
const merged = mergeQQBotAccountConfig(params.cfg, accountId);
|
|
8824
|
+
return merged.allowFrom ?? [];
|
|
8825
|
+
},
|
|
8621
8826
|
formatAllowFrom: (params) => params.allowFrom.map((entry) => String(entry).trim()).filter(Boolean).map((entry) => entry.toLowerCase())
|
|
8622
8827
|
},
|
|
8623
8828
|
security: {
|
|
@@ -8631,16 +8836,30 @@ var qqbotPlugin = {
|
|
|
8631
8836
|
}
|
|
8632
8837
|
},
|
|
8633
8838
|
setup: {
|
|
8634
|
-
resolveAccountId: () =>
|
|
8839
|
+
resolveAccountId: (params) => params.accountId ?? resolveDefaultQQBotAccountId(params.cfg),
|
|
8635
8840
|
applyAccountConfig: (params) => {
|
|
8841
|
+
const accountId = params.accountId ?? DEFAULT_ACCOUNT_ID;
|
|
8636
8842
|
const existing = params.cfg.channels?.qqbot ?? {};
|
|
8843
|
+
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
8844
|
+
return {
|
|
8845
|
+
...params.cfg,
|
|
8846
|
+
channels: {
|
|
8847
|
+
...params.cfg.channels,
|
|
8848
|
+
qqbot: { ...existing, ...params.config, enabled: true }
|
|
8849
|
+
}
|
|
8850
|
+
};
|
|
8851
|
+
}
|
|
8852
|
+
const accounts = existing.accounts ?? {};
|
|
8637
8853
|
return {
|
|
8638
8854
|
...params.cfg,
|
|
8639
8855
|
channels: {
|
|
8640
8856
|
...params.cfg.channels,
|
|
8641
8857
|
qqbot: {
|
|
8642
8858
|
...existing,
|
|
8643
|
-
|
|
8859
|
+
accounts: {
|
|
8860
|
+
...accounts,
|
|
8861
|
+
[accountId]: { ...accounts[accountId], ...params.config, enabled: true }
|
|
8862
|
+
}
|
|
8644
8863
|
}
|
|
8645
8864
|
}
|
|
8646
8865
|
};
|
|
@@ -8667,8 +8886,8 @@ var qqbotPlugin = {
|
|
|
8667
8886
|
accountId: ctx.accountId
|
|
8668
8887
|
});
|
|
8669
8888
|
},
|
|
8670
|
-
stopAccount: async (
|
|
8671
|
-
|
|
8889
|
+
stopAccount: async (ctx) => {
|
|
8890
|
+
stopQQBotMonitorForAccount(ctx.accountId);
|
|
8672
8891
|
},
|
|
8673
8892
|
getStatus: () => ({ connected: true })
|
|
8674
8893
|
}
|
|
@@ -8684,6 +8903,8 @@ var plugin = {
|
|
|
8684
8903
|
additionalProperties: false,
|
|
8685
8904
|
properties: {
|
|
8686
8905
|
enabled: { type: "boolean" },
|
|
8906
|
+
name: { type: "string" },
|
|
8907
|
+
defaultAccount: { type: "string" },
|
|
8687
8908
|
appId: { type: "string" },
|
|
8688
8909
|
clientSecret: { type: "string" },
|
|
8689
8910
|
asr: {
|
|
@@ -8704,7 +8925,43 @@ var plugin = {
|
|
|
8704
8925
|
groupAllowFrom: { type: "array", items: { type: "string" } },
|
|
8705
8926
|
historyLimit: { type: "integer", minimum: 0 },
|
|
8706
8927
|
textChunkLimit: { type: "integer", minimum: 1 },
|
|
8707
|
-
replyFinalOnly: { type: "boolean" }
|
|
8928
|
+
replyFinalOnly: { type: "boolean" },
|
|
8929
|
+
maxFileSizeMB: { type: "number" },
|
|
8930
|
+
mediaTimeoutMs: { type: "number" },
|
|
8931
|
+
accounts: {
|
|
8932
|
+
type: "object",
|
|
8933
|
+
additionalProperties: {
|
|
8934
|
+
type: "object",
|
|
8935
|
+
additionalProperties: false,
|
|
8936
|
+
properties: {
|
|
8937
|
+
name: { type: "string" },
|
|
8938
|
+
enabled: { type: "boolean" },
|
|
8939
|
+
appId: { type: "string" },
|
|
8940
|
+
clientSecret: { type: "string" },
|
|
8941
|
+
asr: {
|
|
8942
|
+
type: "object",
|
|
8943
|
+
additionalProperties: false,
|
|
8944
|
+
properties: {
|
|
8945
|
+
enabled: { type: "boolean" },
|
|
8946
|
+
appId: { type: "string" },
|
|
8947
|
+
secretId: { type: "string" },
|
|
8948
|
+
secretKey: { type: "string" }
|
|
8949
|
+
}
|
|
8950
|
+
},
|
|
8951
|
+
markdownSupport: { type: "boolean" },
|
|
8952
|
+
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
8953
|
+
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
8954
|
+
requireMention: { type: "boolean" },
|
|
8955
|
+
allowFrom: { type: "array", items: { type: "string" } },
|
|
8956
|
+
groupAllowFrom: { type: "array", items: { type: "string" } },
|
|
8957
|
+
historyLimit: { type: "integer", minimum: 0 },
|
|
8958
|
+
textChunkLimit: { type: "integer", minimum: 1 },
|
|
8959
|
+
replyFinalOnly: { type: "boolean" },
|
|
8960
|
+
maxFileSizeMB: { type: "number" },
|
|
8961
|
+
mediaTimeoutMs: { type: "number" }
|
|
8962
|
+
}
|
|
8963
|
+
}
|
|
8964
|
+
}
|
|
8708
8965
|
}
|
|
8709
8966
|
},
|
|
8710
8967
|
register(api) {
|