mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:40:44 +00:00
refactor: prune stale qqbot helpers
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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<boolean> {
|
||||
return isQQBotAccountConfigured(cfg, accountId);
|
||||
}
|
||||
|
||||
async function linkViaQrCode(params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId: string;
|
||||
|
||||
@@ -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<string, unknown>): 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.
|
||||
*/
|
||||
|
||||
@@ -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<string, SendQueueItem["type"]> = {
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
// ============ 发送队列执行 ============
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user