import { execFile } from "node:child_process"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { getPlatformAdapter } from "../adapter/index.js"; import { formatErrorMessage } from "./format.js"; import { debugLog, debugWarn } from "./log.js";
/** Return a path under `~/.openclaw/qqbot` without creating it. */
export function getQQBotDataPath(...subPaths: string[]): string { return path.join(getHomeDir(), ".openclaw", "qqbot", ...subPaths);
}
/** Return a path under `~/.openclaw/qqbot`, creating it on demand. */
export function getQQBotDataDir(...subPaths: string[]): string { const dir = getQQBotDataPath(...subPaths); if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
} return dir;
}
/** Return a path under `~/.openclaw/media/qqbot`, creating it on demand. */
export function getQQBotMediaDir(...subPaths: string[]): string { const dir = getQQBotMediaPath(...subPaths); if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
} return dir;
}
for (const p of commonPaths) { if (p && fs.existsSync(p)) { const works = await testExecutable(p, ["-version"]); if (works) {
_ffmpegPath = p;
debugLog(`[platform] ffmpeg found at: ${p}`); return _ffmpegPath;
}
}
}
/** Check whether silk-wasm can run in the current environment. */
export async function checkSilkWasmAvailable(): Promise<boolean> { if (_silkWasmAvailable !== null) { return _silkWasmAvailable;
} try { const { isSilk } = await import("silk-wasm");
isSilk(new Uint8Array(0));
_silkWasmAvailable = true;
debugLog("[platform] silk-wasm: available");
} catch (err) {
_silkWasmAvailable = false;
debugWarn(`[platform] silk-wasm: NOT available (${formatErrorMessage(err)})`);
} return _silkWasmAvailable;
}
// ---- Tilde expansion and path normalization ----
/** Expand `~` to the current user's home directory. */
export function expandTilde(p: string): string { if (!p) { return p;
} if (p === "~") { return getHomeDir();
} if (p.startsWith("~/") || p.startsWith("~\\")) { return path.join(getHomeDir(), p.slice(2));
} return p;
}
/** Normalize a user-provided path by trimming, stripping `file://`, and expanding `~`. */
export function normalizePath(p: string): string {
let result = p.trim(); if (result.startsWith("file://")) {
result = result.slice("file://".length); try {
result = decodeURIComponent(result);
} catch { // Keep the raw string if decoding fails.
}
} return expandTilde(result);
}
// ---- Local path detection ----
/** Return true when the string looks like a local filesystem path rather than a URL. */
export function isLocalPath(p: string): boolean { if (!p) { returnfalse;
} if (p.startsWith("file://")) { returntrue;
} if (p === "~" || p.startsWith("~/") || p.startsWith("~\\")) { returntrue;
} if (p.startsWith("/")) { returntrue;
} if (/^[a-zA-Z]:[\\/]/.test(p)) { returntrue;
} if (p.startsWith("\\\\")) { returntrue;
} if (p.startsWith("./") || p.startsWith("../")) { returntrue;
} if (p.startsWith(".\\") || p.startsWith("..\\")) { returntrue;
} returnfalse;
}
/** Looser local-path heuristic used for markdown-extracted paths. */
export function looksLikeLocalPath(p: string): boolean { if (isLocalPath(p)) { returntrue;
} return /^(?:Users|home|tmp|var|private|[A-Z]:)/i.test(p);
}
/** Remap legacy or hallucinated QQ Bot local media paths to real files when possible. */
export function resolveQQBotLocalMediaPath(p: string): string { const normalized = normalizePath(p); if (!isLocalPath(normalized) || fs.existsSync(normalized)) { return normalized;
}
const resolvedCandidate = path.resolve(candidate); if (!fs.existsSync(resolvedCandidate)) { returnnull;
}
const canonicalCandidate = fs.realpathSync(resolvedCandidate); // Trust both the QQ Bot-owned subdirectory and OpenClaw's shared `~/.openclaw/media` // root. Core helpers like `saveMediaBuffer(..., "outbound", ...)` place framework // attachments under sibling directories (e.g. `media/outbound/`) that are already // part of the core media allowlist; we mirror that so auto-routed sends work // without leaving the plugin's trust boundary. const allowedRoots = [getOpenClawMediaDir(), getQQBotMediaPath()];
for (const root of allowedRoots) { const resolvedRoot = path.resolve(root); const canonicalRoot = fs.existsSync(resolvedRoot)
? fs.realpathSync(resolvedRoot)
: resolvedRoot; if (isPathWithinRoot(canonicalCandidate, canonicalRoot)) { return canonicalCandidate;
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.