function envBool(key: string, fallback: boolean): boolean { const raw = process.env[key]?.trim().toLowerCase(); if (!raw) return fallback; return raw === "1" || raw === "true" || raw === "yes"; } function envInt(key: string, fallback: number): number { const raw = process.env[key]?.trim(); if (!raw) return fallback; const n = Number.parseInt(raw, 10); return Number.isFinite(n) && n > 0 ? n : fallback; } function parseDurationMs(value: string | undefined, fallbackMs: number): number { if (!value?.trim()) return fallbackMs; const m = value.trim().match(/^(\d+)(ms|s|m|h)$/i); if (!m) return fallbackMs; const n = Number.parseInt(m[1], 10); const unit = m[2].toLowerCase(); if (unit === "ms") return n; if (unit === "s") return n * 1000; if (unit === "m") return n * 60_000; return n * 3_600_000; } /** 已停用的搜尋環境變數(若存在會被忽略) */ export const LEGACY_SEARCH_ENV_KEYS = [ "SERPAPI_API_KEY", "SERPER_API_KEY", "GOOGLE_SEARCH_API_KEY", "GOOGLE_CSE_API_KEY", "GOOGLE_CSE_CX", "GOOGLE_CSE_ID", "BING_SEARCH_API_KEY", "TAVILY_API_KEY", "EXA_API_KEY", "SEARXNG_BASE_URL", "DUCKDUCKGO_ENABLED", ] as const; export function detectLegacySearchEnvKeys(): string[] { return LEGACY_SEARCH_ENV_KEYS.filter((k) => !!process.env[k]?.trim()); } export function getSearchConfig() { return { threads: { enabled: envBool("THREADS_API_ENABLED", true), queryLimitPerDay: envInt("THREADS_QUERY_LIMIT_PER_DAY", 2200), }, brave: { enabled: envBool("BRAVE_SEARCH_ENABLED", true), apiKey: process.env.BRAVE_SEARCH_API_KEY?.trim() ?? "", baseUrl: process.env.BRAVE_SEARCH_BASE_URL?.trim() || "https://api.search.brave.com/res/v1/web/search", dailyLimit: envInt("BRAVE_DAILY_LIMIT", 30), resultLimit: envInt("BRAVE_RESULT_LIMIT", 10), cacheTtlMs: parseDurationMs(process.env.BRAVE_CACHE_TTL, 4 * 3_600_000), scanMaxQueries: envInt("SCAN_BRAVE_MAX_QUERIES", 8), }, crawler: { enabled: envBool("CRAWLER_ENABLED", true), cacheTtlMs: parseDurationMs(process.env.CRAWLER_CACHE_TTL, 3_600_000), }, dedupe: { notifyTtlMs: 24 * 3_600_000, }, threadsCacheTtlMs: parseDurationMs(process.env.THREADS_SEARCH_CACHE_TTL, 15 * 60_000), }; } export type SearchConfig = ReturnType;