在 Sub-Store 内添加 Webshare 节点
𝕄𝔼𝕃𝕆𝕊
·
2026-05-01
·
via LINUX DO - 最新话题
之前写了个在 Sub-Store 里添加 webshare 节点的脚本 ( 适用于 Sub-Store 的 WebShare 节点生成脚本 ),但终归缺少了流量信息,现在补齐,需要在参数设置里设置 apiKey 。效果如图: 默认: • 无 NODE_PREFIX:节点名类似 01 • 无 DIALER_PROXY • 必填只需要 apiKey • 可选: • nodePrefix=WEB | 家宽 • dialerProxy=Relay • nodePrefix=none 表示不要前缀 • dialerProxy=none 表示不要前置代理 async function operator(proxies, targetPlatform, context) { proxies = Array.isArray(proxies) ? proxies : []; var SUBS_KEY = "subs"; var A = typeof $arguments !== "undefined" && $arguments ? $arguments : {}; var $ = typeof $substore !== "undefined" ? $substore : null; var PU = typeof ProxyUtils !== "undefined" && ProxyUtils ? ProxyUtils : {}; var B = PU.Buffer || (typeof Buffer !== "undefined" ? Buffer : null); var R = typeof scriptResourceCache !== "undefined" ? scriptResourceCache : null; var source = context && context.source ? context.source : null; var BASE = "https://proxy.webshare.io"; var VERSION = 12; var TIMEOUT = 8000; var NODE_TTL = 12 * 3600 * 1000; var NODE_KEEP = 7 * 24 * 3600 * 1000; var INFO_TTL = 30 * 60 * 1000; var INFO_KEEP = 7 * 24 * 3600 * 1000; var PAGE_SIZE = 100; var MAX_PAGES = 20; function arg(name, def) { var v = A ? A[name] : undefined; return v === undefined || v === null ? def || "" : String(v).trim(); } function isOff(v) { return /^(|0|false|no|none|null|off)$/i.test(String(v == null ? "" : v).trim()); } var API = String(arg("apiKey") || arg("apikey") || arg("token")).trim(); var NODE_PREFIX = arg("nodePrefix", ""); var DIALER_PROXY = arg("dialerProxy", ""); if (isOff(NODE_PREFIX)) NODE_PREFIX = ""; if (isOff(DIALER_PROXY)) DIALER_PROXY = ""; if (!$ || !$.http || !$.http.get) throw new Error("当前环境缺少 $substore.http.get"); if (!API) throw new Error("Sub-Store arguments 未填写:apiKey"); function log(s) { console.log("[Webshare] " + s); } function warn(s) { console.log("[Webshare] WARN: " + s); } function yes(v) { return /^(1|true|yes|y|on)$/i.test(String(v == null ? "" : v).trim()); } function hash(s) { var h = 0; s = String(s || ""); for (var i = 0; i < s.length; i++) { h = ((h << 5) - h + s.charCodeAt(i)) | 0; } return (h >>> 0).toString(16); } var PREFIX = "webshare:v" + VERSION + ":" + hash(API + "\n" + NODE_PREFIX + "\n" + DIALER_PROXY); var K_NODES = PREFIX + ":nodes"; var K_INFO = PREFIX + ":userinfo"; function redact(s) { s = String(s == null ? "" : s); if (API) s = s.split(API).join("[ApiKey]"); return s.replace(/Token\s+[A-Za-z0-9._-]+/gi, "Token [ApiKey]"); } function msg(e) { return redact(e && e.message ? e.message : e).slice(0, 500); } function makeError(message, props) { var e = new Error(message); if (props) { for (var k in props) e[k] = props[k]; } return e; } function opt() { return typeof $options !== "undefined" && $options ? $options : {}; } function flag(names) { var o = opt(); var q = o._req && o._req.query ? o._req.query : {}; for (var i = 0; i < names.length; i++) { var k = names[i]; if (yes(A[k]) || yes(o[k]) || yes(q[k])) return true; } return false; } function bodyText(body) { if (body == null) return ""; if (typeof body === "string") return body; if (B && B.from) { try { return B.from(body).toString("utf8"); } catch (e) {} } try { return JSON.stringify(body); } catch (e) { return String(body); } } function parseJson(x) { if (!x) return null; if (typeof x !== "string") return x; try { return JSON.parse(x); } catch (e) { return null; } } function isTransient(status) { status = Number(status); return [0, 408, 425, 429, 500, 502, 503, 504, 522, 523, 524].indexOf(status) >= 0; } async function getJson(path) { var url = /^https?:\/\//i.test(String(path)) ? String(path) : BASE + path; var r; try { r = await $.http.get.call($.http, { url: url, timeout: TIMEOUT, headers: { Authorization: "Token " + API, Accept: "application/json", "User-Agent": "Mozilla/5.0" } }); } catch (e) { throw makeError("请求失败:" + msg(e), { transient: true }); } var status = Number((r && (r.statusCode || r.status)) || 0); var text = bodyText(r && r.body); var data = null; try { data = text ? JSON.parse(text.replace(/^\uFEFF/, "")) : null; } catch (e) {} if (status < 200 || status >= 300) { throw makeError("HTTP " + status + ":" + redact(text).slice(0, 300), { status: status, transient: isTransient(status), auth: status === 401 || status === 403 }); } if (data === null) { throw makeError("响应不是 JSON", { transient: true }); } return data; } async function getPaged(path) { var out = []; var url = path; for (var i = 0; url && i < MAX_PAGES; i++) { var data = await getJson(url); var rows = []; if (data && Array.isArray(data.results)) rows = data.results; else if (Array.isArray(data)) rows = data; for (var j = 0; j < rows.length; j++) out.push(rows[j]); url = data && data.next ? String(data.next) : ""; } return out; } function cacheRead(key, freshMs, keepMs) { if (!R) return null; var o; try { o = parseJson(R.get(key)); } catch (e) { return null; } if (!o || o.v !== VERSION) return null; var at = Number(o.at || 0); var age = Date.now() - at; if (!at || !isFinite(age) || age < 0 || age >= keepMs) { cacheDel(key); return null; } o.age = age; o.fresh = age < freshMs; return o; } function cacheWrite(key, data, ttl) { if (!R) return; try { var o = { v: VERSION, at: Date.now() }; for (var k in data) o[k] = data[k]; R.set(key, JSON.stringify(o), ttl); } catch (e) { warn("缓存写入失败:" + msg(e)); } } function cacheDel(key) { if (!R) return; try { if (typeof R.delete === "function") return R.delete(key); if (typeof R.del === "function") return R.del(key); if (typeof R.remove === "function") return R.remove(key); R.set(key, "", 1000); } catch (e) {} } function clearCache() { cacheDel(K_NODES); cacheDel(K_INFO); log("已清理缓存"); } function padN(n, len) { var s = String(n); while (s.length < len) s = "0" + s; return s; } function num(x) { var n = Number(x); return isFinite(n) ? n : 0; } function cleanIso(s) { s = String(s || "").trim(); if (!s) return ""; return s.replace(/(\.\d{3})\d+(Z|[+-]\d{2}:?\d{2})$/i, "$1$2"); } function nowIso() { return new Date().toISOString(); } function toUnix(x) { if (x == null || x === "") return 0; if (typeof x === "number") { if (!isFinite(x) || x <= 0) return 0; return x > 1e12 ? Math.floor(x / 1000) : Math.floor(x); } var s = String(x).trim(); if (!s) return 0; if (/^\d+$/.test(s)) { var n = Number(s); if (!isFinite(n) || n <= 0) return 0; return n > 1e12 || s.length >= 13 ? Math.floor(n / 1000) : Math.floor(n); } var t = Date.parse(cleanIso(s)); return isFinite(t) ? Math.floor(t / 1000) : 0; } function flagEmoji(cc) { cc = String(cc || "US").toUpperCase(); if (cc === "ZZ") return "🌐"; if (!/^[A-Z]{2}$/.test(cc)) return ""; if (typeof String.fromCodePoint !== "function") return ""; return ( String.fromCodePoint(0x1f1e6 + cc.charCodeAt(0) - 65) + String.fromCodePoint(0x1f1e6 + cc.charCodeAt(1) - 65) ); } async function fetchNodes() { var rows = await getPaged("/api/v2/proxy/list/?mode=direct&page=1&page_size=" + PAGE_SIZE); var valid = []; for (var i = 0; i < rows.length; i++) { var p = rows[i]; if ( p && p.proxy_address && p.port && p.username && p.password && p.valid !== false && String(p.valid).toLowerCase() !== "false" ) { valid.push(p); } } if (!valid.length) { throw makeError("Webshare 没有返回可用代理", { business: true }); } var nodes = []; for (var j = 0; j < valid.length; j++) { var x = valid[j]; var icon = flagEmoji(x.country_code || x.country || "US"); var idx = padN(j + 1, 2); var label = NODE_PREFIX ? NODE_PREFIX + " " + idx : idx; var name = (icon ? icon + " " : "") + label; var node = { name: name, type: "socks5", server: String(x.proxy_address), port: Number(x.port), username: String(x.username), password: String(x.password) }; if (DIALER_PROXY) { node["dialer-proxy"] = DIALER_PROXY; } nodes.push(node); } return nodes; } async function fetchInfo() { var subscription = await getJson("/api/v2/subscription/"); var start = cleanIso(subscription && subscription.start_date); var end = cleanIso(subscription && subscription.end_date); var lte = nowIso(); if (!start) { throw makeError("subscription.start_date 为空,无法生成流量统计区间", { business: true }); } log("统计区间:" + start + " ~ " + lte); var statsPath = "/api/v2/stats/aggregate/" + "?timestamp__gte=" + encodeURIComponent(start) + "×tamp__lte=" + encodeURIComponent(lte); var res = await Promise.all([ getPaged("/api/v2/subscription/plan/"), getJson(statsPath) ]); var plans = res[0]; var stats = res[1]; var limitGb = 0; for (var i = 0; i < plans.length; i++) { var p = plans[i]; if (String((p && p.status) || "").toLowerCase() === "active") { limitGb += Math.max(0, num(p.bandwidth_limit)); } } if (limitGb <= 0) { throw makeError("未找到有效 active plan 流量额度", { business: true }); } var used = Math.max(0, Math.round(num(stats && stats.bandwidth_total))); var total = Math.round(limitGb * Math.pow(1024, 3)); var expire = toUnix(end); if (!expire) { throw makeError("subscription.end_date 解析失败,无法生成到期时间", { business: true }); } return "upload=0;download=" + used + ";total=" + total + ";expire=" + expire; } function currentSubName() { if (!source || typeof source !== "object" || source._collection) return ""; if (source.name) return source.name; for (var k in source) { if (k.charAt(0) === "_") continue; var sub = source[k]; if (sub && sub.name) return sub.name; if (sub && (sub.url || sub.content)) return k; } return ""; } function writeInfo(info) { if (!$ || !$.read || !$.write || !info) return false; try { var name = currentSubName(); if (!name) return false; var subs = $.read(SUBS_KEY) || []; if (typeof subs === "string") subs = JSON.parse(subs); if (!Array.isArray(subs)) return false; var sub = null; for (var i = 0; i < subs.length; i++) { if (subs[i] && subs[i].name === name) { sub = subs[i]; break; } } if (!sub) return false; if (sub.subUserinfo !== info) { sub.subUserinfo = info; $.write(subs, SUBS_KEY); log("已写入流量信息:" + info); } return true; } catch (e) { warn("写入流量信息失败:" + msg(e)); return false; } } async function ensureInfo(force) { var old = cacheRead(K_INFO, INFO_TTL, INFO_KEEP); if (old && old.fresh && !force) { writeInfo(old.info); return old.info; } try { var info = await fetchInfo(); cacheWrite(K_INFO, { info: info }, INFO_KEEP); writeInfo(info); log("生成流量信息:" + info); return info; } catch (e) { if (old && old.info && e && e.transient) { warn("流量刷新失败,使用旧缓存:" + msg(e)); writeInfo(old.info); return old.info; } warn("流量刷新失败:" + msg(e)); return ""; } } function append(nodes) { return proxies.concat(nodes); } if (flag(["clearCache", "clearcache", "clear-cache"])) { clearCache(); } var force = flag(["refresh", "force", "noCache", "nocache", "no-cache"]); var oldNodes = cacheRead(K_NODES, NODE_TTL, NODE_KEEP); if (oldNodes && oldNodes.fresh && !force && Array.isArray(oldNodes.nodes) && oldNodes.nodes.length) { log("命中节点缓存,节点数:" + oldNodes.nodes.length); await ensureInfo(false); return append(oldNodes.nodes); } try { var nodes = await fetchNodes(); cacheWrite(K_NODES, { nodes: nodes }, NODE_KEEP); await ensureInfo(force); log("生成节点数:" + nodes.length); return append(nodes); } catch (e) { if (oldNodes && Array.isArray(oldNodes.nodes) && oldNodes.nodes.length && e && e.transient) { warn("节点刷新失败,使用旧缓存:" + msg(e)); await ensureInfo(force); return append(oldNodes.nodes); } throw e; } } 1 个帖子 - 1 位参与者 阅读完整话题
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。