Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/Java/Openclaw/extensions/qqbot/src/engine/utils/   (KI Agentensystem Version 22©)  Datei vom 26.3.2026 mit Größe 10 kB image not shown  

Quelle  platform.ts

  Sprache: JAVA
 

Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

/**
 * Cross-platform path and detection helpers for core/ modules.
 *
 * Provides home/data/media directory helpers, platform detection,
 * ffmpeg/silk-wasm availability checks — all without importing
 * `openclaw/plugin-sdk`. The temp-directory fallback is delegated
 * to the PlatformAdapter.
 */

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

/**
 * Resolve the current user's home directory safely across platforms.
 *
 * Priority:
 * 1. `os.homedir()`
 * 2. `$HOME` or `%USERPROFILE%`
 * 3. PlatformAdapter.getTempDir() as a last resort
 */
export function getHomeDir(): string {
  try {
    const home = os.homedir();
    if (home && fs.existsSync(home)) {
      return home;
    }
  } catch {
    /* fallback */
  }

  const envHome = process.env.HOME || process.env.USERPROFILE;
  if (envHome && fs.existsSync(envHome)) {
    return envHome;
  }

  return getPlatformAdapter().getTempDir();
}

/** 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` without creating it.
 *
 * Unlike `getQQBotDataPath`, this lives under OpenClaw's core media allowlist so
 * downloaded images and audio can be accessed by framework media tooling.
 */
export function getQQBotMediaPath(...subPaths: string[]): string {
  return path.join(getHomeDir(), ".openclaw", "media", "qqbot", ...subPaths);
}

/** 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;
}

/**
 * Return `~/.openclaw/media`, OpenClaw's shared media root.
 *
 * This mirrors the directory that core's `buildMediaLocalRoots` exposes as an
 * allowlisted location (see `openclaw/src/media/local-roots.ts`). Using it as a
 * QQ Bot payload root lets the plugin trust framework-produced files that live
 * in sibling subdirectories such as `outbound/` (written by
 * `saveMediaBuffer(..., "outbound", ...)`) or `inbound/`, while still keeping
 * the check anchored to a single, well-known directory.
 */
export function getOpenClawMediaDir(): string {
  return path.join(getHomeDir(), ".openclaw", "media");
}

// ---- Basic platform information ----

export type PlatformType = "darwin" | "linux" | "win32" | "other";

export function getPlatform(): PlatformType {
  const p = process.platform;
  if (p === "darwin" || p === "linux" || p === "win32") {
    return p;
  }
  return "other";
}

export function isWindows(): boolean {
  return process.platform === "win32";
}

/** Return the preferred temporary directory. */
export function getTempDir(): string {
  return getPlatformAdapter().getTempDir();
}

// ---- ffmpeg detection ----

let _ffmpegPath: string | null | undefined;
let _ffmpegCheckPromise: Promise<string | null> | null = null;

/** Detect ffmpeg and return an executable path when available. */
export function detectFfmpeg(): Promise<string | null> {
  if (_ffmpegPath !== undefined) {
    return Promise.resolve(_ffmpegPath);
  }
  if (_ffmpegCheckPromise) {
    return _ffmpegCheckPromise;
  }

  _ffmpegCheckPromise = (async () => {
    const envPath = process.env.FFMPEG_PATH;
    if (envPath) {
      const ok = await testExecutable(envPath, ["-version"]);
      if (ok) {
        _ffmpegPath = envPath;
        debugLog(`[platform] ffmpeg found via FFMPEG_PATH: ${envPath}`);
        return _ffmpegPath;
      }
      debugWarn(`[platform] FFMPEG_PATH set but not working: ${envPath}`);
    }

    const cmd = isWindows() ? "ffmpeg.exe" : "ffmpeg";
    const ok = await testExecutable(cmd, ["-version"]);
    if (ok) {
      _ffmpegPath = cmd;
      debugLog(`[platform] ffmpeg detected in PATH`);
      return _ffmpegPath;
    }

    const commonPaths = isWindows()
      ? [
          "C:\\ffmpeg\\bin\\ffmpeg.exe",
          path.join(process.env.LOCALAPPDATA || "", "Programs", "ffmpeg", "bin", "ffmpeg.exe"),
          path.join(process.env.ProgramFiles || "", "ffmpeg", "bin", "ffmpeg.exe"),
        ]
      : [
          "/usr/local/bin/ffmpeg",
          "/opt/homebrew/bin/ffmpeg",
          "/usr/bin/ffmpeg",
          "/snap/bin/ffmpeg",
        ];

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

    _ffmpegPath = null;
    return null;
  })().finally(() => {
    _ffmpegCheckPromise = null;
  });

  return _ffmpegCheckPromise;
}

/** Return true when an executable responds successfully to the given args. */
function testExecutable(cmd: string, args: string[]): Promise<boolean> {
  return new Promise((resolve) => {
    execFile(cmd, args, { timeout: 5000 }, (err) => {
      resolve(!err);
    });
  });
}

/** Reset ffmpeg detection state, mainly for tests. */
export function resetFfmpegCache(): void {
  _ffmpegPath = undefined;
  _ffmpegCheckPromise = null;
}

// ---- silk-wasm detection ----

let _silkWasmAvailable: boolean | null = null;

/** 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) {
    return false;
  }
  if (p.startsWith("file://")) {
    return true;
  }
  if (p === "~" || p.startsWith("~/") || p.startsWith("~\\")) {
    return true;
  }
  if (p.startsWith("/")) {
    return true;
  }
  if (/^[a-zA-Z]:[\\/]/.test(p)) {
    return true;
  }
  if (p.startsWith("\\\\")) {
    return true;
  }
  if (p.startsWith("./") || p.startsWith("../")) {
    return true;
  }
  if (p.startsWith(".\\") || p.startsWith("..\\")) {
    return true;
  }
  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 {
  const relative = path.relative(root, candidate);
  return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
}

/** 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 homeDir = getHomeDir();
  const mediaRoot = getQQBotMediaPath();
  const dataRoot = getQQBotDataPath();
  const workspaceRoot = path.join(homeDir, ".openclaw", "workspace", "qqbot");
  const candidateRoots = [
    { from: workspaceRoot, to: mediaRoot },
    { from: dataRoot, to: mediaRoot },
    { from: mediaRoot, to: dataRoot },
  ];

  for (const { from, to } of candidateRoots) {
    if (!isPathWithinRoot(normalized, from)) {
      continue;
    }
    const relative = path.relative(from, normalized);
    const candidate = path.join(to, relative);
    if (fs.existsSync(candidate)) {
      debugWarn(`[platform] Remapped missing QQBot media path ${normalized} -> ${candidate}`);
      return candidate;
    }
  }

  return normalized;
}

/**
 * Resolve a structured-payload local file path and enforce that it stays within
 * QQ Bot-owned storage roots.
 */
export function resolveQQBotPayloadLocalFilePath(p: string): string | null {
  const candidate = resolveQQBotLocalMediaPath(p);
  if (!candidate.trim()) {
    return null;
  }

  const resolvedCandidate = path.resolve(candidate);
  if (!fs.existsSync(resolvedCandidate)) {
    return null;
  }

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

  return null;
}

¤ Dauer der Verarbeitung: 0.18 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

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.