From a6374ac9c323590031e5c122936ba6736780ed06 Mon Sep 17 00:00:00 2001 From: Peng-YM <1048217874pengym@gmail.com> Date: Thu, 26 Nov 2020 13:43:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0Sub-Store=E5=88=86=E6=B5=81?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/sub-store.js | 3937 +++++++++++++++++++++----------------- backend/sub-store.min.js | 4 +- 2 files changed, 2154 insertions(+), 1787 deletions(-) diff --git a/backend/sub-store.js b/backend/sub-store.js index 10497be..b25abd9 100644 --- a/backend/sub-store.js +++ b/backend/sub-store.js @@ -24,10 +24,16 @@ function service() { const SETTINGS_KEY = "settings"; const SUBS_KEY = "subs"; const COLLECTIONS_KEY = "collections"; + const RULES_KEY = "rules"; + const BUILT_IN_KEY = "builtin"; // Initialization if (!$.read(SUBS_KEY)) $.write({}, SUBS_KEY); if (!$.read(COLLECTIONS_KEY)) $.write({}, COLLECTIONS_KEY); if (!$.read(SETTINGS_KEY)) $.write({}, SETTINGS_KEY); + if (!$.read(RULES_KEY)) $.write({}, RULES_KEY); + $.write({ + rules: getBuiltInRules(), + }, BUILT_IN_KEY); // download $app.get("/download/collection/:name", downloadCollection); @@ -53,6 +59,9 @@ function service() { .get(getAllCollections) .post(createCollection); + // rules API + $app.get("/download/rule/:name", downloadRule); + // gist backup $app.get("/api/backup"); @@ -84,10 +93,12 @@ function service() { res.set("location", "https://sub-store.vercel.app/").status(302).end(); }); - // handle preflight request - $app.options("/", async (req, res) => { - res.status(200).end(); - }); + // handle preflight request for QX + if (ENV().isQX) { + $app.options("/", async (req, res) => { + res.status(200).end(); + }); + } $app.all("/", (req, res) => { res.send("Hello from sub-store, made with ❤️ by Peng-YM"); @@ -409,6 +420,48 @@ function service() { }); } + // rule API + async function downloadRule(req, res) { + const {name} = req.params; + const {builtin} = req.query; + const platform = req.query.target || getPlatformFromHeaders(req.headers); + + $.info(`正在下载${builtin ? "内置" : ""}分流订阅:${name}...`); + + let rule; + if (builtin) { + rule = $.read(BUILT_IN_KEY)['rules'][name]; + } + if (rule) { + let rules = []; + for (let i = 0; i < rule.urls.length; i++) { + const url = rule.urls[i]; + $.info(`正在处理URL:${url},进度--${100 * ((i + 1) / rule.urls.length).toFixed(1)}% `); + try { + const {body} = await $.http.get(url); + const currentRules = RuleUtils.parse(body); + rules = rules.concat(currentRules); + } catch (err) { + $.error(`处理分流订阅中的URL: ${url}时出现错误:${err}! 该订阅已被跳过。`); + } + } + // remove duplicates + rules = await RuleUtils.process(rules, [{type: "Remove Duplicate"}]); + // produce output + const output = RuleUtils.produce(rules, platform); + res.send(output); + } else { + // rule not found + $.notify( + `🌍 [Sub-Store] 下载分流订阅失败`, + `❌ 未找到分流订阅:${name}!`, + ); + res.status(404).json({ + status: "failed", + }); + } + } + // settings API function getSettings(req, res) { const settings = $.read(SETTINGS_KEY); @@ -546,18 +599,1848 @@ function service() { $.log(`Use cached for url: ${url}`); return resource; } - const {body} = await $http.get(url); + + let body = ""; + try { + body = await $http.get(url); + } catch (err) { + throw new Error(err); + } finally { + $.write(body, key); + $.write(new Date().getTime(), timeKey); + } if (body.replace(/\s/g, "").length === 0) { throw new Error("订阅内容为空!"); } - $.write(body, key); - $.write(new Date().getTime(), timeKey); return body; } } /****************************************** Proxy Utils **********************************************************/ var ProxyUtils = (function () { + const PROXY_PREPROCESSORS = (function () { + function HTML() { + const name = "HTML"; + const test = raw => /^/.test(raw); + // simply discard HTML + const parse = _ => ""; + return {name, test, parse}; + } + + function Base64Encoded() { + const name = "Base64 Pre-processor"; + + const keys = ["dm1lc3M", "c3NyOi8v", "dHJvamFu", "c3M6Ly", "c3NkOi8v"]; + + const test = function (raw) { + return keys.some(k => raw.indexOf(k) !== -1); + } + const parse = function (raw) { + raw = Base64.safeDecode(raw); + return raw; + } + return {name, test, parse}; + } + + function Clash() { + const name = "Clash Pre-processor"; + const test = function (raw) { + return /proxies/.test(raw); + } + const parse = function (raw) { + // Clash YAML format + // codes are modified from @KOP-XIAO + // https://github.com/KOP-XIAO/QuantumultX + if (raw.indexOf("{") !== -1) { + raw = raw + .replace(/ - /g, " - ") + .replace(/:(?!\s)/g, ": ") + .replace(/\,\"/g, ', "') + .replace(/: {/g, ": {, ") + .replace(/, (\"?host|path|tls|mux|skip\"?)/g, ", $1") + .replace(/{name: /g, '{name: "') + .replace(/, server:/g, '", server:') + .replace(/{|}/g, "") + .replace(/,/g, "\n "); + } + raw = raw.replace(/ -\n.*name/g, " - name") + .replace(/\$|\`/g, "") + .split("proxy-providers:")[0] + .split("proxy-groups:")[0] + .replace(/\"([\w-]+)\"\s*:/g, "$1:") + raw = raw.indexOf("proxies:") === -1 ? "proxies:\n" + raw : "proxies:" + raw.split("proxies:")[1] + const proxies = YAML.eval(raw).proxies; + return proxies.map(p => JSON.stringify(p)).join("\n"); + } + return {name, test, parse}; + } + + function SSD() { + const name = "SSD Pre-processor"; + const test = function (raw) { + return raw.indexOf("ssd://") === 0; + }; + const parse = function (raw) { + // preprocessing for SSD subscription format + const output = []; + let ssdinfo = JSON.parse(Base64.safeDecode(raw.split("ssd://")[1])); + // options (traffic_used, traffic_total, expiry, url) + const traffic_used = ssdinfo.traffic_used; // GB + const traffic_total = ssdinfo.traffic_total; // GB, -1 means unlimited + const expiry = ssdinfo.expiry; // YYYY-MM-DD HH:mm:ss + // default setting + let name = ssdinfo.airport; // name of the airport + let port = ssdinfo.port; + let method = ssdinfo.encryption; + let password = ssdinfo.password; + // servers config + let servers = ssdinfo.servers; + for (let i = 0; i < servers.length; i++) { + let server = servers[i]; + method = server.encryption ? server.encryption : method; + password = server.password ? server.password : password; + let userinfo = Base64.safeEncode(method + ":" + password); + let hostname = server.server; + port = server.port ? server.port : port; + let tag = server.remarks ? server.remarks : i; + let plugin = server.plugin_options + ? "/?plugin=" + + encodeURIComponent(server.plugin + ";" + server.plugin_options) + : ""; + output[i] = + "ss://" + userinfo + "@" + hostname + ":" + port + plugin + "#" + tag; + } + return output.join("\n"); + }; + return {name, test, parse}; + } + + return [ + HTML(), Base64Encoded(), Clash(), SSD() + ]; + })(); + const PROXY_PARSERS = (function () { + // Parse SS URI format (only supports new SIP002, legacy format is depreciated). + // reference: https://shadowsocks.org/en/spec/SIP002-URI-Scheme.html + function URI_SS() { + const name = "URI SS Parser"; + const test = (line) => { + return /^ss:\/\//.test(line); + }; + const parse = (line) => { + const supported = {}; + // parse url + let content = line.split("ss://")[1]; + + const proxy = { + name: decodeURIComponent(line.split("#")[1]), + type: "ss", + supported, + }; + content = content.split("#")[0]; // strip proxy name + // handle IPV4 and IPV6 + const serverAndPort = content.match(/@([^\/]*)(\/|$)/)[1]; + const portIdx = serverAndPort.lastIndexOf(":"); + proxy.server = serverAndPort.substring(0, portIdx); + proxy.port = serverAndPort.substring(portIdx + 1); + + const userInfo = Base64.safeDecode(content.split("@")[0]).split(":"); + proxy.cipher = userInfo[0]; + proxy.password = userInfo[1]; + + // handle obfs + const idx = content.indexOf("?plugin="); + if (idx !== -1) { + const pluginInfo = ( + "plugin=" + + decodeURIComponent(content.split("?plugin=")[1].split("&")[0]) + ).split(";"); + const params = {}; + for (const item of pluginInfo) { + const [key, val] = item.split("="); + if (key) params[key] = val || true; // some options like "tls" will not have value + } + switch (params.plugin) { + case "obfs-local": + case "simple-obfs": + proxy.plugin = "obfs"; + proxy["plugin-opts"] = { + mode: params.obfs, + host: params["obfs-host"], + }; + break; + case "v2ray-plugin": + proxy.supported = { + ...supported, + Loon: false, + Surge: false, + }; + proxy.obfs = "v2ray-plugin"; + proxy["plugin-opts"] = { + mode: "websocket", + host: params["obfs-host"], + path: params.path || "", + tls: params.tls || false, + }; + break; + default: + throw new Error(`Unsupported plugin option: ${params.plugin}`); + } + } + return proxy; + }; + return {name, test, parse}; + } + + // Parse URI SSR format, such as ssr://xxx + function URI_SSR() { + const name = "URI SSR Parser"; + const test = (line) => { + return /^ssr:\/\//.test(line); + }; + const supported = { + Surge: false, + }; + + const parse = (line) => { + line = Base64.safeDecode(line.split("ssr://")[1]); + + // handle IPV6 & IPV4 format + let splitIdx = line.indexOf(":origin"); + if (splitIdx === -1) { + splitIdx = line.indexOf(":auth_"); + } + const serverAndPort = line.substring(0, splitIdx); + const server = serverAndPort.substring(0, serverAndPort.lastIndexOf(":")); + const port = serverAndPort.substring(serverAndPort.lastIndexOf(":") + 1); + + let params = line + .substring(splitIdx + 1) + .split("/?")[0] + .split(":"); + let proxy = { + type: "ssr", + server, + port, + protocol: params[0], + cipher: params[1], + obfs: params[2], + password: Base64.safeDecode(params[3]), + supported, + }; + // get other params + params = {}; + line = line.split("/?")[1].split("&"); + if (line.length > 1) { + for (const item of line) { + const [key, val] = item.split("="); + params[key] = val; + } + } + proxy = { + ...proxy, + name: Base64.safeDecode(params.remarks), + "protocol-param": + Base64.safeDecode(params.protoparam).replace(/\s/g, "") || "", + "obfs-param": + Base64.safeDecode(params.obfsparam).replace(/\s/g, "") || "", + }; + return proxy; + }; + + return {name, test, parse}; + } + + // V2rayN URI VMess format + // reference: https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2) + + // Quantumult VMess format + function URI_VMess() { + const name = "URI VMess Parser"; + const test = (line) => { + return /^vmess:\/\//.test(line); + }; + const parse = (line) => { + const supported = {}; + line = line.split("vmess://")[1]; + const content = Base64.safeDecode(line); + if (/=\s*vmess/.test(content)) { + const partitions = content.split(",").map((p) => p.trim()); + // Quantumult VMess URI format + // get keyword params + const params = {}; + for (const part of partitions) { + if (part.indexOf("=") !== -1) { + const [key, val] = part.split("="); + params[key.trim()] = val.trim(); + } + } + + const proxy = { + name: partitions[0].split("=")[0].trim(), + type: "vmess", + server: partitions[1], + port: partitions[2], + cipher: partitions[3], + uuid: partitions[4].match(/^"(.*)"$/)[1], + tls: params.obfs === "over-tls" || params.obfs === "wss", + udp: JSON.parse(params["udp-relay"] || "false"), + tfo: JSON.parse(params["fast-open"] || "false"), + }; + + // handle ws headers + if (params.obfs === "ws" || params.obfs === "wss") { + proxy.network = "ws"; + proxy["ws-path"] = params["obfs-uri"]; + proxy["ws-headers"] = { + Host: params["obfs-host"] || proxy.server, // if no host provided, use the same as server + }; + } + + // handle scert + if (proxy.tls && params['"tls-verification"'] === "false") { + proxy['skip-cert-verify'] = true; + } + + // handle sni + if (proxy.tls && params["obfs-host"]) { + proxy.sni = params["obfs-host"]; + } + + return proxy; + } else { + // V2rayN URI format + const params = JSON.parse(content); + const proxy = { + name: params.ps, + type: "vmess", + server: params.add, + port: params.port, + cipher: "auto", // V2rayN has no default cipher! use aes-128-gcm as default. + uuid: params.id, + alterId: params.aid || 0, + tls: params.tls === "tls" || params.tls === true, + supported, + }; + // handle obfs + if (params.net === "ws") { + proxy.network = "ws"; + proxy["ws-path"] = params.path; + proxy["ws-headers"] = { + Host: params.host || params.add, + }; + if (proxy.tls && params.host) { + proxy.sni = params.host; + } + } + // handle scert + if (params.verify_cert === false) { + proxy['skip-cert-verify'] = true; + } + return proxy; + } + }; + return {name, test, parse}; + } + + // Trojan URI format + function URI_Trojan() { + const name = "URI Trojan Parser"; + const test = (line) => { + return /^trojan:\/\//.test(line); + }; + + const parse = (line) => { + const supported = {}; + // trojan forces to use 443 port + if (line.indexOf(":443") === -1) { + throw new Error("Trojan port should always be 443!"); + } + line = line.split("trojan://")[1]; + const server = line.split("@")[1].split(":443")[0]; + const name = decodeURIComponent(line.split("#")[1].trim()); + + return { + name: name || `[Trojan] ${server}`, // trojan uri may have no server tag! + type: "trojan", + server, + port: 443, + password: line.split("@")[0], + supported, + }; + }; + return {name, test, parse}; + } + + function Clash_All() { + const name = "Clash Parser"; + const test = (line) => { + try { + JSON.parse(line); + } catch (e) { + return false; + } + return true; + }; + const parse = (line) => JSON.parse(line); + return {name, test, parse}; + } + + function QX_SS() { + const name = "QX SS Parser"; + const test = (line) => { + return ( + /^shadowsocks\s*=/.test(line.split(",")[0].trim()) && + line.indexOf("ssr-protocol") === -1 + ); + }; + const parse = (line) => { + const supported = {}; + const params = getQXParams(line); + const proxy = { + name: params.tag, + type: "ss", + server: params.server, + port: params.port, + cipher: params.method, + password: params.password, + udp: JSON.parse(params["udp-relay"] || "false"), + tfo: JSON.parse(params["fast-open"] || "false"), + supported, + }; + // handle obfs options + if (params.obfs) { + proxy["plugin-opts"] = { + host: params["obfs-host"] || proxy.server, + }; + switch (params.obfs) { + case "http": + case "tls": + proxy.plugin = "obfs"; + proxy["plugin-opts"].mode = params.obfs; + break; + case "ws": + case "wss": + proxy["plugin-opts"] = { + ...proxy["plugin-opts"], + mode: "websocket", + path: params["obfs-uri"] || "/", + tls: params.obfs === "wss", + }; + if (proxy["plugin-opts"].tls && typeof params['tls-verification'] !== "undefined") { + proxy["plugin-opts"]['skip-cert-verify'] = params['tls-verification']; + } + proxy.plugin = "v2ray-plugin"; + // Surge and Loon lack support for v2ray-plugin obfs + proxy.supported.Surge = false; + proxy.supported.Loon = false; + break; + } + } + return proxy; + }; + return {name, test, parse}; + } + + function QX_SSR() { + const name = "QX SSR Parser"; + const test = (line) => { + return ( + /^shadowsocks\s*=/.test(line.split(",")[0].trim()) && + line.indexOf("ssr-protocol") !== -1 + ); + }; + + const parse = (line) => { + const supported = { + Surge: false, + }; + const params = getQXParams(line); + const proxy = { + name: params.tag, + type: "ssr", + server: params.server, + port: params.port, + cipher: params.method, + password: params.password, + protocol: params["ssr-protocol"], + obfs: "plain", // default obfs + "protocol-param": params["ssr-protocol-param"], + udp: JSON.parse(params["udp-relay"] || "false"), + tfo: JSON.parse(params["fast-open"] || "false"), + supported, + }; + // handle obfs options + if (params.obfs) { + proxy.obfs = params.obfs; + proxy["obfs-param"] = params["obfs-host"]; + } + return proxy; + }; + return {name, test, parse}; + } + + function QX_VMess() { + const name = "QX VMess Parser"; + const test = (line) => { + return /^vmess\s*=/.test(line.split(",")[0].trim()); + }; + const parse = (line) => { + const params = getQXParams(line); + const proxy = { + type: "vmess", + name: params.tag, + server: params.server, + port: params.port, + cipher: params.method || "none", + uuid: params.password, + alterId: 0, + tls: params.obfs === "over-tls" || params.obfs === "wss", + udp: JSON.parse(params["udp-relay"] || "false"), + tfo: JSON.parse(params["fast-open"] || "false"), + }; + if (proxy.tls) { + proxy.sni = params["obfs-host"] || params.server; + proxy['skip-cert-verify'] = !JSON.parse(params["tls-verification"] || "true"); + } + // handle ws headers + if (params.obfs === "ws" || params.obfs === "wss") { + proxy.network = "ws"; + proxy["ws-path"] = params["obfs-uri"]; + proxy["ws-headers"] = { + Host: params["obfs-host"] || params.server, // if no host provided, use the same as server + }; + } + return proxy; + }; + + return {name, test, parse}; + } + + function QX_Trojan() { + const name = "QX Trojan Parser"; + const test = (line) => { + return /^trojan\s*=/.test(line.split(",")[0].trim()); + }; + const parse = (line) => { + const params = getQXParams(line); + const proxy = { + type: "trojan", + name: params.tag, + server: params.server, + port: params.port, + password: params.password, + sni: params["tls-host"] || params.server, + udp: JSON.parse(params["udp-relay"] || "false"), + tfo: JSON.parse(params["fast-open"] || "false"), + }; + proxy['skip-cert-verify'] = !JSON.parse(params["tls-verification"] || "true"); + return proxy; + }; + return {name, test, parse}; + } + + function QX_Http() { + const name = "QX HTTP Parser"; + const test = (line) => { + return /^http\s*=/.test(line.split(",")[0].trim()); + }; + const parse = (line) => { + const params = getQXParams(line); + const proxy = { + type: "http", + name: params.tag, + server: params.server, + port: params.port, + tls: JSON.parse(params["over-tls"] || "false"), + udp: JSON.parse(params["udp-relay"] || "false"), + tfo: JSON.parse(params["fast-open"] || "false"), + }; + if (params.username && params.username !== 'none') proxy.username = params.username; + if (params.password && params.password !== 'none') proxy.password = params.password; + if (proxy.tls) { + proxy.sni = params["tls-host"] || proxy.server; + proxy['skip-cert-verify'] = !JSON.parse(params["tls-verification"] || "true"); + } + return proxy; + }; + + return {name, test, parse}; + } + + function getQXParams(line) { + const groups = line.split(","); + const params = {}; + const protocols = ["shadowsocks", "vmess", "http", "trojan"]; + groups.forEach((g) => { + let [key, value] = g.split("="); + key = key.trim(); + value = value.trim(); + if (protocols.indexOf(key) !== -1) { + params.type = key; + const conf = value.split(":"); + params.server = conf[0]; + params.port = conf[1]; + } else { + params[key.trim()] = value.trim(); + } + }); + return params; + } + + function Loon_SS() { + const name = "Loon SS Parser"; + const test = (line) => { + return ( + line.split(",")[0].split("=")[1].trim().toLowerCase() === "shadowsocks" + ); + }; + const parse = (line) => { + const params = line.split("=")[1].split(","); + const proxy = { + name: line.split("=")[0].trim(), + type: "ss", + server: params[1], + port: params[2], + cipher: params[3], + password: params[4].replace(/"/g, ""), + }; + // handle obfs + if (params.length > 5) { + proxy.plugin = "obfs"; + proxy["plugin-opts"] = { + mode: params[5], + host: params[6], + }; + } + return proxy; + }; + return {name, test, parse}; + } + + function Loon_SSR() { + const name = "Loon SSR Parser"; + const test = (line) => { + return ( + line.split(",")[0].split("=")[1].trim().toLowerCase() === "shadowsocksr" + ); + }; + const parse = (line) => { + const params = line.split("=")[1].split(","); + const supported = { + Surge: false, + }; + return { + name: line.split("=")[0].trim(), + type: "ssr", + server: params[1], + port: params[2], + cipher: params[3], + password: params[4].replace(/"/g, ""), + protocol: params[5], + "protocol-param": params[6].match(/{(.*)}/)[1], + supported, + obfs: params[7], + "obfs-param": params[8].match(/{(.*)}/)[1], + }; + }; + return {name, test, parse}; + } + + function Loon_VMess() { + const name = "Loon VMess Parser"; + const test = (line) => { + // distinguish between surge vmess + return ( + /^.*=\s*vmess/i.test(line.split(",")[0]) && + line.indexOf("username") === -1 + ); + }; + const parse = (line) => { + let params = line.split("=")[1].split(","); + const proxy = { + name: line.split("=")[0].trim(), + type: "vmess", + server: params[1], + port: params[2], + cipher: params[3] || "none", + uuid: params[4].replace(/"/g, ""), + alterId: 0, + }; + // get transport options + params = params.splice(5); + for (const item of params) { + const [key, val] = item.split(":"); + params[key] = val; + } + proxy.tls = JSON.parse(params["over-tls"] || "false"); + if (proxy.tls) { + proxy.sni = params["tls-name"] || proxy.server; + proxy['skip-cert-verify'] = JSON.parse(params["skip-cert-verify"] || "false"); + } + switch (params.transport) { + case "tcp": + break; + case "ws": + proxy.network = params.transport; + proxy["ws-path"] = params.path; + proxy["ws-headers"] = { + Host: params.host, + }; + } + if (proxy.tls) { + proxy['skip-cert-verify'] = JSON.parse(params["skip-cert-verify"] || "false"); + } + return proxy; + }; + return {name, test, parse}; + } + + function Loon_Trojan() { + const name = "Loon Trojan Parser"; + const test = (line) => { + return ( + /^.*=\s*trojan/i.test(line.split(",")[0]) && + line.indexOf("password") === -1 + ); + }; + + const parse = (line) => { + const params = line.split("=")[1].split(","); + const proxy = { + name: line.split("=")[0].trim(), + type: "trojan", + server: params[1], + port: params[2], + password: params[3].replace(/"/g, ""), + sni: params[1], // default sni is the server itself + "skip-cert-verify": JSON.parse(params["skip-cert-verify"] || "false"), + }; + // trojan sni + if (params.length > 4) { + const [key, val] = params[4].split(":"); + if (key === "tls-name") proxy.sni = val; + else throw new Error(`Unknown option ${key} for line: \n${line}`); + } + return proxy; + }; + + return {name, test, parse}; + } + + function Loon_Http() { + const name = "Loon HTTP Parser"; + const test = (line) => { + return ( + /^.*=\s*http/i.test(line.split(",")[0]) && + line.split(",").length === 5 && + line.indexOf("username") === -1 && + line.indexOf("password") === -1 + ); + }; + + const parse = (line) => { + const params = line.split("=")[1].split(","); + const proxy = { + name: line.split("=")[0].trim(), + type: "http", + server: params[1], + port: params[2], + tls: params[2] === "443", // port 443 is considered as https type + }; + if (params[3]) proxy.username = params[3]; + if (params[4]) proxy.password = params[4]; + + if (proxy.tls) { + proxy.sni = params["tls-name"] || proxy.server; + proxy['skip-cert-verify'] = JSON.parse(params["skip-cert-verify"] || "false"); + } + + return proxy; + }; + return {name, test, parse}; + } + + function Surge_SS() { + const name = "Surge SS Parser"; + const test = (line) => { + return /^.*=\s*ss/.test(line.split(",")[0]); + }; + const parse = (line) => { + const params = getSurgeParams(line); + const proxy = { + name: params.name, + type: "ss", + server: params.server, + port: params.port, + cipher: params["encrypt-method"], + password: params.password, + tfo: JSON.parse(params.tfo || "false"), + udp: JSON.parse(params["udp-relay"] || "false"), + }; + // handle obfs + if (params.obfs) { + proxy.plugin = "obfs"; + proxy["plugin-opts"] = { + mode: params.obfs, + host: params["obfs-host"], + }; + } + return proxy; + }; + return {name, test, parse}; + } + + function Surge_VMess() { + const name = "Surge VMess Parser"; + const test = (line) => { + return ( + /^.*=\s*vmess/.test(line.split(",")[0]) && line.indexOf("username") !== -1 + ); + }; + const parse = (line) => { + const params = getSurgeParams(line); + const proxy = { + name: params.name, + type: "vmess", + server: params.server, + port: params.port, + uuid: params.username, + alterId: 0, // surge does not have this field + cipher: "none", // surge does not have this field + tls: JSON.parse(params.tls || "false"), + tfo: JSON.parse(params.tfo || "false"), + }; + if (proxy.tls) { + if (typeof params["skip-cert-verify"] !== "undefined") { + proxy['skip-cert-verify'] = params["skip-cert-verify"] === true || params["skip-cert-verify"] === "1"; + } + proxy.sni = params["sni"] || params.server; + } + // use websocket + if (JSON.parse(params.ws || "false")) { + proxy.network = "ws"; + proxy["ws-path"] = params["ws-path"]; + proxy["ws-headers"] = { + Host: params.sni, + }; + } + return proxy; + }; + return {name, test, parse}; + } + + function Surge_Trojan() { + const name = "Surge Trojan Parser"; + const test = (line) => { + return ( + /^.*=\s*trojan/.test(line.split(",")[0]) && line.indexOf("sni") !== -1 + ); + }; + const parse = (line) => { + const params = getSurgeParams(line); + const proxy = { + name: params.name, + type: "trojan", + server: params.server, + port: params.port, + password: params.password, + sni: params.sni || params.server, + tfo: JSON.parse(params.tfo || "false"), + }; + if (typeof params["skip-cert-verify"] !== "undefined") { + proxy['skip-cert-verify'] = params["skip-cert-verify"] === true || params["skip-cert-verify"] === "1"; + } + return proxy; + }; + + return {name, test, parse}; + } + + function Surge_Http() { + const name = "Surge HTTP Parser"; + const test = (line) => { + return ( + /^.*=\s*http/.test(line.split(",")[0]) && !Loon_Http().test(line) + ); + }; + const parse = (line) => { + const params = getSurgeParams(line); + const proxy = { + name: params.name, + type: "http", + server: params.server, + port: params.port, + tls: JSON.parse(params.tls || "false"), + tfo: JSON.parse(params.tfo || "false"), + }; + if (proxy.tls) { + if (typeof params["skip-cert-verify"] !== "undefined") { + proxy['skip-cert-verify'] = params["skip-cert-verify"] === true || params["skip-cert-verify"] === "1"; + } + proxy.sni = params.sni || params.server; + } + if (params.username && params.username !== "none") proxy.username = params.username; + if (params.password && params.password !== "none") proxy.password = params.password; + return proxy; + }; + return {name, test, parse}; + } + + function getSurgeParams(line) { + const params = {}; + params.name = line.split("=")[0].trim(); + const segments = line.split(","); + params.server = segments[1].trim(); + params.port = segments[2].trim(); + for (let i = 3; i < segments.length; i++) { + const item = segments[i]; + if (item.indexOf("=") !== -1) { + const [key, value] = item.split("="); + params[key.trim()] = value.trim(); + } + } + return params; + } + + return [ + URI_SS(), URI_SSR(), URI_VMess(), URI_Trojan(), + Clash_All(), + Surge_SS(), Surge_VMess(), Surge_Trojan(), Surge_Http(), + Loon_SS(), Loon_SSR(), Loon_VMess(), Loon_Trojan(), Loon_Http(), + QX_SS(), QX_SSR(), QX_VMess(), QX_Trojan(), QX_Http() + ]; + })(); + const PROXY_PROCESSORS = (function () { + // force to set some properties (e.g., skip-cert-verify, udp, tfo, etc.) + function SetPropertyOperator({key, value}) { + return { + name: "Set Property Operator", + func: (proxies) => { + return proxies.map((p) => { + p[key] = value; + return p; + }); + }, + }; + } + + // add or remove flag for proxies + function FlagOperator(add = true) { + return { + name: "Flag Operator", + func: (proxies) => { + return proxies.map((proxy) => { + if (!add) { + // no flag + proxy.name = removeFlag(proxy.name); + } else { + // get flag + const newFlag = getFlag(proxy.name); + // remove old flag + proxy.name = removeFlag(proxy.name); + proxy.name = newFlag + " " + proxy.name; + proxy.name = proxy.name.replace(/🇹🇼/g, "🇨🇳"); + } + return proxy; + }); + }, + }; + } + + // sort proxies according to their names + function SortOperator(order = "asc") { + return { + name: "Sort Operator", + func: (proxies) => { + switch (order) { + case "asc": + case "desc": + return proxies.sort((a, b) => { + let res = a.name > b.name ? 1 : -1; + res *= order === "desc" ? -1 : 1; + return res; + }); + case "random": + return shuffle(proxies); + default: + throw new Error("Unknown sort option: " + order); + } + }, + }; + } + + // sort by keywords + function KeywordSortOperator(keywords) { + return { + name: "Keyword Sort Operator", + func: (proxies) => + proxies.sort((a, b) => { + const oA = getKeywordOrder(keywords, a.name); + const oB = getKeywordOrder(keywords, b.name); + if (oA && !oB) return -1; + if (oB && !oA) return 1; + if (oA && oB) return oA < oB ? -1 : 1; + if ((!oA && !oB) || (oA && oB && oA === oB)) + return a.name < b.name ? -1 : 1; // fallback to normal sort + }), + }; + } + + function getKeywordOrder(keywords, str) { + let order = null; + for (let i = 0; i < keywords.length; i++) { + if (str.indexOf(keywords[i]) !== -1) { + order = i + 1; // plus 1 is important! 0 will be treated as false!!! + break; + } + } + return order; + } + + // rename by keywords + // keywords: [{old: "old", now: "now"}] + function KeywordRenameOperator(keywords) { + return { + name: "Keyword Rename Operator", + func: (proxies) => { + return proxies.map((proxy) => { + for (const {old, now} of keywords) { + proxy.name = proxy.name.replaceAll(old, now).trim(); + } + return proxy; + }); + }, + }; + } + + // rename by regex +// keywords: [{expr: "string format regex", now: "now"}] + function RegexRenameOperator(regex) { + return { + name: "Regex Rename Operator", + func: (proxies) => { + return proxies.map((proxy) => { + for (const {expr, now} of regex) { + proxy.name = proxy.name.replace(new RegExp(expr, "g"), now).trim(); + } + return proxy; + }); + }, + }; + } + + // delete keywords operator +// keywords: ['a', 'b', 'c'] + function KeywordDeleteOperator(keywords) { + const keywords_ = keywords.map((k) => { + return { + old: k, + now: "", + }; + }); + return { + name: "Keyword Delete Operator", + func: KeywordRenameOperator(keywords_).func, + }; + } + + // delete regex operator +// regex: ['a', 'b', 'c'] + function RegexDeleteOperator(regex) { + const regex_ = regex.map((r) => { + return { + expr: r, + now: "", + }; + }); + return { + name: "Regex Delete Operator", + func: RegexRenameOperator(regex_).func, + }; + } + + // use base64 encoded script to rename + /** Example script + function operator(proxies) { + // do something + return proxies; + } + + WARNING: + 1. This function name should be `operator`! + 2. Always declare variables before using them! + */ + function ScriptOperator(script) { + return { + name: "Script Operator", + func: (proxies) => { + let output = proxies; + (function () { + // interface to get internal operators + const $get = (name, args) => { + const item = AVAILABLE_OPERATORS[name]; + return item(args); + }; + const $process = (item, proxies) => { + if (item.name.indexOf("Filter") !== -1) { + return ApplyOperator(item, proxies); + } else if (item.name.indexOf("Operator") !== -1) { + return ApplyFilter(item, proxies); + } + }; + eval(script); + output = operator(proxies); + })(); + return output; + }, + }; + } + + /**************************** Filters ***************************************/ + // filter by keywords + function KeywordFilter({keywords = [], keep = true}) { + return { + name: "Keyword Filter", + func: (proxies) => { + return proxies.map((proxy) => { + const selected = keywords.some((k) => proxy.name.indexOf(k) !== -1); + return keep ? selected : !selected; + }); + }, + }; + } + + // filter useless proxies + function UselessFilter() { + const KEYWORDS = [ + "网址", + "流量", + "时间", + "应急", + "过期", + "Bandwidth", + "expire", + ]; + return { + name: "Useless Filter", + func: KeywordFilter({ + keywords: KEYWORDS, + keep: false, + }).func, + }; + } + + // filter by regions + function RegionFilter(regions) { + const REGION_MAP = { + HK: "🇭🇰", + TW: "🇹🇼", + US: "🇺🇸", + SG: "🇸🇬", + JP: "🇯🇵", + UK: "🇬🇧", + }; + return { + name: "Region Filter", + func: (proxies) => { + // this would be high memory usage + return proxies.map((proxy) => { + const flag = getFlag(proxy.name); + return regions.some((r) => REGION_MAP[r] === flag); + }); + }, + }; + } + + // filter by regex + function RegexFilter({regex = [], keep = true}) { + return { + name: "Regex Filter", + func: (proxies) => { + return proxies.map((proxy) => { + const selected = regex.some((r) => { + r = new RegExp(r); + return r.test(proxy.name); + }); + return keep ? selected : !selected; + }); + }, + }; + } + + // filter by proxy types + function TypeFilter(types) { + return { + name: "Type Filter", + func: (proxies) => { + return proxies.map((proxy) => types.some((t) => proxy.type === t)); + }, + }; + } + + // use base64 encoded script to filter proxies + /** Script Example + function func(proxies) { + const selected = FULL(proxies.length, true); + // do something + return selected; + } + WARNING: + 1. This function name should be `func`! + 2. Always declare variables before using them! + */ + function ScriptFilter(script) { + return { + name: "Script Filter", + func: (proxies) => { + let output = FULL(proxies.length, true); + !(function () { + eval(script); + output = filter(proxies); + })(); + return output; + }, + }; + } + + /******************************** Utility Functions *********************************************/ + // get proxy flag according to its name + function getFlag(name) { + // flags from @KOP-XIAO: https://github.com/KOP-XIAO/QuantumultX/blob/master/Scripts/resource-parser.js + const flags = { + "🇦🇨": ["AC"], + "🇦🇹": ["奥地利", "维也纳"], + "🇦🇺": ["AU", "Australia", "Sydney", "澳大利亚", "澳洲", "墨尔本", "悉尼"], + "🇧🇪": ["BE", "比利时"], + "🇧🇬": ["保加利亚", "Bulgaria"], + "🇧🇷": ["BR", "Brazil", "巴西", "圣保罗"], + "🇨🇦": [ + "CA", + "Canada", + "Waterloo", + "加拿大", + "蒙特利尔", + "温哥华", + "楓葉", + "枫叶", + "滑铁卢", + "多伦多", + ], + "🇨🇭": ["瑞士", "苏黎世", "Switzerland"], + "🇩🇪": ["DE", "German", "GERMAN", "德国", "德國", "法兰克福"], + "🇩🇰": ["丹麦"], + "🇪🇸": ["ES", "西班牙", "Spain"], + "🇪🇺": ["EU", "欧盟", "欧罗巴"], + "🇫🇮": ["Finland", "芬兰", "赫尔辛基"], + "🇫🇷": ["FR", "France", "法国", "法國", "巴黎"], + "🇬🇧": ["UK", "GB", "England", "United Kingdom", "英国", "伦敦", "英"], + "🇲🇴": ["MO", "Macao", "澳门", "CTM"], + "🇭🇺": ["匈牙利", "Hungary"], + "🇭🇰": [ + "HK", + "Hongkong", + "Hong Kong", + "香港", + "深港", + "沪港", + "呼港", + "HKT", + "HKBN", + "HGC", + "WTT", + "CMI", + "穗港", + "京港", + "港", + ], + "🇮🇩": ["Indonesia", "印尼", "印度尼西亚", "雅加达"], + "🇮🇪": ["Ireland", "爱尔兰", "都柏林"], + "🇮🇳": ["India", "印度", "孟买", "Mumbai"], + "🇰🇵": ["KP", "朝鲜"], + "🇰🇷": ["KR", "Korea", "KOR", "韩国", "首尔", "韩", "韓"], + "🇱🇻": ["Latvia", "Latvija", "拉脱维亚"], + "🇲🇽️": ["MEX", "MX", "墨西哥"], + "🇲🇾": ["MY", "Malaysia", "马来西亚", "吉隆坡"], + "🇳🇱": ["NL", "Netherlands", "荷兰", "荷蘭", "尼德蘭", "阿姆斯特丹"], + "🇵🇭": ["PH", "Philippines", "菲律宾"], + "🇷🇴": ["RO", "罗马尼亚"], + "🇷🇺": [ + "RU", + "Russia", + "俄罗斯", + "俄羅斯", + "伯力", + "莫斯科", + "圣彼得堡", + "西伯利亚", + "新西伯利亚", + "京俄", + "杭俄", + ], + "🇸🇦": ["沙特", "迪拜"], + "🇸🇪": ["SE", "Sweden"], + "🇸🇬": [ + "SG", + "Singapore", + "新加坡", + "狮城", + "沪新", + "京新", + "泉新", + "穗新", + "深新", + "杭新", + "广新", + ], + "🇹🇭": ["TH", "Thailand", "泰国", "泰國", "曼谷"], + "🇹🇷": ["TR", "Turkey", "土耳其", "伊斯坦布尔"], + "🇹🇼": [ + "TW", + "Taiwan", + "台湾", + "台北", + "台中", + "新北", + "彰化", + "CHT", + "台", + "HINET", + ], + "🇺🇸": [ + "US", + "USA", + "America", + "United States", + "美国", + "美", + "京美", + "波特兰", + "达拉斯", + "俄勒冈", + "凤凰城", + "费利蒙", + "硅谷", + "矽谷", + "拉斯维加斯", + "洛杉矶", + "圣何塞", + "圣克拉拉", + "西雅图", + "芝加哥", + "沪美", + "哥伦布", + "纽约", + ], + "🇻🇳": ["VN", "越南", "胡志明市"], + "🇮🇹": ["Italy", "IT", "Nachash", "意大利", "米兰", "義大利"], + "🇿🇦": ["South Africa", "南非"], + "🇦🇪": ["United Arab Emirates", "阿联酋"], + "🇯🇵": [ + "JP", + "Japan", + "日", + "日本", + "东京", + "大阪", + "埼玉", + "沪日", + "穗日", + "川日", + "中日", + "泉日", + "杭日", + "深日", + "辽日", + "广日", + ], + "🇦🇷": ["AR", "阿根廷"], + "🇳🇴": ["Norway", "挪威", "NO"], + "🇨🇳": [ + "CN", + "China", + "回国", + "中国", + "江苏", + "北京", + "上海", + "广州", + "深圳", + "杭州", + "徐州", + "青岛", + "宁波", + "镇江", + "back", + ], + "🏳️‍🌈": ["流量", "时间", "应急", "过期", "Bandwidth", "expire"], + }; + for (let k of Object.keys(flags)) { + if (flags[k].some((item) => name.indexOf(item) !== -1)) { + return k; + } + } + // no flag found + const oldFlag = (name.match( + /[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]/ + ) || [])[0]; + return oldFlag || "🏴‍☠️"; + } + + // remove flag + function removeFlag(str) { + return str + .replace(/[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]/g, "") + .trim(); + } + + // shuffle array + function shuffle(array) { + let currentIndex = array.length, + temporaryValue, + randomIndex; + + // While there remain elements to shuffle... + while (0 !== currentIndex) { + // Pick a remaining element... + randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex -= 1; + + // And swap it with the current element. + temporaryValue = array[currentIndex]; + array[currentIndex] = array[randomIndex]; + array[randomIndex] = temporaryValue; + } + + return array; + } + + // some logical functions for proxy filters + function AND(...args) { + return args.reduce((a, b) => a.map((c, i) => b[i] && c)); + } + + function OR(...args) { + return args.reduce((a, b) => a.map((c, i) => b[i] || c)); + } + + function NOT(array) { + return array.map((c) => !c); + } + + function FULL(length, bool) { + return [...Array(length).keys()].map(() => bool); + } + + function clone(object) { + return JSON.parse(JSON.stringify(object)); + } + + // apply filter to proxies + function ApplyFilter(filter, proxies) { + // select proxies + let selected = FULL(proxies.length, true); + try { + selected = AND(selected, filter.func(proxies)); + } catch (err) { + console.log(`Cannot apply filter ${filter.name}\n Reason: ${err}`); + } + return proxies.filter((_, i) => selected[i]); + } + + // apply operator to proxies + function ApplyOperator(operator, proxies) { + let output = clone(proxies); + try { + const output_ = operator.func(output); + if (output_) output = output_; + } catch (err) { + // print log and skip this operator + console.log(`Cannot apply operator ${op.name}! Reason: ${err}`); + } + return output; + } + + function Apply(process, proxies) { + if (process.name.indexOf("Filter") !== -1) { + return ApplyFilter(process, proxies); + } else if (process.name.indexOf("Operator") !== -1) { + return ApplyOperator(process, proxies); + } + } + + return { + "Keyword Filter": KeywordFilter, + "Useless Filter": UselessFilter, + "Region Filter": RegionFilter, + "Regex Filter": RegexFilter, + "Type Filter": TypeFilter, + "Script Filter": ScriptFilter, + + "Set Property Operator": SetPropertyOperator, + "Flag Operator": FlagOperator, + "Sort Operator": SortOperator, + "Keyword Sort Operator": KeywordSortOperator, + "Keyword Rename Operator": KeywordRenameOperator, + "Keyword Delete Operator": KeywordDeleteOperator, + "Regex Rename Operator": RegexRenameOperator, + "Regex Delete Operator": RegexDeleteOperator, + "Script Operator": ScriptOperator, + + "Apply": Apply, + }; + })(); + const PROXY_PRODUCERS = (function () { + function QX_Producer() { + const targetPlatform = "QX"; + const produce = (proxy) => { + let obfs_opts; + let tls_opts; + switch (proxy.type) { + case "ss": + obfs_opts = ""; + if (proxy.plugin === "obfs") { + const {host, mode} = proxy['plugin-opts']; + obfs_opts = `,obfs=${mode}${ + host ? ",obfs-host=" + host : "" + }`; + } + if (proxy.plugin === "v2ray-plugin") { + const {tls, host, path} = proxy["plugin-opts"]; + obfs_opts = `,obfs=${tls ? "wss" : "ws"}${ + host ? ",obfs-host=" + host : "" + }${ + path ? ",obfs-uri=" + path : "" + }`; + } + return `shadowsocks=${proxy.server}:${proxy.port},method=${ + proxy.cipher + },password=${proxy.password}${obfs_opts}${ + proxy.tfo ? ",fast-open=true" : ",fast-open=false" + }${proxy.udp ? ",udp-relay=true" : ",udp-relay=false"},tag=${ + proxy.name + }`; + case "ssr": + return `shadowsocks=${proxy.server}:${proxy.port},method=${ + proxy.cipher + },password=${proxy.password},ssr-protocol=${proxy.protocol}${ + proxy["protocol-param"] + ? ",ssr-protocol-param=" + proxy["protocol-param"] + : "" + }${proxy.obfs ? ",obfs=" + proxy.obfs : ""}${ + proxy["obfs-param"] ? ",obfs-host=" + proxy["obfs-param"] : "" + }${proxy.tfo ? ",fast-open=true" : ",fast-open=false"}${ + proxy.udp ? ",udp-relay=true" : ",udp-relay=false" + },tag=${proxy.name}`; + case "vmess": + obfs_opts = ""; + if (proxy.network === "ws") { + // websocket + if (proxy.tls) { + // ws-tls + obfs_opts = `,obfs=wss${ + proxy.sni ? ",obfs-host=" + proxy.sni : "" + }${ + proxy["ws-path"] ? ",obfs-uri=" + proxy["ws-path"] : "" + },tls-verification=${proxy['skip-cert-verify'] ? "false" : "true"}`; + } else { + // ws + obfs_opts = `,obfs=ws${ + proxy["ws-headers"].Host ? ",obfs-host=" + proxy["ws-headers"].Host : "" + }${ + proxy["ws-path"] ? ",obfs-uri=" + proxy["ws-path"] : "" + }`; + } + } else { + // tcp + if (proxy.tls) { + obfs_opts = `,obfs=over-tls${ + proxy.sni ? ",obfs-host=" + proxy.sni : "" + },tls-verification=${proxy['skip-cert-verify'] ? "false" : "true"}`; + } + } + return `vmess=${proxy.server}:${proxy.port},method=${ + proxy.cipher === "auto" ? "none" : proxy.cipher + },password=${proxy.uuid}${obfs_opts}${ + proxy.tfo ? ",fast-open=true" : ",fast-open=false" + }${proxy.udp ? ",udp-relay=true" : ",udp-relay=false"},tag=${ + proxy.name + }`; + case "trojan": + return `trojan=${proxy.server}:${proxy.port},password=${ + proxy.password + }${proxy.sni ? ",tls-host=" + proxy.sni : ""},over-tls=true,tls-verification=${ + proxy['skip-cert-verify'] ? "false" : "true" + }${proxy.tfo ? ",fast-open=true" : ",fast-open=false"}${ + proxy.udp ? ",udp-relay=true" : ",udp-relay=false" + },tag=${proxy.name}`; + case "http": + tls_opts = ""; + if (proxy.tls) { + tls_opts = `,over-tls=true,tls-verification=${ + proxy['skip-cert-verify'] ? "false" : "true" + }${ + proxy.sni ? ",tls-host=" + proxy.sni : "" + }`; + } + return `http=${proxy.server}:${proxy.port},username=${ + proxy.username + },password=${proxy.password}${tls_opts}${ + proxy.tfo ? ",fast-open=true" : ",fast-open=false" + },tag=${proxy.name}`; + } + throw new Error( + `Platform ${targetPlatform} does not support proxy type: ${proxy.type}` + ); + }; + return {produce}; + } + + function Loon_Producer() { + const targetPlatform = "Loon"; + const produce = (proxy) => { + let obfs_opts, tls_opts; + switch (proxy.type) { + case "ss": + obfs_opts = ",,"; + if (proxy.plugin) { + if (proxy.plugin === "obfs") { + const {mode, host} = proxy["plugin-opts"]; + obfs_opts = `,${mode},${host || ""}`; + } else { + throw new Error( + `Platform ${targetPlatform} does not support obfs option: ${proxy.obfs}` + ); + } + } + + return `${proxy.name}=shadowsocks,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"${obfs_opts}`; + case "ssr": + return `${proxy.name}=shadowsocksr,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}",${proxy.protocol},{${proxy["protocol-param"] || ""}},${proxy.obfs},{${proxy["obfs-param"] || ""}}`; + case "vmess": + obfs_opts = ""; + if (proxy.network === "ws") { + const host = proxy["ws-headers"].Host || proxy.server; + obfs_opts = `,transport:ws,host:${host},path:${ + proxy["ws-path"] || "/" + }`; + } else { + obfs_opts = `,transport:tcp`; + } + if (proxy.tls) { + obfs_opts += `${ + proxy.sni ? ",tls-name:" + proxy.sni : "" + },skip-cert-verify:${proxy['skip-cert-verify'] || "false"}`; + } + return `${proxy.name}=vmess,${proxy.server},${proxy.port},${ + proxy.cipher === "auto" ? "none" : proxy.cipher + },"${proxy.uuid}",over-tls:${proxy.tls || "false"}${obfs_opts}`; + case "trojan": + return `${proxy.name}=trojan,${proxy.server},${proxy.port},"${ + proxy.password + }"${ + proxy.sni ? ",tls-name:" + proxy.sni : "" + },skip-cert-verify:${ + proxy['skip-cert-verify'] || "false" + }`; + case "http": + tls_opts = ""; + const base = `${proxy.name}=${proxy.tls ? "http" : "https"},${ + proxy.server + },${proxy.port},${proxy.username || ""},${proxy.password || ""}`; + if (proxy.tls) { + // https + tls_opts = `${ + proxy.sni ? ",tls-name:" + proxy.sni : "" + },skip-cert-verify:${proxy['skip-cert-verify']}`; + return base + tls_opts; + } else return base; + } + throw new Error( + `Platform ${targetPlatform} does not support proxy type: ${proxy.type}` + ); + }; + return {produce}; + } + + function Surge_Producer() { + const targetPlatform = "Surge"; + const produce = (proxy) => { + let obfs_opts, tls_opts; + switch (proxy.type) { + case "ss": + obfs_opts = ""; + if (proxy.plugin) { + const {host, mode} = proxy['plugin-opts']; + if (proxy.plugin === "obfs") { + obfs_opts = `,obfs=${mode}${ + host ? ",obfs-host=" + host : "" + }`; + } else { + throw new Error( + `Platform ${targetPlatform} does not support obfs option: ${proxy.obfs}` + ); + } + } + return `${proxy.name}=ss,${proxy.server}, ${proxy.port},encrypt-method=${ + proxy.cipher + },password=${proxy.password}${obfs_opts},tfo=${ + proxy.tfo || "false" + },udp-relay=${proxy.udp || "false"}`; + case "vmess": + tls_opts = ""; + let config = `${proxy.name}=vmess,${proxy.server},${ + proxy.port + },username=${proxy.uuid},tls=${proxy.tls || "false"},tfo=${proxy.tfo || "false"}`; + if (proxy.network === "ws") { + const path = proxy["ws-path"] || "/"; + const host = proxy["ws-headers"].Host; + config += `,ws=true${path ? ",ws-path=" + path : ""}${ + host ? ",ws-headers=HOST:" + host : "" + }`; + } + if (proxy.tls) { + config += `${ + typeof proxy['skip-cert-verify'] !== "undefined" + ? ",skip-cert-verify=" + proxy['skip-cert-verify'] + : "" + }`; + config += proxy.sni ? `,sni=${proxy.sni}` : ""; + } + return config; + case "trojan": + return `${proxy.name}=trojan,${proxy.server},${proxy.port},password=${ + proxy.password + }${ + typeof proxy['skip-cert-verify'] !== "undefined" + ? ",skip-cert-verify=" + proxy['skip-cert-verify'] + : "" + }${proxy.sni ? ",sni=" + proxy.sni : ""},tfo=${proxy.tfo || "false"}`; + case "http": + tls_opts = ", tls=false"; + if (proxy.tls) { + tls_opts = `,tls=true,skip-cert-verify=${proxy['skip-cert-verify']},sni=${proxy.sni}`; + } + return `${proxy.name}=http, ${proxy.server}, ${proxy.port}${ + proxy.username ? ",username=" + proxy.username : "" + }${ + proxy.password ? ",password=" + proxy.password : "" + }${tls_opts},tfo=${proxy.tfo || "false"}`; + } + throw new Error( + `Platform ${targetPlatform} does not support proxy type: ${proxy.type}` + ); + }; + return {produce}; + } + + function Clash_Producer() { + const type = "ALL"; + const produce = (proxies) => { + return "proxies:\n" + proxies.map(proxy => { + delete proxy.supported; + return " - " + JSON.stringify(proxy) + "\n"; + }).join(""); + }; + return {type, produce}; + } + + function URI_Producer() { + const type = "SINGLE"; + const produce = (proxy) => { + let result = ""; + switch (proxy.type) { + case "ss": + const userinfo = `${proxy.cipher}:${proxy.password}`; + result = `ss://${Base64.safeEncode(userinfo)}@${proxy.server}:${ + proxy.port + }/`; + if (proxy.plugin) { + result += "?plugin="; + const opts = proxy["plugin-opts"]; + switch (proxy.plugin) { + case "obfs": + result += encodeURIComponent( + `simple-obfs;obfs=${opts.mode}${ + opts.host ? ";obfs-host=" + opts.host : "" + }` + ); + break; + case "v2ray-plugin": + result += encodeURIComponent( + `v2ray-plugin;obfs=${opts.mode}${ + opts.host ? ";obfs-host" + opts.host : "" + }${opts.tls ? ";tls" : ""}` + ); + break; + default: + throw new Error(`Unsupported plugin option: ${proxy.plugin}`); + } + } + result += `#${encodeURIComponent(proxy.name)}`; + break; + case "ssr": + result = `${proxy.server}:${proxy.port}:${proxy.protocol}:${ + proxy.cipher + }:${proxy.obfs}:${Base64.safeEncode(proxy.password)}/`; + result += `?remarks=${Base64.safeEncode(proxy.name)}${ + proxy["obfs-param"] + ? "&obfsparam=" + Base64.safeEncode(proxy["obfs-param"]) + : "" + }${ + proxy["protocol-param"] + ? "&protocolparam=" + Base64.safeEncode(proxy["protocol-param"]) + : "" + }`; + result = "ssr://" + Base64.safeEncode(result); + break; + case "vmess": + // V2RayN URI format + result = { + ps: proxy.name, + add: proxy.server, + port: proxy.port, + id: proxy.uuid, + type: "", + aid: 0, + net: proxy.network || "tcp", + tls: proxy.tls ? "tls" : "", + }; + // obfs + if (proxy.network === "ws") { + result.path = proxy["ws-path"] || "/"; + result.host = proxy["ws-headers"].Host || proxy.server; + } + result = "vmess://" + Base64.safeEncode(JSON.stringify(result)); + break; + case "trojan": + result = `trojan://${proxy.password}@${proxy.server}:${proxy.port}#${encodeURIComponent(proxy.name)}`; + break; + default: + throw new Error(`Cannot handle proxy type: ${proxy.type}`); + } + return result; + } + return {type, produce}; + } + + function JSON_Producer() { + const type = "ALL"; + const produce = proxies => JSON.stringify(proxies, null, 2); + return {type, produce}; + } + + + return { + "QX": QX_Producer(), + "Surge": Surge_Producer(), + "Loon": Loon_Producer(), + "Clash": Clash_Producer(), + "URI": URI_Producer(), + "JSON": JSON_Producer() + } + })(); + function preprocess(raw) { for (const processor of PROXY_PREPROCESSORS) { try { @@ -704,1823 +2587,307 @@ var ProxyUtils = (function () { } })(); -var PROXY_PREPROCESSORS = (function () { - function Base64Encoded() { - const name = "Base64 Pre-processor"; +/****************************************** Rule Utils **********************************************************/ +var RuleUtils = (function () { + const RULE_PARSERS = (function () { + // Rule set format for Surge + function SurgeRuleSet() { + const name = "Surge Rule Set Parser" - const keys = ["dm1lc3M", "c3NyOi8v", "dHJvamFu", "c3M6Ly", "c3NkOi8v"]; + const SURGE_RULE_TYPES = [ + // Domain-based rules + "DOMAIN", "DOMAIN-SUFFIX", "DOMAIN-KEYWORD", + // IP based rules + "IP-CIDR", "IP-CIDR6", + // HTTP rules + "USER-AGENT", "URL-REGEX", + // Misc rules + "DEST-PORT", "SRC-IP", "IN-PORT", "PROTOCOL" + ]; - const test = function (raw) { - return keys.some(k => raw.indexOf(k) !== -1); - } - const parse = function (raw) { - raw = Base64.safeDecode(raw); - return raw; - } - return {name, test, parse}; - } - - function Clash() { - const name = "Clash Pre-processor"; - const test = function (raw) { - return /proxies/.test(raw); - } - const parse = function (raw) { - // Clash YAML format - // codes are modified from @KOP-XIAO - // https://github.com/KOP-XIAO/QuantumultX - if (raw.indexOf("{") !== -1) { - raw = raw - .replace(/ - /g, " - ") - .replace(/:(?!\s)/g, ": ") - .replace(/\,\"/g, ', "') - .replace(/: {/g, ": {, ") - .replace(/, (\"?host|path|tls|mux|skip\"?)/g, ", $1") - .replace(/{name: /g, '{name: "') - .replace(/, server:/g, '", server:') - .replace(/{|}/g, "") - .replace(/,/g, "\n "); - } - raw = raw.replace(/ -\n.*name/g, " - name") - .replace(/\$|\`/g, "") - .split("proxy-providers:")[0] - .split("proxy-groups:")[0] - .replace(/\"([\w-]+)\"\s*:/g, "$1:") - raw = raw.indexOf("proxies:") === -1 ? "proxies:\n" + raw : "proxies:" + raw.split("proxies:")[1] - const proxies = YAML.eval(raw).proxies; - return proxies.map(p => JSON.stringify(p)).join("\n"); - } - return {name, test, parse}; - } - - function SSD() { - const name = "SSD Pre-processor"; - const test = function (raw) { - return raw.indexOf("ssd://") === 0; - }; - const parse = function (raw) { - // preprocessing for SSD subscription format - const output = []; - let ssdinfo = JSON.parse(Base64.safeDecode(raw.split("ssd://")[1])); - // options (traffic_used, traffic_total, expiry, url) - const traffic_used = ssdinfo.traffic_used; // GB - const traffic_total = ssdinfo.traffic_total; // GB, -1 means unlimited - const expiry = ssdinfo.expiry; // YYYY-MM-DD HH:mm:ss - // default setting - let name = ssdinfo.airport; // name of the airport - let port = ssdinfo.port; - let method = ssdinfo.encryption; - let password = ssdinfo.password; - // servers config - let servers = ssdinfo.servers; - for (let i = 0; i < servers.length; i++) { - let server = servers[i]; - method = server.encryption ? server.encryption : method; - password = server.password ? server.password : password; - let userinfo = Base64.safeEncode(method + ":" + password); - let hostname = server.server; - port = server.port ? server.port : port; - let tag = server.remarks ? server.remarks : i; - let plugin = server.plugin_options - ? "/?plugin=" + - encodeURIComponent(server.plugin + ";" + server.plugin_options) - : ""; - output[i] = - "ss://" + userinfo + "@" + hostname + ":" + port + plugin + "#" + tag; - } - return output.join("\n"); - }; - return {name, test, parse}; - } - - return [ - Base64Encoded(), Clash(), SSD() - ]; -})(); - -var PROXY_PARSERS = (function () { - // Parse SS URI format (only supports new SIP002, legacy format is depreciated). - // reference: https://shadowsocks.org/en/spec/SIP002-URI-Scheme.html - function URI_SS() { - const name = "URI SS Parser"; - const test = (line) => { - return /^ss:\/\//.test(line); - }; - const parse = (line) => { - const supported = {}; - // parse url - let content = line.split("ss://")[1]; - - const proxy = { - name: decodeURIComponent(line.split("#")[1]), - type: "ss", - supported, - }; - content = content.split("#")[0]; // strip proxy name - // handle IPV4 and IPV6 - const serverAndPort = content.match(/@([^\/]*)(\/|$)/)[1]; - const portIdx = serverAndPort.lastIndexOf(":"); - proxy.server = serverAndPort.substring(0, portIdx); - proxy.port = serverAndPort.substring(portIdx + 1); - - const userInfo = Base64.safeDecode(content.split("@")[0]).split(":"); - proxy.cipher = userInfo[0]; - proxy.password = userInfo[1]; - - // handle obfs - const idx = content.indexOf("?plugin="); - if (idx !== -1) { - const pluginInfo = ( - "plugin=" + - decodeURIComponent(content.split("?plugin=")[1].split("&")[0]) - ).split(";"); - const params = {}; - for (const item of pluginInfo) { - const [key, val] = item.split("="); - if (key) params[key] = val || true; // some options like "tls" will not have value - } - switch (params.plugin) { - case "obfs-local": - case "simple-obfs": - proxy.plugin = "obfs"; - proxy["plugin-opts"] = { - mode: params.obfs, - host: params["obfs-host"], - }; - break; - case "v2ray-plugin": - proxy.supported = { - ...supported, - Loon: false, - Surge: false, - }; - proxy.obfs = "v2ray-plugin"; - proxy["plugin-opts"] = { - mode: "websocket", - host: params["obfs-host"], - path: params.path || "", - tls: params.tls || false, - }; - break; - default: - throw new Error(`Unsupported plugin option: ${params.plugin}`); - } - } - return proxy; - }; - return {name, test, parse}; - } - - // Parse URI SSR format, such as ssr://xxx - function URI_SSR() { - const name = "URI SSR Parser"; - const test = (line) => { - return /^ssr:\/\//.test(line); - }; - const supported = { - Surge: false, - }; - - const parse = (line) => { - line = Base64.safeDecode(line.split("ssr://")[1]); - - // handle IPV6 & IPV4 format - let splitIdx = line.indexOf(":origin"); - if (splitIdx === -1) { - splitIdx = line.indexOf(":auth_"); - } - const serverAndPort = line.substring(0, splitIdx); - const server = serverAndPort.substring(0, serverAndPort.lastIndexOf(":")); - const port = serverAndPort.substring(serverAndPort.lastIndexOf(":") + 1); - - let params = line - .substring(splitIdx + 1) - .split("/?")[0] - .split(":"); - let proxy = { - type: "ssr", - server, - port, - protocol: params[0], - cipher: params[1], - obfs: params[2], - password: Base64.safeDecode(params[3]), - supported, - }; - // get other params - params = {}; - line = line.split("/?")[1].split("&"); - if (line.length > 1) { - for (const item of line) { - const [key, val] = item.split("="); - params[key] = val; - } - } - proxy = { - ...proxy, - name: Base64.safeDecode(params.remarks), - "protocol-param": - Base64.safeDecode(params.protoparam).replace(/\s/g, "") || "", - "obfs-param": - Base64.safeDecode(params.obfsparam).replace(/\s/g, "") || "", - }; - return proxy; - }; - - return {name, test, parse}; - } - - // V2rayN URI VMess format - // reference: https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2) - - // Quantumult VMess format - function URI_VMess() { - const name = "URI VMess Parser"; - const test = (line) => { - return /^vmess:\/\//.test(line); - }; - const parse = (line) => { - const supported = {}; - line = line.split("vmess://")[1]; - const content = Base64.safeDecode(line); - if (/=\s*vmess/.test(content)) { - const partitions = content.split(",").map((p) => p.trim()); - // Quantumult VMess URI format - // get keyword params - const params = {}; - for (const part of partitions) { - if (part.indexOf("=") !== -1) { - const [key, val] = part.split("="); - params[key.trim()] = val.trim(); - } - } - - const proxy = { - name: partitions[0].split("=")[0].trim(), - type: "vmess", - server: partitions[1], - port: partitions[2], - cipher: partitions[3], - uuid: partitions[4].match(/^"(.*)"$/)[1], - tls: params.obfs === "over-tls" || params.obfs === "wss", - udp: JSON.parse(params["udp-relay"] || "false"), - tfo: JSON.parse(params["fast-open"] || "false"), - }; - - // handle ws headers - if (params.obfs === "ws" || params.obfs === "wss") { - proxy.network = "ws"; - proxy["ws-path"] = params["obfs-uri"]; - proxy["ws-headers"] = { - Host: params["obfs-host"] || proxy.server, // if no host provided, use the same as server - }; - } - - // handle scert - if (proxy.tls && params['"tls-verification"'] === "false") { - proxy['skip-cert-verify'] = true; - } - - // handle sni - if (proxy.tls && params["obfs-host"]) { - proxy.sni = params["obfs-host"]; - } - - return proxy; - } else { - // V2rayN URI format - const params = JSON.parse(content); - const proxy = { - name: params.ps, - type: "vmess", - server: params.add, - port: params.port, - cipher: "auto", // V2rayN has no default cipher! use aes-128-gcm as default. - uuid: params.id, - alterId: params.aid || 0, - tls: params.tls === "tls" || params.tls === true, - supported, - }; - // handle obfs - if (params.net === "ws") { - proxy.network = "ws"; - proxy["ws-path"] = params.path; - proxy["ws-headers"] = { - Host: params.host || params.add, - }; - if (proxy.tls && params.host) { - proxy.sni = params.host; - } - } - // handle scert - if (params.verify_cert === false) { - proxy['skip-cert-verify'] = true; - } - return proxy; - } - }; - return {name, test, parse}; - } - - // Trojan URI format - function URI_Trojan() { - const name = "URI Trojan Parser"; - const test = (line) => { - return /^trojan:\/\//.test(line); - }; - - const parse = (line) => { - const supported = {}; - // trojan forces to use 443 port - if (line.indexOf(":443") === -1) { - throw new Error("Trojan port should always be 443!"); - } - line = line.split("trojan://")[1]; - const server = line.split("@")[1].split(":443")[0]; - const name = decodeURIComponent(line.split("#")[1].trim()); - - return { - name: name || `[Trojan] ${server}`, // trojan uri may have no server tag! - type: "trojan", - server, - port: 443, - password: line.split("@")[0], - supported, - }; - }; - return {name, test, parse}; - } - - function Clash_All() { - const name = "Clash Parser"; - const test = (line) => { - try { - JSON.parse(line); - } catch (e) { - return false; - } - return true; - }; - const parse = (line) => JSON.parse(line); - return {name, test, parse}; - } - - function QX_SS() { - const name = "QX SS Parser"; - const test = (line) => { - return ( - /^shadowsocks\s*=/.test(line.split(",")[0].trim()) && - line.indexOf("ssr-protocol") === -1 + const test = (raw) => ( + raw.indexOf("payload:") !== 0 && + SURGE_RULE_TYPES.some(k => raw.indexOf(k) !== -1) ); - }; - const parse = (line) => { - const supported = {}; - const params = getQXParams(line); - const proxy = { - name: params.tag, - type: "ss", - server: params.server, - port: params.port, - cipher: params.method, - password: params.password, - udp: JSON.parse(params["udp-relay"] || "false"), - tfo: JSON.parse(params["fast-open"] || "false"), - supported, - }; - // handle obfs options - if (params.obfs) { - proxy["plugin-opts"] = { - host: params["obfs-host"] || proxy.server, - }; - switch (params.obfs) { - case "http": - case "tls": - proxy.plugin = "obfs"; - proxy["plugin-opts"].mode = params.obfs; - break; - case "ws": - case "wss": - proxy["plugin-opts"] = { - ...proxy["plugin-opts"], - mode: "websocket", - path: params["obfs-uri"] || "/", - tls: params.obfs === "wss", + + const parse = (raw) => { + const lines = raw.split("\n"); + const result = []; + for (let line of lines) { + line = line.trim(); + // skip comments + if (/\s*#/.test(line)) continue; + if (!SURGE_RULE_TYPES.some(k => line.indexOf(k) === 0)) continue; + try { + const params = line.split(",").map(w => w.trim()); + const rule = { + type: params[0], + content: params[1], }; - if (proxy["plugin-opts"].tls && typeof params['tls-verification'] !== "undefined") { - proxy["plugin-opts"]['skip-cert-verify'] = params['tls-verification']; + if (rule.type === "IP-CIDR" || rule.type === "IP-CIDR6") { + rule.options = params.slice(2) } - proxy.plugin = "v2ray-plugin"; - // Surge and Loon lack support for v2ray-plugin obfs - proxy.supported.Surge = false; - proxy.supported.Loon = false; - break; + result.push(rule); + } catch (e) { + console.error(`Failed to parse line: ${line}\n Reason: ${e}`); + } } - } - return proxy; - }; - return {name, test, parse}; - } - - function QX_SSR() { - const name = "QX SSR Parser"; - const test = (line) => { - return ( - /^shadowsocks\s*=/.test(line.split(",")[0].trim()) && - line.indexOf("ssr-protocol") !== -1 - ); - }; - - const parse = (line) => { - const supported = { - Surge: false, + return result; }; - const params = getQXParams(line); - const proxy = { - name: params.tag, - type: "ssr", - server: params.server, - port: params.port, - cipher: params.method, - password: params.password, - protocol: params["ssr-protocol"], - obfs: "plain", // default obfs - "protocol-param": params["ssr-protocol-param"], - udp: JSON.parse(params["udp-relay"] || "false"), - tfo: JSON.parse(params["fast-open"] || "false"), - supported, - }; - // handle obfs options - if (params.obfs) { - proxy.obfs = params.obfs; - proxy["obfs-param"] = params["obfs-host"]; - } - return proxy; - }; - return {name, test, parse}; - } - function QX_VMess() { - const name = "QX VMess Parser"; - const test = (line) => { - return /^vmess\s*=/.test(line.split(",")[0].trim()); - }; - const parse = (line) => { - const params = getQXParams(line); - const proxy = { - type: "vmess", - name: params.tag, - server: params.server, - port: params.port, - cipher: params.method || "none", - uuid: params.password, - alterId: 0, - tls: params.obfs === "over-tls" || params.obfs === "wss", - udp: JSON.parse(params["udp-relay"] || "false"), - tfo: JSON.parse(params["fast-open"] || "false"), - }; - if (proxy.tls) { - proxy.sni = params["obfs-host"] || params.server; - proxy['skip-cert-verify'] = !JSON.parse(params["tls-verification"] || "true"); - } - // handle ws headers - if (params.obfs === "ws" || params.obfs === "wss") { - proxy.network = "ws"; - proxy["ws-path"] = params["obfs-uri"]; - proxy["ws-headers"] = { - Host: params["obfs-host"] || params.server, // if no host provided, use the same as server - }; - } - return proxy; - }; - - return {name, test, parse}; - } - - function QX_Trojan() { - const name = "QX Trojan Parser"; - const test = (line) => { - return /^trojan\s*=/.test(line.split(",")[0].trim()); - }; - const parse = (line) => { - const params = getQXParams(line); - const proxy = { - type: "trojan", - name: params.tag, - server: params.server, - port: params.port, - password: params.password, - sni: params["tls-host"] || params.server, - udp: JSON.parse(params["udp-relay"] || "false"), - tfo: JSON.parse(params["fast-open"] || "false"), - }; - proxy['skip-cert-verify'] = !JSON.parse(params["tls-verification"] || "true"); - return proxy; - }; - return {name, test, parse}; - } - - function QX_Http() { - const name = "QX HTTP Parser"; - const test = (line) => { - return /^http\s*=/.test(line.split(",")[0].trim()); - }; - const parse = (line) => { - const params = getQXParams(line); - const proxy = { - type: "http", - name: params.tag, - server: params.server, - port: params.port, - tls: JSON.parse(params["over-tls"] || "false"), - udp: JSON.parse(params["udp-relay"] || "false"), - tfo: JSON.parse(params["fast-open"] || "false"), - }; - if (params.username && params.username !== 'none') proxy.username = params.username; - if (params.password && params.password !== 'none') proxy.password = params.password; - if (proxy.tls) { - proxy.sni = params["tls-host"] || proxy.server; - proxy['skip-cert-verify'] = !JSON.parse(params["tls-verification"] || "true"); - } - return proxy; - }; - - return {name, test, parse}; - } - - function getQXParams(line) { - const groups = line.split(","); - const params = {}; - const protocols = ["shadowsocks", "vmess", "http", "trojan"]; - groups.forEach((g) => { - let [key, value] = g.split("="); - key = key.trim(); - value = value.trim(); - if (protocols.indexOf(key) !== -1) { - params.type = key; - const conf = value.split(":"); - params.server = conf[0]; - params.port = conf[1]; - } else { - params[key.trim()] = value.trim(); - } - }); - return params; - } - - function Loon_SS() { - const name = "Loon SS Parser"; - const test = (line) => { - return ( - line.split(",")[0].split("=")[1].trim().toLowerCase() === "shadowsocks" - ); - }; - const parse = (line) => { - const params = line.split("=")[1].split(","); - const proxy = { - name: line.split("=")[0].trim(), - type: "ss", - server: params[1], - port: params[2], - cipher: params[3], - password: params[4].replace(/"/g, ""), - }; - // handle obfs - if (params.length > 5) { - proxy.plugin = "obfs"; - proxy["plugin-opts"] = { - mode: params[5], - host: params[6], - }; - } - return proxy; - }; - return {name, test, parse}; - } - - function Loon_SSR() { - const name = "Loon SSR Parser"; - const test = (line) => { - return ( - line.split(",")[0].split("=")[1].trim().toLowerCase() === "shadowsocksr" - ); - }; - const parse = (line) => { - const params = line.split("=")[1].split(","); - const supported = { - Surge: false, - }; - return { - name: line.split("=")[0].trim(), - type: "ssr", - server: params[1], - port: params[2], - cipher: params[3], - password: params[4].replace(/"/g, ""), - protocol: params[5], - "protocol-param": params[6].match(/{(.*)}/)[1], - supported, - obfs: params[7], - "obfs-param": params[8].match(/{(.*)}/)[1], - }; - }; - return {name, test, parse}; - } - - function Loon_VMess() { - const name = "Loon VMess Parser"; - const test = (line) => { - // distinguish between surge vmess - return ( - /^.*=\s*vmess/i.test(line.split(",")[0]) && - line.indexOf("username") === -1 - ); - }; - const parse = (line) => { - let params = line.split("=")[1].split(","); - const proxy = { - name: line.split("=")[0].trim(), - type: "vmess", - server: params[1], - port: params[2], - cipher: params[3] || "none", - uuid: params[4].replace(/"/g, ""), - alterId: 0, - }; - // get transport options - params = params.splice(5); - for (const item of params) { - const [key, val] = item.split(":"); - params[key] = val; - } - proxy.tls = JSON.parse(params["over-tls"] || "false"); - if (proxy.tls) { - proxy.sni = params["tls-name"] || proxy.server; - proxy['skip-cert-verify'] = JSON.parse(params["skip-cert-verify"] || "false"); - } - switch (params.transport) { - case "tcp": - break; - case "ws": - proxy.network = params.transport; - proxy["ws-path"] = params.path; - proxy["ws-headers"] = { - Host: params.host, - }; - } - if (proxy.tls) { - proxy['skip-cert-verify'] = JSON.parse(params["skip-cert-verify"] || "false"); - } - return proxy; - }; - return {name, test, parse}; - } - - function Loon_Trojan() { - const name = "Loon Trojan Parser"; - const test = (line) => { - return ( - /^.*=\s*trojan/i.test(line.split(",")[0]) && - line.indexOf("password") === -1 - ); - }; - - const parse = (line) => { - const params = line.split("=")[1].split(","); - const proxy = { - name: line.split("=")[0].trim(), - type: "trojan", - server: params[1], - port: params[2], - password: params[3].replace(/"/g, ""), - sni: params[1], // default sni is the server itself - "skip-cert-verify": JSON.parse(params["skip-cert-verify"] || "false"), - }; - // trojan sni - if (params.length > 4) { - const [key, val] = params[4].split(":"); - if (key === "tls-name") proxy.sni = val; - else throw new Error(`Unknown option ${key} for line: \n${line}`); - } - return proxy; - }; - - return {name, test, parse}; - } - - function Loon_Http() { - const name = "Loon HTTP Parser"; - const test = (line) => { - return ( - /^.*=\s*http/i.test(line.split(",")[0]) && - line.split(",").length === 5 && - line.indexOf("username") === -1 && - line.indexOf("password") === -1 - ); - }; - - const parse = (line) => { - const params = line.split("=")[1].split(","); - const proxy = { - name: line.split("=")[0].trim(), - type: "http", - server: params[1], - port: params[2], - tls: params[2] === "443", // port 443 is considered as https type - }; - if (params[3]) proxy.username = params[3]; - if (params[4]) proxy.password = params[4]; - - if (proxy.tls) { - proxy.sni = params["tls-name"] || proxy.server; - proxy['skip-cert-verify'] = JSON.parse(params["skip-cert-verify"] || "false"); - } - - return proxy; - }; - return {name, test, parse}; - } - - function Surge_SS() { - const name = "Surge SS Parser"; - const test = (line) => { - return /^.*=\s*ss/.test(line.split(",")[0]); - }; - const parse = (line) => { - const params = getSurgeParams(line); - const proxy = { - name: params.name, - type: "ss", - server: params.server, - port: params.port, - cipher: params["encrypt-method"], - password: params.password, - tfo: JSON.parse(params.tfo || "false"), - udp: JSON.parse(params["udp-relay"] || "false"), - }; - // handle obfs - if (params.obfs) { - proxy.plugin = "obfs"; - proxy["plugin-opts"] = { - mode: params.obfs, - host: params["obfs-host"], - }; - } - return proxy; - }; - return {name, test, parse}; - } - - function Surge_VMess() { - const name = "Surge VMess Parser"; - const test = (line) => { - return ( - /^.*=\s*vmess/.test(line.split(",")[0]) && line.indexOf("username") !== -1 - ); - }; - const parse = (line) => { - const params = getSurgeParams(line); - const proxy = { - name: params.name, - type: "vmess", - server: params.server, - port: params.port, - uuid: params.username, - alterId: 0, // surge does not have this field - cipher: "none", // surge does not have this field - tls: JSON.parse(params.tls || "false"), - tfo: JSON.parse(params.tfo || "false"), - }; - if (proxy.tls) { - if (typeof params["skip-cert-verify"] !== "undefined") { - proxy['skip-cert-verify'] = params["skip-cert-verify"] === true || params["skip-cert-verify"] === "1"; - } - proxy.sni = params["sni"] || params.server; - } - // use websocket - if (JSON.parse(params.ws || "false")) { - proxy.network = "ws"; - proxy["ws-path"] = params["ws-path"]; - proxy["ws-headers"] = { - Host: params.sni, - }; - } - return proxy; - }; - return {name, test, parse}; - } - - function Surge_Trojan() { - const name = "Surge Trojan Parser"; - const test = (line) => { - return ( - /^.*=\s*trojan/.test(line.split(",")[0]) && line.indexOf("sni") !== -1 - ); - }; - const parse = (line) => { - const params = getSurgeParams(line); - const proxy = { - name: params.name, - type: "trojan", - server: params.server, - port: params.port, - password: params.password, - sni: params.sni || params.server, - tfo: JSON.parse(params.tfo || "false"), - }; - if (typeof params["skip-cert-verify"] !== "undefined") { - proxy['skip-cert-verify'] = params["skip-cert-verify"] === true || params["skip-cert-verify"] === "1"; - } - return proxy; - }; - - return {name, test, parse}; - } - - function Surge_Http() { - const name = "Surge HTTP Parser"; - const test = (line) => { - return ( - /^.*=\s*http/.test(line.split(",")[0]) && !Loon_Http().test(line) - ); - }; - const parse = (line) => { - const params = getSurgeParams(line); - const proxy = { - name: params.name, - type: "http", - server: params.server, - port: params.port, - tls: JSON.parse(params.tls || "false"), - tfo: JSON.parse(params.tfo || "false"), - }; - if (proxy.tls) { - if (typeof params["skip-cert-verify"] !== "undefined") { - proxy['skip-cert-verify'] = params["skip-cert-verify"] === true || params["skip-cert-verify"] === "1"; - } - proxy.sni = params.sni || params.server; - } - if (params.username && params.username !== "none") proxy.username = params.username; - if (params.password && params.password !== "none") proxy.password = params.password; - return proxy; - }; - return {name, test, parse}; - } - - function getSurgeParams(line) { - const params = {}; - params.name = line.split("=")[0].trim(); - const segments = line.split(","); - params.server = segments[1].trim(); - params.port = segments[2].trim(); - for (let i = 3; i < segments.length; i++) { - const item = segments[i]; - if (item.indexOf("=") !== -1) { - const [key, value] = item.split("="); - params[key.trim()] = value.trim(); - } + return {name, test, parse}; } - return params; - } - return [ - URI_SS(), URI_SSR(), URI_VMess(), URI_Trojan(), - Clash_All(), - Surge_SS(), Surge_VMess(), Surge_Trojan(), Surge_Http(), - Loon_SS(), Loon_SSR(), Loon_VMess(), Loon_Trojan(), Loon_Http(), - QX_SS(), QX_SSR(), QX_VMess(), QX_Trojan(), QX_Http() - ]; -})(); + function ClashRuleProvider() { + const name = "Clash Rule Provider"; + const CLASH_RULE_TYPES = [ + // Domain-based rules + "DOMAIN", "DOMAIN-SUFFIX", "DOMAIN-KEYWORD", + // IP based rules + "IP-CIDR", "IP-CIDR6", + // HTTP rules + "USER-AGENT", "URL-REGEX", + // Process rules + "PROCESS-NAME", + // Misc rules + "DST-PORT", "SRC-IP-CIDR", "SRC-PORT" + ]; -var PROXY_PROCESSORS = (function () { - // force to set some properties (e.g., skip-cert-verify, udp, tfo, etc.) - function SetPropertyOperator({key, value}) { - return { - name: "Set Property Operator", - func: (proxies) => { - return proxies.map((p) => { - p[key] = value; - return p; - }); - }, - }; - } - - // add or remove flag for proxies - function FlagOperator(add = true) { - return { - name: "Flag Operator", - func: (proxies) => { - return proxies.map((proxy) => { - if (!add) { - // no flag - proxy.name = removeFlag(proxy.name); - } else { - // get flag - const newFlag = getFlag(proxy.name); - // remove old flag - proxy.name = removeFlag(proxy.name); - proxy.name = newFlag + " " + proxy.name; - proxy.name = proxy.name.replace(/🇹🇼/g, "🇨🇳"); - } - return proxy; - }); - }, - }; - } - - // sort proxies according to their names - function SortOperator(order = "asc") { - return { - name: "Sort Operator", - func: (proxies) => { - switch (order) { - case "asc": - case "desc": - return proxies.sort((a, b) => { - let res = a.name > b.name ? 1 : -1; - res *= order === "desc" ? -1 : 1; - return res; - }); - case "random": - return shuffle(proxies); - default: - throw new Error("Unknown sort option: " + order); + const test = (raw) => ( + raw.indexOf("payload:") === 0 && + CLASH_RULE_TYPES.some(k => raw.indexOf(k) !== -1) + ); + const parse = (raw) => { + const result = []; + try { + const conf = YAML.eval(raw); + const payload = conf["payload"] + .map( + rule => rule.replace("DST-PORT", "DEST-PORT") + .replace("SRC-IP-CIDR", "SRC-IP") + .replace("SRC-PORT", "IN-PORT") + ) + .join("\n"); + return SurgeRuleSet().parse(payload); + } catch (e) { + console.error(`Cannot parse rules: ${e}`); } - }, - }; - } - - // sort by keywords - function KeywordSortOperator(keywords) { - return { - name: "Keyword Sort Operator", - func: (proxies) => - proxies.sort((a, b) => { - const oA = getKeywordOrder(keywords, a.name); - const oB = getKeywordOrder(keywords, b.name); - if (oA && !oB) return -1; - if (oB && !oA) return 1; - if (oA && oB) return oA < oB ? -1 : 1; - if ((!oA && !oB) || (oA && oB && oA === oB)) - return a.name < b.name ? -1 : 1; // fallback to normal sort - }), - }; - } - - function getKeywordOrder(keywords, str) { - let order = null; - for (let i = 0; i < keywords.length; i++) { - if (str.indexOf(keywords[i]) !== -1) { - order = i + 1; // plus 1 is important! 0 will be treated as false!!! - break; - } + return result; + }; + return {name, test, parse}; } - return order; - } - // rename by keywords - // keywords: [{old: "old", now: "now"}] - function KeywordRenameOperator(keywords) { - return { - name: "Keyword Rename Operator", - func: (proxies) => { - return proxies.map((proxy) => { - for (const {old, now} of keywords) { - proxy.name = proxy.name.replaceAll(old, now).trim(); - } - return proxy; - }); - }, - }; - } - - // rename by regex -// keywords: [{expr: "string format regex", now: "now"}] - function RegexRenameOperator(regex) { - return { - name: "Regex Rename Operator", - func: (proxies) => { - return proxies.map((proxy) => { - for (const {expr, now} of regex) { - proxy.name = proxy.name.replace(new RegExp(expr, "g"), now).trim(); - } - return proxy; - }); - }, - }; - } - - // delete keywords operator -// keywords: ['a', 'b', 'c'] - function KeywordDeleteOperator(keywords) { - const keywords_ = keywords.map((k) => { - return { - old: k, - now: "", + function QX() { + const name = "QX Filter"; + const QX_RULE_TYPES = [ + "host", "host-suffix", "host-keyword", + "ip-cidr", "ip6-cidr", + "user-agent" + ]; + const test = (raw) => ( + QX_RULE_TYPES.some(k => raw.indexOf(k.toLowerCase()) === 0) + ) + const parse = (raw) => { + const lines = raw.split("\n"); + for (let i = 0; i < lines.length; i++) { + lines[i] = lines[i] + .replace(/host-suffix/i, "DOMAIN-SUFFIX") + .replace(/host-keyword/i, "DOMAIN-KEYWORD") + .replace(/host/i, "DOMAIN") + .replace("ip-cidr", "IP-CIDR") + .replace(/ip6-cidr/i, "IP-CIDR6") + .replace("user-agent", "USER-AGENT"); + } + return SurgeRuleSet().parse(lines.join("\n")); }; - }); - return { - name: "Keyword Delete Operator", - func: KeywordRenameOperator(keywords_).func, - }; - } + return {name, test, parse}; + } - // delete regex operator -// regex: ['a', 'b', 'c'] - function RegexDeleteOperator(regex) { - const regex_ = regex.map((r) => { + return [SurgeRuleSet(), QX(), ClashRuleProvider()]; + })(); + const RULE_PROCESSORS = (function () { + function RemoveDuplicate() { return { - expr: r, - now: "", - }; - }); - return { - name: "Regex Delete Operator", - func: RegexRenameOperator(regex_).func, - }; - } - - // use base64 encoded script to rename - /** Example script - function operator(proxies) { - // do something - return proxies; - } - - WARNING: - 1. This function name should be `operator`! - 2. Always declare variables before using them! - */ - function ScriptOperator(script) { - return { - name: "Script Operator", - func: (proxies) => { - let output = proxies; - (function () { - // interface to get internal operators - const $get = (name, args) => { - const item = AVAILABLE_OPERATORS[name]; - return item(args); - }; - const $process = (item, proxies) => { - if (item.name.indexOf("Filter") !== -1) { - return ApplyOperator(item, proxies); - } else if (item.name.indexOf("Operator") !== -1) { - return ApplyFilter(item, proxies); + func: rules => { + const seen = new Set(); + const result = []; + rules.forEach(rule => { + const options = rule.options || []; + options.sort(); + const key = `${rule.type},${rule.content},${JSON.stringify(options)}`; + if (!seen.has(key)) { + result.push(rule) + seen.add(key); } - }; - eval(script); - output = operator(proxies); - })(); - return output; - }, - }; - } - - /**************************** Filters ***************************************/ - // filter by keywords - function KeywordFilter({keywords = [], keep = true}) { - return { - name: "Keyword Filter", - func: (proxies) => { - return proxies.map((proxy) => { - const selected = keywords.some((k) => proxy.name.indexOf(k) !== -1); - return keep ? selected : !selected; - }); - }, - }; - } - - // filter useless proxies - function UselessFilter() { - const KEYWORDS = [ - "网址", - "流量", - "时间", - "应急", - "过期", - "Bandwidth", - "expire", - ]; - return { - name: "Useless Filter", - func: KeywordFilter({ - keywords: KEYWORDS, - keep: false, - }).func, - }; - } - - // filter by regions - function RegionFilter(regions) { - const REGION_MAP = { - HK: "🇭🇰", - TW: "🇹🇼", - US: "🇺🇸", - SG: "🇸🇬", - JP: "🇯🇵", - UK: "🇬🇧", - }; - return { - name: "Region Filter", - func: (proxies) => { - // this would be high memory usage - return proxies.map((proxy) => { - const flag = getFlag(proxy.name); - return regions.some((r) => REGION_MAP[r] === flag); - }); - }, - }; - } - - // filter by regex - function RegexFilter({regex = [], keep = true}) { - return { - name: "Regex Filter", - func: (proxies) => { - return proxies.map((proxy) => { - const selected = regex.some((r) => { - r = new RegExp(r); - return r.test(proxy.name); }); - return keep ? selected : !selected; - }); - }, - }; - } + return result; + } + } + } - // filter by proxy types - function TypeFilter(types) { return { - name: "Type Filter", - func: (proxies) => { - return proxies.map((proxy) => types.some((t) => proxy.type === t)); - }, + "Remove Duplicate": RemoveDuplicate }; - } + })(); + const RULE_PRODUCERS = (function () { + function QXFilter() { + const type = "SINGLE"; + const func = (rule) => { + // skip unsupported rules + const UNSUPPORTED = [ + "URL-REGEX", "DEST-PORT", "SRC-IP", "IN-PORT", "PROTOCOL" + ]; + if (UNSUPPORTED.indexOf(rule.type) !== -1) return null; - // use base64 encoded script to filter proxies - /** Script Example - function func(proxies) { - const selected = FULL(proxies.length, true); - // do something - return selected; - } - WARNING: - 1. This function name should be `func`! - 2. Always declare variables before using them! - */ - function ScriptFilter(script) { - return { - name: "Script Filter", - func: (proxies) => { - let output = FULL(proxies.length, true); - !(function () { - eval(script); - output = filter(proxies); - })(); + const TRANSFORM = { + "DOMAIN-KEYWORD": "HOST-KEYWORD", + "DOMAIN-SUFFIX": "HOST-SUFFIX", + "DOMAIN": "HOST", + "IP-CIDR6": "IP6-CIDR" + }; + + let output = `${TRANSFORM[rule.type] || rule.type},${rule.content}`; + if (rule.type === "IP-CIDR" || rule.type === "IP-CIDR6") { + output += rule.options ? `,${rule.options[0]}` : ""; + } + output += ",SUB-STORE" return output; - }, - }; - } + } + return {type, func}; + } - /******************************** Utility Functions *********************************************/ - // get proxy flag according to its name - function getFlag(name) { - // flags from @KOP-XIAO: https://github.com/KOP-XIAO/QuantumultX/blob/master/Scripts/resource-parser.js - const flags = { - "🇦🇨": ["AC"], - "🇦🇹": ["奥地利", "维也纳"], - "🇦🇺": ["AU", "Australia", "Sydney", "澳大利亚", "澳洲", "墨尔本", "悉尼"], - "🇧🇪": ["BE", "比利时"], - "🇧🇬": ["保加利亚", "Bulgaria"], - "🇧🇷": ["BR", "Brazil", "巴西", "圣保罗"], - "🇨🇦": [ - "CA", - "Canada", - "Waterloo", - "加拿大", - "蒙特利尔", - "温哥华", - "楓葉", - "枫叶", - "滑铁卢", - "多伦多", - ], - "🇨🇭": ["瑞士", "苏黎世", "Switzerland"], - "🇩🇪": ["DE", "German", "GERMAN", "德国", "德國", "法兰克福"], - "🇩🇰": ["丹麦"], - "🇪🇸": ["ES", "西班牙", "Spain"], - "🇪🇺": ["EU", "欧盟", "欧罗巴"], - "🇫🇮": ["Finland", "芬兰", "赫尔辛基"], - "🇫🇷": ["FR", "France", "法国", "法國", "巴黎"], - "🇬🇧": ["UK", "GB", "England", "United Kingdom", "英国", "伦敦", "英"], - "🇲🇴": ["MO", "Macao", "澳门", "CTM"], - "🇭🇺": ["匈牙利", "Hungary"], - "🇭🇰": [ - "HK", - "Hongkong", - "Hong Kong", - "香港", - "深港", - "沪港", - "呼港", - "HKT", - "HKBN", - "HGC", - "WTT", - "CMI", - "穗港", - "京港", - "港", - ], - "🇮🇩": ["Indonesia", "印尼", "印度尼西亚", "雅加达"], - "🇮🇪": ["Ireland", "爱尔兰", "都柏林"], - "🇮🇳": ["India", "印度", "孟买", "Mumbai"], - "🇰🇵": ["KP", "朝鲜"], - "🇰🇷": ["KR", "Korea", "KOR", "韩国", "首尔", "韩", "韓"], - "🇱🇻": ["Latvia", "Latvija", "拉脱维亚"], - "🇲🇽️": ["MEX", "MX", "墨西哥"], - "🇲🇾": ["MY", "Malaysia", "马来西亚", "吉隆坡"], - "🇳🇱": ["NL", "Netherlands", "荷兰", "荷蘭", "尼德蘭", "阿姆斯特丹"], - "🇵🇭": ["PH", "Philippines", "菲律宾"], - "🇷🇴": ["RO", "罗马尼亚"], - "🇷🇺": [ - "RU", - "Russia", - "俄罗斯", - "俄羅斯", - "伯力", - "莫斯科", - "圣彼得堡", - "西伯利亚", - "新西伯利亚", - "京俄", - "杭俄", - ], - "🇸🇦": ["沙特", "迪拜"], - "🇸🇪": ["SE", "Sweden"], - "🇸🇬": [ - "SG", - "Singapore", - "新加坡", - "狮城", - "沪新", - "京新", - "泉新", - "穗新", - "深新", - "杭新", - "广新", - ], - "🇹🇭": ["TH", "Thailand", "泰国", "泰國", "曼谷"], - "🇹🇷": ["TR", "Turkey", "土耳其", "伊斯坦布尔"], - "🇹🇼": [ - "TW", - "Taiwan", - "台湾", - "台北", - "台中", - "新北", - "彰化", - "CHT", - "台", - "HINET", - ], - "🇺🇸": [ - "US", - "USA", - "America", - "United States", - "美国", - "美", - "京美", - "波特兰", - "达拉斯", - "俄勒冈", - "凤凰城", - "费利蒙", - "硅谷", - "矽谷", - "拉斯维加斯", - "洛杉矶", - "圣何塞", - "圣克拉拉", - "西雅图", - "芝加哥", - "沪美", - "哥伦布", - "纽约", - ], - "🇻🇳": ["VN", "越南", "胡志明市"], - "🇮🇹": ["Italy", "IT", "Nachash", "意大利", "米兰", "義大利"], - "🇿🇦": ["South Africa", "南非"], - "🇦🇪": ["United Arab Emirates", "阿联酋"], - "🇯🇵": [ - "JP", - "Japan", - "日", - "日本", - "东京", - "大阪", - "埼玉", - "沪日", - "穗日", - "川日", - "中日", - "泉日", - "杭日", - "深日", - "辽日", - "广日", - ], - "🇦🇷": ["AR", "阿根廷"], - "🇳🇴": ["Norway", "挪威", "NO"], - "🇨🇳": [ - "CN", - "China", - "回国", - "中国", - "江苏", - "北京", - "上海", - "广州", - "深圳", - "杭州", - "徐州", - "青岛", - "宁波", - "镇江", - "back", - ], - "🏳️‍🌈": ["流量", "时间", "应急", "过期", "Bandwidth", "expire"], + function SurgeRuleSet() { + const type = "SINGLE"; + const func = (rule) => { + let output = `${rule.type},${rule.content}`; + if (rule.type === "IP-CIDR" || rule.type === "IP-CIDR6") { + output += rule.options ? `,${rule.options[0]}` : ""; + } + return output; + } + return {type, func}; + } + + function LoonRules() { + const type = "SINGLE"; + const func = (rule) => { + // skip unsupported rules + const UNSUPPORTED = [ + "DEST-PORT", "SRC-IP", "IN-PORT", "PROTOCOL" + ]; + if (UNSUPPORTED.indexOf(rule.type) !== -1) return null; + return SurgeRuleSet().func(rule); + } + return {type, func}; + } + + function ClashRuleProvider() { + const type = "ALL"; + const func = (rules) => { + const TRANSFORM = { + "DEST-PORT": "DST-PORT", + "SRC-IP": "SRC-IP-CIDR", + "IN-PORT": "SRC-PORT" + }; + const conf = { + payload: rules.map(rule => { + let output = `${TRANSFORM[rule.type] || rule.type},${rule.content}`; + if (rule.type === "IP-CIDR" || rule.type === "IP-CIDR6") { + output += rule.options ? `,${rule.options[0]}` : ""; + } + return output; + }) + } + return YAML.stringify(conf); + } + return {type, func}; + } + + return { + "QX": QXFilter(), + "Surge": SurgeRuleSet(), + "Loon": LoonRules(), + "Clash": ClashRuleProvider() }; - for (let k of Object.keys(flags)) { - if (flags[k].some((item) => name.indexOf(item) !== -1)) { - return k; + })(); + + function parse(raw) { + for (const parser of RULE_PARSERS) { + let matched; + try { + matched = parser.test(raw); + } catch { + matched = false; + } + if (matched) { + console.log(`Rule parser [${parser.name}] is activated!`); + return parser.parse(raw); } } - // no flag found - const oldFlag = (name.match( - /[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]/ - ) || [])[0]; - return oldFlag || "🏴‍☠️"; } - // remove flag - function removeFlag(str) { - return str - .replace(/[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]/g, "") - .trim(); - } - - // shuffle array - function shuffle(array) { - let currentIndex = array.length, - temporaryValue, - randomIndex; - - // While there remain elements to shuffle... - while (0 !== currentIndex) { - // Pick a remaining element... - randomIndex = Math.floor(Math.random() * currentIndex); - currentIndex -= 1; - - // And swap it with the current element. - temporaryValue = array[currentIndex]; - array[currentIndex] = array[randomIndex]; - array[randomIndex] = temporaryValue; + async function process(rules, operators) { + for (const item of operators) { + if (!RULE_PROCESSORS[item.type]) { + console.error(`Unknown operator: ${item.type}!`); + continue; + } + const op = RULE_PROCESSORS[item.type](item.args); + try { + console.log( + `Applying operator "${item.type}" with arguments: \n >>> ${ + JSON.stringify(item.args) || "None" + }` + ); + rules = op.func(rules); + } catch (err) { + console.error(`Failed to apply operator "${item.type}"!\n REASON: ${err}`); + } } - - return array; + return rules; } - // some logical functions for proxy filters - function AND(...args) { - return args.reduce((a, b) => a.map((c, i) => b[i] && c)); - } - - function OR(...args) { - return args.reduce((a, b) => a.map((c, i) => b[i] || c)); - } - - function NOT(array) { - return array.map((c) => !c); - } - - function FULL(length, bool) { - return [...Array(length).keys()].map(() => bool); - } - - function clone(object) { - return JSON.parse(JSON.stringify(object)); - } - - // apply filter to proxies - function ApplyFilter(filter, proxies) { - // select proxies - let selected = FULL(proxies.length, true); - try { - selected = AND(selected, filter.func(proxies)); - } catch (err) { - console.log(`Cannot apply filter ${filter.name}\n Reason: ${err}`); + function produce(rules, targetPlatform) { + const producer = RULE_PRODUCERS[targetPlatform]; + if (!producer) { + throw new Error(`Target platform: ${targetPlatform} is not supported!`); } - return proxies.filter((_, i) => selected[i]); - } - - // apply operator to proxies - function ApplyOperator(operator, proxies) { - let output = clone(proxies); - try { - const output_ = operator.func(output); - if (output_) output = output_; - } catch (err) { - // print log and skip this operator - console.log(`Cannot apply operator ${op.name}! Reason: ${err}`); - } - return output; - } - - function Apply(process, proxies) { - if (process.name.indexOf("Filter") !== -1) { - return ApplyFilter(process, proxies); - } else if (process.name.indexOf("Operator") !== -1) { - return ApplyOperator(process, proxies); + if (typeof producer.type === "undefined" || producer.type === 'SINGLE') { + return rules + .map(rule => { + try { + return producer.func(rule); + } catch (err) { + console.log( + `ERROR: cannot produce rule: ${JSON.stringify( + rule + )}\nReason: ${err}` + ); + return ""; + } + }) + .filter(line => line.length > 0) + .join("\n"); + } else if (producer.type === "ALL") { + return producer.func(rules); } } + return {parse, process, produce}; +})(); + +function getBuiltInRules() { return { - "Keyword Filter": KeywordFilter, - "Useless Filter": UselessFilter, - "Region Filter": RegionFilter, - "Regex Filter": RegexFilter, - "Type Filter": TypeFilter, - "Script Filter": ScriptFilter, - - "Set Property Operator": SetPropertyOperator, - "Flag Operator": FlagOperator, - "Sort Operator": SortOperator, - "Keyword Sort Operator": KeywordSortOperator, - "Keyword Rename Operator": KeywordRenameOperator, - "Keyword Delete Operator": KeywordDeleteOperator, - "Regex Rename Operator": RegexRenameOperator, - "Regex Delete Operator": RegexDeleteOperator, - "Script Operator": ScriptOperator, - - "Apply": Apply, + "AD": { + "name": "AD", + "description": "", + "urls": [ + "https://raw.githubusercontent.com/privacy-protection-tools/anti-AD/master/anti-ad-surge.txt", + "https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Providers/BanAD.yaml" + ] + } }; -})(); - -var PROXY_PRODUCERS = (function () { - function QX_Producer() { - const targetPlatform = "QX"; - const produce = (proxy) => { - let obfs_opts; - let tls_opts; - switch (proxy.type) { - case "ss": - obfs_opts = ""; - if (proxy.plugin === "obfs") { - const {host, mode} = proxy['plugin-opts']; - obfs_opts = `,obfs=${mode}${ - host ? ",obfs-host=" + host : "" - }`; - } - if (proxy.plugin === "v2ray-plugin") { - const {tls, host, path} = proxy["plugin-opts"]; - obfs_opts = `,obfs=${tls ? "wss" : "ws"}${ - host ? ",obfs-host=" + host : "" - }${ - path ? ",obfs-uri=" + path : "" - }`; - } - return `shadowsocks=${proxy.server}:${proxy.port},method=${ - proxy.cipher - },password=${proxy.password}${obfs_opts}${ - proxy.tfo ? ",fast-open=true" : ",fast-open=false" - }${proxy.udp ? ",udp-relay=true" : ",udp-relay=false"},tag=${ - proxy.name - }`; - case "ssr": - return `shadowsocks=${proxy.server}:${proxy.port},method=${ - proxy.cipher - },password=${proxy.password},ssr-protocol=${proxy.protocol}${ - proxy["protocol-param"] - ? ",ssr-protocol-param=" + proxy["protocol-param"] - : "" - }${proxy.obfs ? ",obfs=" + proxy.obfs : ""}${ - proxy["obfs-param"] ? ",obfs-host=" + proxy["obfs-param"] : "" - }${proxy.tfo ? ",fast-open=true" : ",fast-open=false"}${ - proxy.udp ? ",udp-relay=true" : ",udp-relay=false" - },tag=${proxy.name}`; - case "vmess": - obfs_opts = ""; - if (proxy.network === "ws") { - // websocket - if (proxy.tls) { - // ws-tls - obfs_opts = `,obfs=wss${ - proxy.sni ? ",obfs-host=" + proxy.sni : "" - }${ - proxy["ws-path"] ? ",obfs-uri=" + proxy["ws-path"] : "" - },tls-verification=${proxy['skip-cert-verify'] ? "false" : "true"}`; - } else { - // ws - obfs_opts = `,obfs=ws${ - proxy["ws-headers"].Host ? ",obfs-host=" + proxy["ws-headers"].Host : "" - }${ - proxy["ws-path"] ? ",obfs-uri=" + proxy["ws-path"] : "" - }`; - } - } else { - // tcp - if (proxy.tls) { - obfs_opts = `,obfs=over-tls${ - proxy.sni ? ",obfs-host=" + proxy.sni : "" - },tls-verification=${proxy['skip-cert-verify'] ? "false" : "true"}`; - } - } - return `vmess=${proxy.server}:${proxy.port},method=${ - proxy.cipher === "auto" ? "none" : proxy.cipher - },password=${proxy.uuid}${obfs_opts}${ - proxy.tfo ? ",fast-open=true" : ",fast-open=false" - }${proxy.udp ? ",udp-relay=true" : ",udp-relay=false"},tag=${ - proxy.name - }`; - case "trojan": - return `trojan=${proxy.server}:${proxy.port},password=${ - proxy.password - }${proxy.sni ? ",tls-host=" + proxy.sni : ""},over-tls=true,tls-verification=${ - proxy['skip-cert-verify'] ? "false" : "true" - }${proxy.tfo ? ",fast-open=true" : ",fast-open=false"}${ - proxy.udp ? ",udp-relay=true" : ",udp-relay=false" - },tag=${proxy.name}`; - case "http": - tls_opts = ""; - if (proxy.tls) { - tls_opts = `,over-tls=true,tls-verification=${ - proxy['skip-cert-verify'] ? "false" : "true" - }${ - proxy.sni ? ",tls-host=" + proxy.sni : "" - }`; - } - return `http=${proxy.server}:${proxy.port},username=${ - proxy.username - },password=${proxy.password}${tls_opts}${ - proxy.tfo ? ",fast-open=true" : ",fast-open=false" - },tag=${proxy.name}`; - } - throw new Error( - `Platform ${targetPlatform} does not support proxy type: ${proxy.type}` - ); - }; - return {produce}; - } - - function Loon_Producer() { - const targetPlatform = "Loon"; - const produce = (proxy) => { - let obfs_opts, tls_opts; - switch (proxy.type) { - case "ss": - obfs_opts = ",,"; - if (proxy.plugin) { - if (proxy.plugin === "obfs") { - const {mode, host} = proxy["plugin-opts"]; - obfs_opts = `,${mode},${host || ""}`; - } else { - throw new Error( - `Platform ${targetPlatform} does not support obfs option: ${proxy.obfs}` - ); - } - } - - return `${proxy.name}=shadowsocks,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"${obfs_opts}`; - case "ssr": - return `${proxy.name}=shadowsocksr,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}",${proxy.protocol},{${proxy["protocol-param"] || ""}},${proxy.obfs},{${proxy["obfs-param"] || ""}}`; - case "vmess": - obfs_opts = ""; - if (proxy.network === "ws") { - const host = proxy["ws-headers"].Host || proxy.server; - obfs_opts = `,transport:ws,host:${host},path:${ - proxy["ws-path"] || "/" - }`; - } else { - obfs_opts = `,transport:tcp`; - } - if (proxy.tls) { - obfs_opts += `${ - proxy.sni ? ",tls-name:" + proxy.sni : "" - },skip-cert-verify:${proxy['skip-cert-verify'] || "false"}`; - } - return `${proxy.name}=vmess,${proxy.server},${proxy.port},${ - proxy.cipher === "auto" ? "none" : proxy.cipher - },"${proxy.uuid}",over-tls:${proxy.tls || "false"}${obfs_opts}`; - case "trojan": - return `${proxy.name}=trojan,${proxy.server},${proxy.port},"${ - proxy.password - }"${ - proxy.sni ? ",tls-name:" + proxy.sni : "" - },skip-cert-verify:${ - proxy['skip-cert-verify'] || "false" - }`; - case "http": - tls_opts = ""; - const base = `${proxy.name}=${proxy.tls ? "http" : "https"},${ - proxy.server - },${proxy.port},${proxy.username || ""},${proxy.password || ""}`; - if (proxy.tls) { - // https - tls_opts = `${ - proxy.sni ? ",tls-name:" + proxy.sni : "" - },skip-cert-verify:${proxy['skip-cert-verify']}`; - return base + tls_opts; - } else return base; - } - throw new Error( - `Platform ${targetPlatform} does not support proxy type: ${proxy.type}` - ); - }; - return {produce}; - } - - function Surge_Producer() { - const targetPlatform = "Surge"; - const produce = (proxy) => { - let obfs_opts, tls_opts; - switch (proxy.type) { - case "ss": - obfs_opts = ""; - if (proxy.plugin) { - const {host, mode} = proxy['plugin-opts']; - if (proxy.plugin === "obfs") { - obfs_opts = `,obfs=${mode}${ - host ? ",obfs-host=" + host : "" - }`; - } else { - throw new Error( - `Platform ${targetPlatform} does not support obfs option: ${proxy.obfs}` - ); - } - } - return `${proxy.name}=ss,${proxy.server}, ${proxy.port},encrypt-method=${ - proxy.cipher - },password=${proxy.password}${obfs_opts},tfo=${ - proxy.tfo || "false" - },udp-relay=${proxy.udp || "false"}`; - case "vmess": - tls_opts = ""; - let config = `${proxy.name}=vmess,${proxy.server},${ - proxy.port - },username=${proxy.uuid},tls=${proxy.tls || "false"},tfo=${proxy.tfo || "false"}`; - if (proxy.network === "ws") { - const path = proxy["ws-path"] || "/"; - const host = proxy["ws-headers"].Host; - config += `,ws=true${path ? ",ws-path=" + path : ""}${ - host ? ",ws-headers=HOST:" + host : "" - }`; - } - if (proxy.tls) { - config += `${ - typeof proxy['skip-cert-verify'] !== "undefined" - ? ",skip-cert-verify=" + proxy['skip-cert-verify'] - : "" - }`; - config += proxy.sni ? `,sni=${proxy.sni}` : ""; - } - return config; - case "trojan": - return `${proxy.name}=trojan,${proxy.server},${proxy.port},password=${ - proxy.password - }${ - typeof proxy['skip-cert-verify'] !== "undefined" - ? ",skip-cert-verify=" + proxy['skip-cert-verify'] - : "" - }${proxy.sni ? ",sni=" + proxy.sni : ""},tfo=${proxy.tfo || "false"}`; - case "http": - tls_opts = ", tls=false"; - if (proxy.tls) { - tls_opts = `,tls=true,skip-cert-verify=${proxy['skip-cert-verify']},sni=${proxy.sni}`; - } - return `${proxy.name}=http, ${proxy.server}, ${proxy.port}${ - proxy.username ? ",username=" + proxy.username : "" - }${ - proxy.password ? ",password=" + proxy.password : "" - }${tls_opts},tfo=${proxy.tfo || "false"}`; - } - throw new Error( - `Platform ${targetPlatform} does not support proxy type: ${proxy.type}` - ); - }; - return {produce}; - } - - function Clash_Producer() { - const type = "ALL"; - const produce = (proxies) => { - return "proxies:\n" + proxies.map(proxy => { - delete proxy.supported; - return " - " + JSON.stringify(proxy) + "\n"; - }).join(""); - }; - return {type, produce}; - } - - function URI_Producer() { - const type = "SINGLE"; - const produce = (proxy) => { - let result = ""; - switch (proxy.type) { - case "ss": - const userinfo = `${proxy.cipher}:${proxy.password}`; - result = `ss://${Base64.safeEncode(userinfo)}@${proxy.server}:${ - proxy.port - }/`; - if (proxy.plugin) { - result += "?plugin="; - const opts = proxy["plugin-opts"]; - switch (proxy.plugin) { - case "obfs": - result += encodeURIComponent( - `simple-obfs;obfs=${opts.mode}${ - opts.host ? ";obfs-host=" + opts.host : "" - }` - ); - break; - case "v2ray-plugin": - result += encodeURIComponent( - `v2ray-plugin;obfs=${opts.mode}${ - opts.host ? ";obfs-host" + opts.host : "" - }${opts.tls ? ";tls" : ""}` - ); - break; - default: - throw new Error(`Unsupported plugin option: ${proxy.plugin}`); - } - } - result += `#${encodeURIComponent(proxy.name)}`; - break; - case "ssr": - result = `${proxy.server}:${proxy.port}:${proxy.protocol}:${ - proxy.cipher - }:${proxy.obfs}:${Base64.safeEncode(proxy.password)}/`; - result += `?remarks=${Base64.safeEncode(proxy.name)}${ - proxy["obfs-param"] - ? "&obfsparam=" + Base64.safeEncode(proxy["obfs-param"]) - : "" - }${ - proxy["protocol-param"] - ? "&protocolparam=" + Base64.safeEncode(proxy["protocol-param"]) - : "" - }`; - result = "ssr://" + Base64.safeEncode(result); - break; - case "vmess": - // V2RayN URI format - result = { - ps: proxy.name, - add: proxy.server, - port: proxy.port, - id: proxy.uuid, - type: "", - aid: 0, - net: proxy.network || "tcp", - tls: proxy.tls ? "tls" : "", - }; - // obfs - if (proxy.network === "ws") { - result.path = proxy["ws-path"] || "/"; - result.host = proxy["ws-headers"].Host || proxy.server; - } - result = "vmess://" + Base64.safeEncode(JSON.stringify(result)); - break; - case "trojan": - result = `trojan://${proxy.password}@${proxy.server}:${proxy.port}#${encodeURIComponent(proxy.name)}`; - break; - default: - throw new Error(`Cannot handle proxy type: ${proxy.type}`); - } - return result; - } - return {type, produce}; - } - - function JSON_Producer() { - const type = "ALL"; - const produce = proxies => JSON.stringify(proxies, null, 2); - return {type, produce}; - } - - - return { - "QX": QX_Producer(), - "Surge": Surge_Producer(), - "Loon": Loon_Producer(), - "Clash": Clash_Producer(), - "URI": URI_Producer(), - "JSON": JSON_Producer() - } -})(); +} /****************************************** Supporting Functions **********************************************************/ /** diff --git a/backend/sub-store.min.js b/backend/sub-store.min.js index 1e34b56..c30d0b9 100644 --- a/backend/sub-store.min.js +++ b/backend/sub-store.min.js @@ -1,2 +1,2 @@ -// UPDATED AT: 2020年11月25日 星期三 12时04分02秒 CST -const $=API("sub-store"),Base64=new Base64Code;function service(){console.log("========== Sub-Store ===========");const e=express(),t="settings",s="subs",r="collections";function n(e){const t=Object.keys(e);let s="";for(let r of t)if(/USER-AGENT/i.test(r)){s=e[r];break}return-1!==s.indexOf("Quantumult%20X")?"QX":-1!==s.indexOf("Surge")?"Surge":-1!==s.indexOf("Decar")||-1!==s.indexOf("Loon")?"Loon":"JSON"}async function o(e,t=!0){const s=HTTP({headers:{"User-Agent":"Quantumult%20X"}}),r="#"+Base64.safeEncode(e),n=$.read(r),o=`#TIME-${Base64.safeEncode(e)}`,a=(new Date).getTime()-$.read(o)>864e5;if(t&&n&&!a)return $.log(`Use cached for url: ${e}`),n;const{body:i}=await s.get(e);if(0===i.replace(/\s/g,"").length)throw new Error("订阅内容为空!");return $.write(i,r),$.write((new Date).getTime(),o),i}$.read(s)||$.write({},s),$.read(r)||$.write({},r),$.read(t)||$.write({},t),e.get("/download/collection/:name",async function(e,t){const{name:a}=e.params,{cache:i}=e.query||"false",p=e.query.target||n(e.headers),l=$.read(r),c=$.read(s),u=l[a];if($.info(`正在下载组合订阅:${a}`),u){const e=u.subscriptions;let s=[];for(let t=0;te!==n);$.write(a,r),t.json({status:"success"})}),e.route("/api/subs").get(function(e,t){const r=$.read(s);t.json({status:"success",data:r})}).post(function(e,t){const r=e.body,n=$.read(s);$.info(`正在创建订阅: ${r.name}`),n[r.name]&&t.status(500).json({status:"failed",message:`订阅${r.name}已存在!`});/^[\w-_]*$/.test(r.name)?(n[r.name]=r,$.write(n,s),t.status(201).json({status:"success",data:r})):t.status(500).json({status:"failed",message:`订阅名称 ${r.name} 中含有非法字符!名称中只能包含英文字母、数字、下划线、横杠。`})}),e.route("/api/collection/:name").get(function(e,t){const{name:s}=e.params,n=$.read(r)[s];n?t.json({status:"success",data:n}):t.status(404).json({status:"failed",message:`未找到订阅集:${s}!`})}).patch(function(e,t){const{name:s}=e.params;let n=e.body;const o=$.read(r);if(o[s]){const e={...o[s],...n};$.info(`正在更新组合订阅:${s}...`),delete o[s],o[n.name||s]=e,$.write(o,r),t.json({status:"success",data:e})}else t.status(500).json({status:"failed",message:`订阅集${s}不存在,无法更新!`})}).delete(function(e,t){const{name:s}=e.params;$.info(`正在删除组合订阅:${s}`);let n=$.read(r);delete n[s],$.write(n,r),t.json({status:"success"})}),e.route("/api/collections").get(function(e,t){const s=$.read(r);t.json({status:"success",data:s})}).post(function(e,t){const s=e.body;$.info(`正在创建组合订阅:${s.name}`);const n=$.read(r);n[s.name]&&t.status(500).json({status:"failed",message:`订阅集${s.name}已存在!`});/^[\w-_]*$/.test(s.name)?(n[s.name]=s,$.write(n,r),t.status(201).json({status:"success",data:s})):t.status(500).json({status:"failed",message:`订阅集名称 ${s.name} 中含有非法字符!名称中只能包含英文字母、数字、下划线、横杠。`})}),e.get("/api/backup"),e.route("/api/storage").get((e,t)=>{t.json($.read("#sub-store"))}).post((e,t)=>{const s=e.body;$.write(JSON.stringify(s),"#sub-store"),t.end()}),e.route("/api/settings").get(function(e,s){const r=$.read(t);s.json(r)}).patch(function(e,s){const r=e.body,n=$.read(t);$.write({...n,...r},t),s.json({status:"success"})}),e.get("/api/utils/IP_API/:server",async function(e,t){const s=decodeURIComponent(e.params.server),r=await $.http.get(`http://ip-api.com/json/${s}?lang=zh-CN`).then(e=>JSON.parse(e.body));t.json(r)}),e.post("/api/utils/refresh",async function(e,t){const{url:s}=e.body;$.info(`Refreshing cache for URL: ${s}`);try{const e=await o(s,!1);$.write(e,`#${Base64.safeEncode(s)}`),t.json({status:"success"})}catch(e){t.status(500).json({status:"failed",message:`无法刷新资源 ${s}: ${e}`})}}),e.get("/api/utils/env",function(e,t){const{isNode:s,isQX:r,isLoon:n,isSurge:o}=ENV();let a="Node";s&&(a="Node");r&&(a="QX");n&&(a="Loon");o&&(a="Surge");t.json({backend:a})}),e.get("/api/utils/backup",async function(e,s){const{action:r}=e.query,{gistToken:n}=$.read(t);if(n){const e=new Gist("Auto Generated Sub-Store Backup",n);try{let t;switch(r){case"upload":t=$.read("#sub-store"),$.info("上传备份中..."),await e.upload(t);break;case"download":$.info("还原备份中..."),t=await e.download(),$.write(t,"#sub-store")}s.json({status:"success"})}catch(e){const t=`${"upload"===r?"上传":"下载"}备份失败!${e}`;$.error(t),s.status(500).json({status:"failed",message:t})}}else s.status(500).json({status:"failed",message:"未找到Gist备份Token!"})}),e.get("/",async(e,t)=>{t.set("location","https://sub-store.vercel.app/").status(302).end()}),e.options("/",async(e,t)=>{t.status(200).end()}),e.all("/",(e,t)=>{t.send("Hello from sub-store, made with ❤️ by Peng-YM")}),e.start()}service();var ProxyUtils=function(){function e(e,t){let s;try{s=e.test(t)}catch(e){s=!1}return s}return{parse:function(t){const s=(t=function(e){for(const t of PROXY_PREPROCESSORS)try{if(t.test(e))return $.log(`Pre-processor [${t.name}] activated`),t.parse(e)}catch(e){$.error(`Parser [${t.name}] failed\n Reason: ${e}`)}return e}(t)).split("\n"),r=[];let n;for(let t of s){if(0===(t=t.trim()).length)continue;let s=n&&e(n,t);if(!s)for(const r of PROXY_PARSERS)if(e(r,t)){n=r,s=!0,$.log(`Proxy parser: ${r.name} is activated`);break}if(s)try{const e=n.parse(t);e||$.error(`Parser ${n.name} return nothing for \n${t}\n`),r.push(e)}catch(e){$.error(`Failed to parse line: \n ${t}\n Reason: ${e.stack}`)}else $.error(`Failed to find a rule to parse line: \n${t}\n`)}return r},process:async function(e,t=[]){for(const s of t){let t;if(-1!==s.type.indexOf("Script")){const{mode:e,content:r}=s.args;t="link"===e?await $.http.get(r).then(e=>e.body).catch(e=>{throw new Error(`Error when downloading remote script: ${s.args.content}.\n Reason: ${e}`)}):r}const r=PROXY_PROCESSORS[s.type];if(r)try{$.log(`Applying "${s.type}" with arguments:\n >>> ${JSON.stringify(s.args,null,2)||"None"}`),e=-1!==s.type.indexOf("Script")?PROXY_PROCESSORS.Apply(r(t),e):PROXY_PROCESSORS.Apply(r(s.args),e)}catch(e){$.error(`Failed to apply "${s.type}"!\n REASON: ${e}`)}else $.error(`Unknown operator: "${s.type}"`)}return e},produce:function(e,t){const s=PROXY_PRODUCERS[t];if(!s)throw new Error(`Target platform: ${t} is not supported!`);return e=e.filter(e=>!(e.supported&&!1===e.supported[t])),$.log(`Producing proxies for target: ${t}`),void 0===s.type||"SINGLE"===s.type?e.map(e=>{try{return s.produce(e)}catch(t){return $.error(`Cannot produce proxy: ${JSON.stringify(e,null,2)}\nReason: ${t}`),""}}).filter(e=>e.length>0).join("\n"):"ALL"===s.type?s.produce(e):void 0}}}(),PROXY_PREPROCESSORS=function(){return[function(){const e=["dm1lc3M","c3NyOi8v","dHJvamFu","c3M6Ly","c3NkOi8v"];return{name:"Base64 Pre-processor",test:function(t){return e.some(e=>-1!==t.indexOf(e))},parse:function(e){return e=Base64.safeDecode(e)}}}(),{name:"Clash Pre-processor",test:function(e){return/proxies/.test(e)},parse:function(e){return-1!==e.indexOf("{")&&(e=e.replace(/ - /g," - ").replace(/:(?!\s)/g,": ").replace(/\,\"/g,', "').replace(/: {/g,": {, ").replace(/, (\"?host|path|tls|mux|skip\"?)/g,", $1").replace(/{name: /g,'{name: "').replace(/, server:/g,'", server:').replace(/{|}/g,"").replace(/,/g,"\n ")),e=-1===(e=e.replace(/ -\n.*name/g," - name").replace(/\$|\`/g,"").split("proxy-providers:")[0].split("proxy-groups:")[0].replace(/\"([\w-]+)\"\s*:/g,"$1:")).indexOf("proxies:")?"proxies:\n"+e:"proxies:"+e.split("proxies:")[1],YAML.eval(e).proxies.map(e=>JSON.stringify(e)).join("\n")}},{name:"SSD Pre-processor",test:function(e){return 0===e.indexOf("ssd://")},parse:function(e){const t=[];let s=JSON.parse(Base64.safeDecode(e.split("ssd://")[1]));s.traffic_used,s.traffic_total,s.expiry,s.airport;let r=s.port,n=s.encryption,o=s.password,a=s.servers;for(let e=0;e{let[t,n]=e.split("=");if(t=t.trim(),n=n.trim(),-1!==r.indexOf(t)){s.type=t;const e=n.split(":");s.server=e[0],s.port=e[1]}else s[t.trim()]=n.trim()}),s}function t(){return{name:"Loon HTTP Parser",test:e=>/^.*=\s*http/i.test(e.split(",")[0])&&5===e.split(",").length&&-1===e.indexOf("username")&&-1===e.indexOf("password"),parse:e=>{const t=e.split("=")[1].split(","),s={name:e.split("=")[0].trim(),type:"http",server:t[1],port:t[2],tls:"443"===t[2]};return t[3]&&(s.username=t[3]),t[4]&&(s.password=t[4]),s.tls&&(s.sni=t["tls-name"]||s.server,s["skip-cert-verify"]=JSON.parse(t["skip-cert-verify"]||"false")),s}}}function s(e){const t={};t.name=e.split("=")[0].trim();const s=e.split(",");t.server=s[1].trim(),t.port=s[2].trim();for(let e=3;e/^ss:\/\//.test(e),parse:e=>{const t={};let s=e.split("ss://")[1];const r={name:decodeURIComponent(e.split("#")[1]),type:"ss",supported:t},n=(s=s.split("#")[0]).match(/@([^\/]*)(\/|$)/)[1],o=n.lastIndexOf(":");r.server=n.substring(0,o),r.port=n.substring(o+1);const a=Base64.safeDecode(s.split("@")[0]).split(":");if(r.cipher=a[0],r.password=a[1],-1!==s.indexOf("?plugin=")){const e=("plugin="+decodeURIComponent(s.split("?plugin=")[1].split("&")[0])).split(";"),n={};for(const t of e){const[e,s]=t.split("=");e&&(n[e]=s||!0)}switch(n.plugin){case"obfs-local":case"simple-obfs":r.plugin="obfs",r["plugin-opts"]={mode:n.obfs,host:n["obfs-host"]};break;case"v2ray-plugin":r.supported={...t,Loon:!1,Surge:!1},r.obfs="v2ray-plugin",r["plugin-opts"]={mode:"websocket",host:n["obfs-host"],path:n.path||"",tls:n.tls||!1};break;default:throw new Error(`Unsupported plugin option: ${n.plugin}`)}}return r}},function(){const e={Surge:!1};return{name:"URI SSR Parser",test:e=>/^ssr:\/\//.test(e),parse:t=>{let s=(t=Base64.safeDecode(t.split("ssr://")[1])).indexOf(":origin");-1===s&&(s=t.indexOf(":auth_"));const r=t.substring(0,s),n=r.substring(0,r.lastIndexOf(":")),o=r.substring(r.lastIndexOf(":")+1);let a=t.substring(s+1).split("/?")[0].split(":"),i={type:"ssr",server:n,port:o,protocol:a[0],cipher:a[1],obfs:a[2],password:Base64.safeDecode(a[3]),supported:e};if(a={},(t=t.split("/?")[1].split("&")).length>1)for(const e of t){const[t,s]=e.split("=");a[t]=s}return i={...i,name:Base64.safeDecode(a.remarks),"protocol-param":Base64.safeDecode(a.protoparam).replace(/\s/g,"")||"","obfs-param":Base64.safeDecode(a.obfsparam).replace(/\s/g,"")||""}}}}(),{name:"URI VMess Parser",test:e=>/^vmess:\/\//.test(e),parse:e=>{const t={};e=e.split("vmess://")[1];const s=Base64.safeDecode(e);if(/=\s*vmess/.test(s)){const e=s.split(",").map(e=>e.trim()),t={};for(const s of e)if(-1!==s.indexOf("=")){const[e,r]=s.split("=");t[e.trim()]=r.trim()}const r={name:e[0].split("=")[0].trim(),type:"vmess",server:e[1],port:e[2],cipher:e[3],uuid:e[4].match(/^"(.*)"$/)[1],tls:"over-tls"===t.obfs||"wss"===t.obfs,udp:JSON.parse(t["udp-relay"]||"false"),tfo:JSON.parse(t["fast-open"]||"false")};return"ws"!==t.obfs&&"wss"!==t.obfs||(r.network="ws",r["ws-path"]=t["obfs-uri"],r["ws-headers"]={Host:t["obfs-host"]||r.server}),r.tls&&"false"===t['"tls-verification"']&&(r["skip-cert-verify"]=!0),r.tls&&t["obfs-host"]&&(r.sni=t["obfs-host"]),r}{const e=JSON.parse(s),r={name:e.ps,type:"vmess",server:e.add,port:e.port,cipher:"auto",uuid:e.id,alterId:e.aid||0,tls:"tls"===e.tls||!0===e.tls,supported:t};return"ws"===e.net&&(r.network="ws",r["ws-path"]=e.path,r["ws-headers"]={Host:e.host||e.add},r.tls&&e.host&&(r.sni=e.host)),!1===e.verify_cert&&(r["skip-cert-verify"]=!0),r}}},{name:"URI Trojan Parser",test:e=>/^trojan:\/\//.test(e),parse:e=>{if(-1===e.indexOf(":443"))throw new Error("Trojan port should always be 443!");const t=(e=e.split("trojan://")[1]).split("@")[1].split(":443")[0];return{name:decodeURIComponent(e.split("#")[1].trim())||`[Trojan] ${t}`,type:"trojan",server:t,port:443,password:e.split("@")[0],supported:{}}}},{name:"Clash Parser",test:e=>{try{JSON.parse(e)}catch(e){return!1}return!0},parse:e=>JSON.parse(e)},{name:"Surge SS Parser",test:e=>/^.*=\s*ss/.test(e.split(",")[0]),parse:e=>{const t=s(e),r={name:t.name,type:"ss",server:t.server,port:t.port,cipher:t["encrypt-method"],password:t.password,tfo:JSON.parse(t.tfo||"false"),udp:JSON.parse(t["udp-relay"]||"false")};return t.obfs&&(r.plugin="obfs",r["plugin-opts"]={mode:t.obfs,host:t["obfs-host"]}),r}},{name:"Surge VMess Parser",test:e=>/^.*=\s*vmess/.test(e.split(",")[0])&&-1!==e.indexOf("username"),parse:e=>{const t=s(e),r={name:t.name,type:"vmess",server:t.server,port:t.port,uuid:t.username,alterId:0,cipher:"none",tls:JSON.parse(t.tls||"false"),tfo:JSON.parse(t.tfo||"false")};return r.tls&&(void 0!==t["skip-cert-verify"]&&(r["skip-cert-verify"]=!0===t["skip-cert-verify"]||"1"===t["skip-cert-verify"]),r.sni=t.sni||t.server),JSON.parse(t.ws||"false")&&(r.network="ws",r["ws-path"]=t["ws-path"],r["ws-headers"]={Host:t.sni}),r}},{name:"Surge Trojan Parser",test:e=>/^.*=\s*trojan/.test(e.split(",")[0])&&-1!==e.indexOf("sni"),parse:e=>{const t=s(e),r={name:t.name,type:"trojan",server:t.server,port:t.port,password:t.password,sni:t.sni||t.server,tfo:JSON.parse(t.tfo||"false")};return void 0!==t["skip-cert-verify"]&&(r["skip-cert-verify"]=!0===t["skip-cert-verify"]||"1"===t["skip-cert-verify"]),r}},{name:"Surge HTTP Parser",test:e=>/^.*=\s*http/.test(e.split(",")[0])&&!t().test(e),parse:e=>{const t=s(e),r={name:t.name,type:"http",server:t.server,port:t.port,tls:JSON.parse(t.tls||"false"),tfo:JSON.parse(t.tfo||"false")};return r.tls&&(void 0!==t["skip-cert-verify"]&&(r["skip-cert-verify"]=!0===t["skip-cert-verify"]||"1"===t["skip-cert-verify"]),r.sni=t.sni||t.server),t.username&&"none"!==t.username&&(r.username=t.username),t.password&&"none"!==t.password&&(r.password=t.password),r}},{name:"Loon SS Parser",test:e=>"shadowsocks"===e.split(",")[0].split("=")[1].trim().toLowerCase(),parse:e=>{const t=e.split("=")[1].split(","),s={name:e.split("=")[0].trim(),type:"ss",server:t[1],port:t[2],cipher:t[3],password:t[4].replace(/"/g,"")};return t.length>5&&(s.plugin="obfs",s["plugin-opts"]={mode:t[5],host:t[6]}),s}},{name:"Loon SSR Parser",test:e=>"shadowsocksr"===e.split(",")[0].split("=")[1].trim().toLowerCase(),parse:e=>{const t=e.split("=")[1].split(",");return{name:e.split("=")[0].trim(),type:"ssr",server:t[1],port:t[2],cipher:t[3],password:t[4].replace(/"/g,""),protocol:t[5],"protocol-param":t[6].match(/{(.*)}/)[1],supported:{Surge:!1},obfs:t[7],"obfs-param":t[8].match(/{(.*)}/)[1]}}},{name:"Loon VMess Parser",test:e=>/^.*=\s*vmess/i.test(e.split(",")[0])&&-1===e.indexOf("username"),parse:e=>{let t=e.split("=")[1].split(",");const s={name:e.split("=")[0].trim(),type:"vmess",server:t[1],port:t[2],cipher:t[3]||"none",uuid:t[4].replace(/"/g,""),alterId:0};t=t.splice(5);for(const e of t){const[s,r]=e.split(":");t[s]=r}switch(s.tls=JSON.parse(t["over-tls"]||"false"),s.tls&&(s.sni=t["tls-name"]||s.server,s["skip-cert-verify"]=JSON.parse(t["skip-cert-verify"]||"false")),t.transport){case"tcp":break;case"ws":s.network=t.transport,s["ws-path"]=t.path,s["ws-headers"]={Host:t.host}}return s.tls&&(s["skip-cert-verify"]=JSON.parse(t["skip-cert-verify"]||"false")),s}},{name:"Loon Trojan Parser",test:e=>/^.*=\s*trojan/i.test(e.split(",")[0])&&-1===e.indexOf("password"),parse:e=>{const t=e.split("=")[1].split(","),s={name:e.split("=")[0].trim(),type:"trojan",server:t[1],port:t[2],password:t[3].replace(/"/g,""),sni:t[1],"skip-cert-verify":JSON.parse(t["skip-cert-verify"]||"false")};if(t.length>4){const[r,n]=t[4].split(":");if("tls-name"!==r)throw new Error(`Unknown option ${r} for line: \n${e}`);s.sni=n}return s}},t(),{name:"QX SS Parser",test:e=>/^shadowsocks\s*=/.test(e.split(",")[0].trim())&&-1===e.indexOf("ssr-protocol"),parse:t=>{const s=e(t),r={name:s.tag,type:"ss",server:s.server,port:s.port,cipher:s.method,password:s.password,udp:JSON.parse(s["udp-relay"]||"false"),tfo:JSON.parse(s["fast-open"]||"false"),supported:{}};if(s.obfs)switch(r["plugin-opts"]={host:s["obfs-host"]||r.server},s.obfs){case"http":case"tls":r.plugin="obfs",r["plugin-opts"].mode=s.obfs;break;case"ws":case"wss":r["plugin-opts"]={...r["plugin-opts"],mode:"websocket",path:s["obfs-uri"]||"/",tls:"wss"===s.obfs},r["plugin-opts"].tls&&void 0!==s["tls-verification"]&&(r["plugin-opts"]["skip-cert-verify"]=s["tls-verification"]),r.plugin="v2ray-plugin",r.supported.Surge=!1,r.supported.Loon=!1}return r}},{name:"QX SSR Parser",test:e=>/^shadowsocks\s*=/.test(e.split(",")[0].trim())&&-1!==e.indexOf("ssr-protocol"),parse:t=>{const s=e(t),r={name:s.tag,type:"ssr",server:s.server,port:s.port,cipher:s.method,password:s.password,protocol:s["ssr-protocol"],obfs:"plain","protocol-param":s["ssr-protocol-param"],udp:JSON.parse(s["udp-relay"]||"false"),tfo:JSON.parse(s["fast-open"]||"false"),supported:{Surge:!1}};return s.obfs&&(r.obfs=s.obfs,r["obfs-param"]=s["obfs-host"]),r}},{name:"QX VMess Parser",test:e=>/^vmess\s*=/.test(e.split(",")[0].trim()),parse:t=>{const s=e(t),r={type:"vmess",name:s.tag,server:s.server,port:s.port,cipher:s.method||"none",uuid:s.password,alterId:0,tls:"over-tls"===s.obfs||"wss"===s.obfs,udp:JSON.parse(s["udp-relay"]||"false"),tfo:JSON.parse(s["fast-open"]||"false")};return r.tls&&(r.sni=s["obfs-host"]||s.server,r["skip-cert-verify"]=!JSON.parse(s["tls-verification"]||"true")),"ws"!==s.obfs&&"wss"!==s.obfs||(r.network="ws",r["ws-path"]=s["obfs-uri"],r["ws-headers"]={Host:s["obfs-host"]||s.server}),r}},{name:"QX Trojan Parser",test:e=>/^trojan\s*=/.test(e.split(",")[0].trim()),parse:t=>{const s=e(t),r={type:"trojan",name:s.tag,server:s.server,port:s.port,password:s.password,sni:s["tls-host"]||s.server,udp:JSON.parse(s["udp-relay"]||"false"),tfo:JSON.parse(s["fast-open"]||"false")};return r["skip-cert-verify"]=!JSON.parse(s["tls-verification"]||"true"),r}},{name:"QX HTTP Parser",test:e=>/^http\s*=/.test(e.split(",")[0].trim()),parse:t=>{const s=e(t),r={type:"http",name:s.tag,server:s.server,port:s.port,tls:JSON.parse(s["over-tls"]||"false"),udp:JSON.parse(s["udp-relay"]||"false"),tfo:JSON.parse(s["fast-open"]||"false")};return s.username&&"none"!==s.username&&(r.username=s.username),s.password&&"none"!==s.password&&(r.password=s.password),r.tls&&(r.sni=s["tls-host"]||r.server,r["skip-cert-verify"]=!JSON.parse(s["tls-verification"]||"true")),r}}]}(),PROXY_PROCESSORS=function(){function SetPropertyOperator({key:e,value:t}){return{name:"Set Property Operator",func:s=>s.map(s=>(s[e]=t,s))}}function FlagOperator(e=!0){return{name:"Flag Operator",func:t=>t.map(t=>{if(e){const e=getFlag(t.name);t.name=removeFlag(t.name),t.name=e+" "+t.name,t.name=t.name.replace(/🇹🇼/g,"🇨🇳")}else t.name=removeFlag(t.name);return t})}}function SortOperator(e="asc"){return{name:"Sort Operator",func:t=>{switch(e){case"asc":case"desc":return t.sort((t,s)=>{let r=t.name>s.name?1:-1;return r*="desc"===e?-1:1});case"random":return shuffle(t);default:throw new Error("Unknown sort option: "+e)}}}}function KeywordSortOperator(e){return{name:"Keyword Sort Operator",func:t=>t.sort((t,s)=>{const r=getKeywordOrder(e,t.name),n=getKeywordOrder(e,s.name);return r&&!n?-1:n&&!r?1:r&&n?rt.map(t=>{for(const{old:s,now:r}of e)t.name=t.name.replaceAll(s,r).trim();return t})}}function RegexRenameOperator(e){return{name:"Regex Rename Operator",func:t=>t.map(t=>{for(const{expr:s,now:r}of e)t.name=t.name.replace(new RegExp(s,"g"),r).trim();return t})}}function KeywordDeleteOperator(e){return{name:"Keyword Delete Operator",func:KeywordRenameOperator(e.map(e=>({old:e,now:""}))).func}}function RegexDeleteOperator(e){return{name:"Regex Delete Operator",func:RegexRenameOperator(e.map(e=>({expr:e,now:""}))).func}}function ScriptOperator(script){return{name:"Script Operator",func:proxies=>{let output=proxies;return function(){const $get=(e,t)=>{return(0,AVAILABLE_OPERATORS[e])(t)},$process=(e,t)=>-1!==e.name.indexOf("Filter")?ApplyOperator(e,t):-1!==e.name.indexOf("Operator")?ApplyFilter(e,t):void 0;eval(script),output=operator(proxies)}(),output}}}function KeywordFilter({keywords:e=[],keep:t=!0}){return{name:"Keyword Filter",func:s=>s.map(s=>{const r=e.some(e=>-1!==s.name.indexOf(e));return t?r:!r})}}function UselessFilter(){return{name:"Useless Filter",func:KeywordFilter({keywords:["网址","流量","时间","应急","过期","Bandwidth","expire"],keep:!1}).func}}function RegionFilter(e){const t={HK:"🇭🇰",TW:"🇹🇼",US:"🇺🇸",SG:"🇸🇬",JP:"🇯🇵",UK:"🇬🇧"};return{name:"Region Filter",func:s=>s.map(s=>{const r=getFlag(s.name);return e.some(e=>t[e]===r)})}}function RegexFilter({regex:e=[],keep:t=!0}){return{name:"Regex Filter",func:s=>s.map(s=>{const r=e.some(e=>(e=new RegExp(e)).test(s.name));return t?r:!r})}}function TypeFilter(e){return{name:"Type Filter",func:t=>t.map(t=>e.some(e=>t.type===e))}}function ScriptFilter(script){return{name:"Script Filter",func:proxies=>{let output=FULL(proxies.length,!0);return function(){eval(script),output=filter(proxies)}(),output}}}function getFlag(e){const t={"🇦🇨":["AC"],"🇦🇹":["奥地利","维也纳"],"🇦🇺":["AU","Australia","Sydney","澳大利亚","澳洲","墨尔本","悉尼"],"🇧🇪":["BE","比利时"],"🇧🇬":["保加利亚","Bulgaria"],"🇧🇷":["BR","Brazil","巴西","圣保罗"],"🇨🇦":["CA","Canada","Waterloo","加拿大","蒙特利尔","温哥华","楓葉","枫叶","滑铁卢","多伦多"],"🇨🇭":["瑞士","苏黎世","Switzerland"],"🇩🇪":["DE","German","GERMAN","德国","德國","法兰克福"],"🇩🇰":["丹麦"],"🇪🇸":["ES","西班牙","Spain"],"🇪🇺":["EU","欧盟","欧罗巴"],"🇫🇮":["Finland","芬兰","赫尔辛基"],"🇫🇷":["FR","France","法国","法國","巴黎"],"🇬🇧":["UK","GB","England","United Kingdom","英国","伦敦","英"],"🇲🇴":["MO","Macao","澳门","CTM"],"🇭🇺":["匈牙利","Hungary"],"🇭🇰":["HK","Hongkong","Hong Kong","香港","深港","沪港","呼港","HKT","HKBN","HGC","WTT","CMI","穗港","京港","港"],"🇮🇩":["Indonesia","印尼","印度尼西亚","雅加达"],"🇮🇪":["Ireland","爱尔兰","都柏林"],"🇮🇳":["India","印度","孟买","Mumbai"],"🇰🇵":["KP","朝鲜"],"🇰🇷":["KR","Korea","KOR","韩国","首尔","韩","韓"],"🇱🇻":["Latvia","Latvija","拉脱维亚"],"🇲🇽️":["MEX","MX","墨西哥"],"🇲🇾":["MY","Malaysia","马来西亚","吉隆坡"],"🇳🇱":["NL","Netherlands","荷兰","荷蘭","尼德蘭","阿姆斯特丹"],"🇵🇭":["PH","Philippines","菲律宾"],"🇷🇴":["RO","罗马尼亚"],"🇷🇺":["RU","Russia","俄罗斯","俄羅斯","伯力","莫斯科","圣彼得堡","西伯利亚","新西伯利亚","京俄","杭俄"],"🇸🇦":["沙特","迪拜"],"🇸🇪":["SE","Sweden"],"🇸🇬":["SG","Singapore","新加坡","狮城","沪新","京新","泉新","穗新","深新","杭新","广新"],"🇹🇭":["TH","Thailand","泰国","泰國","曼谷"],"🇹🇷":["TR","Turkey","土耳其","伊斯坦布尔"],"🇹🇼":["TW","Taiwan","台湾","台北","台中","新北","彰化","CHT","台","HINET"],"🇺🇸":["US","USA","America","United States","美国","美","京美","波特兰","达拉斯","俄勒冈","凤凰城","费利蒙","硅谷","矽谷","拉斯维加斯","洛杉矶","圣何塞","圣克拉拉","西雅图","芝加哥","沪美","哥伦布","纽约"],"🇻🇳":["VN","越南","胡志明市"],"🇮🇹":["Italy","IT","Nachash","意大利","米兰","義大利"],"🇿🇦":["South Africa","南非"],"🇦🇪":["United Arab Emirates","阿联酋"],"🇯🇵":["JP","Japan","日","日本","东京","大阪","埼玉","沪日","穗日","川日","中日","泉日","杭日","深日","辽日","广日"],"🇦🇷":["AR","阿根廷"],"🇳🇴":["Norway","挪威","NO"],"🇨🇳":["CN","China","回国","中国","江苏","北京","上海","广州","深圳","杭州","徐州","青岛","宁波","镇江","back"],"🏳️‍🌈":["流量","时间","应急","过期","Bandwidth","expire"]};for(let s of Object.keys(t))if(t[s].some(t=>-1!==e.indexOf(t)))return s;return(e.match(/[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]/)||[])[0]||"🏴‍☠️"}function removeFlag(e){return e.replace(/[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]/g,"").trim()}function shuffle(e){let t,s,r=e.length;for(;0!==r;)s=Math.floor(Math.random()*r),t=e[r-=1],e[r]=e[s],e[s]=t;return e}function AND(...e){return e.reduce((e,t)=>e.map((e,s)=>t[s]&&e))}function OR(...e){return e.reduce((e,t)=>e.map((e,s)=>t[s]||e))}function NOT(e){return e.map(e=>!e)}function FULL(e,t){return[...Array(e).keys()].map(()=>t)}function clone(e){return JSON.parse(JSON.stringify(e))}function ApplyFilter(e,t){let s=FULL(t.length,!0);try{s=AND(s,e.func(t))}catch(t){console.log(`Cannot apply filter ${e.name}\n Reason: ${t}`)}return t.filter((e,t)=>s[t])}function ApplyOperator(e,t){let s=clone(t);try{const t=e.func(s);t&&(s=t)}catch(e){console.log(`Cannot apply operator ${op.name}! Reason: ${e}`)}return s}function Apply(e,t){return-1!==e.name.indexOf("Filter")?ApplyFilter(e,t):-1!==e.name.indexOf("Operator")?ApplyOperator(e,t):void 0}return{"Keyword Filter":KeywordFilter,"Useless Filter":UselessFilter,"Region Filter":RegionFilter,"Regex Filter":RegexFilter,"Type Filter":TypeFilter,"Script Filter":ScriptFilter,"Set Property Operator":SetPropertyOperator,"Flag Operator":FlagOperator,"Sort Operator":SortOperator,"Keyword Sort Operator":KeywordSortOperator,"Keyword Rename Operator":KeywordRenameOperator,"Keyword Delete Operator":KeywordDeleteOperator,"Regex Rename Operator":RegexRenameOperator,"Regex Delete Operator":RegexDeleteOperator,"Script Operator":ScriptOperator,Apply:Apply}}(),PROXY_PRODUCERS=function(){return{QX:{produce:e=>{let t,s;switch(e.type){case"ss":if(t="","obfs"===e.plugin){const{host:s,mode:r}=e["plugin-opts"];t=`,obfs=${r}${s?",obfs-host="+s:""}`}if("v2ray-plugin"===e.plugin){const{tls:s,host:r,path:n}=e["plugin-opts"];t=`,obfs=${s?"wss":"ws"}${r?",obfs-host="+r:""}${n?",obfs-uri="+n:""}`}return`shadowsocks=${e.server}:${e.port},method=${e.cipher},password=${e.password}${t}${e.tfo?",fast-open=true":",fast-open=false"}${e.udp?",udp-relay=true":",udp-relay=false"},tag=${e.name}`;case"ssr":return`shadowsocks=${e.server}:${e.port},method=${e.cipher},password=${e.password},ssr-protocol=${e.protocol}${e["protocol-param"]?",ssr-protocol-param="+e["protocol-param"]:""}${e.obfs?",obfs="+e.obfs:""}${e["obfs-param"]?",obfs-host="+e["obfs-param"]:""}${e.tfo?",fast-open=true":",fast-open=false"}${e.udp?",udp-relay=true":",udp-relay=false"},tag=${e.name}`;case"vmess":return t="","ws"===e.network?t=e.tls?`,obfs=wss${e.sni?",obfs-host="+e.sni:""}${e["ws-path"]?",obfs-uri="+e["ws-path"]:""},tls-verification=${e["skip-cert-verify"]?"false":"true"}`:`,obfs=ws${e["ws-headers"].Host?",obfs-host="+e["ws-headers"].Host:""}${e["ws-path"]?",obfs-uri="+e["ws-path"]:""}`:e.tls&&(t=`,obfs=over-tls${e.sni?",obfs-host="+e.sni:""},tls-verification=${e["skip-cert-verify"]?"false":"true"}`),`vmess=${e.server}:${e.port},method=${"auto"===e.cipher?"none":e.cipher},password=${e.uuid}${t}${e.tfo?",fast-open=true":",fast-open=false"}${e.udp?",udp-relay=true":",udp-relay=false"},tag=${e.name}`;case"trojan":return`trojan=${e.server}:${e.port},password=${e.password}${e.sni?",tls-host="+e.sni:""},over-tls=true,tls-verification=${e["skip-cert-verify"]?"false":"true"}${e.tfo?",fast-open=true":",fast-open=false"}${e.udp?",udp-relay=true":",udp-relay=false"},tag=${e.name}`;case"http":return s="",e.tls&&(s=`,over-tls=true,tls-verification=${e["skip-cert-verify"]?"false":"true"}${e.sni?",tls-host="+e.sni:""}`),`http=${e.server}:${e.port},username=${e.username},password=${e.password}${s}${e.tfo?",fast-open=true":",fast-open=false"},tag=${e.name}`}throw new Error(`Platform QX does not support proxy type: ${e.type}`)}},Surge:{produce:e=>{let t,s;switch(e.type){case"ss":if(t="",e.plugin){const{host:s,mode:r}=e["plugin-opts"];if("obfs"!==e.plugin)throw new Error(`Platform Surge does not support obfs option: ${e.obfs}`);t=`,obfs=${r}${s?",obfs-host="+s:""}`}return`${e.name}=ss,${e.server}, ${e.port},encrypt-method=${e.cipher},password=${e.password}${t},tfo=${e.tfo||"false"},udp-relay=${e.udp||"false"}`;case"vmess":s="";let r=`${e.name}=vmess,${e.server},${e.port},username=${e.uuid},tls=${e.tls||"false"},tfo=${e.tfo||"false"}`;if("ws"===e.network){const t=e["ws-path"]||"/",s=e["ws-headers"].Host;r+=`,ws=true${t?",ws-path="+t:""}${s?",ws-headers=HOST:"+s:""}`}return e.tls&&(r+=`${void 0!==e["skip-cert-verify"]?",skip-cert-verify="+e["skip-cert-verify"]:""}`,r+=e.sni?`,sni=${e.sni}`:""),r;case"trojan":return`${e.name}=trojan,${e.server},${e.port},password=${e.password}${void 0!==e["skip-cert-verify"]?",skip-cert-verify="+e["skip-cert-verify"]:""}${e.sni?",sni="+e.sni:""},tfo=${e.tfo||"false"}`;case"http":return s=", tls=false",e.tls&&(s=`,tls=true,skip-cert-verify=${e["skip-cert-verify"]},sni=${e.sni}`),`${e.name}=http, ${e.server}, ${e.port}${e.username?",username="+e.username:""}${e.password?",password="+e.password:""}${s},tfo=${e.tfo||"false"}`}throw new Error(`Platform Surge does not support proxy type: ${e.type}`)}},Loon:{produce:e=>{let t,s;switch(e.type){case"ss":if(t=",,",e.plugin){if("obfs"!==e.plugin)throw new Error(`Platform Loon does not support obfs option: ${e.obfs}`);{const{mode:s,host:r}=e["plugin-opts"];t=`,${s},${r||""}`}}return`${e.name}=shadowsocks,${e.server},${e.port},${e.cipher},"${e.password}"${t}`;case"ssr":return`${e.name}=shadowsocksr,${e.server},${e.port},${e.cipher},"${e.password}",${e.protocol},{${e["protocol-param"]||""}},${e.obfs},{${e["obfs-param"]||""}}`;case"vmess":return t="",t="ws"===e.network?`,transport:ws,host:${e["ws-headers"].Host||e.server},path:${e["ws-path"]||"/"}`:",transport:tcp",e.tls&&(t+=`${e.sni?",tls-name:"+e.sni:""},skip-cert-verify:${e["skip-cert-verify"]||"false"}`),`${e.name}=vmess,${e.server},${e.port},${"auto"===e.cipher?"none":e.cipher},"${e.uuid}",over-tls:${e.tls||"false"}${t}`;case"trojan":return`${e.name}=trojan,${e.server},${e.port},"${e.password}"${e.sni?",tls-name:"+e.sni:""},skip-cert-verify:${e["skip-cert-verify"]||"false"}`;case"http":s="";const r=`${e.name}=${e.tls?"http":"https"},${e.server},${e.port},${e.username||""},${e.password||""}`;return e.tls?r+(s=`${e.sni?",tls-name:"+e.sni:""},skip-cert-verify:${e["skip-cert-verify"]}`):r}throw new Error(`Platform Loon does not support proxy type: ${e.type}`)}},Clash:{type:"ALL",produce:e=>"proxies:\n"+e.map(e=>(delete e.supported," - "+JSON.stringify(e)+"\n")).join("")},URI:{type:"SINGLE",produce:e=>{let t="";switch(e.type){case"ss":const s=`${e.cipher}:${e.password}`;if(t=`ss://${Base64.safeEncode(s)}@${e.server}:${e.port}/`,e.plugin){t+="?plugin=";const s=e["plugin-opts"];switch(e.plugin){case"obfs":t+=encodeURIComponent(`simple-obfs;obfs=${s.mode}${s.host?";obfs-host="+s.host:""}`);break;case"v2ray-plugin":t+=encodeURIComponent(`v2ray-plugin;obfs=${s.mode}${s.host?";obfs-host"+s.host:""}${s.tls?";tls":""}`);break;default:throw new Error(`Unsupported plugin option: ${e.plugin}`)}}t+=`#${encodeURIComponent(e.name)}`;break;case"ssr":t=`${e.server}:${e.port}:${e.protocol}:${e.cipher}:${e.obfs}:${Base64.safeEncode(e.password)}/`,t+=`?remarks=${Base64.safeEncode(e.name)}${e["obfs-param"]?"&obfsparam="+Base64.safeEncode(e["obfs-param"]):""}${e["protocol-param"]?"&protocolparam="+Base64.safeEncode(e["protocol-param"]):""}`,t="ssr://"+Base64.safeEncode(t);break;case"vmess":t={ps:e.name,add:e.server,port:e.port,id:e.uuid,type:"",aid:0,net:e.network||"tcp",tls:e.tls?"tls":""},"ws"===e.network&&(t.path=e["ws-path"]||"/",t.host=e["ws-headers"].Host||e.server),t="vmess://"+Base64.safeEncode(JSON.stringify(t));break;case"trojan":t=`trojan://${e.password}@${e.server}:${e.port}#${encodeURIComponent(e.name)}`;break;default:throw new Error(`Cannot handle proxy type: ${e.type}`)}return t}},JSON:{type:"ALL",produce:e=>JSON.stringify(e,null,2)}}}();function ENV(){const e="undefined"!=typeof $task,t="undefined"!=typeof $loon,s="undefined"!=typeof $httpClient&&!t,r="function"==typeof require&&"undefined"!=typeof $jsbox;return{isQX:e,isLoon:t,isSurge:s,isNode:"function"==typeof require&&!r,isJSBox:r,isRequest:"undefined"!=typeof $request,isScriptable:"undefined"!=typeof importModule}}function HTTP(e={baseURL:""}){const{isQX:t,isLoon:s,isSurge:r,isScriptable:n,isNode:o}=ENV(),a=/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*)/;const i={};return["GET","POST","PUT","DELETE","HEAD","OPTIONS","PATCH"].forEach(p=>i[p.toLowerCase()]=(i=>(function(i,p){p="string"==typeof p?{url:p}:p;const l=e.baseURL;l&&!a.test(p.url||"")&&(p.url=l?l+p.url:p.url);const c=(p={...e,...p}).timeout,u={onRequest:()=>{},onResponse:e=>e,onTimeout:()=>{},...p.events};let f,d;if(u.onRequest(i,p),t)f=$task.fetch({method:i,...p});else if(s||r||o)f=new Promise((e,t)=>{(o?require("request"):$httpClient)[i.toLowerCase()](p,(s,r,n)=>{s?t(s):e({statusCode:r.status||r.statusCode,headers:r.headers,body:n})})});else if(n){const e=new Request(p.url);e.method=i,e.headers=p.headers,e.body=p.body,f=new Promise((t,s)=>{e.loadString().then(s=>{t({statusCode:e.response.statusCode,headers:e.response.headers,body:s})}).catch(e=>s(e))})}const h=c?new Promise((e,t)=>{d=setTimeout(()=>(u.onTimeout(),t(`${i} URL: ${p.url} exceeds the timeout ${c} ms`)),c)}):null;return(h?Promise.race([h,f]).then(e=>(clearTimeout(d),e)):f).then(e=>u.onResponse(e))})(p,i))),i}function API(e="untitled",t=!1){const{isQX:s,isLoon:r,isSurge:n,isNode:o,isJSBox:a,isScriptable:i}=ENV();return new class{constructor(e,t){this.name=e,this.debug=t,this.http=HTTP(),this.env=ENV(),this.node=(()=>{if(o){return{fs:require("fs")}}return null})(),this.initCache();Promise.prototype.delay=function(e){return this.then(function(t){return((e,t)=>new Promise(function(s){setTimeout(s.bind(null,t),e)}))(e,t)})}}initCache(){if(s&&(this.cache=JSON.parse($prefs.valueForKey(this.name)||"{}")),(r||n)&&(this.cache=JSON.parse($persistentStore.read(this.name)||"{}")),o){let e="root.json";this.node.fs.existsSync(e)||this.node.fs.writeFileSync(e,JSON.stringify({}),{flag:"wx"},e=>console.log(e)),this.root={},e=`${this.name}.json`,this.node.fs.existsSync(e)?this.cache=JSON.parse(this.node.fs.readFileSync(`${this.name}.json`)):(this.node.fs.writeFileSync(e,JSON.stringify({}),{flag:"wx"},e=>console.log(e)),this.cache={})}}persistCache(){const e=JSON.stringify(this.cache,null,2);s&&$prefs.setValueForKey(e,this.name),(r||n)&&$persistentStore.write(e,this.name),o&&(this.node.fs.writeFileSync(`${this.name}.json`,e,{flag:"w"},e=>console.log(e)),this.node.fs.writeFileSync("root.json",JSON.stringify(this.root,null,2),{flag:"w"},e=>console.log(e)))}write(e,t){if(this.log(`SET ${t}`),-1!==t.indexOf("#")){if(t=t.substr(1),n||r)return $persistentStore.write(e,t);if(s)return $prefs.setValueForKey(e,t);o&&(this.root[t]=e)}else this.cache[t]=e;this.persistCache()}read(e){return this.log(`READ ${e}`),-1===e.indexOf("#")?this.cache[e]:(e=e.substr(1),n||r?$persistentStore.read(e):s?$prefs.valueForKey(e):o?this.root[e]:void 0)}delete(e){if(this.log(`DELETE ${e}`),-1!==e.indexOf("#")){if(e=e.substr(1),n||r)return $persistentStore.write(null,e);if(s)return $prefs.removeValueForKey(e);o&&delete this.root[e]}else delete this.cache[e];this.persistCache()}notify(e,t="",p="",l={}){const c=l["open-url"],u=l["media-url"];if(s&&$notify(e,t,p,l),n&&$notification.post(e,t,p+`${u?"\n多媒体:"+u:""}`,{url:c}),r){let s={};c&&(s.openUrl=c),u&&(s.mediaUrl=u),"{}"===JSON.stringify(s)?$notification.post(e,t,p):$notification.post(e,t,p,s)}if(o||i){const s=p+(c?`\n点击跳转: ${c}`:"")+(u?`\n多媒体: ${u}`:"");if(a){require("push").schedule({title:e,body:(t?t+"\n":"")+s})}else console.log(`${e}\n${t}\n${s}\n\n`)}}log(e){this.debug&&console.log(`[${this.name}] LOG: ${e}`)}info(e){console.log(`[${this.name}] INFO: ${e}`)}error(e){console.log(`[${this.name}] ERROR: ${e}`)}wait(e){return new Promise(t=>setTimeout(t,e))}done(e={}){s||r||n?$done(e):o&&!a&&"undefined"!=typeof $context&&($context.headers=e.headers,$context.statusCode=e.statusCode,$context.body=e.body)}}(e,t)}function Gist(e,t){const s=HTTP({baseURL:"https://api.github.com",headers:{Authorization:`token ${t}`,"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.141 Safari/537.36"},events:{onResponse:e=>/^[45]/.test(String(e.statusCode))?Promise.reject(`ERROR: ${JSON.parse(e.body).message}`):e}});async function r(){return s.get("/gists").then(t=>{const s=JSON.parse(t.body);for(let t of s)if(t.description===e)return t.id;return-1})}this.upload=async function(t){const n=await r(),o={"Sub-Store":{content:t}};return-1===n?s.post({url:"/gists",body:JSON.stringify({description:e,public:!1,files:o})}):s.patch({url:`/gists/${n}`,body:JSON.stringify({files:o})})},this.download=async function(){const e=await r();if(-1===e)return Promise.reject("未找到Gist备份!");try{const{files:t}=await s.get(`/gists/${e}`).then(e=>JSON.parse(e.body)),r=t["Sub-Store"].raw_url;return await s.get(r).then(e=>e.body)}catch(e){return Promise.reject(e)}}}function express({port:e}={port:3e3}){const{isNode:t}=ENV(),s={"Content-Type":"text/plain;charset=UTF-8","Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"POST,GET,OPTIONS,PATCH,PUT,DELETE","Access-Control-Allow-Headers":"Origin, X-Requested-With, Content-Type, Accept"};if(t){const t=require("express"),r=require("body-parser"),n=t();return n.use(r.json({verify:i})),n.use(r.urlencoded({verify:i,extended:!0})),n.use(r.raw({verify:i,type:"*/*"})),n.use((e,t,r)=>{t.set(s),r()}),n.start=(()=>{n.listen(e,()=>{$.info(`Express started on port: ${e}`)})}),n}const r=[],n=["GET","POST","PUT","DELETE","PATCH","OPTIONS","HEAD'","ALL"],o=(e,t=0)=>{let{method:s,url:n,headers:a,body:i}=e;/json/i.test(a["Content-Type"])&&(i=JSON.parse(i)),s=s.toUpperCase();const{path:u,query:f}=function(e){const t=(e.match(/https?:\/\/[^\/]+(\/[^?]*)/)||[])[1]||"/",s=e.indexOf("?"),r={};if(-1!==s){let t=e.slice(e.indexOf("?")+1).split("&");for(let e=0;em&&(h=r[d],m=e.split("/").length)}if(h){const e=()=>{o(s,n,d)},t={method:s,url:n,path:u,query:f,params:c(h.pattern,u),headers:a,body:i},r=p(),l=h.callback,m=e=>{r.status(500).json({status:"failed",message:`Internal Server Error: ${e}`})};if("AsyncFunction"===l.constructor.name)l(t,r,e).catch(m);else try{l(t,r,e)}catch(e){m(e)}}else{p().status(404).json({status:"failed",message:"ERROR: 404 not found"})}},a={};return n.forEach(e=>{a[e.toLowerCase()]=((t,s)=>{r.push({method:e,pattern:t,callback:s})})}),a.route=(e=>{const t={};return n.forEach(s=>{t[s.toLowerCase()]=(n=>(r.push({method:s,pattern:e,callback:n}),t))}),t}),a.start=(()=>{o($request)}),a;function i(e,t,s,r){s&&s.length&&(e.rawBody=s.toString(r||"utf8"))}function p(){let e=200;const{isQX:t,isLoon:r,isSurge:n}=ENV(),o=s,a={200:"HTTP/1.1 200 OK",201:"HTTP/1.1 201 Created",302:"HTTP/1.1 302 Found",307:"HTTP/1.1 307 Temporary Redirect",308:"HTTP/1.1 308 Permanent Redirect",404:"HTTP/1.1 404 Not Found",500:"HTTP/1.1 500 Internal Server Error"};return new class{status(t){return e=t,this}send(s=""){const i={status:t?a[e]:e,body:s,headers:o};t?$done(i):(r||n)&&$done({response:i})}end(){this.send()}html(e){this.set("Content-Type","text/html;charset=UTF-8"),this.send(e)}json(e){this.set("Content-Type","application/json;charset=UTF-8"),this.send(JSON.stringify(e))}set(e,t){return o[e]=t,this}}}function l(e,t){if(e instanceof RegExp&&e.test(t))return!0;if("/"===e)return!0;if(-1===e.indexOf(":")){const s=t.split("/"),r=e.split("/");for(let e=0;e>>6)+s(128|63&t):s(224|t>>>12&15)+s(128|t>>>6&63)+s(128|63&t):(t=65536+1024*(e.charCodeAt(0)-55296)+(e.charCodeAt(1)-56320),s(240|t>>>18&7)+s(128|t>>>12&63)+s(128|t>>>6&63)+s(128|63&t))},n=/[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g,o=function(t){const s=[0,2,1][t.length%3],r=t.charCodeAt(0)<<16|(t.length>1?t.charCodeAt(1):0)<<8|(t.length>2?t.charCodeAt(2):0);return[e.charAt(r>>>18),e.charAt(r>>>12&63),s>=2?"=":e.charAt(r>>>6&63),s>=1?"=":e.charAt(63&r)].join("")};this.encode=function(e){return"[object Uint8Array]"===Object.prototype.toString.call(e)?e.toString("base64"):function(e){return e.replace(n,r)}(String(e)).replace(/[\s\S]{1,3}/g,o)};const a=/[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3}/g,i=function(e){switch(e.length){case 4:const t=((7&e.charCodeAt(0))<<18|(63&e.charCodeAt(1))<<12|(63&e.charCodeAt(2))<<6|63&e.charCodeAt(3))-65536;return s(55296+(t>>>10))+s(56320+(1023&t));case 3:return s((15&e.charCodeAt(0))<<12|(63&e.charCodeAt(1))<<6|63&e.charCodeAt(2));default:return s((31&e.charCodeAt(0))<<6|63&e.charCodeAt(1))}},p=function(e){const r=e.length,n=r%4,o=(r>0?t[e.charAt(0)]<<18:0)|(r>1?t[e.charAt(1)]<<12:0)|(r>2?t[e.charAt(2)]<<6:0)|(r>3?t[e.charAt(3)]:0),a=[s(o>>>16),s(o>>>8&255),s(255&o)];return a.length-=[0,0,2,1][n],a.join("")},l=function(e){return e.replace(/\S{1,4}/g,p)},c=function(e){return l(e).replace(a,i)};this.decode=function(e){return c(String(e).replace(/[-_]/g,function(e){return"-"===e?"+":"/"}).replace(/[^A-Za-z0-9\+\/]/g,"")).replace(/>/g,">").replace(/</g,"<")},this.safeEncode=function(e){return this.encode(e.replace(/\+/g,"-").replace(/\//g,"_"))},this.safeDecode=function(e){return this.decode(e.replace(/-/g,"+").replace(/_/g,"/"))}}var YAML=function(){var e=[],t=[],s=0,r={regLevel:new RegExp("^([\\s\\-]+)"),invalidLine:new RegExp("^\\-\\-\\-|^\\.\\.\\.|^\\s*#.*|^\\s*$"),dashesString:new RegExp('^\\s*\\"([^\\"]*)\\"\\s*$'),quotesString:new RegExp("^\\s*\\'([^\\']*)\\'\\s*$"),float:new RegExp("^[+-]?[0-9]+\\.[0-9]+(e[+-]?[0-9]+(\\.[0-9]+)?)?$"),integer:new RegExp("^[+-]?[0-9]+$"),array:new RegExp("\\[\\s*(.*)\\s*\\]"),map:new RegExp("\\{\\s*(.*)\\s*\\}"),key_value:new RegExp("([a-z0-9_-][ a-z0-9_-]*):( .+)","i"),single_key_value:new RegExp("^([a-z0-9_-][ a-z0-9_-]*):( .+?)$","i"),key:new RegExp("([a-z0-9_-][ a-z0-9_-]*):( .+)?","i"),item:new RegExp("^-\\s+"),trim:new RegExp("^\\s+|\\s+$"),comment:new RegExp("([^\\'\\\"#]+([\\'\\\"][^\\'\\\"]*[\\'\\\"])*)*(#.*)?")};function n(e){return{parent:null,length:0,level:e,lines:[],children:[],addChild:function(e){this.children.push(e),e.parent=this,++this.length}}}function o(e){var t=null;if("true"==(e=e.replace(r.trim,"")))return!0;if("false"==e)return!1;if(".NaN"==e)return Number.NaN;if("null"==e)return null;if(".inf"==e)return Number.POSITIVE_INFINITY;if("-.inf"==e)return Number.NEGATIVE_INFINITY;if(t=e.match(r.dashesString))return t[1];if(t=e.match(r.quotesString))return t[1];if(t=e.match(r.float))return parseFloat(t[0]);if(t=e.match(r.integer))return parseInt(t[0]);if(isNaN(t=Date.parse(e))){if(t=e.match(r.single_key_value))return(a={})[t[1]]=o(t[2]),a;if(t=e.match(r.array)){for(var s=0,n=" ",a=[],i="",p=!1,l=0,c=t[1].length;l0&&a.push(o(i)),a}if(t=e.match(r.map)){for(s=0,n=" ",a=[],i="",p=!1,l=0,c=t[1].length;l0&&a.push(i);var u={};for(l=0,c=a.length;l"==O[0]?null!=f?f[S]=a(u.shift()):l[S]=a(u.shift()):null!=f?f[S]=o(O):l[S]=o(O)}else null!=f?f[S]=s(u):l[S]=s(u)}else v.match(/^-\s*$/)?(m&&(m=!1,void 0===l.length&&(l=[])),null!=f&&l.push(f),f={},m=!0):(p=v.match(/^-\s*(.*)/))&&(null!=f?f.push(o(p[1])):(m&&(m=!1,void 0===l.length&&(l=[])),l.push(o(p[1]))))}null!=f&&(m&&(m=!1,void 0===l.length&&(l=[])),l.push(f))}for($=h.length-1;$>=0;--$)n.splice.call(n,h[$],1);return l}(s.children)}return{eval:function(o){e=[],t=[],s=(new Date).getTime();var a=p(function(t){var s,o=r.regLevel,a=r.invalidLine,i=t.split("\n"),p=0,l=0,c=[],u=new n(-1),f=new n(0);u.addChild(f);var d=[],h="";c.push(f),d.push(p);for(var m=0,$=i.length;m<$;++m)if(!(h=i[m]).match(a)){if((p=(s=o.exec(h))?s[1].length:0)>l){var g=f;f=new n(p),g.addChild(f),c.push(f),d.push(p)}else if(p=0;--w)if(d[w]==p){f=new n(p),c.push(f),d.push(p),null!=c[w].parent&&c[w].parent.addChild(f),y=!0;break}if(!y)return void e.push("Error: Invalid indentation at line "+m+": "+h)}f.lines.push(h.replace(r.trim,"")),l=p}return u}(function(e){var t,s=e.split("\n"),n=r.comment;for(var o in s)(t="string"==typeof s[o]&&s[o].match(n))&&void 0!==t[3]&&(s[o]=t[0].substr(0,t[0].length-t[3].length));return s.join("\n")}(o)));return s=(new Date).getTime()-s,a},getErrors:function(){return e},getProcessingTime:function(){return s}}}(); \ No newline at end of file +// UPDATED AT: 2020年11月26日 星期四 13时40分37秒 CST +const $=API("sub-store"),Base64=new Base64Code;function service(){console.log("========== Sub-Store ===========");const e=express(),t="settings",s="subs",r="collections",n="builtin";function o(e){const t=Object.keys(e);let s="";for(let r of t)if(/USER-AGENT/i.test(r)){s=e[r];break}return-1!==s.indexOf("Quantumult%20X")?"QX":-1!==s.indexOf("Surge")?"Surge":-1!==s.indexOf("Decar")||-1!==s.indexOf("Loon")?"Loon":"JSON"}async function a(e,t=!0){const s=HTTP({headers:{"User-Agent":"Quantumult%20X"}}),r="#"+Base64.safeEncode(e),n=$.read(r),o=`#TIME-${Base64.safeEncode(e)}`,a=(new Date).getTime()-$.read(o)>864e5;if(t&&n&&!a)return $.log(`Use cached for url: ${e}`),n;let i="";try{i=await s.get(e)}catch(e){throw new Error(e)}finally{$.write(i,r),$.write((new Date).getTime(),o)}if(0===i.replace(/\s/g,"").length)throw new Error("订阅内容为空!");return i}$.read(s)||$.write({},s),$.read(r)||$.write({},r),$.read(t)||$.write({},t),$.read("rules")||$.write({},"rules"),$.write({rules:getBuiltInRules()},n),e.get("/download/collection/:name",async function(e,t){const{name:n}=e.params,{cache:i}=e.query||"false",p=e.query.target||o(e.headers),l=$.read(r),c=$.read(s),u=l[n];if($.info(`正在下载组合订阅:${n}`),u){const e=u.subscriptions;let s=[];for(let t=0;te!==n);$.write(a,r),t.json({status:"success"})}),e.route("/api/subs").get(function(e,t){const r=$.read(s);t.json({status:"success",data:r})}).post(function(e,t){const r=e.body,n=$.read(s);$.info(`正在创建订阅: ${r.name}`),n[r.name]&&t.status(500).json({status:"failed",message:`订阅${r.name}已存在!`});/^[\w-_]*$/.test(r.name)?(n[r.name]=r,$.write(n,s),t.status(201).json({status:"success",data:r})):t.status(500).json({status:"failed",message:`订阅名称 ${r.name} 中含有非法字符!名称中只能包含英文字母、数字、下划线、横杠。`})}),e.route("/api/collection/:name").get(function(e,t){const{name:s}=e.params,n=$.read(r)[s];n?t.json({status:"success",data:n}):t.status(404).json({status:"failed",message:`未找到订阅集:${s}!`})}).patch(function(e,t){const{name:s}=e.params;let n=e.body;const o=$.read(r);if(o[s]){const e={...o[s],...n};$.info(`正在更新组合订阅:${s}...`),delete o[s],o[n.name||s]=e,$.write(o,r),t.json({status:"success",data:e})}else t.status(500).json({status:"failed",message:`订阅集${s}不存在,无法更新!`})}).delete(function(e,t){const{name:s}=e.params;$.info(`正在删除组合订阅:${s}`);let n=$.read(r);delete n[s],$.write(n,r),t.json({status:"success"})}),e.route("/api/collections").get(function(e,t){const s=$.read(r);t.json({status:"success",data:s})}).post(function(e,t){const s=e.body;$.info(`正在创建组合订阅:${s.name}`);const n=$.read(r);n[s.name]&&t.status(500).json({status:"failed",message:`订阅集${s.name}已存在!`});/^[\w-_]*$/.test(s.name)?(n[s.name]=s,$.write(n,r),t.status(201).json({status:"success",data:s})):t.status(500).json({status:"failed",message:`订阅集名称 ${s.name} 中含有非法字符!名称中只能包含英文字母、数字、下划线、横杠。`})}),e.get("/download/rule/:name",async function(e,t){const{name:s}=e.params,{builtin:r}=e.query,a=e.query.target||o(e.headers);let i;$.info(`正在下载${r?"内置":""}分流订阅:${s}...`),r&&(i=$.read(n).rules[s]);if(i){let e=[];for(let t=0;t{t.json($.read("#sub-store"))}).post((e,t)=>{const s=e.body;$.write(JSON.stringify(s),"#sub-store"),t.end()}),e.route("/api/settings").get(function(e,s){const r=$.read(t);s.json(r)}).patch(function(e,s){const r=e.body,n=$.read(t);$.write({...n,...r},t),s.json({status:"success"})}),e.get("/api/utils/IP_API/:server",async function(e,t){const s=decodeURIComponent(e.params.server),r=await $.http.get(`http://ip-api.com/json/${s}?lang=zh-CN`).then(e=>JSON.parse(e.body));t.json(r)}),e.post("/api/utils/refresh",async function(e,t){const{url:s}=e.body;$.info(`Refreshing cache for URL: ${s}`);try{const e=await a(s,!1);$.write(e,`#${Base64.safeEncode(s)}`),t.json({status:"success"})}catch(e){t.status(500).json({status:"failed",message:`无法刷新资源 ${s}: ${e}`})}}),e.get("/api/utils/env",function(e,t){const{isNode:s,isQX:r,isLoon:n,isSurge:o}=ENV();let a="Node";s&&(a="Node");r&&(a="QX");n&&(a="Loon");o&&(a="Surge");t.json({backend:a})}),e.get("/api/utils/backup",async function(e,s){const{action:r}=e.query,{gistToken:n}=$.read(t);if(n){const e=new Gist("Auto Generated Sub-Store Backup",n);try{let t;switch(r){case"upload":t=$.read("#sub-store"),$.info("上传备份中..."),await e.upload(t);break;case"download":$.info("还原备份中..."),t=await e.download(),$.write(t,"#sub-store")}s.json({status:"success"})}catch(e){const t=`${"upload"===r?"上传":"下载"}备份失败!${e}`;$.error(t),s.status(500).json({status:"failed",message:t})}}else s.status(500).json({status:"failed",message:"未找到Gist备份Token!"})}),e.get("/",async(e,t)=>{t.set("location","https://sub-store.vercel.app/").status(302).end()}),ENV().isQX&&e.options("/",async(e,t)=>{t.status(200).end()}),e.all("/",(e,t)=>{t.send("Hello from sub-store, made with ❤️ by Peng-YM")}),e.start()}service();var ProxyUtils=function(){const PROXY_PREPROCESSORS=function(){return[{name:"HTML",test:e=>/^/.test(e),parse:e=>""},function(){const e=["dm1lc3M","c3NyOi8v","dHJvamFu","c3M6Ly","c3NkOi8v"];return{name:"Base64 Pre-processor",test:function(t){return e.some(e=>-1!==t.indexOf(e))},parse:function(e){return e=Base64.safeDecode(e)}}}(),{name:"Clash Pre-processor",test:function(e){return/proxies/.test(e)},parse:function(e){return-1!==e.indexOf("{")&&(e=e.replace(/ - /g," - ").replace(/:(?!\s)/g,": ").replace(/\,\"/g,', "').replace(/: {/g,": {, ").replace(/, (\"?host|path|tls|mux|skip\"?)/g,", $1").replace(/{name: /g,'{name: "').replace(/, server:/g,'", server:').replace(/{|}/g,"").replace(/,/g,"\n ")),e=-1===(e=e.replace(/ -\n.*name/g," - name").replace(/\$|\`/g,"").split("proxy-providers:")[0].split("proxy-groups:")[0].replace(/\"([\w-]+)\"\s*:/g,"$1:")).indexOf("proxies:")?"proxies:\n"+e:"proxies:"+e.split("proxies:")[1],YAML.eval(e).proxies.map(e=>JSON.stringify(e)).join("\n")}},{name:"SSD Pre-processor",test:function(e){return 0===e.indexOf("ssd://")},parse:function(e){const t=[];let s=JSON.parse(Base64.safeDecode(e.split("ssd://")[1]));s.traffic_used,s.traffic_total,s.expiry,s.airport;let r=s.port,n=s.encryption,o=s.password,a=s.servers;for(let e=0;e{let[t,n]=e.split("=");if(t=t.trim(),n=n.trim(),-1!==r.indexOf(t)){s.type=t;const e=n.split(":");s.server=e[0],s.port=e[1]}else s[t.trim()]=n.trim()}),s}function t(){return{name:"Loon HTTP Parser",test:e=>/^.*=\s*http/i.test(e.split(",")[0])&&5===e.split(",").length&&-1===e.indexOf("username")&&-1===e.indexOf("password"),parse:e=>{const t=e.split("=")[1].split(","),s={name:e.split("=")[0].trim(),type:"http",server:t[1],port:t[2],tls:"443"===t[2]};return t[3]&&(s.username=t[3]),t[4]&&(s.password=t[4]),s.tls&&(s.sni=t["tls-name"]||s.server,s["skip-cert-verify"]=JSON.parse(t["skip-cert-verify"]||"false")),s}}}function s(e){const t={};t.name=e.split("=")[0].trim();const s=e.split(",");t.server=s[1].trim(),t.port=s[2].trim();for(let e=3;e/^ss:\/\//.test(e),parse:e=>{const t={};let s=e.split("ss://")[1];const r={name:decodeURIComponent(e.split("#")[1]),type:"ss",supported:t},n=(s=s.split("#")[0]).match(/@([^\/]*)(\/|$)/)[1],o=n.lastIndexOf(":");r.server=n.substring(0,o),r.port=n.substring(o+1);const a=Base64.safeDecode(s.split("@")[0]).split(":");if(r.cipher=a[0],r.password=a[1],-1!==s.indexOf("?plugin=")){const e=("plugin="+decodeURIComponent(s.split("?plugin=")[1].split("&")[0])).split(";"),n={};for(const t of e){const[e,s]=t.split("=");e&&(n[e]=s||!0)}switch(n.plugin){case"obfs-local":case"simple-obfs":r.plugin="obfs",r["plugin-opts"]={mode:n.obfs,host:n["obfs-host"]};break;case"v2ray-plugin":r.supported={...t,Loon:!1,Surge:!1},r.obfs="v2ray-plugin",r["plugin-opts"]={mode:"websocket",host:n["obfs-host"],path:n.path||"",tls:n.tls||!1};break;default:throw new Error(`Unsupported plugin option: ${n.plugin}`)}}return r}},function(){const e={Surge:!1};return{name:"URI SSR Parser",test:e=>/^ssr:\/\//.test(e),parse:t=>{let s=(t=Base64.safeDecode(t.split("ssr://")[1])).indexOf(":origin");-1===s&&(s=t.indexOf(":auth_"));const r=t.substring(0,s),n=r.substring(0,r.lastIndexOf(":")),o=r.substring(r.lastIndexOf(":")+1);let a=t.substring(s+1).split("/?")[0].split(":"),i={type:"ssr",server:n,port:o,protocol:a[0],cipher:a[1],obfs:a[2],password:Base64.safeDecode(a[3]),supported:e};if(a={},(t=t.split("/?")[1].split("&")).length>1)for(const e of t){const[t,s]=e.split("=");a[t]=s}return i={...i,name:Base64.safeDecode(a.remarks),"protocol-param":Base64.safeDecode(a.protoparam).replace(/\s/g,"")||"","obfs-param":Base64.safeDecode(a.obfsparam).replace(/\s/g,"")||""}}}}(),{name:"URI VMess Parser",test:e=>/^vmess:\/\//.test(e),parse:e=>{const t={};e=e.split("vmess://")[1];const s=Base64.safeDecode(e);if(/=\s*vmess/.test(s)){const e=s.split(",").map(e=>e.trim()),t={};for(const s of e)if(-1!==s.indexOf("=")){const[e,r]=s.split("=");t[e.trim()]=r.trim()}const r={name:e[0].split("=")[0].trim(),type:"vmess",server:e[1],port:e[2],cipher:e[3],uuid:e[4].match(/^"(.*)"$/)[1],tls:"over-tls"===t.obfs||"wss"===t.obfs,udp:JSON.parse(t["udp-relay"]||"false"),tfo:JSON.parse(t["fast-open"]||"false")};return"ws"!==t.obfs&&"wss"!==t.obfs||(r.network="ws",r["ws-path"]=t["obfs-uri"],r["ws-headers"]={Host:t["obfs-host"]||r.server}),r.tls&&"false"===t['"tls-verification"']&&(r["skip-cert-verify"]=!0),r.tls&&t["obfs-host"]&&(r.sni=t["obfs-host"]),r}{const e=JSON.parse(s),r={name:e.ps,type:"vmess",server:e.add,port:e.port,cipher:"auto",uuid:e.id,alterId:e.aid||0,tls:"tls"===e.tls||!0===e.tls,supported:t};return"ws"===e.net&&(r.network="ws",r["ws-path"]=e.path,r["ws-headers"]={Host:e.host||e.add},r.tls&&e.host&&(r.sni=e.host)),!1===e.verify_cert&&(r["skip-cert-verify"]=!0),r}}},{name:"URI Trojan Parser",test:e=>/^trojan:\/\//.test(e),parse:e=>{if(-1===e.indexOf(":443"))throw new Error("Trojan port should always be 443!");const t=(e=e.split("trojan://")[1]).split("@")[1].split(":443")[0];return{name:decodeURIComponent(e.split("#")[1].trim())||`[Trojan] ${t}`,type:"trojan",server:t,port:443,password:e.split("@")[0],supported:{}}}},{name:"Clash Parser",test:e=>{try{JSON.parse(e)}catch(e){return!1}return!0},parse:e=>JSON.parse(e)},{name:"Surge SS Parser",test:e=>/^.*=\s*ss/.test(e.split(",")[0]),parse:e=>{const t=s(e),r={name:t.name,type:"ss",server:t.server,port:t.port,cipher:t["encrypt-method"],password:t.password,tfo:JSON.parse(t.tfo||"false"),udp:JSON.parse(t["udp-relay"]||"false")};return t.obfs&&(r.plugin="obfs",r["plugin-opts"]={mode:t.obfs,host:t["obfs-host"]}),r}},{name:"Surge VMess Parser",test:e=>/^.*=\s*vmess/.test(e.split(",")[0])&&-1!==e.indexOf("username"),parse:e=>{const t=s(e),r={name:t.name,type:"vmess",server:t.server,port:t.port,uuid:t.username,alterId:0,cipher:"none",tls:JSON.parse(t.tls||"false"),tfo:JSON.parse(t.tfo||"false")};return r.tls&&(void 0!==t["skip-cert-verify"]&&(r["skip-cert-verify"]=!0===t["skip-cert-verify"]||"1"===t["skip-cert-verify"]),r.sni=t.sni||t.server),JSON.parse(t.ws||"false")&&(r.network="ws",r["ws-path"]=t["ws-path"],r["ws-headers"]={Host:t.sni}),r}},{name:"Surge Trojan Parser",test:e=>/^.*=\s*trojan/.test(e.split(",")[0])&&-1!==e.indexOf("sni"),parse:e=>{const t=s(e),r={name:t.name,type:"trojan",server:t.server,port:t.port,password:t.password,sni:t.sni||t.server,tfo:JSON.parse(t.tfo||"false")};return void 0!==t["skip-cert-verify"]&&(r["skip-cert-verify"]=!0===t["skip-cert-verify"]||"1"===t["skip-cert-verify"]),r}},{name:"Surge HTTP Parser",test:e=>/^.*=\s*http/.test(e.split(",")[0])&&!t().test(e),parse:e=>{const t=s(e),r={name:t.name,type:"http",server:t.server,port:t.port,tls:JSON.parse(t.tls||"false"),tfo:JSON.parse(t.tfo||"false")};return r.tls&&(void 0!==t["skip-cert-verify"]&&(r["skip-cert-verify"]=!0===t["skip-cert-verify"]||"1"===t["skip-cert-verify"]),r.sni=t.sni||t.server),t.username&&"none"!==t.username&&(r.username=t.username),t.password&&"none"!==t.password&&(r.password=t.password),r}},{name:"Loon SS Parser",test:e=>"shadowsocks"===e.split(",")[0].split("=")[1].trim().toLowerCase(),parse:e=>{const t=e.split("=")[1].split(","),s={name:e.split("=")[0].trim(),type:"ss",server:t[1],port:t[2],cipher:t[3],password:t[4].replace(/"/g,"")};return t.length>5&&(s.plugin="obfs",s["plugin-opts"]={mode:t[5],host:t[6]}),s}},{name:"Loon SSR Parser",test:e=>"shadowsocksr"===e.split(",")[0].split("=")[1].trim().toLowerCase(),parse:e=>{const t=e.split("=")[1].split(",");return{name:e.split("=")[0].trim(),type:"ssr",server:t[1],port:t[2],cipher:t[3],password:t[4].replace(/"/g,""),protocol:t[5],"protocol-param":t[6].match(/{(.*)}/)[1],supported:{Surge:!1},obfs:t[7],"obfs-param":t[8].match(/{(.*)}/)[1]}}},{name:"Loon VMess Parser",test:e=>/^.*=\s*vmess/i.test(e.split(",")[0])&&-1===e.indexOf("username"),parse:e=>{let t=e.split("=")[1].split(",");const s={name:e.split("=")[0].trim(),type:"vmess",server:t[1],port:t[2],cipher:t[3]||"none",uuid:t[4].replace(/"/g,""),alterId:0};t=t.splice(5);for(const e of t){const[s,r]=e.split(":");t[s]=r}switch(s.tls=JSON.parse(t["over-tls"]||"false"),s.tls&&(s.sni=t["tls-name"]||s.server,s["skip-cert-verify"]=JSON.parse(t["skip-cert-verify"]||"false")),t.transport){case"tcp":break;case"ws":s.network=t.transport,s["ws-path"]=t.path,s["ws-headers"]={Host:t.host}}return s.tls&&(s["skip-cert-verify"]=JSON.parse(t["skip-cert-verify"]||"false")),s}},{name:"Loon Trojan Parser",test:e=>/^.*=\s*trojan/i.test(e.split(",")[0])&&-1===e.indexOf("password"),parse:e=>{const t=e.split("=")[1].split(","),s={name:e.split("=")[0].trim(),type:"trojan",server:t[1],port:t[2],password:t[3].replace(/"/g,""),sni:t[1],"skip-cert-verify":JSON.parse(t["skip-cert-verify"]||"false")};if(t.length>4){const[r,n]=t[4].split(":");if("tls-name"!==r)throw new Error(`Unknown option ${r} for line: \n${e}`);s.sni=n}return s}},t(),{name:"QX SS Parser",test:e=>/^shadowsocks\s*=/.test(e.split(",")[0].trim())&&-1===e.indexOf("ssr-protocol"),parse:t=>{const s=e(t),r={name:s.tag,type:"ss",server:s.server,port:s.port,cipher:s.method,password:s.password,udp:JSON.parse(s["udp-relay"]||"false"),tfo:JSON.parse(s["fast-open"]||"false"),supported:{}};if(s.obfs)switch(r["plugin-opts"]={host:s["obfs-host"]||r.server},s.obfs){case"http":case"tls":r.plugin="obfs",r["plugin-opts"].mode=s.obfs;break;case"ws":case"wss":r["plugin-opts"]={...r["plugin-opts"],mode:"websocket",path:s["obfs-uri"]||"/",tls:"wss"===s.obfs},r["plugin-opts"].tls&&void 0!==s["tls-verification"]&&(r["plugin-opts"]["skip-cert-verify"]=s["tls-verification"]),r.plugin="v2ray-plugin",r.supported.Surge=!1,r.supported.Loon=!1}return r}},{name:"QX SSR Parser",test:e=>/^shadowsocks\s*=/.test(e.split(",")[0].trim())&&-1!==e.indexOf("ssr-protocol"),parse:t=>{const s=e(t),r={name:s.tag,type:"ssr",server:s.server,port:s.port,cipher:s.method,password:s.password,protocol:s["ssr-protocol"],obfs:"plain","protocol-param":s["ssr-protocol-param"],udp:JSON.parse(s["udp-relay"]||"false"),tfo:JSON.parse(s["fast-open"]||"false"),supported:{Surge:!1}};return s.obfs&&(r.obfs=s.obfs,r["obfs-param"]=s["obfs-host"]),r}},{name:"QX VMess Parser",test:e=>/^vmess\s*=/.test(e.split(",")[0].trim()),parse:t=>{const s=e(t),r={type:"vmess",name:s.tag,server:s.server,port:s.port,cipher:s.method||"none",uuid:s.password,alterId:0,tls:"over-tls"===s.obfs||"wss"===s.obfs,udp:JSON.parse(s["udp-relay"]||"false"),tfo:JSON.parse(s["fast-open"]||"false")};return r.tls&&(r.sni=s["obfs-host"]||s.server,r["skip-cert-verify"]=!JSON.parse(s["tls-verification"]||"true")),"ws"!==s.obfs&&"wss"!==s.obfs||(r.network="ws",r["ws-path"]=s["obfs-uri"],r["ws-headers"]={Host:s["obfs-host"]||s.server}),r}},{name:"QX Trojan Parser",test:e=>/^trojan\s*=/.test(e.split(",")[0].trim()),parse:t=>{const s=e(t),r={type:"trojan",name:s.tag,server:s.server,port:s.port,password:s.password,sni:s["tls-host"]||s.server,udp:JSON.parse(s["udp-relay"]||"false"),tfo:JSON.parse(s["fast-open"]||"false")};return r["skip-cert-verify"]=!JSON.parse(s["tls-verification"]||"true"),r}},{name:"QX HTTP Parser",test:e=>/^http\s*=/.test(e.split(",")[0].trim()),parse:t=>{const s=e(t),r={type:"http",name:s.tag,server:s.server,port:s.port,tls:JSON.parse(s["over-tls"]||"false"),udp:JSON.parse(s["udp-relay"]||"false"),tfo:JSON.parse(s["fast-open"]||"false")};return s.username&&"none"!==s.username&&(r.username=s.username),s.password&&"none"!==s.password&&(r.password=s.password),r.tls&&(r.sni=s["tls-host"]||r.server,r["skip-cert-verify"]=!JSON.parse(s["tls-verification"]||"true")),r}}]}(),PROXY_PROCESSORS=function(){function SetPropertyOperator({key:e,value:t}){return{name:"Set Property Operator",func:s=>s.map(s=>(s[e]=t,s))}}function FlagOperator(e=!0){return{name:"Flag Operator",func:t=>t.map(t=>{if(e){const e=getFlag(t.name);t.name=removeFlag(t.name),t.name=e+" "+t.name,t.name=t.name.replace(/🇹🇼/g,"🇨🇳")}else t.name=removeFlag(t.name);return t})}}function SortOperator(e="asc"){return{name:"Sort Operator",func:t=>{switch(e){case"asc":case"desc":return t.sort((t,s)=>{let r=t.name>s.name?1:-1;return r*="desc"===e?-1:1});case"random":return shuffle(t);default:throw new Error("Unknown sort option: "+e)}}}}function KeywordSortOperator(e){return{name:"Keyword Sort Operator",func:t=>t.sort((t,s)=>{const r=getKeywordOrder(e,t.name),n=getKeywordOrder(e,s.name);return r&&!n?-1:n&&!r?1:r&&n?rt.map(t=>{for(const{old:s,now:r}of e)t.name=t.name.replaceAll(s,r).trim();return t})}}function RegexRenameOperator(e){return{name:"Regex Rename Operator",func:t=>t.map(t=>{for(const{expr:s,now:r}of e)t.name=t.name.replace(new RegExp(s,"g"),r).trim();return t})}}function KeywordDeleteOperator(e){return{name:"Keyword Delete Operator",func:KeywordRenameOperator(e.map(e=>({old:e,now:""}))).func}}function RegexDeleteOperator(e){return{name:"Regex Delete Operator",func:RegexRenameOperator(e.map(e=>({expr:e,now:""}))).func}}function ScriptOperator(script){return{name:"Script Operator",func:proxies=>{let output=proxies;return function(){const $get=(e,t)=>{return(0,AVAILABLE_OPERATORS[e])(t)},$process=(e,t)=>-1!==e.name.indexOf("Filter")?ApplyOperator(e,t):-1!==e.name.indexOf("Operator")?ApplyFilter(e,t):void 0;eval(script),output=operator(proxies)}(),output}}}function KeywordFilter({keywords:e=[],keep:t=!0}){return{name:"Keyword Filter",func:s=>s.map(s=>{const r=e.some(e=>-1!==s.name.indexOf(e));return t?r:!r})}}function UselessFilter(){return{name:"Useless Filter",func:KeywordFilter({keywords:["网址","流量","时间","应急","过期","Bandwidth","expire"],keep:!1}).func}}function RegionFilter(e){const t={HK:"🇭🇰",TW:"🇹🇼",US:"🇺🇸",SG:"🇸🇬",JP:"🇯🇵",UK:"🇬🇧"};return{name:"Region Filter",func:s=>s.map(s=>{const r=getFlag(s.name);return e.some(e=>t[e]===r)})}}function RegexFilter({regex:e=[],keep:t=!0}){return{name:"Regex Filter",func:s=>s.map(s=>{const r=e.some(e=>(e=new RegExp(e)).test(s.name));return t?r:!r})}}function TypeFilter(e){return{name:"Type Filter",func:t=>t.map(t=>e.some(e=>t.type===e))}}function ScriptFilter(script){return{name:"Script Filter",func:proxies=>{let output=FULL(proxies.length,!0);return function(){eval(script),output=filter(proxies)}(),output}}}function getFlag(e){const t={"🇦🇨":["AC"],"🇦🇹":["奥地利","维也纳"],"🇦🇺":["AU","Australia","Sydney","澳大利亚","澳洲","墨尔本","悉尼"],"🇧🇪":["BE","比利时"],"🇧🇬":["保加利亚","Bulgaria"],"🇧🇷":["BR","Brazil","巴西","圣保罗"],"🇨🇦":["CA","Canada","Waterloo","加拿大","蒙特利尔","温哥华","楓葉","枫叶","滑铁卢","多伦多"],"🇨🇭":["瑞士","苏黎世","Switzerland"],"🇩🇪":["DE","German","GERMAN","德国","德國","法兰克福"],"🇩🇰":["丹麦"],"🇪🇸":["ES","西班牙","Spain"],"🇪🇺":["EU","欧盟","欧罗巴"],"🇫🇮":["Finland","芬兰","赫尔辛基"],"🇫🇷":["FR","France","法国","法國","巴黎"],"🇬🇧":["UK","GB","England","United Kingdom","英国","伦敦","英"],"🇲🇴":["MO","Macao","澳门","CTM"],"🇭🇺":["匈牙利","Hungary"],"🇭🇰":["HK","Hongkong","Hong Kong","香港","深港","沪港","呼港","HKT","HKBN","HGC","WTT","CMI","穗港","京港","港"],"🇮🇩":["Indonesia","印尼","印度尼西亚","雅加达"],"🇮🇪":["Ireland","爱尔兰","都柏林"],"🇮🇳":["India","印度","孟买","Mumbai"],"🇰🇵":["KP","朝鲜"],"🇰🇷":["KR","Korea","KOR","韩国","首尔","韩","韓"],"🇱🇻":["Latvia","Latvija","拉脱维亚"],"🇲🇽️":["MEX","MX","墨西哥"],"🇲🇾":["MY","Malaysia","马来西亚","吉隆坡"],"🇳🇱":["NL","Netherlands","荷兰","荷蘭","尼德蘭","阿姆斯特丹"],"🇵🇭":["PH","Philippines","菲律宾"],"🇷🇴":["RO","罗马尼亚"],"🇷🇺":["RU","Russia","俄罗斯","俄羅斯","伯力","莫斯科","圣彼得堡","西伯利亚","新西伯利亚","京俄","杭俄"],"🇸🇦":["沙特","迪拜"],"🇸🇪":["SE","Sweden"],"🇸🇬":["SG","Singapore","新加坡","狮城","沪新","京新","泉新","穗新","深新","杭新","广新"],"🇹🇭":["TH","Thailand","泰国","泰國","曼谷"],"🇹🇷":["TR","Turkey","土耳其","伊斯坦布尔"],"🇹🇼":["TW","Taiwan","台湾","台北","台中","新北","彰化","CHT","台","HINET"],"🇺🇸":["US","USA","America","United States","美国","美","京美","波特兰","达拉斯","俄勒冈","凤凰城","费利蒙","硅谷","矽谷","拉斯维加斯","洛杉矶","圣何塞","圣克拉拉","西雅图","芝加哥","沪美","哥伦布","纽约"],"🇻🇳":["VN","越南","胡志明市"],"🇮🇹":["Italy","IT","Nachash","意大利","米兰","義大利"],"🇿🇦":["South Africa","南非"],"🇦🇪":["United Arab Emirates","阿联酋"],"🇯🇵":["JP","Japan","日","日本","东京","大阪","埼玉","沪日","穗日","川日","中日","泉日","杭日","深日","辽日","广日"],"🇦🇷":["AR","阿根廷"],"🇳🇴":["Norway","挪威","NO"],"🇨🇳":["CN","China","回国","中国","江苏","北京","上海","广州","深圳","杭州","徐州","青岛","宁波","镇江","back"],"🏳️‍🌈":["流量","时间","应急","过期","Bandwidth","expire"]};for(let s of Object.keys(t))if(t[s].some(t=>-1!==e.indexOf(t)))return s;return(e.match(/[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]/)||[])[0]||"🏴‍☠️"}function removeFlag(e){return e.replace(/[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]/g,"").trim()}function shuffle(e){let t,s,r=e.length;for(;0!==r;)s=Math.floor(Math.random()*r),t=e[r-=1],e[r]=e[s],e[s]=t;return e}function AND(...e){return e.reduce((e,t)=>e.map((e,s)=>t[s]&&e))}function OR(...e){return e.reduce((e,t)=>e.map((e,s)=>t[s]||e))}function NOT(e){return e.map(e=>!e)}function FULL(e,t){return[...Array(e).keys()].map(()=>t)}function clone(e){return JSON.parse(JSON.stringify(e))}function ApplyFilter(e,t){let s=FULL(t.length,!0);try{s=AND(s,e.func(t))}catch(t){console.log(`Cannot apply filter ${e.name}\n Reason: ${t}`)}return t.filter((e,t)=>s[t])}function ApplyOperator(e,t){let s=clone(t);try{const t=e.func(s);t&&(s=t)}catch(e){console.log(`Cannot apply operator ${op.name}! Reason: ${e}`)}return s}function Apply(e,t){return-1!==e.name.indexOf("Filter")?ApplyFilter(e,t):-1!==e.name.indexOf("Operator")?ApplyOperator(e,t):void 0}return{"Keyword Filter":KeywordFilter,"Useless Filter":UselessFilter,"Region Filter":RegionFilter,"Regex Filter":RegexFilter,"Type Filter":TypeFilter,"Script Filter":ScriptFilter,"Set Property Operator":SetPropertyOperator,"Flag Operator":FlagOperator,"Sort Operator":SortOperator,"Keyword Sort Operator":KeywordSortOperator,"Keyword Rename Operator":KeywordRenameOperator,"Keyword Delete Operator":KeywordDeleteOperator,"Regex Rename Operator":RegexRenameOperator,"Regex Delete Operator":RegexDeleteOperator,"Script Operator":ScriptOperator,Apply:Apply}}(),PROXY_PRODUCERS=function(){return{QX:{produce:e=>{let t,s;switch(e.type){case"ss":if(t="","obfs"===e.plugin){const{host:s,mode:r}=e["plugin-opts"];t=`,obfs=${r}${s?",obfs-host="+s:""}`}if("v2ray-plugin"===e.plugin){const{tls:s,host:r,path:n}=e["plugin-opts"];t=`,obfs=${s?"wss":"ws"}${r?",obfs-host="+r:""}${n?",obfs-uri="+n:""}`}return`shadowsocks=${e.server}:${e.port},method=${e.cipher},password=${e.password}${t}${e.tfo?",fast-open=true":",fast-open=false"}${e.udp?",udp-relay=true":",udp-relay=false"},tag=${e.name}`;case"ssr":return`shadowsocks=${e.server}:${e.port},method=${e.cipher},password=${e.password},ssr-protocol=${e.protocol}${e["protocol-param"]?",ssr-protocol-param="+e["protocol-param"]:""}${e.obfs?",obfs="+e.obfs:""}${e["obfs-param"]?",obfs-host="+e["obfs-param"]:""}${e.tfo?",fast-open=true":",fast-open=false"}${e.udp?",udp-relay=true":",udp-relay=false"},tag=${e.name}`;case"vmess":return t="","ws"===e.network?t=e.tls?`,obfs=wss${e.sni?",obfs-host="+e.sni:""}${e["ws-path"]?",obfs-uri="+e["ws-path"]:""},tls-verification=${e["skip-cert-verify"]?"false":"true"}`:`,obfs=ws${e["ws-headers"].Host?",obfs-host="+e["ws-headers"].Host:""}${e["ws-path"]?",obfs-uri="+e["ws-path"]:""}`:e.tls&&(t=`,obfs=over-tls${e.sni?",obfs-host="+e.sni:""},tls-verification=${e["skip-cert-verify"]?"false":"true"}`),`vmess=${e.server}:${e.port},method=${"auto"===e.cipher?"none":e.cipher},password=${e.uuid}${t}${e.tfo?",fast-open=true":",fast-open=false"}${e.udp?",udp-relay=true":",udp-relay=false"},tag=${e.name}`;case"trojan":return`trojan=${e.server}:${e.port},password=${e.password}${e.sni?",tls-host="+e.sni:""},over-tls=true,tls-verification=${e["skip-cert-verify"]?"false":"true"}${e.tfo?",fast-open=true":",fast-open=false"}${e.udp?",udp-relay=true":",udp-relay=false"},tag=${e.name}`;case"http":return s="",e.tls&&(s=`,over-tls=true,tls-verification=${e["skip-cert-verify"]?"false":"true"}${e.sni?",tls-host="+e.sni:""}`),`http=${e.server}:${e.port},username=${e.username},password=${e.password}${s}${e.tfo?",fast-open=true":",fast-open=false"},tag=${e.name}`}throw new Error(`Platform QX does not support proxy type: ${e.type}`)}},Surge:{produce:e=>{let t,s;switch(e.type){case"ss":if(t="",e.plugin){const{host:s,mode:r}=e["plugin-opts"];if("obfs"!==e.plugin)throw new Error(`Platform Surge does not support obfs option: ${e.obfs}`);t=`,obfs=${r}${s?",obfs-host="+s:""}`}return`${e.name}=ss,${e.server}, ${e.port},encrypt-method=${e.cipher},password=${e.password}${t},tfo=${e.tfo||"false"},udp-relay=${e.udp||"false"}`;case"vmess":s="";let r=`${e.name}=vmess,${e.server},${e.port},username=${e.uuid},tls=${e.tls||"false"},tfo=${e.tfo||"false"}`;if("ws"===e.network){const t=e["ws-path"]||"/",s=e["ws-headers"].Host;r+=`,ws=true${t?",ws-path="+t:""}${s?",ws-headers=HOST:"+s:""}`}return e.tls&&(r+=`${void 0!==e["skip-cert-verify"]?",skip-cert-verify="+e["skip-cert-verify"]:""}`,r+=e.sni?`,sni=${e.sni}`:""),r;case"trojan":return`${e.name}=trojan,${e.server},${e.port},password=${e.password}${void 0!==e["skip-cert-verify"]?",skip-cert-verify="+e["skip-cert-verify"]:""}${e.sni?",sni="+e.sni:""},tfo=${e.tfo||"false"}`;case"http":return s=", tls=false",e.tls&&(s=`,tls=true,skip-cert-verify=${e["skip-cert-verify"]},sni=${e.sni}`),`${e.name}=http, ${e.server}, ${e.port}${e.username?",username="+e.username:""}${e.password?",password="+e.password:""}${s},tfo=${e.tfo||"false"}`}throw new Error(`Platform Surge does not support proxy type: ${e.type}`)}},Loon:{produce:e=>{let t,s;switch(e.type){case"ss":if(t=",,",e.plugin){if("obfs"!==e.plugin)throw new Error(`Platform Loon does not support obfs option: ${e.obfs}`);{const{mode:s,host:r}=e["plugin-opts"];t=`,${s},${r||""}`}}return`${e.name}=shadowsocks,${e.server},${e.port},${e.cipher},"${e.password}"${t}`;case"ssr":return`${e.name}=shadowsocksr,${e.server},${e.port},${e.cipher},"${e.password}",${e.protocol},{${e["protocol-param"]||""}},${e.obfs},{${e["obfs-param"]||""}}`;case"vmess":return t="",t="ws"===e.network?`,transport:ws,host:${e["ws-headers"].Host||e.server},path:${e["ws-path"]||"/"}`:",transport:tcp",e.tls&&(t+=`${e.sni?",tls-name:"+e.sni:""},skip-cert-verify:${e["skip-cert-verify"]||"false"}`),`${e.name}=vmess,${e.server},${e.port},${"auto"===e.cipher?"none":e.cipher},"${e.uuid}",over-tls:${e.tls||"false"}${t}`;case"trojan":return`${e.name}=trojan,${e.server},${e.port},"${e.password}"${e.sni?",tls-name:"+e.sni:""},skip-cert-verify:${e["skip-cert-verify"]||"false"}`;case"http":s="";const r=`${e.name}=${e.tls?"http":"https"},${e.server},${e.port},${e.username||""},${e.password||""}`;return e.tls?r+(s=`${e.sni?",tls-name:"+e.sni:""},skip-cert-verify:${e["skip-cert-verify"]}`):r}throw new Error(`Platform Loon does not support proxy type: ${e.type}`)}},Clash:{type:"ALL",produce:e=>"proxies:\n"+e.map(e=>(delete e.supported," - "+JSON.stringify(e)+"\n")).join("")},URI:{type:"SINGLE",produce:e=>{let t="";switch(e.type){case"ss":const s=`${e.cipher}:${e.password}`;if(t=`ss://${Base64.safeEncode(s)}@${e.server}:${e.port}/`,e.plugin){t+="?plugin=";const s=e["plugin-opts"];switch(e.plugin){case"obfs":t+=encodeURIComponent(`simple-obfs;obfs=${s.mode}${s.host?";obfs-host="+s.host:""}`);break;case"v2ray-plugin":t+=encodeURIComponent(`v2ray-plugin;obfs=${s.mode}${s.host?";obfs-host"+s.host:""}${s.tls?";tls":""}`);break;default:throw new Error(`Unsupported plugin option: ${e.plugin}`)}}t+=`#${encodeURIComponent(e.name)}`;break;case"ssr":t=`${e.server}:${e.port}:${e.protocol}:${e.cipher}:${e.obfs}:${Base64.safeEncode(e.password)}/`,t+=`?remarks=${Base64.safeEncode(e.name)}${e["obfs-param"]?"&obfsparam="+Base64.safeEncode(e["obfs-param"]):""}${e["protocol-param"]?"&protocolparam="+Base64.safeEncode(e["protocol-param"]):""}`,t="ssr://"+Base64.safeEncode(t);break;case"vmess":t={ps:e.name,add:e.server,port:e.port,id:e.uuid,type:"",aid:0,net:e.network||"tcp",tls:e.tls?"tls":""},"ws"===e.network&&(t.path=e["ws-path"]||"/",t.host=e["ws-headers"].Host||e.server),t="vmess://"+Base64.safeEncode(JSON.stringify(t));break;case"trojan":t=`trojan://${e.password}@${e.server}:${e.port}#${encodeURIComponent(e.name)}`;break;default:throw new Error(`Cannot handle proxy type: ${e.type}`)}return t}},JSON:{type:"ALL",produce:e=>JSON.stringify(e,null,2)}}}();function preprocess(e){for(const t of PROXY_PREPROCESSORS)try{if(t.test(e))return $.log(`Pre-processor [${t.name}] activated`),t.parse(e)}catch(e){$.error(`Parser [${t.name}] failed\n Reason: ${e}`)}return e}function safeMatch(e,t){let s;try{s=e.test(t)}catch(e){s=!1}return s}function parse(e){const t=(e=preprocess(e)).split("\n"),s=[];let r;for(let e of t){if(0===(e=e.trim()).length)continue;let t=r&&safeMatch(r,e);if(!t)for(const s of PROXY_PARSERS)if(safeMatch(s,e)){r=s,t=!0,$.log(`Proxy parser: ${s.name} is activated`);break}if(t)try{const t=r.parse(e);t||$.error(`Parser ${r.name} return nothing for \n${e}\n`),s.push(t)}catch(t){$.error(`Failed to parse line: \n ${e}\n Reason: ${t.stack}`)}else $.error(`Failed to find a rule to parse line: \n${e}\n`)}return s}async function process(e,t=[]){for(const s of t){let t;if(-1!==s.type.indexOf("Script")){const{mode:e,content:r}=s.args;t="link"===e?await $.http.get(r).then(e=>e.body).catch(e=>{throw new Error(`Error when downloading remote script: ${s.args.content}.\n Reason: ${e}`)}):r}const r=PROXY_PROCESSORS[s.type];if(r)try{$.log(`Applying "${s.type}" with arguments:\n >>> ${JSON.stringify(s.args,null,2)||"None"}`),e=-1!==s.type.indexOf("Script")?PROXY_PROCESSORS.Apply(r(t),e):PROXY_PROCESSORS.Apply(r(s.args),e)}catch(e){$.error(`Failed to apply "${s.type}"!\n REASON: ${e}`)}else $.error(`Unknown operator: "${s.type}"`)}return e}function produce(e,t){const s=PROXY_PRODUCERS[t];if(!s)throw new Error(`Target platform: ${t} is not supported!`);return e=e.filter(e=>!(e.supported&&!1===e.supported[t])),$.log(`Producing proxies for target: ${t}`),void 0===s.type||"SINGLE"===s.type?e.map(e=>{try{return s.produce(e)}catch(t){return $.error(`Cannot produce proxy: ${JSON.stringify(e,null,2)}\nReason: ${t}`),""}}).filter(e=>e.length>0).join("\n"):"ALL"===s.type?s.produce(e):void 0}return{parse:parse,process:process,produce:produce}}(),RuleUtils=function(){const e=function(){function e(){const e=["DOMAIN","DOMAIN-SUFFIX","DOMAIN-KEYWORD","IP-CIDR","IP-CIDR6","USER-AGENT","URL-REGEX","DEST-PORT","SRC-IP","IN-PORT","PROTOCOL"];return{name:"Surge Rule Set Parser",test:t=>0!==t.indexOf("payload:")&&e.some(e=>-1!==t.indexOf(e)),parse:t=>{const s=t.split("\n"),r=[];for(let t of s)if(t=t.trim(),!/\s*#/.test(t)&&e.some(e=>0===t.indexOf(e)))try{const e=t.split(",").map(e=>e.trim()),s={type:e[0],content:e[1]};"IP-CIDR"!==s.type&&"IP-CIDR6"!==s.type||(s.options=e.slice(2)),r.push(s)}catch(e){console.error(`Failed to parse line: ${t}\n Reason: ${e}`)}return r}}}return[e(),function(){const t=["host","host-suffix","host-keyword","ip-cidr","ip6-cidr","user-agent"];return{name:"QX Filter",test:e=>t.some(t=>0===e.indexOf(t.toLowerCase())),parse:t=>{const s=t.split("\n");for(let e=0;e0===e.indexOf("payload:")&&t.some(t=>-1!==e.indexOf(t)),parse:t=>{try{const s=YAML.eval(t).payload.map(e=>e.replace("DST-PORT","DEST-PORT").replace("SRC-IP-CIDR","SRC-IP").replace("SRC-PORT","IN-PORT")).join("\n");return e().parse(s)}catch(e){console.error(`Cannot parse rules: ${e}`)}return[]}}}()]}(),t=function(){return{"Remove Duplicate":function(){return{func:e=>{const t=new Set,s=[];return e.forEach(e=>{const r=e.options||[];r.sort();const n=`${e.type},${e.content},${JSON.stringify(r)}`;t.has(n)||(s.push(e),t.add(n))}),s}}}}}(),s=function(){return{QX:{type:"SINGLE",func:e=>{if(-1!==["URL-REGEX","DEST-PORT","SRC-IP","IN-PORT","PROTOCOL"].indexOf(e.type))return null;let t=`${{"DOMAIN-KEYWORD":"HOST-KEYWORD","DOMAIN-SUFFIX":"HOST-SUFFIX",DOMAIN:"HOST","IP-CIDR6":"IP6-CIDR"}[e.type]||e.type},${e.content}`;return"IP-CIDR"!==e.type&&"IP-CIDR6"!==e.type||(t+=e.options?`,${e.options[0]}`:""),t+=",SUB-STORE"}},Surge:{type:"SINGLE",func:e=>{let t=`${e.type},${e.content}`;return"IP-CIDR"!==e.type&&"IP-CIDR6"!==e.type||(t+=e.options?`,${e.options[0]}`:""),t}},Loon:{type:"SINGLE",func:e=>-1!==["DEST-PORT","SRC-IP","IN-PORT","PROTOCOL"].indexOf(e.type)?null:(e=>{let t=`${e.type},${e.content}`;return"IP-CIDR"!==e.type&&"IP-CIDR6"!==e.type||(t+=e.options?`,${e.options[0]}`:""),t})(e)},Clash:{type:"ALL",func:e=>{const t={"DEST-PORT":"DST-PORT","SRC-IP":"SRC-IP-CIDR","IN-PORT":"SRC-PORT"},s={payload:e.map(e=>{let s=`${t[e.type]||e.type},${e.content}`;return"IP-CIDR"!==e.type&&"IP-CIDR6"!==e.type||(s+=e.options?`,${e.options[0]}`:""),s})};return YAML.stringify(s)}}}}();return{parse:function(t){for(const s of e){let e;try{e=s.test(t)}catch{e=!1}if(e)return console.log(`Rule parser [${s.name}] is activated!`),s.parse(t)}},process:async function(e,s){for(const r of s){if(!t[r.type]){console.error(`Unknown operator: ${r.type}!`);continue}const s=t[r.type](r.args);try{console.log(`Applying operator "${r.type}" with arguments: \n >>> ${JSON.stringify(r.args)||"None"}`),e=s.func(e)}catch(e){console.error(`Failed to apply operator "${r.type}"!\n REASON: ${e}`)}}return e},produce:function(e,t){const r=s[t];if(!r)throw new Error(`Target platform: ${t} is not supported!`);return void 0===r.type||"SINGLE"===r.type?e.map(e=>{try{return r.func(e)}catch(t){return console.log(`ERROR: cannot produce rule: ${JSON.stringify(e)}\nReason: ${t}`),""}}).filter(e=>e.length>0).join("\n"):"ALL"===r.type?r.func(e):void 0}}}();function getBuiltInRules(){return{AD:{name:"AD",description:"",urls:["https://raw.githubusercontent.com/privacy-protection-tools/anti-AD/master/anti-ad-surge.txt","https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Providers/BanAD.yaml"]}}}function ENV(){const e="undefined"!=typeof $task,t="undefined"!=typeof $loon,s="undefined"!=typeof $httpClient&&!t,r="function"==typeof require&&"undefined"!=typeof $jsbox;return{isQX:e,isLoon:t,isSurge:s,isNode:"function"==typeof require&&!r,isJSBox:r,isRequest:"undefined"!=typeof $request,isScriptable:"undefined"!=typeof importModule}}function HTTP(e={baseURL:""}){const{isQX:t,isLoon:s,isSurge:r,isScriptable:n,isNode:o}=ENV(),a=/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*)/;const i={};return["GET","POST","PUT","DELETE","HEAD","OPTIONS","PATCH"].forEach(p=>i[p.toLowerCase()]=(i=>(function(i,p){p="string"==typeof p?{url:p}:p;const l=e.baseURL;l&&!a.test(p.url||"")&&(p.url=l?l+p.url:p.url);const c=(p={...e,...p}).timeout,u={onRequest:()=>{},onResponse:e=>e,onTimeout:()=>{},...p.events};let f,d;if(u.onRequest(i,p),t)f=$task.fetch({method:i,...p});else if(s||r||o)f=new Promise((e,t)=>{(o?require("request"):$httpClient)[i.toLowerCase()](p,(s,r,n)=>{s?t(s):e({statusCode:r.status||r.statusCode,headers:r.headers,body:n})})});else if(n){const e=new Request(p.url);e.method=i,e.headers=p.headers,e.body=p.body,f=new Promise((t,s)=>{e.loadString().then(s=>{t({statusCode:e.response.statusCode,headers:e.response.headers,body:s})}).catch(e=>s(e))})}const h=c?new Promise((e,t)=>{d=setTimeout(()=>(u.onTimeout(),t(`${i} URL: ${p.url} exceeds the timeout ${c} ms`)),c)}):null;return(h?Promise.race([h,f]).then(e=>(clearTimeout(d),e)):f).then(e=>u.onResponse(e))})(p,i))),i}function API(e="untitled",t=!1){const{isQX:s,isLoon:r,isSurge:n,isNode:o,isJSBox:a,isScriptable:i}=ENV();return new class{constructor(e,t){this.name=e,this.debug=t,this.http=HTTP(),this.env=ENV(),this.node=(()=>{if(o){return{fs:require("fs")}}return null})(),this.initCache();Promise.prototype.delay=function(e){return this.then(function(t){return((e,t)=>new Promise(function(s){setTimeout(s.bind(null,t),e)}))(e,t)})}}initCache(){if(s&&(this.cache=JSON.parse($prefs.valueForKey(this.name)||"{}")),(r||n)&&(this.cache=JSON.parse($persistentStore.read(this.name)||"{}")),o){let e="root.json";this.node.fs.existsSync(e)||this.node.fs.writeFileSync(e,JSON.stringify({}),{flag:"wx"},e=>console.log(e)),this.root={},e=`${this.name}.json`,this.node.fs.existsSync(e)?this.cache=JSON.parse(this.node.fs.readFileSync(`${this.name}.json`)):(this.node.fs.writeFileSync(e,JSON.stringify({}),{flag:"wx"},e=>console.log(e)),this.cache={})}}persistCache(){const e=JSON.stringify(this.cache,null,2);s&&$prefs.setValueForKey(e,this.name),(r||n)&&$persistentStore.write(e,this.name),o&&(this.node.fs.writeFileSync(`${this.name}.json`,e,{flag:"w"},e=>console.log(e)),this.node.fs.writeFileSync("root.json",JSON.stringify(this.root,null,2),{flag:"w"},e=>console.log(e)))}write(e,t){if(this.log(`SET ${t}`),-1!==t.indexOf("#")){if(t=t.substr(1),n||r)return $persistentStore.write(e,t);if(s)return $prefs.setValueForKey(e,t);o&&(this.root[t]=e)}else this.cache[t]=e;this.persistCache()}read(e){return this.log(`READ ${e}`),-1===e.indexOf("#")?this.cache[e]:(e=e.substr(1),n||r?$persistentStore.read(e):s?$prefs.valueForKey(e):o?this.root[e]:void 0)}delete(e){if(this.log(`DELETE ${e}`),-1!==e.indexOf("#")){if(e=e.substr(1),n||r)return $persistentStore.write(null,e);if(s)return $prefs.removeValueForKey(e);o&&delete this.root[e]}else delete this.cache[e];this.persistCache()}notify(e,t="",p="",l={}){const c=l["open-url"],u=l["media-url"];if(s&&$notify(e,t,p,l),n&&$notification.post(e,t,p+`${u?"\n多媒体:"+u:""}`,{url:c}),r){let s={};c&&(s.openUrl=c),u&&(s.mediaUrl=u),"{}"===JSON.stringify(s)?$notification.post(e,t,p):$notification.post(e,t,p,s)}if(o||i){const s=p+(c?`\n点击跳转: ${c}`:"")+(u?`\n多媒体: ${u}`:"");if(a){require("push").schedule({title:e,body:(t?t+"\n":"")+s})}else console.log(`${e}\n${t}\n${s}\n\n`)}}log(e){this.debug&&console.log(`[${this.name}] LOG: ${e}`)}info(e){console.log(`[${this.name}] INFO: ${e}`)}error(e){console.log(`[${this.name}] ERROR: ${e}`)}wait(e){return new Promise(t=>setTimeout(t,e))}done(e={}){s||r||n?$done(e):o&&!a&&"undefined"!=typeof $context&&($context.headers=e.headers,$context.statusCode=e.statusCode,$context.body=e.body)}}(e,t)}function Gist(e,t){const s=HTTP({baseURL:"https://api.github.com",headers:{Authorization:`token ${t}`,"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.141 Safari/537.36"},events:{onResponse:e=>/^[45]/.test(String(e.statusCode))?Promise.reject(`ERROR: ${JSON.parse(e.body).message}`):e}});async function r(){return s.get("/gists").then(t=>{const s=JSON.parse(t.body);for(let t of s)if(t.description===e)return t.id;return-1})}this.upload=async function(t){const n=await r(),o={"Sub-Store":{content:t}};return-1===n?s.post({url:"/gists",body:JSON.stringify({description:e,public:!1,files:o})}):s.patch({url:`/gists/${n}`,body:JSON.stringify({files:o})})},this.download=async function(){const e=await r();if(-1===e)return Promise.reject("未找到Gist备份!");try{const{files:t}=await s.get(`/gists/${e}`).then(e=>JSON.parse(e.body)),r=t["Sub-Store"].raw_url;return await s.get(r).then(e=>e.body)}catch(e){return Promise.reject(e)}}}function express({port:e}={port:3e3}){const{isNode:t}=ENV(),s={"Content-Type":"text/plain;charset=UTF-8","Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"POST,GET,OPTIONS,PATCH,PUT,DELETE","Access-Control-Allow-Headers":"Origin, X-Requested-With, Content-Type, Accept"};if(t){const t=require("express"),r=require("body-parser"),n=t();return n.use(r.json({verify:i})),n.use(r.urlencoded({verify:i,extended:!0})),n.use(r.raw({verify:i,type:"*/*"})),n.use((e,t,r)=>{t.set(s),r()}),n.start=(()=>{n.listen(e,()=>{$.info(`Express started on port: ${e}`)})}),n}const r=[],n=["GET","POST","PUT","DELETE","PATCH","OPTIONS","HEAD'","ALL"],o=(e,t=0)=>{let{method:s,url:n,headers:a,body:i}=e;/json/i.test(a["Content-Type"])&&(i=JSON.parse(i)),s=s.toUpperCase();const{path:u,query:f}=function(e){const t=(e.match(/https?:\/\/[^\/]+(\/[^?]*)/)||[])[1]||"/",s=e.indexOf("?"),r={};if(-1!==s){let t=e.slice(e.indexOf("?")+1).split("&");for(let e=0;em&&(h=r[d],m=e.split("/").length)}if(h){const e=()=>{o(s,n,d)},t={method:s,url:n,path:u,query:f,params:c(h.pattern,u),headers:a,body:i},r=p(),l=h.callback,m=e=>{r.status(500).json({status:"failed",message:`Internal Server Error: ${e}`})};if("AsyncFunction"===l.constructor.name)l(t,r,e).catch(m);else try{l(t,r,e)}catch(e){m(e)}}else{p().status(404).json({status:"failed",message:"ERROR: 404 not found"})}},a={};return n.forEach(e=>{a[e.toLowerCase()]=((t,s)=>{r.push({method:e,pattern:t,callback:s})})}),a.route=(e=>{const t={};return n.forEach(s=>{t[s.toLowerCase()]=(n=>(r.push({method:s,pattern:e,callback:n}),t))}),t}),a.start=(()=>{o($request)}),a;function i(e,t,s,r){s&&s.length&&(e.rawBody=s.toString(r||"utf8"))}function p(){let e=200;const{isQX:t,isLoon:r,isSurge:n}=ENV(),o=s,a={200:"HTTP/1.1 200 OK",201:"HTTP/1.1 201 Created",302:"HTTP/1.1 302 Found",307:"HTTP/1.1 307 Temporary Redirect",308:"HTTP/1.1 308 Permanent Redirect",404:"HTTP/1.1 404 Not Found",500:"HTTP/1.1 500 Internal Server Error"};return new class{status(t){return e=t,this}send(s=""){const i={status:t?a[e]:e,body:s,headers:o};t?$done(i):(r||n)&&$done({response:i})}end(){this.send()}html(e){this.set("Content-Type","text/html;charset=UTF-8"),this.send(e)}json(e){this.set("Content-Type","application/json;charset=UTF-8"),this.send(JSON.stringify(e))}set(e,t){return o[e]=t,this}}}function l(e,t){if(e instanceof RegExp&&e.test(t))return!0;if("/"===e)return!0;if(-1===e.indexOf(":")){const s=t.split("/"),r=e.split("/");for(let e=0;e>>6)+s(128|63&t):s(224|t>>>12&15)+s(128|t>>>6&63)+s(128|63&t):(t=65536+1024*(e.charCodeAt(0)-55296)+(e.charCodeAt(1)-56320),s(240|t>>>18&7)+s(128|t>>>12&63)+s(128|t>>>6&63)+s(128|63&t))},n=/[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g,o=function(t){const s=[0,2,1][t.length%3],r=t.charCodeAt(0)<<16|(t.length>1?t.charCodeAt(1):0)<<8|(t.length>2?t.charCodeAt(2):0);return[e.charAt(r>>>18),e.charAt(r>>>12&63),s>=2?"=":e.charAt(r>>>6&63),s>=1?"=":e.charAt(63&r)].join("")};this.encode=function(e){return"[object Uint8Array]"===Object.prototype.toString.call(e)?e.toString("base64"):function(e){return e.replace(n,r)}(String(e)).replace(/[\s\S]{1,3}/g,o)};const a=/[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3}/g,i=function(e){switch(e.length){case 4:const t=((7&e.charCodeAt(0))<<18|(63&e.charCodeAt(1))<<12|(63&e.charCodeAt(2))<<6|63&e.charCodeAt(3))-65536;return s(55296+(t>>>10))+s(56320+(1023&t));case 3:return s((15&e.charCodeAt(0))<<12|(63&e.charCodeAt(1))<<6|63&e.charCodeAt(2));default:return s((31&e.charCodeAt(0))<<6|63&e.charCodeAt(1))}},p=function(e){const r=e.length,n=r%4,o=(r>0?t[e.charAt(0)]<<18:0)|(r>1?t[e.charAt(1)]<<12:0)|(r>2?t[e.charAt(2)]<<6:0)|(r>3?t[e.charAt(3)]:0),a=[s(o>>>16),s(o>>>8&255),s(255&o)];return a.length-=[0,0,2,1][n],a.join("")},l=function(e){return e.replace(/\S{1,4}/g,p)},c=function(e){return l(e).replace(a,i)};this.decode=function(e){return c(String(e).replace(/[-_]/g,function(e){return"-"===e?"+":"/"}).replace(/[^A-Za-z0-9\+\/]/g,"")).replace(/>/g,">").replace(/</g,"<")},this.safeEncode=function(e){return this.encode(e.replace(/\+/g,"-").replace(/\//g,"_"))},this.safeDecode=function(e){return this.decode(e.replace(/-/g,"+").replace(/_/g,"/"))}}var YAML=function(){var e=[],t=[],s=0,r={regLevel:new RegExp("^([\\s\\-]+)"),invalidLine:new RegExp("^\\-\\-\\-|^\\.\\.\\.|^\\s*#.*|^\\s*$"),dashesString:new RegExp('^\\s*\\"([^\\"]*)\\"\\s*$'),quotesString:new RegExp("^\\s*\\'([^\\']*)\\'\\s*$"),float:new RegExp("^[+-]?[0-9]+\\.[0-9]+(e[+-]?[0-9]+(\\.[0-9]+)?)?$"),integer:new RegExp("^[+-]?[0-9]+$"),array:new RegExp("\\[\\s*(.*)\\s*\\]"),map:new RegExp("\\{\\s*(.*)\\s*\\}"),key_value:new RegExp("([a-z0-9_-][ a-z0-9_-]*):( .+)","i"),single_key_value:new RegExp("^([a-z0-9_-][ a-z0-9_-]*):( .+?)$","i"),key:new RegExp("([a-z0-9_-][ a-z0-9_-]*):( .+)?","i"),item:new RegExp("^-\\s+"),trim:new RegExp("^\\s+|\\s+$"),comment:new RegExp("([^\\'\\\"#]+([\\'\\\"][^\\'\\\"]*[\\'\\\"])*)*(#.*)?")};function n(e){return{parent:null,length:0,level:e,lines:[],children:[],addChild:function(e){this.children.push(e),e.parent=this,++this.length}}}function o(e){var t=null;if("true"==(e=e.replace(r.trim,"")))return!0;if("false"==e)return!1;if(".NaN"==e)return Number.NaN;if("null"==e)return null;if(".inf"==e)return Number.POSITIVE_INFINITY;if("-.inf"==e)return Number.NEGATIVE_INFINITY;if(t=e.match(r.dashesString))return t[1];if(t=e.match(r.quotesString))return t[1];if(t=e.match(r.float))return parseFloat(t[0]);if(t=e.match(r.integer))return parseInt(t[0]);if(isNaN(t=Date.parse(e))){if(t=e.match(r.single_key_value))return(a={})[t[1]]=o(t[2]),a;if(t=e.match(r.array)){for(var s=0,n=" ",a=[],i="",p=!1,l=0,c=t[1].length;l0&&a.push(o(i)),a}if(t=e.match(r.map)){for(s=0,n=" ",a=[],i="",p=!1,l=0,c=t[1].length;l0&&a.push(i);var u={};for(l=0,c=a.length;l"==v[0]?null!=f?f[O]=a(u.shift()):l[O]=a(u.shift()):null!=f?f[O]=o(v):l[O]=o(v)}else null!=f?f[O]=s(u):l[O]=s(u)}else S.match(/^-\s*$/)?(m&&(m=!1,void 0===l.length&&(l=[])),null!=f&&l.push(f),f={},m=!0):(p=S.match(/^-\s*(.*)/))&&(null!=f?f.push(o(p[1])):(m&&(m=!1,void 0===l.length&&(l=[])),l.push(o(p[1]))))}null!=f&&(m&&(m=!1,void 0===l.length&&(l=[])),l.push(f))}for($=h.length-1;$>=0;--$)n.splice.call(n,h[$],1);return l}(s.children)}return{eval:function(o){e=[],t=[],s=(new Date).getTime();var a=p(function(t){var s,o=r.regLevel,a=r.invalidLine,i=t.split("\n"),p=0,l=0,c=[],u=new n(-1),f=new n(0);u.addChild(f);var d=[],h="";c.push(f),d.push(p);for(var m=0,$=i.length;m<$;++m)if(!(h=i[m]).match(a)){if((p=(s=o.exec(h))?s[1].length:0)>l){var g=f;f=new n(p),g.addChild(f),c.push(f),d.push(p)}else if(p=0;--w)if(d[w]==p){f=new n(p),c.push(f),d.push(p),null!=c[w].parent&&c[w].parent.addChild(f),y=!0;break}if(!y)return void e.push("Error: Invalid indentation at line "+m+": "+h)}f.lines.push(h.replace(r.trim,"")),l=p}return u}(function(e){var t,s=e.split("\n"),n=r.comment;for(var o in s)(t="string"==typeof s[o]&&s[o].match(n))&&void 0!==t[3]&&(s[o]=t[0].substr(0,t[0].length-t[3].length));return s.join("\n")}(o)));return s=(new Date).getTime()-s,a},getErrors:function(){return e},getProcessingTime:function(){return s}}}(); \ No newline at end of file