Lumi/plugins/lumi_ai_web_search/backend/settings.js
2026-06-13 21:32:36 +02:00

88 lines
3.5 KiB
JavaScript

const fs = require("fs");
const path = require("path");
const metadata = require("../tool_info.json");
function defaults() {
return Object.fromEntries(
Object.entries(metadata.settings_schema).map(([key, field]) => [key, structuredClone(field.default)])
);
}
function readSettings(dataDir) {
const fallback = defaults();
try {
const stored = JSON.parse(fs.readFileSync(settingsPath(dataDir), "utf8"));
return normalizeSettings({ ...fallback, ...stored });
} catch {
return normalizeSettings(fallback);
}
}
function writeSettings(dataDir, value) {
const normalized = normalizeSettings({ ...defaults(), ...value });
fs.mkdirSync(dataDir, { recursive: true });
const file = settingsPath(dataDir);
const temporary = `${file}.${process.pid}.tmp`;
fs.writeFileSync(temporary, `${JSON.stringify(normalized, null, 2)}\n`, { mode: 0o600 });
try { fs.chmodSync(temporary, 0o600); } catch {}
fs.renameSync(temporary, file);
return normalized;
}
function normalizeSettings(value) {
const mode = value.policy_mode === "blacklist" ? "blacklist" : "whitelist";
const adapter = value.provider_adapter === "generic_json" ? "generic_json" : "searxng_json";
const safeSearch = ["off", "moderate", "strict"].includes(value.safe_search) ? value.safe_search : "strict";
return {
enabled: value.enabled === true,
policy_mode: mode,
url_rules: stringList(value.url_rules, 200),
max_results: integer(value.max_results, 1, 10, 5),
search_timeout_ms: integer(value.search_timeout_ms, 1000, 30000, 8000),
cache_ttl_seconds: integer(value.cache_ttl_seconds, 0, 3600, 300),
safe_search: safeSearch,
allowed_origins: stringList(value.allowed_origins, 6)
.filter((origin) => ["webui", "discord", "twitch", "youtube", "kick", "other"].includes(origin)),
webui_output_chars: integer(value.webui_output_chars, 300, 12000, 4000),
discord_output_chars: integer(value.discord_output_chars, 200, 4000, 1200),
twitch_output_chars: integer(value.twitch_output_chars, 120, 1000, 350),
youtube_output_chars: integer(value.youtube_output_chars, 120, 1500, 500),
kick_output_chars: integer(value.kick_output_chars, 120, 1000, 350),
other_output_chars: integer(value.other_output_chars, 120, 2000, 500),
provider_adapter: adapter,
provider_endpoint: String(value.provider_endpoint || "").trim(),
provider_api_key: String(value.provider_api_key || "").trim(),
provider_api_key_header: value.provider_api_key_header === "Authorization" ? "Authorization" : "X-API-Key",
provider_api_key_prefix: String(value.provider_api_key_prefix || "").trim().slice(0, 32),
provider_query_parameter: /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/.test(String(value.provider_query_parameter || ""))
? String(value.provider_query_parameter)
: "q",
show_source_links: value.show_source_links !== false,
allow_full_page_fetch: value.allow_full_page_fetch === true,
requests_per_minute: integer(value.requests_per_minute, 1, 60, 6)
};
}
function settingsPath(dataDir) {
return path.join(dataDir, "settings.json");
}
function integer(value, minimum, maximum, fallback) {
const number = Number.parseInt(value, 10);
return Number.isFinite(number) ? Math.max(minimum, Math.min(maximum, number)) : fallback;
}
function stringList(value, limit) {
const rows = Array.isArray(value) ? value : String(value || "").split(/\r?\n|,/);
return [...new Set(rows.map((entry) => String(entry).trim()).filter(Boolean))].slice(0, limit);
}
module.exports = {
defaults,
normalizeSettings,
readSettings,
settingsPath,
writeSettings
};