From 0ac1a07f7c78a71f5df1bfbed22c7aba0d84375c Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 1 May 2026 09:02:40 +0100 Subject: [PATCH] refactor: prune unused extension helpers --- .../qqbot/src/engine/config/credentials.ts | 44 ---------------- .../src/engine/messaging/media-source.ts | 40 -------------- .../src/engine/messaging/media-type-detect.ts | 52 ------------------- .../engine/messaging/streaming-media-send.ts | 52 ------------------- .../qqbot/src/engine/utils/file-utils.ts | 11 ---- .../qqbot/src/engine/utils/image-size.ts | 9 ---- extensions/qqbot/src/engine/utils/platform.ts | 14 ----- .../qqbot/src/engine/utils/request-context.ts | 15 ------ .../qqbot/src/engine/utils/upload-cache.ts | 11 ---- .../slack/src/monitor/channel-config.ts | 38 +------------- extensions/tlon/src/monitor/discovery.ts | 24 +-------- extensions/tlon/src/urbit/story.ts | 15 ------ .../src/providers/twilio/twiml-policy.ts | 2 - extensions/voice-call/src/telephony-audio.ts | 6 +-- .../src/auto-reply/monitor/commands.ts | 10 ---- .../whatsapp/src/inbound/send-result.ts | 6 --- 16 files changed, 3 insertions(+), 346 deletions(-) diff --git a/extensions/qqbot/src/engine/config/credentials.ts b/extensions/qqbot/src/engine/config/credentials.ts index 94fea6fa15e..5320c9d4617 100644 --- a/extensions/qqbot/src/engine/config/credentials.ts +++ b/extensions/qqbot/src/engine/config/credentials.ts @@ -74,47 +74,3 @@ export function clearAccountCredentials( return { nextCfg, cleared, changed }; } - -// ---- Setup: clear a single credential field ---- - -export type CredentialField = "appId" | "clientSecret"; - -/** - * Clear a single credential field from a QQBot account config. - * - * Used by setup flows when switching to env-backed credential resolution. - * Returns a new config with the specified field removed. - */ -export function clearCredentialField( - cfg: Record, - accountId: string, - field: CredentialField, -): Record { - const next = { ...cfg }; - const channels = asRecord(cfg.channels); - const qqbot = { ...asRecord(channels?.qqbot) }; - - const clearField = (entry: Record) => { - if (field === "appId") { - delete entry.appId; - return; - } - delete entry.clientSecret; - delete entry.clientSecretFile; - }; - - if (accountId === DEFAULT_ACCOUNT_ID) { - clearField(qqbot); - } else { - const accounts = { ...(qqbot.accounts as Record> | undefined) }; - if (accounts[accountId]) { - const entry = { ...accounts[accountId] }; - clearField(entry); - accounts[accountId] = entry; - qqbot.accounts = accounts; - } - } - - next.channels = { ...channels, qqbot }; - return next; -} diff --git a/extensions/qqbot/src/engine/messaging/media-source.ts b/extensions/qqbot/src/engine/messaging/media-source.ts index 61973cb7068..7ced94cad9b 100644 --- a/extensions/qqbot/src/engine/messaging/media-source.ts +++ b/extensions/qqbot/src/engine/messaging/media-source.ts @@ -213,43 +213,3 @@ export async function normalizeSource( mime: raw.mime, }; } - -// ============ Materialization helpers ============ - -/** - * Read a {@link MediaSource} into the `{ url?, fileData?, fileName? }` shape - * expected by {@link MediaApi.uploadMedia} today (one-shot upload path). - * - * Chunked upload (future) should bypass this helper and feed the uploader - * directly from the `localPath` / `buffer` branch. - */ -export async function materializeForOneShotUpload( - source: MediaSource, -): Promise<{ url?: string; fileData?: string; fileName?: string }> { - switch (source.kind) { - case "url": - return { url: source.url }; - case "base64": - return { fileData: source.data }; - case "localPath": { - const opened = await openLocalFile(source.path); - try { - const buf = await opened.handle.readFile(); - return { fileData: buf.toString("base64") }; - } finally { - await opened.close(); - } - } - case "buffer": - return { - fileData: source.buffer.toString("base64"), - fileName: source.fileName, - }; - default: { - const _exhaustive: never = source; - throw new Error( - `materializeForOneShotUpload: unsupported MediaSource kind: ${JSON.stringify(_exhaustive)}`, - ); - } - } -} diff --git a/extensions/qqbot/src/engine/messaging/media-type-detect.ts b/extensions/qqbot/src/engine/messaging/media-type-detect.ts index f7cc80f54a3..f3365b795af 100644 --- a/extensions/qqbot/src/engine/messaging/media-type-detect.ts +++ b/extensions/qqbot/src/engine/messaging/media-type-detect.ts @@ -9,15 +9,6 @@ /** Supported media kind for QQ Bot outbound routing. */ export type MediaKind = "image" | "voice" | "video" | "file"; -/** Display labels for media kinds. */ -export const MEDIA_KIND_LABELS: Record = { - image: "Image", - voice: "Voice", - video: "Video", - file: "File", - media: "Media", -}; - const IMAGE_EXTENSIONS = new Set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"]); const VIDEO_EXTENSIONS = new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".wmv"]); const AUDIO_EXTENSIONS = new Set([ @@ -77,46 +68,3 @@ export function isAudioFile(filePath: string, mimeType?: string): boolean { } return AUDIO_EXTENSIONS.has(getCleanExtension(filePath)); } - -/** - * Auto-detect the media kind from a file path and optional MIME type. - * - * Priority: audio → video → image → file (default). - */ -export function detectMediaKind(filePath: string, mimeType?: string): MediaKind { - if (isAudioFile(filePath, mimeType)) { - return "voice"; - } - if (isVideoFile(filePath, mimeType)) { - return "video"; - } - if (isImageFile(filePath, mimeType)) { - return "image"; - } - return "file"; -} - -/** Return true when the source is a remote HTTP(S) URL. */ -export function isHttpSource(source: string): boolean { - return source.startsWith("http://") || source.startsWith("https://"); -} - -/** Return true when the source is a Base64 data URL. */ -export function isDataSource(source: string): boolean { - return source.startsWith("data:"); -} - -/** Return true when the source is a remote URL or data URL. */ -export function isRemoteOrDataSource(source: string): boolean { - return isHttpSource(source) || isDataSource(source); -} - -/** Common MIME type mapping for image extensions. */ -export const IMAGE_MIME_TYPES: Record = { - ".jpg": "image/jpeg", - ".jpeg": "image/jpeg", - ".png": "image/png", - ".gif": "image/gif", - ".webp": "image/webp", - ".bmp": "image/bmp", -}; diff --git a/extensions/qqbot/src/engine/messaging/streaming-media-send.ts b/extensions/qqbot/src/engine/messaging/streaming-media-send.ts index a3d174958de..1e7e95cb668 100644 --- a/extensions/qqbot/src/engine/messaging/streaming-media-send.ts +++ b/extensions/qqbot/src/engine/messaging/streaming-media-send.ts @@ -363,47 +363,6 @@ export function splitByMediaTags( }; } -/** - * 从文本中解析出完整的发送队列(含标签前后的纯文本) - * - * 与 splitByMediaTags 的区别: - * - splitByMediaTags 分为 before / queue / after 三段(供流式模式的中断-恢复) - * - parseMediaTagsToSendQueue 返回一个扁平的完整队列(供普通模式按顺序发送) - * - * 适用于 gateway.ts deliver 回调和 outbound.ts sendText。 - */ -export function parseMediaTagsToSendQueue( - text: string, - log?: { - info?: (msg: string) => void; - debug?: (msg: string) => void; - error?: (msg: string) => void; - }, -): { hasMediaTags: boolean; sendQueue: SendQueueItem[] } { - const split = splitByMediaTags(text, log); - - if (!split.hasMediaTags) { - return { hasMediaTags: false, sendQueue: [] }; - } - - const sendQueue: SendQueueItem[] = []; - - // 标签前的文本 - if (split.textBeforeFirstTag) { - sendQueue.push({ type: "text", content: split.textBeforeFirstTag }); - } - - // 媒体队列(含标签间文本) - sendQueue.push(...split.mediaQueue); - - // 标签后的文本 - if (split.textAfterLastTag) { - sendQueue.push({ type: "text", content: split.textAfterLastTag }); - } - - return { hasMediaTags: true, sendQueue }; -} - // ============ 发送队列执行 ============ /** @@ -527,17 +486,6 @@ export async function executeSendQueue( } } -/** - * 从文本中剥离所有媒体标签(用于最终显示) - */ -export function stripMediaTags(text: string): string { - const regex = createMediaTagRegex(); - return text - .replace(regex, "") - .replace(/\n{3,}/g, "\n\n") - .trim(); -} - /** * 检测文本中是否有未闭合的媒体标签,如果有则截断到安全位置。 * diff --git a/extensions/qqbot/src/engine/utils/file-utils.ts b/extensions/qqbot/src/engine/utils/file-utils.ts index 078ea400898..396d3408e53 100644 --- a/extensions/qqbot/src/engine/utils/file-utils.ts +++ b/extensions/qqbot/src/engine/utils/file-utils.ts @@ -107,17 +107,6 @@ export async function fileExistsAsync(filePath: string): Promise { } } -/** Get file size asynchronously. */ -export async function getFileSizeAsync(filePath: string): Promise { - const stat = await fs.promises.stat(filePath); - return stat.size; -} - -/** Return true when a file should be treated as large. */ -export function isLargeFile(sizeBytes: number): boolean { - return sizeBytes >= LARGE_FILE_THRESHOLD; -} - /** Format a byte count into a human-readable size string. */ export function formatFileSize(bytes: number): string { if (bytes < 1024) { diff --git a/extensions/qqbot/src/engine/utils/image-size.ts b/extensions/qqbot/src/engine/utils/image-size.ts index 865f2e3befd..d3b311e2b66 100644 --- a/extensions/qqbot/src/engine/utils/image-size.ts +++ b/extensions/qqbot/src/engine/utils/image-size.ts @@ -247,12 +247,3 @@ export function formatQQBotMarkdownImage(url: string, size: ImageSize | null): s export function hasQQBotImageSize(markdownImage: string): boolean { return /!\[#\d+px\s+#\d+px\]/.test(markdownImage); } - -/** Extract width and height from QQBot markdown image syntax: `![#Wpx #Hpx](url)`. */ -export function extractQQBotImageSize(markdownImage: string): ImageSize | null { - const match = markdownImage.match(/!\[#(\d+)px\s+#(\d+)px\]/); - if (match) { - return { width: Number.parseInt(match[1], 10), height: Number.parseInt(match[2], 10) }; - } - return null; -} diff --git a/extensions/qqbot/src/engine/utils/platform.ts b/extensions/qqbot/src/engine/utils/platform.ts index 2686980ecec..6da03f9b1a6 100644 --- a/extensions/qqbot/src/engine/utils/platform.ts +++ b/extensions/qqbot/src/engine/utils/platform.ts @@ -185,12 +185,6 @@ function testExecutable(cmd: string, args: string[]): Promise { }); } -/** Reset ffmpeg detection state, mainly for tests. */ -export function resetFfmpegCache(): void { - _ffmpegPath = undefined; - _ffmpegCheckPromise = null; -} - // ---- silk-wasm detection ---- let _silkWasmAvailable: boolean | null = null; @@ -273,14 +267,6 @@ export function isLocalPath(p: string): boolean { return false; } -/** Looser local-path heuristic used for markdown-extracted paths. */ -export function looksLikeLocalPath(p: string): boolean { - if (isLocalPath(p)) { - return true; - } - return /^(?:Users|home|tmp|var|private|[A-Z]:)/i.test(p); -} - // ---- QQBot media path resolution ---- function isPathWithinRoot(candidate: string, root: string): boolean { diff --git a/extensions/qqbot/src/engine/utils/request-context.ts b/extensions/qqbot/src/engine/utils/request-context.ts index ac579cdd0f9..ba682546430 100644 --- a/extensions/qqbot/src/engine/utils/request-context.ts +++ b/extensions/qqbot/src/engine/utils/request-context.ts @@ -58,18 +58,3 @@ export function runWithRequestContext(ctx: RequestContext, fn: () => T): T { export function getRequestContext(): RequestContext | undefined { return store.getStore(); } - -/** - * Convenience accessor for the current request's fully qualified - * delivery target. - */ -export function getRequestTarget(): string | undefined { - return store.getStore()?.target; -} - -/** - * Convenience accessor for the current request's account ID. - */ -export function getRequestAccountId(): string | undefined { - return store.getStore()?.accountId; -} diff --git a/extensions/qqbot/src/engine/utils/upload-cache.ts b/extensions/qqbot/src/engine/utils/upload-cache.ts index fcb912abd97..2ca8e7a6b6e 100644 --- a/extensions/qqbot/src/engine/utils/upload-cache.ts +++ b/extensions/qqbot/src/engine/utils/upload-cache.ts @@ -94,14 +94,3 @@ export function setCachedFileInfo( `[upload-cache] Cache SET: key=${key.slice(0, 40)}..., ttl=${effectiveTtl}s, uuid=${fileUuid}`, ); } - -/** Return cache stats for diagnostics. */ -export function getUploadCacheStats(): { size: number; maxSize: number } { - return { size: cache.size, maxSize: MAX_CACHE_SIZE }; -} - -/** Clear the upload cache. */ -export function clearUploadCache(): void { - cache.clear(); - debugLog(`[upload-cache] Cache cleared`); -} diff --git a/extensions/slack/src/monitor/channel-config.ts b/extensions/slack/src/monitor/channel-config.ts index 955dadb9cf4..6729093bdbf 100644 --- a/extensions/slack/src/monitor/channel-config.ts +++ b/extensions/slack/src/monitor/channel-config.ts @@ -4,10 +4,9 @@ import { resolveChannelEntryMatchWithFallback, type ChannelMatchSource, } from "openclaw/plugin-sdk/channel-targets"; -import type { SlackReactionNotificationMode } from "openclaw/plugin-sdk/config-types"; import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; import type { SlackMessageEvent } from "../types.js"; -import { allowListMatches, normalizeAllowListLower, normalizeSlackSlug } from "./allow-list.js"; +import { normalizeSlackSlug } from "./allow-list.js"; export type SlackChannelConfigResolved = { allowed: boolean; @@ -40,41 +39,6 @@ function firstDefined(...values: Array) { return undefined; } -export function shouldEmitSlackReactionNotification(params: { - mode: SlackReactionNotificationMode | undefined; - botId?: string | null; - messageAuthorId?: string | null; - userId: string; - userName?: string | null; - allowlist?: Array | null; - allowNameMatching?: boolean; -}) { - const { mode, botId, messageAuthorId, userId, userName, allowlist } = params; - const effectiveMode = mode ?? "own"; - if (effectiveMode === "off") { - return false; - } - if (effectiveMode === "own") { - if (!botId || !messageAuthorId) { - return false; - } - return messageAuthorId === botId; - } - if (effectiveMode === "allowlist") { - if (!Array.isArray(allowlist) || allowlist.length === 0) { - return false; - } - const users = normalizeAllowListLower(allowlist); - return allowListMatches({ - allowList: users, - id: userId, - name: userName ?? undefined, - allowNameMatching: params.allowNameMatching, - }); - } - return true; -} - export function resolveSlackChannelLabel(params: { channelId?: string; channelName?: string }) { const channelName = params.channelName?.trim(); if (channelName) { diff --git a/extensions/tlon/src/monitor/discovery.ts b/extensions/tlon/src/monitor/discovery.ts index 71d99a13720..1a19b1cdfc1 100644 --- a/extensions/tlon/src/monitor/discovery.ts +++ b/extensions/tlon/src/monitor/discovery.ts @@ -1,28 +1,6 @@ import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime"; import type { Foreigns } from "../urbit/foreigns.js"; -import { asRecord, formatChangesDate, formatErrorMessage } from "./utils.js"; - -export async function fetchGroupChanges( - api: { scry: (path: string) => Promise }, - runtime: RuntimeEnv, - daysAgo = 5, -) { - try { - const changeDate = formatChangesDate(daysAgo); - runtime.log?.(`[tlon] Fetching group changes since ${daysAgo} days ago (${changeDate})...`); - const changes = await api.scry(`/groups-ui/v5/changes/${changeDate}.json`); - if (changes) { - runtime.log?.("[tlon] Successfully fetched changes data"); - return changes; - } - return null; - } catch (error: unknown) { - runtime.log?.( - `[tlon] Failed to fetch changes (falling back to full init): ${formatErrorMessage(error)}`, - ); - return null; - } -} +import { asRecord, formatErrorMessage } from "./utils.js"; export interface InitData { channels: string[]; diff --git a/extensions/tlon/src/urbit/story.ts b/extensions/tlon/src/urbit/story.ts index ad4e786cb35..cebee8dae6b 100644 --- a/extensions/tlon/src/urbit/story.ts +++ b/extensions/tlon/src/urbit/story.ts @@ -330,18 +330,3 @@ export function markdownToStory(markdown: string): Story { return story; } - -/** - * Convert plain text to simple story (no markdown parsing) - */ -export function textToStory(text: string): Story { - return [{ inline: [text] }]; -} - -/** - * Check if text contains markdown formatting - */ -export function hasMarkdown(text: string): boolean { - // Check for common markdown patterns - return /(\*\*|__|~~|`|^#{1,6}\s|^```|^\s*[-*]\s|\[.*\]\(.*\)|^>\s)/m.test(text); -} diff --git a/extensions/voice-call/src/providers/twilio/twiml-policy.ts b/extensions/voice-call/src/providers/twilio/twiml-policy.ts index c4d4ad19ec3..679fc692ea9 100644 --- a/extensions/voice-call/src/providers/twilio/twiml-policy.ts +++ b/extensions/voice-call/src/providers/twilio/twiml-policy.ts @@ -1,8 +1,6 @@ import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime"; import type { WebhookContext } from "../../types.js"; -export type TwimlResponseKind = "empty" | "pause" | "queue" | "stored" | "stream"; - export type TwimlRequestView = { callStatus: string | null; direction: string | null; diff --git a/extensions/voice-call/src/telephony-audio.ts b/extensions/voice-call/src/telephony-audio.ts index ca128275b41..c6820854b85 100644 --- a/extensions/voice-call/src/telephony-audio.ts +++ b/extensions/voice-call/src/telephony-audio.ts @@ -1,8 +1,4 @@ -export { - convertPcmToMulaw8k, - pcmToMulaw, - resamplePcmTo8k, -} from "openclaw/plugin-sdk/realtime-voice"; +export { convertPcmToMulaw8k, resamplePcmTo8k } from "openclaw/plugin-sdk/realtime-voice"; /** * Chunk audio buffer into 20ms frames for streaming (8kHz mono mu-law). diff --git a/extensions/whatsapp/src/auto-reply/monitor/commands.ts b/extensions/whatsapp/src/auto-reply/monitor/commands.ts index d656df0e709..54adb501940 100644 --- a/extensions/whatsapp/src/auto-reply/monitor/commands.ts +++ b/extensions/whatsapp/src/auto-reply/monitor/commands.ts @@ -1,13 +1,3 @@ -import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; - -export function isStatusCommand(body: string) { - const trimmed = normalizeLowercaseStringOrEmpty(body); - if (!trimmed) { - return false; - } - return trimmed === "/status" || trimmed === "status" || trimmed.startsWith("/status "); -} - export function stripMentionsForCommand( text: string, mentionRegexes: RegExp[], diff --git a/extensions/whatsapp/src/inbound/send-result.ts b/extensions/whatsapp/src/inbound/send-result.ts index 5ff428b0b7a..a24f31f8271 100644 --- a/extensions/whatsapp/src/inbound/send-result.ts +++ b/extensions/whatsapp/src/inbound/send-result.ts @@ -59,9 +59,3 @@ export function combineWhatsAppSendResults( providerAccepted: results.some((result) => result.providerAccepted), }; } - -export function hasAcceptedWhatsAppSendResult( - result: WhatsAppSendResult | undefined, -): result is WhatsAppSendResult { - return result?.providerAccepted === true; -}