refactor: prune unused extension helpers

This commit is contained in:
Peter Steinberger
2026-05-01 09:02:40 +01:00
parent 0c3d1892cd
commit 0ac1a07f7c
16 changed files with 3 additions and 346 deletions

View File

@@ -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<string, unknown>,
accountId: string,
field: CredentialField,
): Record<string, unknown> {
const next = { ...cfg };
const channels = asRecord(cfg.channels);
const qqbot = { ...asRecord(channels?.qqbot) };
const clearField = (entry: Record<string, unknown>) => {
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<string, Record<string, unknown>> | undefined) };
if (accounts[accountId]) {
const entry = { ...accounts[accountId] };
clearField(entry);
accounts[accountId] = entry;
qqbot.accounts = accounts;
}
}
next.channels = { ...channels, qqbot };
return next;
}

View File

@@ -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)}`,
);
}
}
}

View File

@@ -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<MediaKind | "media", string> = {
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<string, string> = {
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".png": "image/png",
".gif": "image/gif",
".webp": "image/webp",
".bmp": "image/bmp",
};

View File

@@ -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();
}
/**
* 检测文本中是否有未闭合的媒体标签,如果有则截断到安全位置。
*

View File

@@ -107,17 +107,6 @@ export async function fileExistsAsync(filePath: string): Promise<boolean> {
}
}
/** Get file size asynchronously. */
export async function getFileSizeAsync(filePath: string): Promise<number> {
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) {

View File

@@ -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;
}

View File

@@ -185,12 +185,6 @@ function testExecutable(cmd: string, args: string[]): Promise<boolean> {
});
}
/** 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 {

View File

@@ -58,18 +58,3 @@ export function runWithRequestContext<T>(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;
}

View File

@@ -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`);
}

View File

@@ -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<T>(...values: Array<T | undefined>) {
return undefined;
}
export function shouldEmitSlackReactionNotification(params: {
mode: SlackReactionNotificationMode | undefined;
botId?: string | null;
messageAuthorId?: string | null;
userId: string;
userName?: string | null;
allowlist?: Array<string | number> | 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) {

View File

@@ -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<unknown> },
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[];

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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).

View File

@@ -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[],

View File

@@ -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;
}