@jerryliang122/openclaw-qqbot 1.0.0
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/LICENSE +22 -0
- package/README.md +967 -0
- package/README.zh.md +790 -0
- package/dist/index.cjs +18072 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1222 -0
- package/index.ts +92 -0
- package/openclaw.plugin.json +38 -0
- package/package.json +69 -0
- package/preload.cjs +19 -0
- package/scripts/link-sdk-core.cjs +268 -0
- package/scripts/proactive-api-server.ts +369 -0
- package/scripts/send-proactive.ts +293 -0
- package/scripts/test-sendmedia.ts +116 -0
- package/skills/qqbot-channel/SKILL.md +285 -0
- package/skills/qqbot-channel/references/api_references.md +521 -0
- package/skills/qqbot-remind/SKILL.md +159 -0
- package/skills/qqbot-upgrade/SKILL.md +56 -0
- package/src/adapter/contract.ts +63 -0
- package/src/adapter/lint.ts +144 -0
- package/src/adapter/media.ts +40 -0
- package/src/adapter/pairing.ts +95 -0
- package/src/adapter/resolve.ts +255 -0
- package/src/adapter/setup.ts +13 -0
- package/src/adapter/webhook.ts +248 -0
- package/src/adapter/workspace.ts +21 -0
- package/src/agent-prompt-adapter.ts +26 -0
- package/src/bot-instance.ts +60 -0
- package/src/channel.ts +230 -0
- package/src/commands/bot-approve.ts +143 -0
- package/src/commands/bot-clear-storage.ts +114 -0
- package/src/commands/bot-group-always.ts +62 -0
- package/src/commands/bot-group-info.ts +48 -0
- package/src/commands/bot-help.ts +40 -0
- package/src/commands/bot-logs.ts +248 -0
- package/src/commands/bot-me.ts +18 -0
- package/src/commands/bot-pairing.ts +50 -0
- package/src/commands/bot-ping.ts +33 -0
- package/src/commands/bot-streaming.ts +55 -0
- package/src/commands/bot-upgrade.ts +56 -0
- package/src/commands/bot-version.ts +41 -0
- package/src/commands/config-util.ts +96 -0
- package/src/commands/index.ts +51 -0
- package/src/config.ts +403 -0
- package/src/constants.ts +6 -0
- package/src/dispatch/body-assembler.ts +308 -0
- package/src/dispatch/ctx-builder.ts +127 -0
- package/src/dispatch/dispatch.ts +667 -0
- package/src/dispatch/envelope-builder.ts +112 -0
- package/src/dispatch/index.ts +2 -0
- package/src/features/approval-capability.ts +302 -0
- package/src/features/approval-helpers.ts +271 -0
- package/src/features/approval-utils.ts +21 -0
- package/src/features/command-panel.ts +301 -0
- package/src/features/credential-backup.ts +74 -0
- package/src/features/group-mode-store.ts +79 -0
- package/src/features/history-store.ts +75 -0
- package/src/features/msgid-cache.ts +55 -0
- package/src/features/outbound-echo-store.ts +46 -0
- package/src/features/proactive-budget.ts +57 -0
- package/src/features/proactive.ts +549 -0
- package/src/features/question-helpers.ts +771 -0
- package/src/features/quota-manager.ts +173 -0
- package/src/features/ref-index-store.ts +289 -0
- package/src/features/secret-input-store.ts +118 -0
- package/src/features/secret-store-cli.ts +324 -0
- package/src/features/typing-refresh.ts +51 -0
- package/src/features/update-checker.ts +166 -0
- package/src/gateway/event-handlers.ts +456 -0
- package/src/gateway/index.ts +3 -0
- package/src/gateway/lifecycle.ts +236 -0
- package/src/gateway/middleware-setup.ts +173 -0
- package/src/gateway/qqbot-gateway.ts +458 -0
- package/src/gateway-adapter.ts +44 -0
- package/src/heartbeat-adapter.ts +57 -0
- package/src/message-adapter.ts +40 -0
- package/src/messaging-adapter.ts +78 -0
- package/src/middleware/access-control.ts +125 -0
- package/src/middleware/attachment.ts +373 -0
- package/src/middleware/inbound-guard.ts +102 -0
- package/src/middleware/policy-injector.ts +71 -0
- package/src/middleware/secret-capture.ts +161 -0
- package/src/middleware/typing.ts +110 -0
- package/src/openclaw-plugin-sdk.d.ts +543 -0
- package/src/outbound/chunker.ts +80 -0
- package/src/outbound/debounce.ts +102 -0
- package/src/outbound/deliver-pipeline.ts +235 -0
- package/src/outbound/index.ts +3 -0
- package/src/outbound/local-file-router.ts +145 -0
- package/src/outbound/media-send.ts +408 -0
- package/src/outbound/outbound-service.ts +298 -0
- package/src/outbound/reply-limiter.ts +139 -0
- package/src/outbound/sanitize.ts +32 -0
- package/src/outbound/streaming-controller.ts +332 -0
- package/src/outbound/target.ts +109 -0
- package/src/outbound-adapter.ts +323 -0
- package/src/plugin-base.ts +42 -0
- package/src/request-context.ts +50 -0
- package/src/runtime.ts +42 -0
- package/src/setup/account-key.ts +41 -0
- package/src/setup/finalize.ts +110 -0
- package/src/setup/login.ts +197 -0
- package/src/setup/surface.ts +40 -0
- package/src/status-adapter.ts +56 -0
- package/src/tools/platform.ts +149 -0
- package/src/tools/remind.ts +308 -0
- package/src/tools/secret-input.ts +185 -0
- package/src/types-augment.d.ts +54 -0
- package/src/types-plugin.ts +82 -0
- package/src/types.ts +620 -0
- package/src/typing-lifecycle.ts +182 -0
- package/src/utils/mention.ts +52 -0
- package/src/utils/pkg-version.ts +23 -0
- package/src/utils/platform.ts +459 -0
- package/src/utils/plugin-logger.ts +104 -0
- package/src/utils/ssrf-guard.ts +132 -0
- package/src/utils/stt.ts +150 -0
- package/src/utils/voice-text.ts +61 -0
- package/tsconfig.json +17 -0
- package/tsup.config.ts +64 -0
package/src/config.ts
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import type { ResolvedQQBotAccount, QQBotAccountConfig, ToolPolicy, GroupConfig, GroupCoalesceConfig } from "./types.js";
|
|
2
|
+
import type { OpenClawConfig, GroupPolicy } from "openclaw/plugin-sdk";
|
|
3
|
+
|
|
4
|
+
// ============ Agent-aware mentionPatterns 解析 ============
|
|
5
|
+
|
|
6
|
+
type AgentEntry = { id?: string; groupChat?: { mentionPatterns?: string[]; historyLimit?: number } };
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 解析 mentionPatterns(agent → global → 空数组)
|
|
10
|
+
*
|
|
11
|
+
* 优先级:
|
|
12
|
+
* 1. agents.list[agentId].groupChat.mentionPatterns
|
|
13
|
+
* 2. messages.groupChat.mentionPatterns
|
|
14
|
+
* 3. []
|
|
15
|
+
*/
|
|
16
|
+
export function resolveMentionPatterns(cfg: OpenClawConfig, agentId?: string): string[] {
|
|
17
|
+
// 1. agent 级别
|
|
18
|
+
if (agentId) {
|
|
19
|
+
const agents = (cfg as Record<string, unknown>).agents as { list?: AgentEntry[] } | undefined;
|
|
20
|
+
const entry = agents?.list?.find((a) => a.id?.trim().toLowerCase() === agentId.trim().toLowerCase());
|
|
21
|
+
const agentGroupChat = entry?.groupChat;
|
|
22
|
+
if (agentGroupChat && Object.hasOwn(agentGroupChat, "mentionPatterns")) {
|
|
23
|
+
return agentGroupChat.mentionPatterns ?? [];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// 2. 全局级别
|
|
27
|
+
const globalGroupChat = (cfg as any)?.messages?.groupChat;
|
|
28
|
+
if (globalGroupChat && typeof globalGroupChat === "object" && Object.hasOwn(globalGroupChat, "mentionPatterns")) {
|
|
29
|
+
return (globalGroupChat as { mentionPatterns?: string[] }).mentionPatterns ?? [];
|
|
30
|
+
}
|
|
31
|
+
// 3. 空数组
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const DEFAULT_ACCOUNT_ID = "default";
|
|
36
|
+
|
|
37
|
+
// 内联 evaluateMatchedGroupAccessForPolicy(openclaw dist 尚未导出,本地实现)
|
|
38
|
+
|
|
39
|
+
type MatchedGroupAccessReason = "allowed" | "disabled" | "missing_match_input" | "empty_allowlist" | "not_allowlisted";
|
|
40
|
+
|
|
41
|
+
interface MatchedGroupAccessDecision {
|
|
42
|
+
allowed: boolean;
|
|
43
|
+
groupPolicy: GroupPolicy;
|
|
44
|
+
reason: MatchedGroupAccessReason;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function evaluateMatchedGroupAccessForPolicy(params: {
|
|
48
|
+
groupPolicy: GroupPolicy;
|
|
49
|
+
allowlistConfigured: boolean;
|
|
50
|
+
allowlistMatched: boolean;
|
|
51
|
+
requireMatchInput?: boolean;
|
|
52
|
+
hasMatchInput?: boolean;
|
|
53
|
+
}): MatchedGroupAccessDecision {
|
|
54
|
+
if (params.groupPolicy === "disabled") {
|
|
55
|
+
return { allowed: false, groupPolicy: params.groupPolicy, reason: "disabled" };
|
|
56
|
+
}
|
|
57
|
+
if (params.groupPolicy === "allowlist") {
|
|
58
|
+
if (params.requireMatchInput && !params.hasMatchInput) {
|
|
59
|
+
return { allowed: false, groupPolicy: params.groupPolicy, reason: "missing_match_input" };
|
|
60
|
+
}
|
|
61
|
+
if (!params.allowlistConfigured) {
|
|
62
|
+
return { allowed: false, groupPolicy: params.groupPolicy, reason: "empty_allowlist" };
|
|
63
|
+
}
|
|
64
|
+
if (!params.allowlistMatched) {
|
|
65
|
+
return { allowed: false, groupPolicy: params.groupPolicy, reason: "not_allowlisted" };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { allowed: true, groupPolicy: params.groupPolicy, reason: "allowed" };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface QQBotChannelConfig extends QQBotAccountConfig {
|
|
72
|
+
/** HTTP/WebSocket User-Agent 追加后缀 */
|
|
73
|
+
userAgentSuffix?: string;
|
|
74
|
+
accounts?: Record<string, QQBotAccountConfig>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ============ 群消息策略 ============
|
|
78
|
+
|
|
79
|
+
const DEFAULT_GROUP_POLICY: GroupPolicy = "open";
|
|
80
|
+
|
|
81
|
+
/** 群历史缓存条数默认值 */
|
|
82
|
+
const DEFAULT_GROUP_HISTORY_LIMIT = 20;
|
|
83
|
+
|
|
84
|
+
/** 群消息合并默认配置 */
|
|
85
|
+
const DEFAULT_GROUP_COALESCE_CONFIG: Required<GroupCoalesceConfig> = {
|
|
86
|
+
enabled: true,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const DEFAULT_GROUP_CONFIG: Omit<Required<GroupConfig>, "prompt" | "coalesce" | "historyMode" | "unmentionedInbound"> & { coalesce: Required<GroupCoalesceConfig>; historyMode: 'clear' | 'rolling'; unmentionedInbound: 'user_request' | 'room_event' } = {
|
|
90
|
+
requireMention: true,
|
|
91
|
+
ignoreOtherMentions: false,
|
|
92
|
+
toolPolicy: "restricted",
|
|
93
|
+
name: "",
|
|
94
|
+
historyLimit: DEFAULT_GROUP_HISTORY_LIMIT,
|
|
95
|
+
historyMode: "clear",
|
|
96
|
+
unmentionedInbound: "user_request",
|
|
97
|
+
coalesce: DEFAULT_GROUP_COALESCE_CONFIG,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/** 默认群消息行为 PE(可通过配置覆盖) */
|
|
101
|
+
const DEFAULT_GROUP_PROMPT = [
|
|
102
|
+
"若发送者为机器人,仅在对方明确@你提问或请求协助具体任务时,以简洁明了的内容回复,",
|
|
103
|
+
"避免与其他机器人产生抢答或多轮无意义对话。",
|
|
104
|
+
"在群聊中优先让人类用户的消息得到响应,机器人之间保持协作而非竞争,确保对话有序不刷屏。",
|
|
105
|
+
].join("");
|
|
106
|
+
|
|
107
|
+
/** 解析群消息策略 */
|
|
108
|
+
export function resolveGroupPolicy(cfg: OpenClawConfig, accountId?: string): GroupPolicy {
|
|
109
|
+
const account = resolveQQBotAccount(cfg, accountId);
|
|
110
|
+
return account.config?.groupPolicy ?? DEFAULT_GROUP_POLICY;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** 解析群白名单(统一转大写) */
|
|
114
|
+
export function resolveGroupAllowFrom(cfg: OpenClawConfig, accountId?: string): string[] {
|
|
115
|
+
const account = resolveQQBotAccount(cfg, accountId);
|
|
116
|
+
return (account.config?.groupAllowFrom ?? []).map((id) => String(id).trim().toUpperCase());
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** 检查指定群是否被允许(使用标准策略引擎) */
|
|
120
|
+
export function isGroupAllowed(cfg: OpenClawConfig, groupOpenid: string, accountId?: string): boolean {
|
|
121
|
+
const account = resolveQQBotAccount(cfg, accountId);
|
|
122
|
+
const policy = account.config?.groupPolicy ?? DEFAULT_GROUP_POLICY;
|
|
123
|
+
const allowList = (account.config?.groupAllowFrom ?? []).map((id) => String(id).trim().toUpperCase());
|
|
124
|
+
const allowlistConfigured = allowList.length > 0;
|
|
125
|
+
const allowlistMatched = allowList.some((id) => id === "*" || id === groupOpenid.toUpperCase());
|
|
126
|
+
|
|
127
|
+
return evaluateMatchedGroupAccessForPolicy({
|
|
128
|
+
groupPolicy: policy,
|
|
129
|
+
allowlistConfigured,
|
|
130
|
+
allowlistMatched,
|
|
131
|
+
}).allowed;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export type ResolvedGroupConfig = Omit<Required<GroupConfig>, "prompt" | "coalesce" | "historyMode" | "unmentionedInbound"> & {
|
|
135
|
+
prompt: string;
|
|
136
|
+
coalesce: Required<GroupCoalesceConfig>;
|
|
137
|
+
historyMode: 'clear' | 'rolling';
|
|
138
|
+
unmentionedInbound: 'user_request' | 'room_event';
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export function resolveGroupConfigFromAccount(account: ResolvedQQBotAccount, groupOpenid: string): ResolvedGroupConfig {
|
|
142
|
+
const groups = account.config?.groups ?? {};
|
|
143
|
+
const wildcardCfg = groups["*"] ?? {};
|
|
144
|
+
const specificCfg = groups[groupOpenid] ?? {};
|
|
145
|
+
const accountDefaultRequireMention = account.config?.defaultRequireMention ?? DEFAULT_GROUP_CONFIG.requireMention;
|
|
146
|
+
const accountDefaultCoalesce = account.config?.groupCoalesce ?? DEFAULT_GROUP_COALESCE_CONFIG;
|
|
147
|
+
|
|
148
|
+
const coalesce = {
|
|
149
|
+
enabled: specificCfg.coalesce?.enabled ?? wildcardCfg.coalesce?.enabled ?? accountDefaultCoalesce.enabled ?? DEFAULT_GROUP_COALESCE_CONFIG.enabled,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
requireMention: specificCfg.requireMention ?? wildcardCfg.requireMention ?? accountDefaultRequireMention,
|
|
154
|
+
ignoreOtherMentions: specificCfg.ignoreOtherMentions ?? wildcardCfg.ignoreOtherMentions ?? DEFAULT_GROUP_CONFIG.ignoreOtherMentions,
|
|
155
|
+
toolPolicy: specificCfg.toolPolicy ?? wildcardCfg.toolPolicy ?? DEFAULT_GROUP_CONFIG.toolPolicy,
|
|
156
|
+
name: specificCfg.name ?? wildcardCfg.name ?? DEFAULT_GROUP_CONFIG.name,
|
|
157
|
+
prompt: specificCfg.prompt ?? wildcardCfg.prompt ?? DEFAULT_GROUP_PROMPT,
|
|
158
|
+
historyLimit: specificCfg.historyLimit ?? wildcardCfg.historyLimit ?? DEFAULT_GROUP_CONFIG.historyLimit,
|
|
159
|
+
historyMode: specificCfg.historyMode ?? wildcardCfg.historyMode ?? DEFAULT_GROUP_CONFIG.historyMode,
|
|
160
|
+
unmentionedInbound: specificCfg.unmentionedInbound ?? wildcardCfg.unmentionedInbound ?? DEFAULT_GROUP_CONFIG.unmentionedInbound,
|
|
161
|
+
coalesce,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function resolveGroupConfig(cfg: OpenClawConfig, groupOpenid: string, accountId?: string): ResolvedGroupConfig {
|
|
166
|
+
return resolveGroupConfigFromAccount(resolveQQBotAccount(cfg, accountId), groupOpenid);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** 解析群历史消息缓存条数 */
|
|
170
|
+
export function resolveHistoryLimit(cfg: OpenClawConfig, groupOpenid: string, accountId?: string): number {
|
|
171
|
+
return Math.max(0, resolveGroupConfig(cfg, groupOpenid, accountId).historyLimit);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** 解析群行为 PE(具体群 > "*" > 默认值) */
|
|
175
|
+
export function resolveGroupPrompt(cfg: OpenClawConfig, groupOpenid: string, accountId?: string): string {
|
|
176
|
+
return resolveGroupConfig(cfg, groupOpenid, accountId).prompt;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** 解析群是否需要 @机器人才响应 */
|
|
180
|
+
export function resolveRequireMention(cfg: OpenClawConfig, groupOpenid: string, accountId?: string): boolean {
|
|
181
|
+
return resolveGroupConfig(cfg, groupOpenid, accountId).requireMention;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** 解析群是否忽略 @了其他人(非 bot)的消息 */
|
|
185
|
+
export function resolveIgnoreOtherMentions(cfg: OpenClawConfig, groupOpenid: string, accountId?: string): boolean {
|
|
186
|
+
return resolveGroupConfig(cfg, groupOpenid, accountId).ignoreOtherMentions;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** 解析群工具策略 */
|
|
190
|
+
export function resolveToolPolicy(cfg: OpenClawConfig, groupOpenid: string, accountId?: string): ToolPolicy {
|
|
191
|
+
return resolveGroupConfig(cfg, groupOpenid, accountId).toolPolicy;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** 解析群名称(优先配置,fallback 为 openid 前 8 位) */
|
|
195
|
+
export function resolveGroupName(cfg: OpenClawConfig, groupOpenid: string, accountId?: string): string {
|
|
196
|
+
const name = resolveGroupConfig(cfg, groupOpenid, accountId).name;
|
|
197
|
+
return name || groupOpenid.slice(0, 8);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* 解析 User-Agent 追加后缀(仅通道级:channels.qqbot.userAgentSuffix)
|
|
202
|
+
*/
|
|
203
|
+
export function resolveUserAgentSuffix(cfg: OpenClawConfig): string {
|
|
204
|
+
const qqbot = cfg.channels?.qqbot as QQBotChannelConfig | undefined;
|
|
205
|
+
return qqbot?.userAgentSuffix ? String(qqbot.userAgentSuffix).trim() : "";
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function normalizeAppId(raw: unknown): string {
|
|
209
|
+
if (raw === null || raw === undefined) return "";
|
|
210
|
+
return String(raw).trim();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function firstNonEmptyEnv(...values: Array<string | undefined>): string {
|
|
214
|
+
for (const value of values) {
|
|
215
|
+
if (value?.trim()) return value.trim();
|
|
216
|
+
}
|
|
217
|
+
return "";
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** 文档变量名优先,同时兼容旧的下划线变量名。 */
|
|
221
|
+
export function resolveQQBotEnvAppId(): string {
|
|
222
|
+
return firstNonEmptyEnv(process.env.QQBOT_APPID);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function resolveQQBotEnvClientSecret(): string {
|
|
226
|
+
return firstNonEmptyEnv(process.env.QQBOT_SECRET);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* 列出所有 QQBot 账户 ID
|
|
231
|
+
*/
|
|
232
|
+
export function listQQBotAccountIds(cfg: OpenClawConfig): string[] {
|
|
233
|
+
const ids = new Set<string>();
|
|
234
|
+
const qqbot = cfg.channels?.qqbot as QQBotChannelConfig | undefined;
|
|
235
|
+
|
|
236
|
+
if (qqbot?.appId || resolveQQBotEnvAppId()) {
|
|
237
|
+
ids.add(DEFAULT_ACCOUNT_ID);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (qqbot?.accounts) {
|
|
241
|
+
for (const accountId of Object.keys(qqbot.accounts)) {
|
|
242
|
+
if (qqbot.accounts[accountId]?.appId) {
|
|
243
|
+
ids.add(accountId);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return Array.from(ids);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 获取默认账户 ID
|
|
253
|
+
*/
|
|
254
|
+
export function resolveDefaultQQBotAccountId(cfg: OpenClawConfig): string {
|
|
255
|
+
const qqbot = cfg.channels?.qqbot as QQBotChannelConfig | undefined;
|
|
256
|
+
// 如果有默认账户配置,返回 default
|
|
257
|
+
if (qqbot?.appId || resolveQQBotEnvAppId()) {
|
|
258
|
+
return DEFAULT_ACCOUNT_ID;
|
|
259
|
+
}
|
|
260
|
+
// 否则返回第一个配置的账户
|
|
261
|
+
if (qqbot?.accounts) {
|
|
262
|
+
const ids = Object.keys(qqbot.accounts);
|
|
263
|
+
if (ids.length > 0) {
|
|
264
|
+
return ids[0];
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return DEFAULT_ACCOUNT_ID;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* 解析 QQBot 账户配置
|
|
272
|
+
*/
|
|
273
|
+
export function resolveQQBotAccount(
|
|
274
|
+
cfg: OpenClawConfig,
|
|
275
|
+
accountId?: string | null
|
|
276
|
+
): ResolvedQQBotAccount {
|
|
277
|
+
const resolvedAccountId = accountId ?? resolveDefaultQQBotAccountId(cfg);
|
|
278
|
+
const qqbot = cfg.channels?.qqbot as QQBotChannelConfig | undefined;
|
|
279
|
+
|
|
280
|
+
// 基础配置
|
|
281
|
+
let accountConfig: QQBotAccountConfig = {};
|
|
282
|
+
let appId = "";
|
|
283
|
+
let clientSecret = "";
|
|
284
|
+
let secretSource: "config" | "file" | "env" | "none" = "none";
|
|
285
|
+
|
|
286
|
+
if (resolvedAccountId === DEFAULT_ACCOUNT_ID) {
|
|
287
|
+
// 默认账户从顶层读取(展开所有字段,避免遗漏新增配置项)
|
|
288
|
+
const { accounts: _accounts, ...topLevelConfig } = qqbot ?? {} as QQBotChannelConfig;
|
|
289
|
+
accountConfig = {
|
|
290
|
+
...topLevelConfig,
|
|
291
|
+
markdownSupport: qqbot?.markdownSupport ?? true,
|
|
292
|
+
};
|
|
293
|
+
appId = normalizeAppId(qqbot?.appId);
|
|
294
|
+
} else {
|
|
295
|
+
// 命名账户从 accounts 读取
|
|
296
|
+
const account = qqbot?.accounts?.[resolvedAccountId];
|
|
297
|
+
accountConfig = account ?? {};
|
|
298
|
+
appId = normalizeAppId(account?.appId);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// 解析 clientSecret
|
|
302
|
+
if (accountConfig.clientSecret) {
|
|
303
|
+
clientSecret = accountConfig.clientSecret;
|
|
304
|
+
secretSource = "config";
|
|
305
|
+
} else if (accountConfig.clientSecretFile) {
|
|
306
|
+
// 从文件读取(运行时处理)
|
|
307
|
+
secretSource = "file";
|
|
308
|
+
} else if (resolvedAccountId === DEFAULT_ACCOUNT_ID && resolveQQBotEnvClientSecret()) {
|
|
309
|
+
clientSecret = resolveQQBotEnvClientSecret();
|
|
310
|
+
secretSource = "env";
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// AppId 也可以从环境变量读取
|
|
314
|
+
if (!appId && resolvedAccountId === DEFAULT_ACCOUNT_ID) {
|
|
315
|
+
appId = normalizeAppId(resolveQQBotEnvAppId());
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
accountId: resolvedAccountId,
|
|
320
|
+
name: accountConfig.name,
|
|
321
|
+
enabled: accountConfig.enabled !== false,
|
|
322
|
+
appId,
|
|
323
|
+
clientSecret,
|
|
324
|
+
secretSource,
|
|
325
|
+
systemPrompt: accountConfig.systemPrompt,
|
|
326
|
+
markdownSupport: accountConfig.markdownSupport !== false,
|
|
327
|
+
commandPanelNative: accountConfig.commands?.native !== false,
|
|
328
|
+
userAgentSuffix: resolveUserAgentSuffix(cfg),
|
|
329
|
+
config: accountConfig,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* 应用账户配置
|
|
335
|
+
*/
|
|
336
|
+
export function applyQQBotAccountConfig(
|
|
337
|
+
cfg: OpenClawConfig,
|
|
338
|
+
accountId: string,
|
|
339
|
+
input: { appId?: string; clientSecret?: string; clientSecretFile?: string; name?: string }
|
|
340
|
+
): OpenClawConfig {
|
|
341
|
+
const next = { ...cfg };
|
|
342
|
+
|
|
343
|
+
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
344
|
+
// 如果没有设置过 allowFrom,默认设置为 ["*"]
|
|
345
|
+
const existingConfig = (next.channels?.qqbot as QQBotChannelConfig) || {};
|
|
346
|
+
const allowFrom = existingConfig.allowFrom ?? ["*"];
|
|
347
|
+
|
|
348
|
+
next.channels = {
|
|
349
|
+
...next.channels,
|
|
350
|
+
qqbot: {
|
|
351
|
+
...(next.channels?.qqbot as Record<string, unknown> || {}),
|
|
352
|
+
enabled: true,
|
|
353
|
+
allowFrom,
|
|
354
|
+
...(input.appId ? { appId: input.appId } : {}),
|
|
355
|
+
...(input.clientSecret
|
|
356
|
+
? { clientSecret: input.clientSecret }
|
|
357
|
+
: input.clientSecretFile
|
|
358
|
+
? { clientSecretFile: input.clientSecretFile }
|
|
359
|
+
: {}),
|
|
360
|
+
...(input.name ? { name: input.name } : {}),
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
} else {
|
|
364
|
+
// 如果没有设置过 allowFrom,默认设置为 ["*"]
|
|
365
|
+
const existingAccountConfig = (next.channels?.qqbot as QQBotChannelConfig)?.accounts?.[accountId] || {};
|
|
366
|
+
const allowFrom = existingAccountConfig.allowFrom ?? ["*"];
|
|
367
|
+
|
|
368
|
+
next.channels = {
|
|
369
|
+
...next.channels,
|
|
370
|
+
qqbot: {
|
|
371
|
+
...(next.channels?.qqbot as Record<string, unknown> || {}),
|
|
372
|
+
enabled: true,
|
|
373
|
+
accounts: {
|
|
374
|
+
...((next.channels?.qqbot as QQBotChannelConfig)?.accounts || {}),
|
|
375
|
+
[accountId]: {
|
|
376
|
+
...((next.channels?.qqbot as QQBotChannelConfig)?.accounts?.[accountId] || {}),
|
|
377
|
+
enabled: true,
|
|
378
|
+
allowFrom,
|
|
379
|
+
...(input.appId ? { appId: input.appId } : {}),
|
|
380
|
+
...(input.clientSecret
|
|
381
|
+
? { clientSecret: input.clientSecret }
|
|
382
|
+
: input.clientSecretFile
|
|
383
|
+
? { clientSecretFile: input.clientSecretFile }
|
|
384
|
+
: {}),
|
|
385
|
+
...(input.name ? { name: input.name } : {}),
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return next;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** 解析群消息合并配置 */
|
|
396
|
+
export function resolveGroupCoalesceConfig(cfg: OpenClawConfig, groupOpenid: string, accountId?: string): Required<GroupCoalesceConfig> {
|
|
397
|
+
return resolveGroupConfig(cfg, groupOpenid, accountId).coalesce;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/** 解析群消息合并是否启用 */
|
|
401
|
+
export function resolveGroupCoalesceEnabled(cfg: OpenClawConfig, groupOpenid: string, accountId?: string): boolean {
|
|
402
|
+
return resolveGroupCoalesceConfig(cfg, groupOpenid, accountId).enabled;
|
|
403
|
+
}
|