| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- /*
- Sub-Store operator (production-friendly)
- - Pull dynamic value from router/runtime and server/runtime JSON
- - Replace vmess server field for matched nodes
- */
- const ROUTER_VALUE_JSON_URL = "http://<router-lan-ip>:8080/current_ip.json";
- const SERVER_VALUE_JSON_URL = "https://git.dewofaurora.de/aurora/vmess-domain-rotator/raw/runtime-state/runtime/current_domain.json";
- const ROUTER_MIN_SPEED_MBPS = 5;
- const NODE_NAME_REGEX = /(argo|cf|vm|优选)/i;
- const CACHE_KEY = "vmess-domain-rotator:current";
- const CACHE_TTL_MS = 5 * 60 * 1000;
- const JSON_VALUE_KEYS = ["domain", "ip"];
- async function fetchRuntimeJson(url, sourceName) {
- const $ = $substore;
- const { body, statusCode } = await $.http.get({
- url,
- headers: {
- Accept: "application/json",
- "Cache-Control": "no-cache"
- },
- timeout: 5000
- });
- if (statusCode < 200 || statusCode >= 300) {
- throw new Error(`${sourceName} http status ${statusCode}`);
- }
- let obj;
- try {
- obj = JSON.parse(body || "{}");
- } catch (e) {
- throw new Error(`${sourceName} invalid json: ${e.message}`);
- }
- return obj;
- }
- function extractValue(obj, sourceName) {
- let value = "";
- for (const key of JSON_VALUE_KEYS) {
- value = String(obj[key] || "").trim().toLowerCase();
- if (value) break;
- }
- if (!value) {
- throw new Error(`${sourceName} empty value field, expected one of: ${JSON_VALUE_KEYS.join(", ")}`);
- }
- return value;
- }
- function parseRouterSpeedMbps(routerObj) {
- const raw = String(routerObj?.top_candidates?.[0]?.download_speed || "").trim();
- if (!raw) {
- return { mbps: null, raw: "", status: "missing", unit: "" };
- }
- const normalized = raw.toLowerCase().replace(/\s+/g, "");
- const match = normalized.match(/([0-9]+(?:\.[0-9]+)?)/);
- if (!match) {
- return { mbps: null, raw, status: "invalid", unit: "" };
- }
- const numeric = Number(match[1]);
- if (!Number.isFinite(numeric) || numeric < 0) {
- return { mbps: null, raw, status: "invalid", unit: "" };
- }
- let mbps = numeric;
- let unit = "unknown";
- if (normalized.includes("gb/s") || normalized.includes("g/s") || normalized.endsWith("gb") || normalized.endsWith("g")) {
- mbps = numeric * 1024;
- unit = "gb/s";
- } else if (normalized.includes("mb/s") || normalized.includes("m/s") || normalized.endsWith("mb") || normalized.endsWith("m")) {
- mbps = numeric;
- unit = "mb/s";
- } else if (normalized.includes("kb/s") || normalized.includes("k/s") || normalized.endsWith("kb") || normalized.endsWith("k")) {
- mbps = numeric / 1024;
- unit = "kb/s";
- } else if (normalized.includes("b/s")) {
- mbps = numeric / (1024 * 1024);
- unit = "b/s";
- }
- const status = unit === "unknown" ? "unknown_unit" : "ok";
- return { mbps, raw, status, unit };
- }
- async function selectValueRouterFirst() {
- const telemetry = {
- router: { ok: false, value: "", status: "", sourceType: "", error: "", speedRaw: "", speedMbps: null, speedParse: "missing", speedUnit: "" },
- server: { ok: false, value: "", status: "", sourceType: "", error: "" }
- };
- try {
- const routerObj = await fetchRuntimeJson(ROUTER_VALUE_JSON_URL, "router");
- const routerValue = extractValue(routerObj, "router");
- const speed = parseRouterSpeedMbps(routerObj);
- telemetry.router.ok = true;
- telemetry.router.value = routerValue;
- telemetry.router.status = String(routerObj?.status || "");
- telemetry.router.sourceType = String(routerObj?.source_type || "");
- telemetry.router.speedRaw = speed.raw;
- telemetry.router.speedMbps = speed.mbps;
- telemetry.router.speedParse = speed.status;
- telemetry.router.speedUnit = speed.unit;
- } catch (e) {
- telemetry.router.error = String(e.message || e);
- }
- try {
- const serverObj = await fetchRuntimeJson(SERVER_VALUE_JSON_URL, "server");
- const serverValue = extractValue(serverObj, "server");
- telemetry.server.ok = true;
- telemetry.server.value = serverValue;
- telemetry.server.status = String(serverObj?.status || "");
- telemetry.server.sourceType = String(serverObj?.source_type || "");
- } catch (e) {
- telemetry.server.error = String(e.message || e);
- }
- if (!telemetry.router.ok && !telemetry.server.ok) {
- throw new Error(`router failed: ${telemetry.router.error}; server failed: ${telemetry.server.error}`);
- }
- if (!telemetry.router.ok && telemetry.server.ok) {
- return { value: telemetry.server.value, chosenSource: "server", reason: "router_fail", telemetry };
- }
- if (telemetry.router.ok && !telemetry.server.ok) {
- return { value: telemetry.router.value, chosenSource: "router", reason: "server_fail", telemetry };
- }
- if (telemetry.router.speedParse === "ok" && telemetry.router.speedMbps < ROUTER_MIN_SPEED_MBPS) {
- return { value: telemetry.server.value, chosenSource: "server", reason: "router_low_speed", telemetry };
- }
- return { value: telemetry.router.value, chosenSource: "router", reason: "router_preferred", telemetry };
- }
- async function operator(proxies = [], targetPlatform, context) {
- const cache = scriptResourceCache;
- let value = cache.get(CACHE_KEY);
- let decision = null;
- if (!value) {
- try {
- decision = await selectValueRouterFirst();
- value = decision.value;
- cache.set(CACHE_KEY, value, CACHE_TTL_MS);
- } catch (e) {
- console.log(`[vmess-domain-rotator] fetch failed: ${e.message}`);
- return proxies;
- }
- }
- let updated = 0;
- for (const p of proxies) {
- if (!p || p.type !== "vmess") continue;
- if (!NODE_NAME_REGEX.test(p.name || "")) continue;
- if (p.server !== value) {
- p.server = value;
- updated += 1;
- }
- }
- if (decision) {
- const t = decision.telemetry;
- console.log(
- `[vmess-domain-rotator] chosen=${decision.chosenSource}, reason=${decision.reason}, router_status=${t.router.status || "n/a"}, server_status=${t.server.status || "n/a"}, router_speed_raw=${t.router.speedRaw || "n/a"}, router_speed_mbps=${t.router.speedMbps == null ? "n/a" : t.router.speedMbps.toFixed(3)}, speed_parse=${t.router.speedParse}, threshold_mbps=${ROUTER_MIN_SPEED_MBPS}, value=${value}, updated=${updated}, total=${proxies.length}, target=${targetPlatform}`
- );
- } else {
- console.log(`[vmess-domain-rotator] chosen=cache, value=${value}, updated=${updated}, total=${proxies.length}, target=${targetPlatform}`);
- }
- return proxies;
- }
|