@tencent-connect/openclaw-qqbot 1.6.6-alpha.3 → 1.6.6-alpha.4
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/src/api.d.ts +7 -2
- package/dist/src/api.js +13 -4
- package/dist/src/channel.d.ts +18 -0
- package/dist/src/channel.js +85 -2
- package/dist/src/config.d.ts +33 -2
- package/dist/src/config.js +124 -0
- package/dist/src/gateway.js +565 -23
- package/dist/src/group-history.d.ts +136 -0
- package/dist/src/group-history.js +226 -0
- package/dist/src/message-gating.d.ts +53 -0
- package/dist/src/message-gating.js +107 -0
- package/dist/src/message-queue.d.ts +36 -0
- package/dist/src/message-queue.js +164 -22
- package/dist/src/outbound.d.ts +4 -4
- package/dist/src/outbound.js +8 -8
- package/dist/src/ref-index-store.js +5 -28
- package/dist/src/slash-commands.d.ts +6 -0
- package/dist/src/slash-commands.js +2 -2
- package/dist/src/types.d.ts +88 -0
- package/dist/src/utils/audio-convert.d.ts +1 -1
- package/dist/src/utils/audio-convert.js +1 -1
- package/dist/src/utils/chunked-upload.d.ts +2 -2
- package/dist/src/utils/chunked-upload.js +5 -5
- package/dist/src/utils/text-parsing.js +7 -18
- package/package.json +2 -3
- package/scripts/upgrade-via-source.sh +62 -15
- package/src/api.ts +21 -4
- package/src/channel.ts +85 -2
- package/src/config.ts +169 -2
- package/src/gateway.ts +661 -28
- package/src/group-history.ts +328 -0
- package/src/message-gating.ts +190 -0
- package/src/message-queue.ts +201 -21
- package/src/openclaw-plugin-sdk.d.ts +65 -0
- package/src/outbound.ts +8 -8
- package/src/ref-index-store.ts +5 -27
- package/src/slash-commands.ts +2 -2
- package/src/types.ts +92 -0
- package/src/utils/audio-convert.ts +1 -1
- package/src/utils/chunked-upload.ts +5 -5
- package/src/utils/text-parsing.ts +7 -14
- package/scripts/prebuild-stub.cjs +0 -172
package/dist/src/api.d.ts
CHANGED
|
@@ -83,11 +83,16 @@ export declare function apiRequest<T = unknown>(accessToken: string, method: str
|
|
|
83
83
|
*/
|
|
84
84
|
export declare const PART_FINISH_RETRYABLE_CODES: Set<number>;
|
|
85
85
|
/**
|
|
86
|
-
* upload_prepare
|
|
86
|
+
* upload_prepare 接口命中此错误码时,携带文件信息抛出 UploadDailyLimitExceededError,
|
|
87
|
+
* 由上层(outbound.ts)构造包含文件路径和大小的兜底文案发送给用户,
|
|
87
88
|
* 而非走通用的"文件发送失败,请稍后重试"
|
|
88
89
|
*/
|
|
89
90
|
export declare const UPLOAD_PREPARE_FALLBACK_CODE = 40093002;
|
|
90
91
|
export declare function getGatewayUrl(accessToken: string): Promise<string>;
|
|
92
|
+
/** 回应按钮交互(INTERACTION_CREATE),避免客户端按钮持续 loading */
|
|
93
|
+
export declare function acknowledgeInteraction(accessToken: string, interactionId: string, code?: 0 | 1 | 2 | 3 | 4 | 5, data?: Record<string, unknown>): Promise<void>;
|
|
94
|
+
/** 获取插件版本号(从 package.json 读取,和 PLUGIN_USER_AGENT 同源) */
|
|
95
|
+
export declare function getApiPluginVersion(): string;
|
|
91
96
|
export interface MessageResponse {
|
|
92
97
|
id: string;
|
|
93
98
|
timestamp: number | string;
|
|
@@ -113,7 +118,7 @@ export declare function sendDmMessage(accessToken: string, guildId: string, cont
|
|
|
113
118
|
id: string;
|
|
114
119
|
timestamp: string;
|
|
115
120
|
}>;
|
|
116
|
-
export declare function sendGroupMessage(accessToken: string, groupOpenid: string, content: string, msgId?: string): Promise<MessageResponse>;
|
|
121
|
+
export declare function sendGroupMessage(accessToken: string, groupOpenid: string, content: string, msgId?: string, messageReference?: string): Promise<MessageResponse>;
|
|
117
122
|
export declare function sendProactiveC2CMessage(accessToken: string, openid: string, content: string): Promise<MessageResponse>;
|
|
118
123
|
export declare function sendProactiveGroupMessage(accessToken: string, groupOpenid: string, content: string): Promise<{
|
|
119
124
|
id: string;
|
package/dist/src/api.js
CHANGED
|
@@ -353,7 +353,8 @@ export const PART_FINISH_RETRYABLE_CODES = new Set([
|
|
|
353
353
|
40093001,
|
|
354
354
|
]);
|
|
355
355
|
/**
|
|
356
|
-
* upload_prepare
|
|
356
|
+
* upload_prepare 接口命中此错误码时,携带文件信息抛出 UploadDailyLimitExceededError,
|
|
357
|
+
* 由上层(outbound.ts)构造包含文件路径和大小的兜底文案发送给用户,
|
|
357
358
|
* 而非走通用的"文件发送失败,请稍后重试"
|
|
358
359
|
*/
|
|
359
360
|
export const UPLOAD_PREPARE_FALLBACK_CODE = 40093002;
|
|
@@ -447,6 +448,14 @@ export async function getGatewayUrl(accessToken) {
|
|
|
447
448
|
const data = await apiRequest(accessToken, "GET", "/gateway");
|
|
448
449
|
return data.url;
|
|
449
450
|
}
|
|
451
|
+
/** 回应按钮交互(INTERACTION_CREATE),避免客户端按钮持续 loading */
|
|
452
|
+
export async function acknowledgeInteraction(accessToken, interactionId, code = 0, data) {
|
|
453
|
+
await apiRequest(accessToken, "PUT", `/interactions/${interactionId}`, { code, ...(data ? { data } : {}) });
|
|
454
|
+
}
|
|
455
|
+
/** 获取插件版本号(从 package.json 读取,和 PLUGIN_USER_AGENT 同源) */
|
|
456
|
+
export function getApiPluginVersion() {
|
|
457
|
+
return _pluginVersion;
|
|
458
|
+
}
|
|
450
459
|
/**
|
|
451
460
|
* 发送消息并自动触发 refIdx 回调
|
|
452
461
|
* 所有消息发送函数统一经过此处,确保每条出站消息的 refIdx 都被捕获
|
|
@@ -519,10 +528,10 @@ export async function sendDmMessage(accessToken, guildId, content, msgId) {
|
|
|
519
528
|
...(msgId ? { msg_id: msgId } : {}),
|
|
520
529
|
});
|
|
521
530
|
}
|
|
522
|
-
export async function sendGroupMessage(accessToken, groupOpenid, content, msgId) {
|
|
531
|
+
export async function sendGroupMessage(accessToken, groupOpenid, content, msgId, messageReference) {
|
|
523
532
|
const msgSeq = msgId ? getNextMsgSeq(msgId) : 1;
|
|
524
|
-
const body = buildMessageBody(content, msgId, msgSeq);
|
|
525
|
-
return
|
|
533
|
+
const body = buildMessageBody(content, msgId, msgSeq, messageReference);
|
|
534
|
+
return sendAndNotify(accessToken, "POST", `/v2/groups/${groupOpenid}/messages`, body, { text: content });
|
|
526
535
|
}
|
|
527
536
|
function buildProactiveMessageBody(content) {
|
|
528
537
|
if (!content || content.trim().length === 0) {
|
package/dist/src/channel.d.ts
CHANGED
|
@@ -9,3 +9,21 @@ export declare const TEXT_CHUNK_LIMIT = 5000;
|
|
|
9
9
|
*/
|
|
10
10
|
export declare function chunkText(text: string, limit: number): string[];
|
|
11
11
|
export declare const qqbotPlugin: ChannelPlugin<ResolvedQQBotAccount>;
|
|
12
|
+
/** 清理 @mention:替换 <@openid> 为 @用户名,去除 @机器人自身 */
|
|
13
|
+
export declare function stripMentionText(text: string, mentions?: Array<{
|
|
14
|
+
member_openid?: string;
|
|
15
|
+
id?: string;
|
|
16
|
+
user_openid?: string;
|
|
17
|
+
is_you?: boolean;
|
|
18
|
+
nickname?: string;
|
|
19
|
+
username?: string;
|
|
20
|
+
}>): string;
|
|
21
|
+
/** 检测消息是否 @了机器人(mentions > eventType > mentionPatterns) */
|
|
22
|
+
export declare function detectWasMentioned({ eventType, mentions, content, mentionPatterns }: {
|
|
23
|
+
eventType?: string;
|
|
24
|
+
mentions?: Array<{
|
|
25
|
+
is_you?: boolean;
|
|
26
|
+
}>;
|
|
27
|
+
content?: string;
|
|
28
|
+
mentionPatterns?: string[];
|
|
29
|
+
}): boolean;
|
package/dist/src/channel.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { applyAccountNameToChannelSection, deleteAccountFromConfigSection, setAccountEnabledInConfigSection, } from "openclaw/plugin-sdk/core";
|
|
2
|
-
import { DEFAULT_ACCOUNT_ID, listQQBotAccountIds, resolveQQBotAccount, applyQQBotAccountConfig, resolveDefaultQQBotAccountId } from "./config.js";
|
|
2
|
+
import { DEFAULT_ACCOUNT_ID, listQQBotAccountIds, resolveQQBotAccount, applyQQBotAccountConfig, resolveDefaultQQBotAccountId, resolveRequireMention, resolveToolPolicy, resolveGroupConfig } from "./config.js";
|
|
3
3
|
import { sendText, sendMedia } from "./outbound.js";
|
|
4
4
|
import { startGateway } from "./gateway.js";
|
|
5
5
|
import { qqbotOnboardingAdapter } from "./onboarding.js";
|
|
@@ -39,8 +39,50 @@ export const qqbotPlugin = {
|
|
|
39
39
|
blockStreaming: true,
|
|
40
40
|
},
|
|
41
41
|
reload: { configPrefixes: ["channels.qqbot"] },
|
|
42
|
+
// ============ 群消息策略适配器 ============
|
|
43
|
+
groups: {
|
|
44
|
+
/** 是否需要 @机器人才响应 */
|
|
45
|
+
resolveRequireMention: ({ cfg, accountId, groupId }) => {
|
|
46
|
+
if (!groupId)
|
|
47
|
+
return undefined;
|
|
48
|
+
return resolveRequireMention(cfg, groupId, accountId ?? undefined);
|
|
49
|
+
},
|
|
50
|
+
/** 群聊工具范围 */
|
|
51
|
+
resolveToolPolicy: ({ cfg, accountId, groupId }) => {
|
|
52
|
+
if (!groupId)
|
|
53
|
+
return undefined;
|
|
54
|
+
const policy = resolveToolPolicy(cfg, groupId, accountId ?? undefined);
|
|
55
|
+
// 将简单字符串策略映射为 GroupToolPolicyConfig 对象
|
|
56
|
+
if (policy === "full")
|
|
57
|
+
return undefined; // full = 默认不限制
|
|
58
|
+
if (policy === "none")
|
|
59
|
+
return { allow: [], deny: ["*"] };
|
|
60
|
+
// restricted: 默认空 allow(框架会使用内置 restricted 列表)
|
|
61
|
+
return { allow: [] };
|
|
62
|
+
},
|
|
63
|
+
/** QQ Bot 平台特有的群聊行为提示 */
|
|
64
|
+
resolveGroupIntroHint: ({ cfg, accountId, groupId }) => {
|
|
65
|
+
if (!groupId)
|
|
66
|
+
return undefined;
|
|
67
|
+
const groupCfg = resolveGroupConfig(cfg, groupId, accountId ?? undefined);
|
|
68
|
+
const hints = [];
|
|
69
|
+
if (groupCfg.name) {
|
|
70
|
+
hints.push(`当前群: ${groupCfg.name}`);
|
|
71
|
+
}
|
|
72
|
+
// bot 互聊防护、@状态行为指引在 gateway.ts 动态注入
|
|
73
|
+
return hints.join(" ") || undefined;
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
// ============ @mention 检测与清理 ============
|
|
77
|
+
mentions: {
|
|
78
|
+
/** 清理 @mention 文本(SDK ChannelMentionAdapter 接口) */
|
|
79
|
+
stripMentions: ({ text, ctx }) => {
|
|
80
|
+
const mentions = ctx?.mentions;
|
|
81
|
+
return stripMentionText(text, mentions);
|
|
82
|
+
},
|
|
83
|
+
},
|
|
42
84
|
// CLI onboarding wizard
|
|
43
|
-
// @ts-
|
|
85
|
+
// @ts-ignore onboarding removed from ChannelPlugin type in 2026.3.23 but still supported at runtime
|
|
44
86
|
onboarding: qqbotOnboardingAdapter,
|
|
45
87
|
config: {
|
|
46
88
|
listAccountIds: (cfg) => listQQBotAccountIds(cfg),
|
|
@@ -367,3 +409,44 @@ export const qqbotPlugin = {
|
|
|
367
409
|
}),
|
|
368
410
|
},
|
|
369
411
|
};
|
|
412
|
+
// ============ 独立的 mention 工具函数(供 gateway.ts 等直接调用) ============
|
|
413
|
+
/** 清理 @mention:替换 <@openid> 为 @用户名,去除 @机器人自身 */
|
|
414
|
+
export function stripMentionText(text, mentions) {
|
|
415
|
+
if (!text || !mentions?.length)
|
|
416
|
+
return text;
|
|
417
|
+
let cleaned = text;
|
|
418
|
+
for (const m of mentions) {
|
|
419
|
+
const openid = m.member_openid ?? m.id ?? m.user_openid;
|
|
420
|
+
if (!openid)
|
|
421
|
+
continue;
|
|
422
|
+
if (m.is_you) {
|
|
423
|
+
cleaned = cleaned.replace(new RegExp(`<@!?${openid}>`, "g"), "").trim();
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
const displayName = m.nickname ?? m.username;
|
|
427
|
+
if (displayName) {
|
|
428
|
+
cleaned = cleaned.replace(new RegExp(`<@!?${openid}>`, "g"), `@${displayName}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return cleaned;
|
|
433
|
+
}
|
|
434
|
+
/** 检测消息是否 @了机器人(mentions > eventType > mentionPatterns) */
|
|
435
|
+
export function detectWasMentioned({ eventType, mentions, content, mentionPatterns }) {
|
|
436
|
+
if (mentions?.some((m) => m.is_you))
|
|
437
|
+
return true;
|
|
438
|
+
if (eventType === "GROUP_AT_MESSAGE_CREATE")
|
|
439
|
+
return true;
|
|
440
|
+
if (mentionPatterns?.length && content) {
|
|
441
|
+
for (const pattern of mentionPatterns) {
|
|
442
|
+
try {
|
|
443
|
+
if (new RegExp(pattern, "i").test(content))
|
|
444
|
+
return true;
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
// 无效正则,跳过
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return false;
|
|
452
|
+
}
|
package/dist/src/config.d.ts
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
|
-
import type { ResolvedQQBotAccount } from "./types.js";
|
|
2
|
-
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
1
|
+
import type { ResolvedQQBotAccount, ToolPolicy, GroupConfig } from "./types.js";
|
|
2
|
+
import type { OpenClawConfig, GroupPolicy } from "openclaw/plugin-sdk";
|
|
3
|
+
/**
|
|
4
|
+
* 解析 mentionPatterns(agent → global → 空数组)
|
|
5
|
+
*
|
|
6
|
+
* 优先级:
|
|
7
|
+
* 1. agents.list[agentId].groupChat.mentionPatterns
|
|
8
|
+
* 2. messages.groupChat.mentionPatterns
|
|
9
|
+
* 3. []
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolveMentionPatterns(cfg: OpenClawConfig, agentId?: string): string[];
|
|
3
12
|
export declare const DEFAULT_ACCOUNT_ID = "default";
|
|
13
|
+
/** 解析群消息策略 */
|
|
14
|
+
export declare function resolveGroupPolicy(cfg: OpenClawConfig, accountId?: string): GroupPolicy;
|
|
15
|
+
/** 解析群白名单(统一转大写) */
|
|
16
|
+
export declare function resolveGroupAllowFrom(cfg: OpenClawConfig, accountId?: string): string[];
|
|
17
|
+
/** 检查指定群是否被允许(使用标准策略引擎) */
|
|
18
|
+
export declare function isGroupAllowed(cfg: OpenClawConfig, groupOpenid: string, accountId?: string): boolean;
|
|
19
|
+
type ResolvedGroupConfig = Omit<Required<GroupConfig>, "prompt"> & Pick<GroupConfig, "prompt">;
|
|
20
|
+
/** 解析指定群配置(具体 groupOpenid > 通配符 "*" > 默认值) */
|
|
21
|
+
export declare function resolveGroupConfig(cfg: OpenClawConfig, groupOpenid: string, accountId?: string): ResolvedGroupConfig;
|
|
22
|
+
/** 解析群历史消息缓存条数 */
|
|
23
|
+
export declare function resolveHistoryLimit(cfg: OpenClawConfig, groupOpenid: string, accountId?: string): number;
|
|
24
|
+
/** 解析群行为 PE(具体群 > "*" > 默认值) */
|
|
25
|
+
export declare function resolveGroupPrompt(cfg: OpenClawConfig, groupOpenid: string, accountId?: string): string;
|
|
26
|
+
/** 解析群是否需要 @机器人才响应 */
|
|
27
|
+
export declare function resolveRequireMention(cfg: OpenClawConfig, groupOpenid: string, accountId?: string): boolean;
|
|
28
|
+
/** 解析群是否忽略 @了其他人(非 bot)的消息 */
|
|
29
|
+
export declare function resolveIgnoreOtherMentions(cfg: OpenClawConfig, groupOpenid: string, accountId?: string): boolean;
|
|
30
|
+
/** 解析群工具策略 */
|
|
31
|
+
export declare function resolveToolPolicy(cfg: OpenClawConfig, groupOpenid: string, accountId?: string): ToolPolicy;
|
|
32
|
+
/** 解析群名称(优先配置,fallback 为 openid 前 8 位) */
|
|
33
|
+
export declare function resolveGroupName(cfg: OpenClawConfig, groupOpenid: string, accountId?: string): string;
|
|
4
34
|
/**
|
|
5
35
|
* 列出所有 QQBot 账户 ID
|
|
6
36
|
*/
|
|
@@ -23,3 +53,4 @@ export declare function applyQQBotAccountConfig(cfg: OpenClawConfig, accountId:
|
|
|
23
53
|
name?: string;
|
|
24
54
|
imageServerBaseUrl?: string;
|
|
25
55
|
}): OpenClawConfig;
|
|
56
|
+
export {};
|
package/dist/src/config.js
CHANGED
|
@@ -1,4 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 解析 mentionPatterns(agent → global → 空数组)
|
|
3
|
+
*
|
|
4
|
+
* 优先级:
|
|
5
|
+
* 1. agents.list[agentId].groupChat.mentionPatterns
|
|
6
|
+
* 2. messages.groupChat.mentionPatterns
|
|
7
|
+
* 3. []
|
|
8
|
+
*/
|
|
9
|
+
export function resolveMentionPatterns(cfg, agentId) {
|
|
10
|
+
// 1. agent 级别
|
|
11
|
+
if (agentId) {
|
|
12
|
+
const agents = cfg.agents;
|
|
13
|
+
const entry = agents?.list?.find((a) => a.id?.trim().toLowerCase() === agentId.trim().toLowerCase());
|
|
14
|
+
const agentGroupChat = entry?.groupChat;
|
|
15
|
+
if (agentGroupChat && Object.hasOwn(agentGroupChat, "mentionPatterns")) {
|
|
16
|
+
return agentGroupChat.mentionPatterns ?? [];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// 2. 全局级别
|
|
20
|
+
const globalGroupChat = cfg?.messages?.groupChat;
|
|
21
|
+
if (globalGroupChat && typeof globalGroupChat === "object" && Object.hasOwn(globalGroupChat, "mentionPatterns")) {
|
|
22
|
+
return globalGroupChat.mentionPatterns ?? [];
|
|
23
|
+
}
|
|
24
|
+
// 3. 空数组
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
1
27
|
export const DEFAULT_ACCOUNT_ID = "default";
|
|
28
|
+
function evaluateMatchedGroupAccessForPolicy(params) {
|
|
29
|
+
if (params.groupPolicy === "disabled") {
|
|
30
|
+
return { allowed: false, groupPolicy: params.groupPolicy, reason: "disabled" };
|
|
31
|
+
}
|
|
32
|
+
if (params.groupPolicy === "allowlist") {
|
|
33
|
+
if (params.requireMatchInput && !params.hasMatchInput) {
|
|
34
|
+
return { allowed: false, groupPolicy: params.groupPolicy, reason: "missing_match_input" };
|
|
35
|
+
}
|
|
36
|
+
if (!params.allowlistConfigured) {
|
|
37
|
+
return { allowed: false, groupPolicy: params.groupPolicy, reason: "empty_allowlist" };
|
|
38
|
+
}
|
|
39
|
+
if (!params.allowlistMatched) {
|
|
40
|
+
return { allowed: false, groupPolicy: params.groupPolicy, reason: "not_allowlisted" };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return { allowed: true, groupPolicy: params.groupPolicy, reason: "allowed" };
|
|
44
|
+
}
|
|
45
|
+
// ============ 群消息策略 ============
|
|
46
|
+
const DEFAULT_GROUP_POLICY = "open";
|
|
47
|
+
/** 群历史缓存条数默认值 */
|
|
48
|
+
const DEFAULT_GROUP_HISTORY_LIMIT = 50;
|
|
49
|
+
const DEFAULT_GROUP_CONFIG = {
|
|
50
|
+
requireMention: true,
|
|
51
|
+
ignoreOtherMentions: false,
|
|
52
|
+
toolPolicy: "restricted",
|
|
53
|
+
name: "",
|
|
54
|
+
historyLimit: DEFAULT_GROUP_HISTORY_LIMIT,
|
|
55
|
+
};
|
|
56
|
+
/** 默认群消息行为 PE(可通过配置覆盖) */
|
|
57
|
+
const DEFAULT_GROUP_PROMPT = [
|
|
58
|
+
"若发送者为机器人,仅在对方明确@你提问或请求协助具体任务时,以简洁明了的内容回复,",
|
|
59
|
+
"避免与其他机器人产生抢答或多轮无意义对话。",
|
|
60
|
+
"在群聊中优先让人类用户的消息得到响应,机器人之间保持协作而非竞争,确保对话有序不刷屏。",
|
|
61
|
+
].join("");
|
|
62
|
+
/** 解析群消息策略 */
|
|
63
|
+
export function resolveGroupPolicy(cfg, accountId) {
|
|
64
|
+
const account = resolveQQBotAccount(cfg, accountId);
|
|
65
|
+
return account.config?.groupPolicy ?? DEFAULT_GROUP_POLICY;
|
|
66
|
+
}
|
|
67
|
+
/** 解析群白名单(统一转大写) */
|
|
68
|
+
export function resolveGroupAllowFrom(cfg, accountId) {
|
|
69
|
+
const account = resolveQQBotAccount(cfg, accountId);
|
|
70
|
+
return (account.config?.groupAllowFrom ?? []).map((id) => String(id).trim().toUpperCase());
|
|
71
|
+
}
|
|
72
|
+
/** 检查指定群是否被允许(使用标准策略引擎) */
|
|
73
|
+
export function isGroupAllowed(cfg, groupOpenid, accountId) {
|
|
74
|
+
const policy = resolveGroupPolicy(cfg, accountId);
|
|
75
|
+
const allowList = resolveGroupAllowFrom(cfg, accountId);
|
|
76
|
+
const allowlistConfigured = allowList.length > 0;
|
|
77
|
+
const allowlistMatched = allowList.some((id) => id === "*" || id === groupOpenid.toUpperCase());
|
|
78
|
+
return evaluateMatchedGroupAccessForPolicy({
|
|
79
|
+
groupPolicy: policy,
|
|
80
|
+
allowlistConfigured,
|
|
81
|
+
allowlistMatched,
|
|
82
|
+
}).allowed;
|
|
83
|
+
}
|
|
84
|
+
/** 解析指定群配置(具体 groupOpenid > 通配符 "*" > 默认值) */
|
|
85
|
+
export function resolveGroupConfig(cfg, groupOpenid, accountId) {
|
|
86
|
+
const account = resolveQQBotAccount(cfg, accountId);
|
|
87
|
+
const groups = account.config?.groups ?? {};
|
|
88
|
+
const wildcardCfg = groups["*"] ?? {};
|
|
89
|
+
const specificCfg = groups[groupOpenid] ?? {};
|
|
90
|
+
return {
|
|
91
|
+
requireMention: specificCfg.requireMention ?? wildcardCfg.requireMention ?? DEFAULT_GROUP_CONFIG.requireMention,
|
|
92
|
+
ignoreOtherMentions: specificCfg.ignoreOtherMentions ?? wildcardCfg.ignoreOtherMentions ?? DEFAULT_GROUP_CONFIG.ignoreOtherMentions,
|
|
93
|
+
toolPolicy: specificCfg.toolPolicy ?? wildcardCfg.toolPolicy ?? DEFAULT_GROUP_CONFIG.toolPolicy,
|
|
94
|
+
name: specificCfg.name ?? wildcardCfg.name ?? DEFAULT_GROUP_CONFIG.name,
|
|
95
|
+
prompt: specificCfg.prompt ?? wildcardCfg.prompt,
|
|
96
|
+
historyLimit: specificCfg.historyLimit ?? wildcardCfg.historyLimit ?? DEFAULT_GROUP_CONFIG.historyLimit,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/** 解析群历史消息缓存条数 */
|
|
100
|
+
export function resolveHistoryLimit(cfg, groupOpenid, accountId) {
|
|
101
|
+
return Math.max(0, resolveGroupConfig(cfg, groupOpenid, accountId).historyLimit);
|
|
102
|
+
}
|
|
103
|
+
/** 解析群行为 PE(具体群 > "*" > 默认值) */
|
|
104
|
+
export function resolveGroupPrompt(cfg, groupOpenid, accountId) {
|
|
105
|
+
const account = resolveQQBotAccount(cfg, accountId);
|
|
106
|
+
const groups = account.config?.groups ?? {};
|
|
107
|
+
return groups[groupOpenid]?.prompt ?? groups["*"]?.prompt ?? DEFAULT_GROUP_PROMPT;
|
|
108
|
+
}
|
|
109
|
+
/** 解析群是否需要 @机器人才响应 */
|
|
110
|
+
export function resolveRequireMention(cfg, groupOpenid, accountId) {
|
|
111
|
+
return resolveGroupConfig(cfg, groupOpenid, accountId).requireMention;
|
|
112
|
+
}
|
|
113
|
+
/** 解析群是否忽略 @了其他人(非 bot)的消息 */
|
|
114
|
+
export function resolveIgnoreOtherMentions(cfg, groupOpenid, accountId) {
|
|
115
|
+
return resolveGroupConfig(cfg, groupOpenid, accountId).ignoreOtherMentions;
|
|
116
|
+
}
|
|
117
|
+
/** 解析群工具策略 */
|
|
118
|
+
export function resolveToolPolicy(cfg, groupOpenid, accountId) {
|
|
119
|
+
return resolveGroupConfig(cfg, groupOpenid, accountId).toolPolicy;
|
|
120
|
+
}
|
|
121
|
+
/** 解析群名称(优先配置,fallback 为 openid 前 8 位) */
|
|
122
|
+
export function resolveGroupName(cfg, groupOpenid, accountId) {
|
|
123
|
+
const name = resolveGroupConfig(cfg, groupOpenid, accountId).name;
|
|
124
|
+
return name || groupOpenid.slice(0, 8);
|
|
125
|
+
}
|
|
2
126
|
function normalizeAppId(raw) {
|
|
3
127
|
if (raw === null || raw === undefined)
|
|
4
128
|
return "";
|