From 4ee6068ced839aee45c72a8ddfc512edcfdcde94 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 1 May 2026 09:10:36 +0100 Subject: [PATCH] refactor: prune stale qqbot helpers --- .../qqbot/src/bridge/approval/capability.ts | 7 +- extensions/qqbot/src/bridge/setup/finalize.ts | 7 -- .../engine/commands/builtin/log-helpers.ts | 26 ---- .../engine/messaging/streaming-media-send.ts | 117 +----------------- .../src/engine/messaging/target-parser.ts | 18 --- extensions/qqbot/src/exec-approvals.ts | 11 -- 6 files changed, 3 insertions(+), 183 deletions(-) diff --git a/extensions/qqbot/src/bridge/approval/capability.ts b/extensions/qqbot/src/bridge/approval/capability.ts index d045ced24de..9a563b165b2 100644 --- a/extensions/qqbot/src/bridge/approval/capability.ts +++ b/extensions/qqbot/src/bridge/approval/capability.ts @@ -11,10 +11,7 @@ * QQBot falls back to "always handle, anyone can approve". */ -import { - createChannelApprovalCapability, - splitChannelApprovalCapability, -} from "openclaw/plugin-sdk/approval-delivery-runtime"; +import { createChannelApprovalCapability } from "openclaw/plugin-sdk/approval-delivery-runtime"; import { createLazyChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-adapter-runtime"; import type { ChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-runtime"; import { resolveApprovalRequestSessionConversation } from "openclaw/plugin-sdk/approval-native-runtime"; @@ -232,8 +229,6 @@ function createQQBotApprovalCapability(): ChannelApprovalCapability { export const qqbotApprovalCapability = createQQBotApprovalCapability(); -export const qqbotNativeApprovalAdapter = splitChannelApprovalCapability(qqbotApprovalCapability); - let _cachedCapability: ChannelApprovalCapability | undefined; export function getQQBotApprovalCapability(): ChannelApprovalCapability { diff --git a/extensions/qqbot/src/bridge/setup/finalize.ts b/extensions/qqbot/src/bridge/setup/finalize.ts index 1681e28b843..63b252b186e 100644 --- a/extensions/qqbot/src/bridge/setup/finalize.ts +++ b/extensions/qqbot/src/bridge/setup/finalize.ts @@ -12,13 +12,6 @@ function isQQBotAccountConfigured(cfg: OpenClawConfig, accountId: string): boole return Boolean(account.appId && account.clientSecret); } -export async function detectQQBotConfigured( - cfg: OpenClawConfig, - accountId: string, -): Promise { - return isQQBotAccountConfigured(cfg, accountId); -} - async function linkViaQrCode(params: { cfg: OpenClawConfig; accountId: string; diff --git a/extensions/qqbot/src/engine/commands/builtin/log-helpers.ts b/extensions/qqbot/src/engine/commands/builtin/log-helpers.ts index 60b509f6bae..940db1f5b00 100644 --- a/extensions/qqbot/src/engine/commands/builtin/log-helpers.ts +++ b/extensions/qqbot/src/engine/commands/builtin/log-helpers.ts @@ -238,32 +238,6 @@ function tailFileLines( } } -function normalizeCommandAllowlistEntry(entry: unknown): string { - if ( - typeof entry === "string" || - typeof entry === "number" || - typeof entry === "boolean" || - typeof entry === "bigint" - ) { - return `${entry}` - .trim() - .replace(/^qqbot:\s*/i, "") - .trim(); - } - return ""; -} - -export function hasExplicitCommandAllowlist(accountConfig?: Record): boolean { - const allowFrom = accountConfig?.allowFrom; - if (!Array.isArray(allowFrom) || allowFrom.length === 0) { - return false; - } - return allowFrom.every((entry) => { - const normalized = normalizeCommandAllowlistEntry(entry); - return normalized.length > 0 && normalized !== "*"; - }); -} - /** * Build the /bot-logs result: collect recent log files, write them to a temp file. */ diff --git a/extensions/qqbot/src/engine/messaging/streaming-media-send.ts b/extensions/qqbot/src/engine/messaging/streaming-media-send.ts index 1e7e95cb668..a962ee239c7 100644 --- a/extensions/qqbot/src/engine/messaging/streaming-media-send.ts +++ b/extensions/qqbot/src/engine/messaging/streaming-media-send.ts @@ -193,8 +193,8 @@ export interface FirstClosedMediaTag { /** * 在文本中查找**第一个**完整闭合的媒体标签 * - * 与 splitByMediaTags 不同,此函数只匹配一个标签就停止, - * 用于流式场景的"循环消费"模式:每次处理一个标签,更新偏移,再找下一个。 + * 只匹配一个标签就停止,用于流式场景的"循环消费"模式: + * 每次处理一个标签,更新偏移,再找下一个。 * * @param text 待检查的文本(应已 normalize 过) * @returns 第一个闭合标签的信息,没有则返回 null @@ -250,119 +250,6 @@ export function findFirstClosedMediaTag( return null; } -/** - * 媒体标签拆分结果 - */ -export interface MediaSplitResult { - /** 是否包含媒体标签 */ - hasMediaTags: boolean; - /** 媒体标签前的纯文本 */ - textBeforeFirstTag: string; - /** 媒体标签后的剩余文本 */ - textAfterLastTag: string; - /** 完整的发送队列(标签间的文本 + 媒体项) */ - mediaQueue: SendQueueItem[]; -} - -/** - * 将文本按富媒体标签拆分为三部分 - * - * 用于两个场景: - * 1. 流式模式:中断-恢复流程(标签前文本 → 结束流式 → 发送媒体 → 新流式 → 标签后文本) - * 2. 普通模式:构建按顺序发送的队列 - */ -export function splitByMediaTags( - text: string, - log?: { - info?: (msg: string) => void; - debug?: (msg: string) => void; - error?: (msg: string) => void; - }, -): MediaSplitResult { - const normalized = normalizeMediaTags(text); - const regex = createMediaTagRegex(); - // 过滤掉代码块内的匹配 - const matches = [...normalized.matchAll(regex)].filter( - (m) => !isInsideCodeBlock(normalized, m.index), - ); - - if (matches.length === 0) { - return { - hasMediaTags: false, - textBeforeFirstTag: normalized, - textAfterLastTag: "", - mediaQueue: [], - }; - } - - // 第一个标签前的纯文本 - const firstMatch = matches[0]; - const textBeforeFirstTag = normalized - .slice(0, firstMatch.index) - .replace(/\n{3,}/g, "\n\n") - .trim(); - - // 最后一个标签后的纯文本 - const lastMatch = matches[matches.length - 1]; - const lastMatchEnd = lastMatch.index + lastMatch[0].length; - const textAfterLastTag = normalized - .slice(lastMatchEnd) - .replace(/\n{3,}/g, "\n\n") - .trim(); - - // 构建媒体发送队列 - const mediaQueue: SendQueueItem[] = []; - let lastIndex = firstMatch.index; - - for (const match of matches) { - // 标签前的文本(标签之间的间隔文本) - const textBetween = normalized - .slice(lastIndex, match.index) - .replace(/\n{3,}/g, "\n\n") - .trim(); - if (textBetween && lastIndex !== firstMatch.index) { - // 只添加非首段的间隔文本(首段由 textBeforeFirstTag 覆盖) - mediaQueue.push({ type: "text", content: textBetween }); - } - - // 解析标签内容 - const tagName = match[1].toLowerCase(); - let mediaPath = match[2]?.trim() ?? ""; - - // 剥离 MEDIA: 前缀 - if (mediaPath.startsWith("MEDIA:")) { - mediaPath = mediaPath.slice("MEDIA:".length); - } - mediaPath = normalizePath(mediaPath); - - // 修复路径编码问题 - mediaPath = fixPathEncoding(mediaPath, log); - - // 根据标签类型加入队列 - const typeMap: Record = { - qqimg: "image", - qqvoice: "voice", - qqvideo: "video", - qqfile: "file", - qqmedia: "media", - }; - const itemType = typeMap[tagName] ?? "image"; - if (mediaPath) { - mediaQueue.push({ type: itemType, content: mediaPath }); - log?.info?.(`Found ${itemType} in <${tagName}>: ${mediaPath.slice(0, 80)}`); - } - - lastIndex = match.index + match[0].length; - } - - return { - hasMediaTags: true, - textBeforeFirstTag, - textAfterLastTag, - mediaQueue, - }; -} - // ============ 发送队列执行 ============ /** diff --git a/extensions/qqbot/src/engine/messaging/target-parser.ts b/extensions/qqbot/src/engine/messaging/target-parser.ts index e0c43be6eda..1831ce576a4 100644 --- a/extensions/qqbot/src/engine/messaging/target-parser.ts +++ b/extensions/qqbot/src/engine/messaging/target-parser.ts @@ -66,24 +66,6 @@ export function parseTarget(to: string): ParsedTarget { return { type: "c2c", id }; } -/** - * Map a parsed target type to a ChatScope for API calls. - * - * Channel and DM targets are not C2C/Group scoped and should be handled - * separately by the caller. - * - * @returns `'c2c'` or `'group'`, or `undefined` for channel targets. - */ -export function targetToChatScope(target: ParsedTarget): "c2c" | "group" | undefined { - if (target.type === "c2c") { - return "c2c"; - } - if (target.type === "group") { - return "group"; - } - return undefined; -} - /** * Normalize a QQ Bot target string into the canonical `qqbot:...` form. * diff --git a/extensions/qqbot/src/exec-approvals.ts b/extensions/qqbot/src/exec-approvals.ts index effedd16234..49c7d67863d 100644 --- a/extensions/qqbot/src/exec-approvals.ts +++ b/extensions/qqbot/src/exec-approvals.ts @@ -215,15 +215,4 @@ const qqbotExecApprovalProfile = createChannelExecApprovalProfile({ export const isQQBotExecApprovalClientEnabled = qqbotExecApprovalProfile.isClientEnabled; export const isQQBotExecApprovalApprover = qqbotExecApprovalProfile.isApprover; export const isQQBotExecApprovalAuthorizedSender = qqbotExecApprovalProfile.isAuthorizedSender; -export const resolveQQBotExecApprovalTarget = qqbotExecApprovalProfile.resolveTarget; export const shouldHandleQQBotExecApprovalRequest = qqbotExecApprovalProfile.shouldHandleRequest; - -export function isQQBotExecApprovalHandlerConfigured(params: { - cfg: OpenClawConfig; - accountId?: string | null; -}): boolean { - return isChannelExecApprovalClientEnabledFromConfig({ - enabled: resolveQQBotExecApprovalConfig(params)?.enabled, - approverCount: getQQBotExecApprovalApprovers(params).length, - }); -}