From 33ae67ab6075c12d4cf00295693e88c14a1b8e5a Mon Sep 17 00:00:00 2001 From: Peng-YM <1048217874pengym@gmail.com> Date: Tue, 29 Sep 2020 17:09:29 +0800 Subject: [PATCH] Add Clash and Subscription producer --- backend/sub-store.js | 6280 +++++++++++++++++++------------------- backend/sub-store.min.js | 2 +- 2 files changed, 3158 insertions(+), 3124 deletions(-) diff --git a/backend/sub-store.js b/backend/sub-store.js index c68018a..2321b98 100644 --- a/backend/sub-store.js +++ b/backend/sub-store.js @@ -12,42 +12,47 @@ const $ = API("sub-store"); const $app = express(); $.http = HTTP({ - headers: { - "User-Agent": "Quantumult%20X", - }, + headers: { + "User-Agent": "Quantumult%20X", + }, }); // Constants const SETTINGS_KEY = "settings"; const SUBS_KEY = "subs"; const COLLECTIONS_KEY = "collections"; + const AVAILABLE_FILTERS = { - "Keyword Filter": KeywordFilter, - "Useless Filter": UselessFilter, - "Region Filter": RegionFilter, - "Regex Filter": RegexFilter, - "Type Filter": TypeFilter, - "Script Filter": ScriptFilter, + "Keyword Filter": KeywordFilter, + "Useless Filter": UselessFilter, + "Region Filter": RegionFilter, + "Regex Filter": RegexFilter, + "Type Filter": TypeFilter, + "Script Filter": ScriptFilter, }; const AVAILABLE_OPERATORS = { - "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, + "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, }; +const AVAILABLE_PRODUCERS = [ + Raw_Producer, URI_Producer, + Surge_Producer, Loon_Producer, QX_Producer, Clash_Producer, Sub_Producer +]; + // SOME INITIALIZATIONS if (!$.read(SUBS_KEY)) $.write({}, SUBS_KEY); if (!$.read(COLLECTIONS_KEY)) $.write({}, COLLECTIONS_KEY); if (!$.read(SETTINGS_KEY)) $.write({}, SETTINGS_KEY); // BACKEND API -$.info("Initializing Express..."); // download $app.get("/download/collection/:name", downloadCollection); @@ -65,888 +70,886 @@ $app.post("/api/refresh", refreshResource); // collections $app - .route("/api/collection/:name") - .get(getCollection) - .patch(updateCollection) - .delete(deleteCollection); + .route("/api/collection/:name") + .get(getCollection) + .patch(updateCollection) + .delete(deleteCollection); $app - .route("/api/collection") - .get(getAllCollections) - .post(newCollection) - .delete(deleteAllCollections); + .route("/api/collection") + .get(getAllCollections) + .post(newCollection) + .delete(deleteAllCollections); // settings $app.route("/api/settings") - .get(getSettings) - .patch(updateSettings); + .get(getSettings) + .patch(updateSettings); // backup $app.get("/api/backup", gistBackup); $app.all("/", async (req, res) => { - res.send("Hello from Sub-Store! Made with ❤️ by Peng-YM."); + res.send("Hello from Sub-Store! Made with ❤️ by Peng-YM."); }); -$.info("Express initialized"); $app.start(); async function IP_API(req, res) { - const server = decodeURIComponent(req.params.server); - const result = await $.http - .get(`http://ip-api.com/json/${server}?lang=zh-CN`) - .then((resp) => JSON.parse(resp.body)); - res.json(result); + const server = decodeURIComponent(req.params.server); + const result = await $.http + .get(`http://ip-api.com/json/${server}?lang=zh-CN`) + .then((resp) => JSON.parse(resp.body)); + res.json(result); } async function downloadResource(url) { - let raw = await $.http - .get(url) - .then((resp) => resp.body); - // trim Clash config to save memory - const start = raw.indexOf("proxies:"); - if (start !== -1) { - const end = raw.lastIndexOf("}") + 1; - raw = raw.substring(start, end); - } - return raw; + let raw = await $.http + .get(url) + .then((resp) => resp.body); + // trim Clash config to save memory + const start = raw.indexOf("proxies:"); + if (start !== -1) { + const end = raw.lastIndexOf("}") + 1; + raw = raw.substring(start, end); + } + return raw; } async function gistBackup(req, res) { - const {action} = req.query; - // read token - const { gistToken } = $.read(SETTINGS_KEY); - if (!gistToken) { - res.status(500).json({ - status: "failed", - message: "未找到Gist备份Token!" - }); - } else { - const gist = new Gist("Auto Generated Sub-Store Backup", gistToken); - try{ - let content; - switch (action) { - case "upload": - content = $.read("#sub-store"); - await gist.upload(JSON.stringify(content)); - $.info(`上传备份中...`); - break; - case "download": - content = await gist.download(); - // restore settings - $.write(content,"#sub-store"); - $.info(`还原备份中...`); - break; - } - res.json({ - status: "success", - }); - } catch (err) { - const msg = `${action === "upload" ? "上传" : "下载"}备份失败!${err}`; - $.error(msg); - res.status(500).json({ - status: "failed", - message: msg - }); - } + const {action} = req.query; + // read token + const {gistToken} = $.read(SETTINGS_KEY); + if (!gistToken) { + res.status(500).json({ + status: "failed", + message: "未找到Gist备份Token!" + }); + } else { + const gist = new Gist("Auto Generated Sub-Store Backup", gistToken); + try { + let content; + switch (action) { + case "upload": + content = $.read("#sub-store"); + await gist.upload(JSON.stringify(content)); + $.info(`上传备份中...`); + break; + case "download": + content = await gist.download(); + // restore settings + $.write(content, "#sub-store"); + $.info(`还原备份中...`); + break; + } + res.json({ + status: "success", + }); + } catch (err) { + const msg = `${action === "upload" ? "上传" : "下载"}备份失败!${err}`; + $.error(msg); + res.status(500).json({ + status: "failed", + message: msg + }); + } - } + } } // settings async function getSettings(req, res) { - const settings = $.read(SETTINGS_KEY); - res.json(settings); + const settings = $.read(SETTINGS_KEY); + res.json(settings); } async function updateSettings(req, res) { - const data = req.body; - const settings = $.read(SETTINGS_KEY); - $.write({ - ...settings, - ...data - }, SETTINGS_KEY); - res.json({ - status: "success" - }); + const data = req.body; + const settings = $.read(SETTINGS_KEY); + $.write({ + ...settings, + ...data + }, SETTINGS_KEY); + res.json({ + status: "success" + }); } /**************************** API -- Subscriptions ***************************************/ // refresh resource async function refreshResource(req, res) { - const Base64 = new Base64Code(); - const { url } = req.body; - const raw = await downloadResource(url); - $.write(raw, `#${Base64.safeEncode(url)}`); - res.json({ - status: "success", - }); + const Base64 = new Base64Code(); + const {url} = req.body; + const raw = await downloadResource(url); + $.write(raw, `#${Base64.safeEncode(url)}`); + res.json({ + status: "success", + }); } // download subscription, for APP only async function downloadSub(req, res) { - const { name } = req.params; - const platform = req.query.target || getPlatformFromHeaders(req.headers); - const allSubs = $.read(SUBS_KEY); - if (allSubs[name]) { - const sub = allSubs[name]; - try { - const output = await parseSub(sub, platform); - res.send(output); - } catch (err) { - res.status(500).json({ - status: "failed", - message: err, - }); + const {name} = req.params; + const platform = req.query.target || getPlatformFromHeaders(req.headers); + const allSubs = $.read(SUBS_KEY); + if (allSubs[name]) { + const sub = allSubs[name]; + try { + const output = await parseSub(sub, platform); + res.send(output); + } catch (err) { + res.status(500).json({ + status: "failed", + message: err, + }); + } + } else { + res.status(404).json({ + status: "failed", + message: `订阅${name}不存在!`, + }); } - } else { - res.status(404).json({ - status: "failed", - message: `订阅${name}不存在!`, - }); - } } async function parseSub(sub, platform) { - let raw; - const key = new Base64Code().safeEncode(sub.url); - if (platform === "Raw" || platform === "URI") { - const cache = $.read(`#${key}`); - if (!cache) { - raw = await downloadResource(sub.url); - $.write(raw, `#${key}`); + let raw; + const key = new Base64Code().safeEncode(sub.url); + if (platform === "Raw" || platform === "URI") { + const cache = $.read(`#${key}`); + if (!cache) { + raw = await downloadResource(sub.url); + $.write(raw, `#${key}`); + } else { + // 我也不知道这里为什么要等10ms,不加Surge报错。 + await $.wait(10); + raw = cache; + } } else { - // 我也不知道这里为什么要等10ms,不加Surge报错。 - await $.wait(10); - raw = cache; + // always download from url + raw = await downloadResource(sub.url); + $.write(raw, `#${sub.url}`); } - } else { - // always download from url - raw = await downloadResource(sub.url); - $.write(raw, `#${sub.url}`); - } - $.info( - "=======================================================================" - ); - $.info( - `Processing subscription: ${sub.name}, target platform ==> ${platform}.` - ); - $.info(`Initializing parsers...`); - const $parser = ProxyParser(platform); - // Parsers - $parser.addParsers([ - Clash_All, - // URI format parsers - URI_SS, - URI_SSR, - URI_VMess, - URI_Trojan, - // Quantumult X platform - QX_SS, - QX_SSR, - QX_VMess, - QX_Trojan, - QX_Http, - // Loon platform - Loon_SS, - Loon_SSR, - Loon_VMess, - Loon_Trojan, - Loon_Http, - // Surge platform - Surge_SS, - Surge_VMess, - Surge_Trojan, - Surge_Http, - ]); + $.info( + "=======================================================================" + ); + $.info( + `Processing subscription: ${sub.name}, target platform ==> ${platform}.` + ); + $.info(`Initializing parsers...`); + const $parser = ProxyParser(platform); + // Parsers + $parser.addParsers([ + Clash_All, + // URI format parsers + URI_SS, + URI_SSR, + URI_VMess, + URI_Trojan, + // Quantumult X platform + QX_SS, + QX_SSR, + QX_VMess, + QX_Trojan, + QX_Http, + // Loon platform + Loon_SS, + Loon_SSR, + Loon_VMess, + Loon_Trojan, + Loon_Http, + // Surge platform + Surge_SS, + Surge_VMess, + Surge_Trojan, + Surge_Http, + ]); - $.info(`Parsers initialized.`); - let proxies = $parser.parse(raw); + $.info(`Parsers initialized.`); + let proxies = $parser.parse(raw); - for (const item of sub.process || []) { - let script; - // process script - if (item.type.indexOf("Script") !== -1) { - const { mode, content } = item.args; - if (mode === "link") { - // if this is remote script, download it - script = await $.http - .get(content) - .then((resp) => resp.body) - .catch((err) => { - throw new Error( - `Error when downloading remote script: ${item.args.content}.\n Reason: ${err}` - ); - }); - } else { - script = content; - } - } - if (item.type.indexOf("Filter") !== -1) { - const filter = AVAILABLE_FILTERS[item.type]; - if (filter) { - $.info( - `Applying filter "${item.type}" with arguments:\n >>> ${ - JSON.stringify(item.args) || "None" - }` - ); - - try { - if (item.type.indexOf("Script") !== -1) { - proxies = processFilter(filter(script), proxies); - } else { - proxies = processFilter(filter(item.args), proxies); - } - } catch (err) { - $.error(`Failed to apply filter "${item.type}"!\n REASON: ${err}`); + for (const item of sub.process || []) { + let script; + // process script + if (item.type.indexOf("Script") !== -1) { + const {mode, content} = item.args; + if (mode === "link") { + // if this is remote script, download it + script = await $.http + .get(content) + .then((resp) => resp.body) + .catch((err) => { + throw new Error( + `Error when downloading remote script: ${item.args.content}.\n Reason: ${err}` + ); + }); + } else { + script = content; + } } - } - } else if (item.type.indexOf("Operator") !== -1) { - const operator = AVAILABLE_OPERATORS[item.type]; - if (operator) { - $.info( - `Applying operator "${item.type}" with arguments: \n >>> ${ - JSON.stringify(item.args) || "None" - }` - ); - try { - if (item.type.indexOf("Script") !== -1) { - proxies = processOperator(operator(script), proxies); - } else { - proxies = processOperator(operator(item.args), proxies); - } - } catch (err) { - `Failed to apply operator "${item.type}"!\n REASON: ${err}`; - } - } - } - } + if (item.type.indexOf("Filter") !== -1) { + const filter = AVAILABLE_FILTERS[item.type]; + if (filter) { + $.info( + `Applying filter "${item.type}" with arguments:\n >>> ${ + JSON.stringify(item.args) || "None" + }` + ); - // Producers - $parser.addProducers([ - QX_Producer, - Loon_Producer, - Surge_Producer, - Raw_Producer, - URI_Producer, - ]); - return $parser.produce(proxies); + try { + if (item.type.indexOf("Script") !== -1) { + proxies = processFilter(filter(script), proxies); + } else { + proxies = processFilter(filter(item.args), proxies); + } + } catch (err) { + $.error(`Failed to apply filter "${item.type}"!\n REASON: ${err}`); + } + } + } else if (item.type.indexOf("Operator") !== -1) { + const operator = AVAILABLE_OPERATORS[item.type]; + if (operator) { + $.info( + `Applying operator "${item.type}" with arguments: \n >>> ${ + JSON.stringify(item.args) || "None" + }` + ); + try { + if (item.type.indexOf("Script") !== -1) { + proxies = processOperator(operator(script), proxies); + } else { + proxies = processOperator(operator(item.args), proxies); + } + } catch (err) { + `Failed to apply operator "${item.type}"!\n REASON: ${err}`; + } + } + } + } + + // Producers + $parser.addProducers(AVAILABLE_PRODUCERS); + return $parser.produce(proxies); } function getFlowHeaders(headers, proxies) { - const subkey = Object.keys(headers).filter((k) => - /SUBSCRIPTION-USERINFO/i.test(k) - )[0]; - if (subkey) { - // from headers - const userinfo = headers[subkey]; - const upload = Number(userinfo.match(/upload=(\d+)/)[1]); - const download = Number(userinfo.match(/download=(\d+)/)[1]); - const total = Number(userinfo.match(/total=(\d+)/)[1]); - const expire = (userinfo.match(/expire=(\d+)/) || [])[1]; - return { - key: "Subscription-Userinfo", - value: `${upload ? "upload=" + upload + ";" : ""}${ - download ? "download=" + download + ";" : "" - }${total ? "total=" + total + ";" : ""}${ - expire ? "expire=" + expire + ";" : "" - }`, - }; - } else { - let remains, expire; - // from fake nodes - for (const p of proxies) { - if (p.name.indexOf("剩余流量") !== -1) { - remains = p.name; - } - if (p.name.indexOf("过期时间") !== -1) { - expire = p.name; - } + const subkey = Object.keys(headers).filter((k) => + /SUBSCRIPTION-USERINFO/i.test(k) + )[0]; + if (subkey) { + // from headers + const userinfo = headers[subkey]; + const upload = Number(userinfo.match(/upload=(\d+)/)[1]); + const download = Number(userinfo.match(/download=(\d+)/)[1]); + const total = Number(userinfo.match(/total=(\d+)/)[1]); + const expire = (userinfo.match(/expire=(\d+)/) || [])[1]; + return { + key: "Subscription-Userinfo", + value: `${upload ? "upload=" + upload + ";" : ""}${ + download ? "download=" + download + ";" : "" + }${total ? "total=" + total + ";" : ""}${ + expire ? "expire=" + expire + ";" : "" + }`, + }; + } else { + let remains, expire; + // from fake nodes + for (const p of proxies) { + if (p.name.indexOf("剩余流量") !== -1) { + remains = p.name; + } + if (p.name.indexOf("过期时间") !== -1) { + expire = p.name; + } + } + return { + key: "Raw-Subscription-Userinfo", + value: `${remains ? "remains=" + remains + ";" : ""}${ + expire ? "expire=" + expire + ";" : "" + }`, + }; } - return { - key: "Raw-Subscription-Userinfo", - value: `${remains ? "remains=" + remains + ";" : ""}${ - expire ? "expire=" + expire + ";" : "" - }`, - }; - } } // Subscriptions async function getSub(req, res) { - const { name } = req.params; - const sub = $.read(SUBS_KEY)[name]; - if (sub) { - res.json({ - status: "success", - data: sub, - }); - } else { - res.status(404).json({ - status: "failed", - message: `未找到订阅:${name}!`, - }); - } + const {name} = req.params; + const sub = $.read(SUBS_KEY)[name]; + if (sub) { + res.json({ + status: "success", + data: sub, + }); + } else { + res.status(404).json({ + status: "failed", + message: `未找到订阅:${name}!`, + }); + } } async function newSub(req, res) { - const sub = req.body; - const allSubs = $.read(SUBS_KEY); - if (allSubs[sub.name]) { - res.status(500).json({ - status: "failed", - message: `订阅${sub.name}已存在!`, - }); - } - // validate name - if (/^[\w-_]*$/.test(sub.name)) { - allSubs[sub.name] = sub; - $.write(allSubs, SUBS_KEY); - res.status(201).json({ - status: "success", - data: sub, - }); - } else { - res.status(500).json({ - status: "failed", - message: `订阅名称 ${sub.name} 中含有非法字符!名称中只能包含英文字母、数字、下划线、横杠。`, - }); - } + const sub = req.body; + const allSubs = $.read(SUBS_KEY); + if (allSubs[sub.name]) { + res.status(500).json({ + status: "failed", + message: `订阅${sub.name}已存在!`, + }); + } + // validate name + if (/^[\w-_]*$/.test(sub.name)) { + allSubs[sub.name] = sub; + $.write(allSubs, SUBS_KEY); + res.status(201).json({ + status: "success", + data: sub, + }); + } else { + res.status(500).json({ + status: "failed", + message: `订阅名称 ${sub.name} 中含有非法字符!名称中只能包含英文字母、数字、下划线、横杠。`, + }); + } } async function updateSub(req, res) { - const { name } = req.params; - $.info(`Updating subscription: ${name}`); - let sub = req.body; - const allSubs = $.read(SUBS_KEY); - if (allSubs[name]) { - const newSub = { - ...allSubs[name], - ...sub, - }; - // allow users to update the subscription name - if (name !== sub.name) { - // we need to find out all collections refer to this name - const allCols = $.read(COLLECTIONS_KEY); - for (const k of Object.keys(allCols)) { - const idx = allCols[k].subscriptions.indexOf(name); - if (idx !== -1) { - allCols[k].subscriptions[idx] = sub.name; + const {name} = req.params; + $.info(`Updating subscription: ${name}`); + let sub = req.body; + const allSubs = $.read(SUBS_KEY); + if (allSubs[name]) { + const newSub = { + ...allSubs[name], + ...sub, + }; + // allow users to update the subscription name + if (name !== sub.name) { + // we need to find out all collections refer to this name + const allCols = $.read(COLLECTIONS_KEY); + for (const k of Object.keys(allCols)) { + const idx = allCols[k].subscriptions.indexOf(name); + if (idx !== -1) { + allCols[k].subscriptions[idx] = sub.name; + } + } + // update subscriptions + delete allSubs[name]; + allSubs[sub.name] = newSub; + } else { + allSubs[name] = newSub; } - } - // update subscriptions - delete allSubs[name]; - allSubs[sub.name] = newSub; + $.write(allSubs, SUBS_KEY); + res.json({ + status: "success", + data: newSub, + }); } else { - allSubs[name] = newSub; + res.status(500).json({ + status: "failed", + message: `订阅${name}不存在,无法更新!`, + }); } - $.write(allSubs, SUBS_KEY); - res.json({ - status: "success", - data: newSub, - }); - } else { - res.status(500).json({ - status: "failed", - message: `订阅${name}不存在,无法更新!`, - }); - } } async function deleteSub(req, res) { - const { name } = req.params; - // delete from subscriptions - let allSubs = $.read(SUBS_KEY); - delete allSubs[name]; - $.write(allSubs, SUBS_KEY); - // delete from collections - let allCols = $.read(COLLECTIONS_KEY); - for (const k of Object.keys(allCols)) { - allCols[k].subscriptions = allCols[k].subscriptions.filter( - (s) => s !== name - ); - } - $.write(allCols, COLLECTIONS_KEY); - res.json({ - status: "success", - }); + const {name} = req.params; + // delete from subscriptions + let allSubs = $.read(SUBS_KEY); + delete allSubs[name]; + $.write(allSubs, SUBS_KEY); + // delete from collections + let allCols = $.read(COLLECTIONS_KEY); + for (const k of Object.keys(allCols)) { + allCols[k].subscriptions = allCols[k].subscriptions.filter( + (s) => s !== name + ); + } + $.write(allCols, COLLECTIONS_KEY); + res.json({ + status: "success", + }); } async function getAllSubs(req, res) { - const allSubs = $.read(SUBS_KEY); - res.json({ - status: "success", - data: allSubs, - }); + const allSubs = $.read(SUBS_KEY); + res.json({ + status: "success", + data: allSubs, + }); } async function deleteAllSubs(req, res) { - $.write({}, SUBS_KEY); - res.json({ - status: "success", - }); + $.write({}, SUBS_KEY); + res.json({ + status: "success", + }); } // Collections async function downloadCollection(req, res) { - const { name } = req.params; - const collection = $.read(COLLECTIONS_KEY)[name]; - const platform = getPlatformFromHeaders(req.headers); - if (collection) { - const subs = collection.subscriptions || []; - const output = await Promise.all( - subs.map(async (id) => { - const sub = $.read(SUBS_KEY)[id]; - try { - return parseSub(sub, platform); - } catch (err) { - console.log(`ERROR when process subscription: ${id}`); - return ""; - } - }) - ); - res.send(output.join("\n")); - } else { - $.notify("[Sub-Store]", `❌ 未找到订阅集:${name}!`); - res.status(404).json({ - status: "failed", - message: `❌ 未找到订阅集:${name}!`, - }); - } + const {name} = req.params; + const collection = $.read(COLLECTIONS_KEY)[name]; + const platform = getPlatformFromHeaders(req.headers); + if (collection) { + const subs = collection.subscriptions || []; + const output = await Promise.all( + subs.map(async (id) => { + const sub = $.read(SUBS_KEY)[id]; + try { + return parseSub(sub, platform); + } catch (err) { + console.log(`ERROR when process subscription: ${id}`); + return ""; + } + }) + ); + res.send(output.join("\n")); + } else { + $.notify("[Sub-Store]", `❌ 未找到订阅集:${name}!`); + res.status(404).json({ + status: "failed", + message: `❌ 未找到订阅集:${name}!`, + }); + } } async function getCollection(req, res) { - const { name } = req.params; - const collection = $.read(COLLECTIONS_KEY)[name]; - if (collection) { - res.json({ - status: "success", - data: collection, - }); - } else { - res.status(404).json({ - status: "failed", - message: `未找到订阅集:${name}!`, - }); - } + const {name} = req.params; + const collection = $.read(COLLECTIONS_KEY)[name]; + if (collection) { + res.json({ + status: "success", + data: collection, + }); + } else { + res.status(404).json({ + status: "failed", + message: `未找到订阅集:${name}!`, + }); + } } async function newCollection(req, res) { - const collection = req.body; - const allCol = $.read(COLLECTIONS_KEY); - if (allCol[collection.name]) { - res.status(500).json({ - status: "failed", - message: `订阅集${collection.name}已存在!`, - }); - } - // validate name - if (/^[\w-_]*$/.test(collection.name)) { - allCol[collection.name] = collection; - $.write(allCol, COLLECTIONS_KEY); - res.status(201).json({ - status: "success", - data: collection, - }); - } else { - res.status(500).json({ - status: "failed", - message: `订阅集名称 ${collection.name} 中含有非法字符!名称中只能包含英文字母、数字、下划线、横杠。`, - }); - } + const collection = req.body; + const allCol = $.read(COLLECTIONS_KEY); + if (allCol[collection.name]) { + res.status(500).json({ + status: "failed", + message: `订阅集${collection.name}已存在!`, + }); + } + // validate name + if (/^[\w-_]*$/.test(collection.name)) { + allCol[collection.name] = collection; + $.write(allCol, COLLECTIONS_KEY); + res.status(201).json({ + status: "success", + data: collection, + }); + } else { + res.status(500).json({ + status: "failed", + message: `订阅集名称 ${collection.name} 中含有非法字符!名称中只能包含英文字母、数字、下划线、横杠。`, + }); + } } async function updateCollection(req, res) { - const { name } = req.params; - let collection = req.body; - const allCol = $.read(COLLECTIONS_KEY); - if (allCol[name]) { - const newCol = { - ...allCol[name], - ...collection, - }; - // allow users to update collection name - delete allCol[name]; - allCol[collection.name || name] = newCol; - $.write(allCol, COLLECTIONS_KEY); - res.json({ - status: "success", - data: newCol, - }); - } else { - res.status(500).json({ - status: "failed", - message: `订阅集${name}不存在,无法更新!`, - }); - } + const {name} = req.params; + let collection = req.body; + const allCol = $.read(COLLECTIONS_KEY); + if (allCol[name]) { + const newCol = { + ...allCol[name], + ...collection, + }; + // allow users to update collection name + delete allCol[name]; + allCol[collection.name || name] = newCol; + $.write(allCol, COLLECTIONS_KEY); + res.json({ + status: "success", + data: newCol, + }); + } else { + res.status(500).json({ + status: "failed", + message: `订阅集${name}不存在,无法更新!`, + }); + } } async function deleteCollection(req, res) { - const { name } = req.params; - let allCol = $.read(COLLECTIONS_KEY); - delete allCol[name]; - $.write(allCol, COLLECTIONS_KEY); - res.json({ - status: "success", - }); + const {name} = req.params; + let allCol = $.read(COLLECTIONS_KEY); + delete allCol[name]; + $.write(allCol, COLLECTIONS_KEY); + res.json({ + status: "success", + }); } async function getAllCollections(req, res) { - const allCols = $.read(COLLECTIONS_KEY); - res.json({ - status: "success", - data: allCols, - }); + const allCols = $.read(COLLECTIONS_KEY); + res.json({ + status: "success", + data: allCols, + }); } async function deleteAllCollections(req, res) { - $.write({}, COLLECTIONS_KEY); - res.json({ - status: "success", - }); + $.write({}, COLLECTIONS_KEY); + res.json({ + status: "success", + }); } /**************************** Proxy Handlers ***************************************/ function ProxyParser(targetPlatform) { - // parser collections - const parsers = []; - const producers = []; + // parser collections + const parsers = []; + const producers = []; - function addParsers(args) { - args.forEach((a) => parsers.push(a())); - $.info(`${args.length} parser added.`); - } + function addParsers(args) { + args.forEach((a) => parsers.push(a())); + $.info(`${args.length} parser added.`); + } - function addProducers(args) { - args.forEach((a) => producers.push(a())); - } + function addProducers(args) { + args.forEach((a) => producers.push(a())); + } - function parse(raw) { - raw = preprocessing(raw); - const lines = raw.split("\n"); - const result = []; - // convert to json format - for (let line of lines) { - line = line.trim(); - if (line.length === 0) continue; // skip empty line - if (line.startsWith("#")) continue; // skip comments - let matched = false; - for (const p of parsers) { - const { patternTest, func } = p; + function parse(raw) { + raw = preprocessing(raw); + const lines = raw.split("\n"); + const result = []; + // convert to json format + for (let line of lines) { + line = line.trim(); + if (line.length === 0) continue; // skip empty line + if (line.startsWith("#")) continue; // skip comments + let matched = false; + for (const p of parsers) { + const {patternTest, func} = p; - // some lines with weird format may produce errors! - let patternMatched; - try { - patternMatched = patternTest(line); - } catch (err) { - patternMatched = false; - } + // some lines with weird format may produce errors! + let patternMatched; + try { + patternMatched = patternTest(line); + } catch (err) { + patternMatched = false; + } - if (patternMatched) { - matched = true; - // run parser safely. - try { - const proxy = func(line); - if (!proxy) { - // failed to parse this line - console.log(`ERROR: parser return nothing for \n${line}\n`); - break; + if (patternMatched) { + matched = true; + // run parser safely. + try { + const proxy = func(line); + if (!proxy) { + // failed to parse this line + console.log(`ERROR: parser return nothing for \n${line}\n`); + break; + } + // skip unsupported proxies + // if proxy.supported is undefined, assume that all platforms are supported. + if (proxy.supported && proxy.supported[targetPlatform] === false) + continue; + result.push(proxy); + break; + } catch (err) { + console.log( + `ERROR: Failed to parse line: \n ${line}\n Reason: ${err}` + ); + } + } } - // skip unsupported proxies - // if proxy.supported is undefined, assume that all platforms are supported. - if (proxy.supported && proxy.supported[targetPlatform] === false) - continue; - result.push(proxy); - break; - } catch (err) { - console.log( - `ERROR: Failed to parse line: \n ${line}\n Reason: ${err}` - ); - } - } - } - if (!matched) { - console.log(`ERROR: Failed to find a rule to parse line: \n${line}\n`); - } - } - return result; - } - - function produce(proxies) { - for (const p of producers) { - if (p.targetPlatform === targetPlatform) { - return proxies - .map((proxy) => { - try { - return p.output(proxy); - } catch (err) { - console.log( - `ERROR: cannot produce proxy: ${JSON.stringify( - proxy - )}\nReason: ${err}` - ); - return ""; + if (!matched) { + console.log(`ERROR: Failed to find a rule to parse line: \n${line}\n`); } - }) - .filter((v) => v.length > 0) // discard empty lines - .join("\n"); - } + } + return result; } - throw new Error( - `Cannot find any producer for target platform: ${targetPlatform}` - ); - } - // preprocess raw input - function preprocessing(raw) { - let output; - if (raw.indexOf("DOCTYPE html") !== -1) { - // HTML format, maybe a wrong URL! - throw new Error("Invalid format HTML!"); - } else if (raw.indexOf("proxies") !== -1) { - console.log(`Preprocessing Clash config...`); - // 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(/\"(name|type|server|port|cipher|password|)\"/g, "$1"); - const proxies = YAML.eval(raw).proxies; - output = proxies.map((p) => JSON.stringify(p)); - } else if (raw.indexOf("ssd://") === 0) { - // preprocessing for SSD subscription format - output = []; - const Base64 = new Base64Code(); - 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; - } - } else { - // check if content is based64 encoded - const Base64 = new Base64Code(); - const keys = ["dm1lc3M", "c3NyOi8v", "dHJvamFu", "c3M6Ly", "c3NkOi8v"]; - if (keys.some((k) => raw.indexOf(k) !== -1)) { - output = Base64.safeDecode(raw); - } else { - output = raw; - } - output = output.split("\n"); - for (let i = 0; i < output.length; i++) { - output[i] = output[i].trim(); // trim lines - } + function produce(proxies) { + for (const p of producers) { + if (p.targetPlatform === targetPlatform) { + if (typeof p.type === 'undefined' || p.type === "SINGLE") { + return proxies + .map((proxy) => { + try { + return p.output(proxy); + } catch (err) { + console.log( + `ERROR: cannot produce proxy: ${JSON.stringify( + proxy + )}\nReason: ${err}` + ); + return ""; + } + }) + .filter((v) => v.length > 0) // discard empty lines + .join("\n"); + } else if (p.type === 'ALL') { + return p.output(proxies); + } + + } + } + throw new Error( + `Cannot find any producer for target platform: ${targetPlatform}` + ); } - return output.join("\n"); - } - return { - parse, - produce, - addParsers, - addProducers, - }; + // preprocess raw input + function preprocessing(raw) { + let output; + if (raw.indexOf("DOCTYPE html") !== -1) { + // HTML format, maybe a wrong URL! + throw new Error("Invalid format HTML!"); + } else if (raw.indexOf("proxies") !== -1) { + console.log(`Preprocessing Clash config...`); + // 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(/\"(name|type|server|port|cipher|password|)\"/g, "$1"); + const proxies = YAML.eval(raw).proxies; + output = proxies.map((p) => JSON.stringify(p)); + } else if (raw.indexOf("ssd://") === 0) { + // preprocessing for SSD subscription format + output = []; + const Base64 = new Base64Code(); + 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; + } + } else { + // check if content is based64 encoded + const Base64 = new Base64Code(); + const keys = ["dm1lc3M", "c3NyOi8v", "dHJvamFu", "c3M6Ly", "c3NkOi8v"]; + if (keys.some((k) => raw.indexOf(k) !== -1)) { + output = Base64.safeDecode(raw); + } else { + output = raw; + } + output = output.split("\n"); + for (let i = 0; i < output.length; i++) { + output[i] = output[i].trim(); // trim lines + } + } + return output.join("\n"); + } + + return { + parse, + produce, + addParsers, + addProducers, + }; } function processFilter(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]); + // 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]); } function processOperator(operator, proxies) { - let output = objClone(proxies); - try { - const output_ = operator.func(output); - if (output_) output = output_; - } catch (err) { - // print log and skip this operator - console.log(`ERROR: cannot apply operator ${op.name}! Reason: ${err}`); - } + let output = objClone(proxies); + try { + const output_ = operator.func(output); + if (output_) output = output_; + } catch (err) { + // print log and skip this operator + console.log(`ERROR: cannot apply operator ${op.name}! Reason: ${err}`); + } - return output; + return output; } /**************************** URI Format ***************************************/ // 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 patternTest = (line) => { - return /^ss:\/\//.test(line); - }; - const Base64 = new Base64Code(); - const supported = { - QX: true, - Loon: true, - Surge: true, - Raw: true, - }; - const func = (line) => { - // parse url - let content = line.split("ss://")[1]; - - const proxy = { - name: decodeURIComponent(line.split("#")[1]), - type: "ss", - supported, + const patternTest = (line) => { + return /^ss:\/\//.test(line); }; - 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 Base64 = new Base64Code(); + const supported = { + QX: true, + Loon: true, + Surge: true, + Raw: true, + }; + const func = (line) => { + // parse url + let content = line.split("ss://")[1]; - const userInfo = Base64.safeDecode(content.split("@")[0]).split(":"); - proxy.cipher = userInfo[0]; - proxy.password = userInfo[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); - // 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, - }; - break; - default: - throw new Error(`Unsupported plugin option: ${params.plugin}`); - } - } - return proxy; - }; - return { patternTest, func }; + 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, + }; + break; + default: + throw new Error(`Unsupported plugin option: ${params.plugin}`); + } + } + return proxy; + }; + return {patternTest, func}; } // Parse URI SSR format, such as ssr://xxx function URI_SSR() { - const patternTest = (line) => { - return /^ssr:\/\//.test(line); - }; - const Base64 = new Base64Code(); - const supported = { - QX: true, - Loon: true, - Surge: false, - Raw: true, - }; - - const func = (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, + const patternTest = (line) => { + return /^ssr:\/\//.test(line); }; - // 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, "") || "", + const Base64 = new Base64Code(); + const supported = { + QX: true, + Loon: true, + Surge: false, + Raw: true, }; - return proxy; - }; - return { patternTest, func }; + const func = (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 {patternTest, func}; } // V2rayN URI VMess format @@ -954,1103 +957,1131 @@ function URI_SSR() { // Quantumult VMess format function URI_VMess() { - const patternTest = (line) => { - return /^vmess:\/\//.test(line); - }; - const Base64 = new Base64Code(); - const supported = { - QX: true, - Loon: true, - Surge: true, - Raw: true, - }; - const func = (line) => { - 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 patternTest = (line) => { + return /^vmess:\/\//.test(line); + }; + const Base64 = new Base64Code(); + const supported = { + QX: true, + Loon: true, + Surge: true, + Raw: true, + }; + const func = (line) => { + 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.scert = 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.scert = true; + } + return proxy; } - } - - 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.scert = 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.scert = true; - } - return proxy; - } - }; - return { patternTest, func }; + }; + return {patternTest, func}; } // Trojan URI format function URI_Trojan() { - const patternTest = (line) => { - return /^trojan:\/\//.test(line); - }; - const supported = { - QX: true, - Loon: true, - Surge: true, - Raw: true, - }; - const func = (line) => { - // 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, + const patternTest = (line) => { + return /^trojan:\/\//.test(line); }; - }; - return { patternTest, func }; + const supported = { + QX: true, + Loon: true, + Surge: true, + Raw: true, + }; + const func = (line) => { + // 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 {patternTest, func}; } /**************************** Clash ***************************************/ function Clash_All() { - const patternTest = (line) => { - return line.indexOf("{") !== -1; - }; - const func = (line) => JSON.parse(line); - return { patternTest, func }; + const patternTest = (line) => { + return line.indexOf("{") !== -1; + }; + const func = (line) => JSON.parse(line); + return {patternTest, func}; } /**************************** Quantumult X ***************************************/ function QX_SS() { - const supported = { - QX: true, - Loon: true, - Surge: true, - Raw: true, - }; - const patternTest = (line) => { - return ( - /^shadowsocks\s*=/.test(line.split(",")[0].trim()) && - line.indexOf("ssr-protocol") === -1 - ); - }; - const func = (line) => { - 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, + const supported = { + QX: true, + Loon: true, + Surge: true, + Raw: true, }; - // 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", - }; - 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 { patternTest, func }; + const patternTest = (line) => { + return ( + /^shadowsocks\s*=/.test(line.split(",")[0].trim()) && + line.indexOf("ssr-protocol") === -1 + ); + }; + const func = (line) => { + 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", + }; + 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 {patternTest, func}; } function QX_SSR() { - const patternTest = (line) => { - return ( - /^shadowsocks\s*=/.test(line.split(",")[0].trim()) && - line.indexOf("ssr-protocol") !== -1 - ); - }; - const supported = { - QX: true, - Loon: true, - Surge: false, - Raw: true, - }; - const func = (line) => { - 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, + const patternTest = (line) => { + return ( + /^shadowsocks\s*=/.test(line.split(",")[0].trim()) && + line.indexOf("ssr-protocol") !== -1 + ); }; - // handle obfs options - if (params.obfs) { - proxy.obfs = params.obfs; - proxy["obfs-param"] = params["obfs-host"]; - } - return proxy; - }; - return { patternTest, func }; + const supported = { + QX: true, + Loon: true, + Surge: false, + Raw: true, + }; + const func = (line) => { + 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 {patternTest, func}; } function QX_VMess() { - const patternTest = (line) => { - return /^vmess\s*=/.test(line.split(",")[0].trim()); - }; - const func = (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"), + const patternTest = (line) => { + return /^vmess\s*=/.test(line.split(",")[0].trim()); + }; + const func = (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.scert = !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; }; - if (proxy.tls) { - proxy.sni = params["obfs-host"] || params.server; - proxy.scert = !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 { patternTest, func }; + return {patternTest, func}; } function QX_Trojan() { - const patternTest = (line) => { - return /^trojan\s*=/.test(line.split(",")[0].trim()); - }; - const func = (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"), + const patternTest = (line) => { + return /^trojan\s*=/.test(line.split(",")[0].trim()); }; - proxy.scert = !JSON.parse(params["tls-verification"] || "true"); - return proxy; - }; - return { patternTest, func }; + const func = (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.scert = !JSON.parse(params["tls-verification"] || "true"); + return proxy; + }; + return {patternTest, func}; } function QX_Http() { - const patternTest = (line) => { - return /^http\s*=/.test(line.split(",")[0].trim()); - }; - const func = (line) => { - const params = getQXParams(line); - const proxy = { - type: "http", - name: params.tag, - server: params.server, - port: params.port, - username: params.username, - password: params.password, - tls: JSON.parse(params["over-tls"] || "false"), - udp: JSON.parse(params["udp-relay"] || "false"), - tfo: JSON.parse(params["fast-open"] || "false"), + const patternTest = (line) => { + return /^http\s*=/.test(line.split(",")[0].trim()); + }; + const func = (line) => { + const params = getQXParams(line); + const proxy = { + type: "http", + name: params.tag, + server: params.server, + port: params.port, + username: params.username, + password: params.password, + tls: JSON.parse(params["over-tls"] || "false"), + udp: JSON.parse(params["udp-relay"] || "false"), + tfo: JSON.parse(params["fast-open"] || "false"), + }; + if (proxy.tls) { + proxy.sni = params["tls-host"] || proxy.server; + proxy.scert = !JSON.parse(params["tls-verification"] || "true"); + } + return proxy; }; - if (proxy.tls) { - proxy.sni = params["tls-host"] || proxy.server; - proxy.scert = !JSON.parse(params["tls-verification"] || "true"); - } - return proxy; - }; - return { patternTest, func }; + return {patternTest, func}; } 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; + 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; } /**************************** Loon ***************************************/ function Loon_SS() { - const patternTest = (line) => { - return ( - line.split(",")[0].split("=")[1].trim().toLowerCase() === "shadowsocks" - ); - }; - const func = (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, ""), + const patternTest = (line) => { + return ( + line.split(",")[0].split("=")[1].trim().toLowerCase() === "shadowsocks" + ); }; - // handle obfs - if (params.length > 5) { - proxy.plugin = "obfs"; - proxy["plugin-opts"] = { - mode: proxy.obfs, - host: params[6], - }; - } - return proxy; - }; - return { patternTest, func }; + const func = (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: proxy.obfs, + host: params[6], + }; + } + return proxy; + }; + return {patternTest, func}; } function Loon_SSR() { - const patternTest = (line) => { - return ( - line.split(",")[0].split("=")[1].trim().toLowerCase() === "shadowsocksr" - ); - }; - const func = (line) => { - const params = line.split("=")[1].split(","); - const supported = { - QX: true, - Loon: true, - Surge: false, - Raw: true, + const patternTest = (line) => { + return ( + line.split(",")[0].split("=")[1].trim().toLowerCase() === "shadowsocksr" + ); }; - 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], + const func = (line) => { + const params = line.split("=")[1].split(","); + const supported = { + QX: true, + Loon: true, + Surge: false, + Raw: true, + }; + 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 { patternTest, func }; + return {patternTest, func}; } function Loon_VMess() { - const patternTest = (line) => { - // distinguish between surge vmess - return ( - /^.*=\s*vmess/i.test(line.split(",")[0]) && - line.indexOf("username") === -1 - ); - }; - const func = (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, + const patternTest = (line) => { + // distinguish between surge vmess + return ( + /^.*=\s*vmess/i.test(line.split(",")[0]) && + line.indexOf("username") === -1 + ); }; - // 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.scert = 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, + const func = (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, }; - } - if (proxy.tls) { - proxy.scert = JSON.parse(params["skip-cert-verify"] || "false"); - } - return proxy; - }; - return { patternTest, func }; + // 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.scert = 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.scert = JSON.parse(params["skip-cert-verify"] || "false"); + } + return proxy; + }; + return {patternTest, func}; } function Loon_Trojan() { - const patternTest = (line) => { - return ( - /^.*=\s*trojan/i.test(line.split(",")[0]) && - line.indexOf("password") === -1 - ); - }; - - const func = (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 - scert: JSON.parse(params["skip-cert-verify"] || "false"), + const patternTest = (line) => { + return ( + /^.*=\s*trojan/i.test(line.split(",")[0]) && + line.indexOf("password") === -1 + ); }; - // trojan sni - if (params.length > 4) { - const [key, val] = params[4].split(":"); - if (key === "tls-name") proxy.sni = val; - else throw new Error(`ERROR: unknown option ${key} for line: \n${line}`); - } - return proxy; - }; - return { patternTest, func }; + const func = (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 + scert: 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(`ERROR: unknown option ${key} for line: \n${line}`); + } + return proxy; + }; + + return {patternTest, func}; } function Loon_Http() { - const patternTest = (line) => { - return ( - /^.*=\s*http/i.test(line.split(",")[0]) && - line.split(",").length === 5 && - line.indexOf("username") === -1 && - line.indexOf("password") === -1 - ); - }; - - const func = (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 - username: (params[3] || "").replace(/"/g, ""), - password: (params[4] || "").replace(/"/g, ""), + const patternTest = (line) => { + return ( + /^.*=\s*http/i.test(line.split(",")[0]) && + line.split(",").length === 5 && + line.indexOf("username") === -1 && + line.indexOf("password") === -1 + ); }; - if (proxy.tls) { - proxy.sni = params["tls-name"] || proxy.server; - proxy.scert = JSON.parse(params["skip-cert-verify"] || "false"); - } - return proxy; - }; - return { patternTest, func }; + const func = (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 + username: (params[3] || "").replace(/"/g, ""), + password: (params[4] || "").replace(/"/g, ""), + }; + if (proxy.tls) { + proxy.sni = params["tls-name"] || proxy.server; + proxy.scert = JSON.parse(params["skip-cert-verify"] || "false"); + } + + return proxy; + }; + return {patternTest, func}; } /**************************** Surge ***************************************/ function Surge_SS() { - const patternTest = (line) => { - return /^.*=\s*ss/.test(line.split(",")[0]); - }; - const func = (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"), + const patternTest = (line) => { + return /^.*=\s*ss/.test(line.split(",")[0]); }; - // handle obfs - if (params.obfs) { - proxy.plugin = "obfs"; - proxy["plugin-opts"] = { - mode: params.obfs, - host: params["obfs-host"], - }; - } - return proxy; - }; - return { patternTest, func }; + const func = (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 {patternTest, func}; } function Surge_VMess() { - const patternTest = (line) => { - return ( - /^.*=\s*vmess/.test(line.split(",")[0]) && line.indexOf("username") !== -1 - ); - }; - const func = (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"), + const patternTest = (line) => { + return ( + /^.*=\s*vmess/.test(line.split(",")[0]) && line.indexOf("username") !== -1 + ); }; - if (proxy.tls) { - proxy.scert = JSON.parse(params["skip-cert-verify"] || "false"); - 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 { patternTest, func }; + const func = (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) { + proxy.scert = JSON.parse(params["skip-cert-verify"] || "false"); + 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 {patternTest, func}; } function Surge_Trojan() { - const patternTest = (line) => { - return ( - /^.*=\s*trojan/.test(line.split(",")[0]) && line.indexOf("sni") !== -1 - ); - }; - const func = (line) => { - const params = getSurgeParams(line); - return { - 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"), - scert: JSON.parse(params["skip-cert-verify"] || "false"), + const patternTest = (line) => { + return ( + /^.*=\s*trojan/.test(line.split(",")[0]) && line.indexOf("sni") !== -1 + ); + }; + const func = (line) => { + const params = getSurgeParams(line); + return { + 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"), + scert: JSON.parse(params["skip-cert-verify"] || "false"), + }; }; - }; - return { patternTest, func }; + return {patternTest, func}; } function Surge_Http() { - const patternTest = (line) => { - return ( - /^.*=\s*http/.test(line.split(",")[0]) && !Loon_Http().patternTest(line) - ); - }; - const func = (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"), + const patternTest = (line) => { + return ( + /^.*=\s*http/.test(line.split(",")[0]) && !Loon_Http().patternTest(line) + ); }; - if (proxy.tls) { - proxy.scert = JSON.parse(params["skip-cert-verify"] || "false"); - proxy.sni = params.sni || params.server; - } - if (params.username !== "none") proxy.username = params.username; - if (params.password !== "none") proxy.password = params.password; - return proxy; - }; - return { patternTest, func }; + const func = (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) { + proxy.scert = JSON.parse(params["skip-cert-verify"] || "false"); + proxy.sni = params.sni || params.server; + } + if (params.username !== "none") proxy.username = params.username; + if (params.password !== "none") proxy.password = params.password; + return proxy; + }; + return {patternTest, func}; } 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(); + 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 params; } /**************************** Output Functions ***************************************/ function QX_Producer() { - const targetPlatform = "QX"; - const output = (proxy) => { - let obfs_opts; - let tls_opts; - switch (proxy.type) { - case "ss": - obfs_opts = ""; - if (proxy.plugin === "obfs") { - obfs_opts = `,obfs=${proxy["plugin-opts"].mode},obfs-host=${proxy["plugin-opts"].host}`; + const targetPlatform = "QX"; + const output = (proxy) => { + let obfs_opts; + let tls_opts; + switch (proxy.type) { + case "ss": + obfs_opts = ""; + if (proxy.plugin === "obfs") { + obfs_opts = `,obfs=${proxy["plugin-opts"].mode},obfs-host=${proxy["plugin-opts"].host}`; + } + if (proxy.plugin === "v2ray-plugin") { + const {tls, host, path} = proxy["plugin-opts"]; + obfs_opts = `,obfs=${tls ? "wss" : "ws"},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,obfs-host=${proxy.sni}${ + proxy["ws-path"] ? ",obfs-uri=" + proxy["ws-path"] : "" + },tls-verification=${proxy.scert ? "false" : "true"}`; + } else { + // ws + obfs_opts = `,obfs=ws,obfs-host=${proxy["ws-headers"].Host}${ + proxy["ws-path"] ? ",obfs-uri=" + proxy["ws-path"] : "" + }`; + } + } else { + // tcp + if (proxy.tls) { + obfs_opts = `,obfs=over-tls,obfs-host=${ + proxy.sni + },tls-verification=${proxy.scert ? "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 : ""},tls-verification=${ + proxy.scert ? "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.scert ? "false" : "true" + },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}`; } - if (proxy.plugin === "v2ray-plugin") { - const { tls, host, path } = proxy["plugin-opts"]; - obfs_opts = `,obfs=${tls ? "wss" : "ws"},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,obfs-host=${proxy.sni}${ - proxy["ws-path"] ? ",obfs-uri=" + proxy["ws-path"] : "" - },tls-verification=${proxy.scert ? "false" : "true"}`; - } else { - // ws - obfs_opts = `,obfs=ws,obfs-host=${proxy["ws-headers"].Host}${ - proxy["ws-path"] ? ",obfs-uri=" + proxy["ws-path"] : "" - }`; - } - } else { - // tcp - if (proxy.tls) { - obfs_opts = `,obfs=over-tls,obfs-host=${ - proxy.sni - },tls-verification=${proxy.scert ? "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 : ""},tls-verification=${ - proxy.scert ? "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.scert ? "false" : "true" - },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 { targetPlatform, output }; + throw new Error( + `Platform ${targetPlatform} does not support proxy type: ${proxy.type}` + ); + }; + return {targetPlatform, output}; } function Loon_Producer() { - const targetPlatform = "Loon"; - const output = (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}` - ); - } - } + const targetPlatform = "Loon"; + const output = (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; - obfs_opts = `,transport:ws,host:${host},path:${ - proxy["ws-path"] || "/" - }`; - } else { - obfs_opts = `,transport:tcp`; + 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; + obfs_opts = `,transport:ws,host:${host},path:${ + proxy["ws-path"] || "/" + }`; + } else { + obfs_opts = `,transport:tcp`; + } + if (proxy.tls) { + obfs_opts += `,tls-name=${proxy.sni},skip-cert-verify:${proxy.scert}`; + } + return `${proxy.name}=vmess,${proxy.server},${proxy.port},${ + proxy.cipher === "auto" ? "none" : proxy.cipher + },"${proxy.uuid}",over-tls:${proxy.tls}${obfs_opts}`; + case "trojan": + return `${proxy.name}=trojan,${proxy.server},${proxy.port},"${ + proxy.password + }"${proxy.sni ? ",tls-name:" + proxy.sni : ""},skip-cert-verify:${ + proxy.scert || "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 = `,skip-cert-verify:${proxy.scert},tls-name:${proxy.sni}`; + return base + tls_opts; + } else return base; } - if (proxy.tls) { - obfs_opts += `,tls-name=${proxy.sni},skip-cert-verify:${proxy.scert}`; - } - return `${proxy.name}=vmess,${proxy.server},${proxy.port},${ - proxy.cipher === "auto" ? "none" : proxy.cipher - },"${proxy.uuid}",over-tls:${proxy.tls}${obfs_opts}`; - case "trojan": - return `${proxy.name}=trojan,${proxy.server},${proxy.port},"${ - proxy.password - }"${proxy.sni ? ",tls-name:" + proxy.sni : ""},skip-cert-verify:${ - proxy.scert || "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 = `,skip-cert-verify:${proxy.scert},tls-name:${proxy.sni}`; - return base + tls_opts; - } else return base; - } - throw new Error( - `Platform ${targetPlatform} does not support proxy type: ${proxy.type}` - ); - }; - return { targetPlatform, output }; + throw new Error( + `Platform ${targetPlatform} does not support proxy type: ${proxy.type}` + ); + }; + return {targetPlatform, output}; } function Surge_Producer() { - const targetPlatform = "Surge"; - const output = (proxy) => { - let obfs_opts, tls_opts; - switch (proxy.type) { - case "ss": - obfs_opts = ""; - if (proxy.plugin) { - if (proxy.plugin === "obfs") { - obfs_opts = `,obfs=${proxy["plugin-opts"].mode},obfs-host=${proxy["plugin-opts"].host}`; - } else { - throw new Error( - `Platform ${targetPlatform} does not support obfs option: ${proxy.obfs}` - ); - } + const targetPlatform = "Surge"; + const output = (proxy) => { + let obfs_opts, tls_opts; + switch (proxy.type) { + case "ss": + obfs_opts = ""; + if (proxy.plugin) { + if (proxy.plugin === "obfs") { + obfs_opts = `,obfs=${proxy["plugin-opts"].mode},obfs-host=${proxy["plugin-opts"].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},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.scert !== "undefined" + ? ",skip-cert-verify=" + proxy.scert + : "" + }`; + config += proxy.sni ? `,sni=${proxy.sni}` : ""; + } + return config; + case "trojan": + return `${proxy.name}=trojan,${proxy.server},${proxy.port},password=${ + proxy.password + }${ + typeof proxy.scert !== "undefined" + ? ",skip-cert-verify=" + proxy.scert + : "" + }${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.scert},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"}`; } - 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},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.scert !== "undefined" - ? ",skip-cert-verify=" + proxy.scert - : "" - }`; - config += proxy.sni ? `,sni=${proxy.sni}` : ""; - } - return config; - case "trojan": - return `${proxy.name}=trojan,${proxy.server},${proxy.port},password=${ - proxy.password - }${ - typeof proxy.scert !== "undefined" - ? ",skip-cert-verify=" + proxy.scert - : "" - }${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.scert},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 { targetPlatform, output }; + throw new Error( + `Platform ${targetPlatform} does not support proxy type: ${proxy.type}` + ); + }; + return {targetPlatform, output}; } function Raw_Producer() { - const targetPlatform = "Raw"; - const output = (proxy) => { - return JSON.stringify(proxy); - }; - return { targetPlatform, output }; + const targetPlatform = "Raw"; + const output = (proxy) => { + return JSON.stringify(proxy); + }; + return {targetPlatform, output}; } function URI_Producer() { - const targetPlatform = "URI"; - const Base64 = new Base64Code(); - const output = (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; + const targetPlatform = "URI"; + const Base64 = new Base64Code(); + const output = (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 = `ssr://${proxy.server}:${proxy.port}:${proxy.protocol}:${ + proxy.cipher + }:${proxy.obfs}:${Base64.safeEncode(proxy.password)}/`; + result += `?remarks=${proxy.name}${ + proxy["obfs-param"] + ? "&obfsparam=" + Base64.safeEncode(proxy["obfs-param"]) + : "" + }${ + proxy["protocol-param"] + ? "&protocolparam=" + Base64.safeEncode(proxy["protocol-param"]) + : "" + }`; + 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}#${proxy.name}`; + break; default: - throw new Error(`Unsupported plugin option: ${proxy.plugin}`); - } + throw new Error(`Cannot handle proxy type: ${proxy.type}`); } - result += `#${encodeURIComponent(proxy.name)}`; - break; - case "ssr": - result = `${proxy.server}:${proxy.port}:${proxy.protocol}:${ - proxy.cipher - }:${proxy.obfs}:${Base64.safeEncode(proxy.password)}/`; - result += `?remarks=${proxy.name}${ - proxy["obfs-param"] - ? "&obfsparam=" + Base64.safeEncode(proxy["obfs-param"]) - : "" - }${ - proxy["protocol-param"] - ? "&protocolparam=" + Base64.safeEncode(proxy["protocol-param"]) - : "" - }`; - result = "vmess://" + 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 = Base64.safeEncode(JSON.stringify(result)); - break; - case "trojan": - result = `trojan://${proxy.password}@${proxy.server}:${proxy.port}#${proxy.name}`; - break; - default: - throw new Error(`Cannot handle proxy type: ${proxy.type}`); - } - return result; - }; - return { targetPlatform, output }; + return result; + }; + return {targetPlatform, output}; +} + +function Clash_Producer() { + const targetPlatform = "Clash"; + const type = "ALL"; + const output = (proxies) => { + return "proxies:\n" + proxies.map(proxy => { + delete proxy.supported; + return " - " + JSON.stringify(proxy) + "\n"; + }).join(""); + }; + return {targetPlatform, type, output}; +} + +function Sub_Producer() { + const targetPlatform = "Sub"; + const type = "ALL"; + const output = (proxies) => { + const urls = proxies.map(proxy => { + try { + return URI_Producer().output(proxy) + "\n"; + } catch (e) { + return ""; + } + }).join(""); + const Base64 = new Base64Code(); + return Base64.encode(urls); + }; + return {targetPlatform, type, output}; } /**************************** Operators ***************************************/ // force to set some properties (e.g., scert, udp, tfo, etc.) -function SetPropertyOperator({ key, value }) { - return { - name: "Set Property Operator", - func: (proxies) => { - return proxies.map((p) => { - p[key] = value; - return p; - }); - }, - }; +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; - }); - }, - }; + 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); - } - }, - }; + 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 - }), - }; + 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; + 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; + 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; - }); - }, - }; + 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; - }); - }, - }; + 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) => { + const keywords_ = keywords.map((k) => { + return { + old: k, + now: "", + }; + }); return { - old: k, - now: "", + name: "Keyword Delete Operator", + func: KeywordRenameOperator(keywords_).func, }; - }); - return { - name: "Keyword Delete Operator", - func: KeywordRenameOperator(keywords_).func, - }; } // delete regex operator // regex: ['a', 'b', 'c'] function RegexDeleteOperator(regex) { - const regex_ = regex.map((r) => { + const regex_ = regex.map((r) => { + return { + expr: r, + now: "", + }; + }); return { - expr: r, - now: "", + name: "Regex Delete Operator", + func: RegexRenameOperator(regex_).func, }; - }); - return { - name: "Regex Delete Operator", - func: RegexRenameOperator(regex_).func, - }; } // use base64 encoded script to rename @@ -2065,111 +2096,111 @@ function RegexDeleteOperator(regex) { 2. Always declare variable before using it! */ 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] || AVAILABLE_FILTERS[name]; - return item(args); - }; - const $process = (item, proxies) => { - if (item.name.indexOf("Filter") !== -1) { - return processOperator(item, proxies); - } else if (item.name.indexOf("Operator") !== -1) { - return processFilter(item, proxies); - } - }; - eval(script); - output = operator(proxies); - })(); - return output; - }, - }; + return { + name: "Script Operator", + func: (proxies) => { + let output = proxies; + (function () { + // interface to get internal operators + const $get = (name, args) => { + const item = AVAILABLE_OPERATORS[name] || AVAILABLE_FILTERS[name]; + return item(args); + }; + const $process = (item, proxies) => { + if (item.name.indexOf("Filter") !== -1) { + return processOperator(item, proxies); + } else if (item.name.indexOf("Operator") !== -1) { + return processFilter(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; - }); - }, - }; +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, - }; + 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); - }); - }, - }; + 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; - }); - }, - }; +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)); - }, - }; + return { + name: "Type Filter", + func: (proxies) => { + return proxies.map((proxy) => types.some((t) => proxy.type === t)); + }, + }; } // use base64 encoded script to filter proxies @@ -2184,282 +2215,282 @@ function TypeFilter(types) { 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; - }, - }; + 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 = { - "🏳️‍🌈": ["流量", "时间", "应急", "过期", "Bandwidth", "expire"], - "🇦🇨": ["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", - ], - }; - for (let k of Object.keys(flags)) { - if (flags[k].some((item) => name.indexOf(item) !== -1)) { - return k; + // flags from @KOP-XIAO: https://github.com/KOP-XIAO/QuantumultX/blob/master/Scripts/resource-parser.js + const flags = { + "🏳️‍🌈": ["流量", "时间", "应急", "过期", "Bandwidth", "expire"], + "🇦🇨": ["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", + ], + }; + 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 || "🏴‍☠️"; + // 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(); + return str + .replace(/[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]/g, "") + .trim(); } // objClone an object function objClone(obj) { - return JSON.parse(JSON.stringify(obj)); + return JSON.parse(JSON.stringify(obj)); } // shuffle array function shuffle(array) { - let currentIndex = array.length, - temporaryValue, - randomIndex; + 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; + // 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; - } + // And swap it with the current element. + temporaryValue = array[currentIndex]; + array[currentIndex] = array[randomIndex]; + array[randomIndex] = temporaryValue; + } - return array; + return array; } // some logical functions for proxy filters function AND(...args) { - return args.reduce((a, b) => a.map((c, i) => b[i] && c)); + 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)); + return args.reduce((a, b) => a.map((c, i) => b[i] || c)); } function NOT(array) { - return array.map((c) => !c); + return array.map((c) => !c); } function FULL(length, bool) { - return [...Array(length).keys()].map(() => bool); + return [...Array(length).keys()].map(() => bool); } // UUID // source: https://stackoverflow.com/questions/105034/how-to-create-guid-uuid function UUID() { - return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { - const r = (Math.random() * 16) | 0, - v = c === "x" ? r : (r & 0x3) | 0x8; - return v.toString(16); - }); + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { + const r = (Math.random() * 16) | 0, + v = c === "x" ? r : (r & 0x3) | 0x8; + return v.toString(16); + }); } // get platform form UA function getPlatformFromHeaders(headers) { - const keys = Object.keys(headers); - let UA = ""; - for (let k of keys) { - if (/USER-AGENT/i.test(k)) { - UA = headers[k]; - break; + const keys = Object.keys(headers); + let UA = ""; + for (let k of keys) { + if (/USER-AGENT/i.test(k)) { + UA = headers[k]; + break; + } + } + if (UA.indexOf("Quantumult%20X") !== -1) { + return "QX"; + } else if (UA.indexOf("Surge") !== -1) { + return "Surge"; + } else if (UA.indexOf("Decar") !== -1 || UA.indexOf("Loon") !== -1) { + return "Loon"; + } else { + // browser + return "Raw"; } - } - if (UA.indexOf("Quantumult%20X") !== -1) { - return "QX"; - } else if (UA.indexOf("Surge") !== -1) { - return "Surge"; - } else if (UA.indexOf("Decar") !== -1 || UA.indexOf("Loon") !== -1) { - return "Loon"; - } else { - // browser - return "Raw"; - } } /*********************************** OpenAPI *************************************/ @@ -2476,783 +2507,786 @@ function ENV() { } function HTTP(defaultOptions = {}) { - const { isQX, isLoon, isSurge } = ENV(); - const methods = ["GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "PATCH"]; + const {isQX, isLoon, isSurge} = ENV(); + const methods = ["GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "PATCH"]; - function send(method, options) { - options = options.hasOwnProperty("url") ? options : { url: options }; - options.url = defaultOptions.baseURL - ? defaultOptions.baseURL + options.url - : options.url; - options = { ...defaultOptions, ...options }; - const timeout = options.timeout; - const events = { - ...{ - onRequest: () => {}, - onResponse: (resp) => resp, - onTimeout: () => {}, - }, - ...options.events, - }; + function send(method, options) { + options = options.hasOwnProperty("url") ? options : {url: options}; + options.url = defaultOptions.baseURL + ? defaultOptions.baseURL + options.url + : options.url; + options = {...defaultOptions, ...options}; + const timeout = options.timeout; + const events = { + ...{ + onRequest: () => { + }, + onResponse: (resp) => resp, + onTimeout: () => { + }, + }, + ...options.events, + }; - events.onRequest(method, options); + events.onRequest(method, options); - let worker; - if (isQX) { - worker = $task.fetch({ method, ...options }); - } else { - worker = new Promise((resolve, reject) => { - const request = isSurge || isLoon ? $httpClient : require("request"); - request[method.toLowerCase()](options, (err, response, body) => { - if (err) reject(err); - else - resolve({ - statusCode: response.status || response.statusCode, - headers: response.headers, - body, + let worker; + if (isQX) { + worker = $task.fetch({method, ...options}); + } else { + worker = new Promise((resolve, reject) => { + const request = isSurge || isLoon ? $httpClient : require("request"); + request[method.toLowerCase()](options, (err, response, body) => { + if (err) reject(err); + else + resolve({ + statusCode: response.status || response.statusCode, + headers: response.headers, + body, + }); + }); }); - }); - }); + } + + let timeoutid; + const timer = timeout + ? new Promise((_, reject) => { + timeoutid = setTimeout(() => { + events.onTimeout(); + return reject( + `${method} URL: ${options.url} exceeds the timeout ${timeout} ms` + ); + }, timeout); + }) + : null; + + return (timer + ? Promise.race([timer, worker]).then((res) => { + clearTimeout(timeoutid); + return res; + }) + : worker + ).then((resp) => events.onResponse(resp)); } - let timeoutid; - const timer = timeout - ? new Promise((_, reject) => { - timeoutid = setTimeout(() => { - events.onTimeout(); - return reject( - `${method} URL: ${options.url} exceeds the timeout ${timeout} ms` - ); - }, timeout); - }) - : null; - - return (timer - ? Promise.race([timer, worker]).then((res) => { - clearTimeout(timeoutid); - return res; - }) - : worker - ).then((resp) => events.onResponse(resp)); - } - - const http = {}; - methods.forEach( - (method) => - (http[method.toLowerCase()] = (options) => send(method, options)) - ); - return http; + const http = {}; + methods.forEach( + (method) => + (http[method.toLowerCase()] = (options) => send(method, options)) + ); + return http; } function API(name = "untitled", debug = false) { - const { isQX, isLoon, isSurge, isNode, isJSBox, isScriptable } = ENV(); - return new (class { - constructor(name, debug) { - this.name = name; - this.debug = debug; + const {isQX, isLoon, isSurge, isNode, isJSBox, isScriptable} = ENV(); + return new (class { + constructor(name, debug) { + this.name = name; + this.debug = debug; - this.http = HTTP(); - this.env = ENV(); + this.http = HTTP(); + this.env = ENV(); - this.node = (() => { - if (isNode) { - const fs = require("fs"); + this.node = (() => { + if (isNode) { + const fs = require("fs"); - return { - fs, - }; - } else { - return null; + return { + fs, + }; + } else { + return null; + } + })(); + this.initCache(); + + const delay = (t, v) => + new Promise(function (resolve) { + setTimeout(resolve.bind(null, v), t); + }); + + Promise.prototype.delay = function (t) { + return this.then(function (v) { + return delay(t, v); + }); + }; } - })(); - this.initCache(); - const delay = (t, v) => - new Promise(function (resolve) { - setTimeout(resolve.bind(null, v), t); - }); + // persistent + // initialize cache + initCache() { + if (isQX) this.cache = JSON.parse($prefs.valueForKey(this.name) || "{}"); + if (isLoon || isSurge) + this.cache = JSON.parse($persistentStore.read(this.name) || "{}"); - Promise.prototype.delay = function (t) { - return this.then(function (v) { - return delay(t, v); - }); - }; - } + if (isNode) { + // create a json for root cache + let fpath = "root.json"; + if (!this.node.fs.existsSync(fpath)) { + this.node.fs.writeFileSync( + fpath, + JSON.stringify({}), + {flag: "wx"}, + (err) => console.log(err) + ); + } + this.root = {}; - // persistent - // initialize cache - initCache() { - if (isQX) this.cache = JSON.parse($prefs.valueForKey(this.name) || "{}"); - if (isLoon || isSurge) - this.cache = JSON.parse($persistentStore.read(this.name) || "{}"); - - if (isNode) { - // create a json for root cache - let fpath = "root.json"; - if (!this.node.fs.existsSync(fpath)) { - this.node.fs.writeFileSync( - fpath, - JSON.stringify({}), - { flag: "wx" }, - (err) => console.log(err) - ); + // create a json file with the given name if not exists + fpath = `${this.name}.json`; + if (!this.node.fs.existsSync(fpath)) { + this.node.fs.writeFileSync( + fpath, + JSON.stringify({}), + {flag: "wx"}, + (err) => console.log(err) + ); + this.cache = {}; + } else { + this.cache = JSON.parse( + this.node.fs.readFileSync(`${this.name}.json`) + ); + } + } } - this.root = {}; - // create a json file with the given name if not exists - fpath = `${this.name}.json`; - if (!this.node.fs.existsSync(fpath)) { - this.node.fs.writeFileSync( - fpath, - JSON.stringify({}), - { flag: "wx" }, - (err) => console.log(err) - ); - this.cache = {}; - } else { - this.cache = JSON.parse( - this.node.fs.readFileSync(`${this.name}.json`) - ); + // store cache + persistCache() { + const data = JSON.stringify(this.cache); + if (isQX) $prefs.setValueForKey(data, this.name); + if (isLoon || isSurge) $persistentStore.write(data, this.name); + if (isNode) { + this.node.fs.writeFileSync( + `${this.name}.json`, + data, + {flag: "w"}, + (err) => console.log(err) + ); + this.node.fs.writeFileSync( + "root.json", + JSON.stringify(this.root), + {flag: "w"}, + (err) => console.log(err) + ); + } } - } - } - // store cache - persistCache() { - const data = JSON.stringify(this.cache); - if (isQX) $prefs.setValueForKey(data, this.name); - if (isLoon || isSurge) $persistentStore.write(data, this.name); - if (isNode) { - this.node.fs.writeFileSync( - `${this.name}.json`, - data, - { flag: "w" }, - (err) => console.log(err) - ); - this.node.fs.writeFileSync( - "root.json", - JSON.stringify(this.root), - { flag: "w" }, - (err) => console.log(err) - ); - } - } + write(data, key) { + this.log(`SET ${key}`); + if (key.indexOf("#") !== -1) { + key = key.substr(1); + if (key === name) { + this.cache = JSON.parse(data); + } + if (isSurge || isLoon) { + $persistentStore.write(data, key); + } + if (isQX) { + $prefs.setValueForKey(data, key); + } + if (isNode) { + this.root[key] = data; + } + } else { + this.cache[key] = data; + } + this.persistCache(); + } - write(data, key) { - this.log(`SET ${key}`); - if (key.indexOf("#") !== -1) { - key = key.substr(1); - if (key === name) { - this.cache = JSON.parse(data); + read(key) { + this.log(`READ ${key}`); + if (key.indexOf("#") !== -1) { + key = key.substr(1); + if (key === name) return this.cache; + if (isSurge || isLoon) { + return $persistentStore.read(key); + } + if (isQX) { + return $prefs.valueForKey(key); + } + if (isNode) { + return this.root[key]; + } + } else { + return this.cache[key]; + } } - if (isSurge || isLoon) { - $persistentStore.write(data, key); - } - if (isQX) { - $prefs.setValueForKey(data, key); - } - if (isNode) { - this.root[key] = data; - } - } else { - this.cache[key] = data; - } - this.persistCache(); - } - read(key) { - this.log(`READ ${key}`); - if (key.indexOf("#") !== -1) { - key = key.substr(1); - if (key === name) return this.cache; - if (isSurge || isLoon) { - return $persistentStore.read(key); + delete(key) { + this.log(`DELETE ${key}`); + if (key.indexOf("#") !== -1) { + key = key.substr(1); + if (isSurge || isLoon) { + $persistentStore.write(null, key); + } + if (isQX) { + $prefs.removeValueForKey(key); + } + if (isNode) { + delete this.root[key]; + } + } else { + delete this.cache[key]; + } + this.persistCache(); } - if (isQX) { - return $prefs.valueForKey(key); + + // notification + notify(title, subtitle = "", content = "", options = {}) { + const openURL = options["open-url"]; + const mediaURL = options["media-url"]; + + const content_ = + content + + (openURL ? `\n点击跳转: ${openURL}` : "") + + (mediaURL ? `\n多媒体: ${mediaURL}` : ""); + + if (isQX) $notify(title, subtitle, content, options); + if (isSurge) $notification.post(title, subtitle, content_); + if (isLoon) { + let opts = {}; + if (openURL) opts["openUrl"] = openURL; + if (mediaURL) opts["mediaUrl"] = mediaURL; + if (JSON.stringify(opts) === "{}") { + $notification.post(title, subtitle, content); + } else { + $notification.post(title, subtitle, content, opts); + } + } + if (isNode || isScriptable) { + if (isJSBox) { + const push = require("push"); + push.schedule({ + title: title, + body: (subtitle ? subtitle + "\n" : "") + content_, + }); + } else { + console.log(`${title}\n${subtitle}\n${content_}\n\n`); + } + } } - if (isNode) { - return this.root[key]; + + // other helper functions + log(msg) { + if (this.debug) console.log(msg); } - } else { - return this.cache[key]; - } - } - delete(key) { - this.log(`DELETE ${key}`); - if (key.indexOf("#") !== -1) { - key = key.substr(1); - if (isSurge || isLoon) { - $persistentStore.write(null, key); + info(msg) { + console.log(msg); } - if (isQX) { - $prefs.removeValueForKey(key); + + error(msg) { + console.log("ERROR: " + msg); } - if (isNode) { - delete this.root[key]; + + wait(millisec) { + return new Promise((resolve) => setTimeout(resolve, millisec)); } - } else { - delete this.cache[key]; - } - this.persistCache(); - } - // notification - notify(title, subtitle = "", content = "", options = {}) { - const openURL = options["open-url"]; - const mediaURL = options["media-url"]; - - const content_ = - content + - (openURL ? `\n点击跳转: ${openURL}` : "") + - (mediaURL ? `\n多媒体: ${mediaURL}` : ""); - - if (isQX) $notify(title, subtitle, content, options); - if (isSurge) $notification.post(title, subtitle, content_); - if (isLoon) { - let opts = {}; - if (openURL) opts["openUrl"] = openURL; - if (mediaURL) opts["mediaUrl"] = mediaURL; - if (JSON.stringify(opts) === "{}") { - $notification.post(title, subtitle, content); - } else { - $notification.post(title, subtitle, content, opts); + done(value = {}) { + if (isQX || isLoon || isSurge) { + $done(value); + } else if (isNode && !isJSBox) { + if (typeof $context !== "undefined") { + $context.headers = value.headers; + $context.statusCode = value.statusCode; + $context.body = value.body; + } + } } - } - if (isNode || isScriptable) { - if (isJSBox) { - const push = require("push"); - push.schedule({ - title: title, - body: (subtitle ? subtitle + "\n" : "") + content_, - }); - } else { - console.log(`${title}\n${subtitle}\n${content_}\n\n`); - } - } - } - - // other helper functions - log(msg) { - if (this.debug) console.log(msg); - } - - info(msg) { - console.log(msg); - } - - error(msg) { - console.log("ERROR: " + msg); - } - - wait(millisec) { - return new Promise((resolve) => setTimeout(resolve, millisec)); - } - - done(value = {}) { - if (isQX || isLoon || isSurge) { - $done(value); - } else if (isNode && !isJSBox) { - if (typeof $context !== "undefined") { - $context.headers = value.headers; - $context.statusCode = value.statusCode; - $context.body = value.body; - } - } - } - })(name, debug); + })(name, debug); } /*********************************** Mini Express *************************************/ function express(port = 3000) { - const { isNode } = ENV(); - const DEFAULT_HEADERS = { - "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", - }; - - // node support - if (isNode) { - const express_ = require("express"); - const bodyParser = require("body-parser"); - const app = express_(); - app.use(bodyParser.json({ verify: rawBodySaver })); - app.use(bodyParser.urlencoded({ verify: rawBodySaver, extended: true })); - app.use(bodyParser.raw({ verify: rawBodySaver, type: "*/*" })); - app.use((req, res, next) => { - res.set(DEFAULT_HEADERS); - next(); - }); - - // adapter - app.start = () => { - app.listen(port, () => { - $.info(`Express started on port: ${port}`); - }); + const {isNode} = ENV(); + const DEFAULT_HEADERS = { + "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", }; - return app; - } - // route handlers - const handlers = []; - - // http methods - const METHODS_NAMES = [ - "GET", - "POST", - "PUT", - "DELETE", - "PATCH", - "OPTIONS", - "HEAD'", - "ALL", - ]; - - // dispatch url to route - const dispatch = (request, start = 0) => { - let { method, url, headers, body } = request; - if (/json/i.test(headers["Content-Type"])) { - body = JSON.parse(body); - } - - method = method.toUpperCase(); - const { path, query } = extractURL(url); - let handler = null; - let i; - - for (i = start; i < handlers.length; i++) { - if (handlers[i].method === "ALL" || method === handlers[i].method) { - const { pattern } = handlers[i]; - if (patternMatched(pattern, path)) { - handler = handlers[i]; - break; - } - } - } - if (handler) { - // dispatch to next handler - const next = () => { - dispatch(method, url, i); - }; - const req = { - method, - url, - path, - query, - params: extractPathParams(handler.pattern, path), - headers, - body, - }; - const res = Response(); - handler.callback(req, res, next).catch((err) => { - res.status(500).json({ - status: "failed", - message: `Internal Server Error: ${err}`, + // node support + if (isNode) { + const express_ = require("express"); + const bodyParser = require("body-parser"); + const app = express_(); + app.use(bodyParser.json({verify: rawBodySaver})); + app.use(bodyParser.urlencoded({verify: rawBodySaver, extended: true})); + app.use(bodyParser.raw({verify: rawBodySaver, type: "*/*"})); + app.use((req, res, next) => { + res.set(DEFAULT_HEADERS); + next(); }); - }); - } else { - // no route, return 404 - const res = Response(); - res.status(404).json({ - status: "failed", - message: "ERROR: 404 not found", - }); + + // adapter + app.start = () => { + app.listen(port, () => { + $.info(`Express started on port: ${port}`); + }); + }; + return app; } - }; - const app = {}; + // route handlers + const handlers = []; - // attach http methods - METHODS_NAMES.forEach((method) => { - app[method.toLowerCase()] = (pattern, callback) => { - // add handler - handlers.push({ method, pattern, callback }); + // http methods + const METHODS_NAMES = [ + "GET", + "POST", + "PUT", + "DELETE", + "PATCH", + "OPTIONS", + "HEAD'", + "ALL", + ]; + + // dispatch url to route + const dispatch = (request, start = 0) => { + let {method, url, headers, body} = request; + if (/json/i.test(headers["Content-Type"])) { + body = JSON.parse(body); + } + + method = method.toUpperCase(); + const {path, query} = extractURL(url); + let handler = null; + let i; + + for (i = start; i < handlers.length; i++) { + if (handlers[i].method === "ALL" || method === handlers[i].method) { + const {pattern} = handlers[i]; + if (patternMatched(pattern, path)) { + handler = handlers[i]; + break; + } + } + } + if (handler) { + // dispatch to next handler + const next = () => { + dispatch(method, url, i); + }; + const req = { + method, + url, + path, + query, + params: extractPathParams(handler.pattern, path), + headers, + body, + }; + const res = Response(); + handler.callback(req, res, next).catch((err) => { + res.status(500).json({ + status: "failed", + message: `Internal Server Error: ${err}`, + }); + }); + } else { + // no route, return 404 + const res = Response(); + res.status(404).json({ + status: "failed", + message: "ERROR: 404 not found", + }); + } }; - }); - // chainable route - app.route = (pattern) => { - const chainApp = {}; + const app = {}; + + // attach http methods METHODS_NAMES.forEach((method) => { - chainApp[method.toLowerCase()] = (callback) => { - // add handler - handlers.push({ method, pattern, callback }); - return chainApp; - }; + app[method.toLowerCase()] = (pattern, callback) => { + // add handler + handlers.push({method, pattern, callback}); + }; }); - return chainApp; - }; - // start service - app.start = () => { - dispatch($request); - }; + // chainable route + app.route = (pattern) => { + const chainApp = {}; + METHODS_NAMES.forEach((method) => { + chainApp[method.toLowerCase()] = (callback) => { + // add handler + handlers.push({method, pattern, callback}); + return chainApp; + }; + }); + return chainApp; + }; - return app; + // start service + app.start = () => { + dispatch($request); + }; - /************************************************ + return app; + + /************************************************ Utility Functions *************************************************/ - function rawBodySaver(req, res, buf, encoding) { - if (buf && buf.length) { - req.rawBody = buf.toString(encoding || "utf8"); + function rawBodySaver(req, res, buf, encoding) { + if (buf && buf.length) { + req.rawBody = buf.toString(encoding || "utf8"); + } } - } - function Response() { - let statusCode = 200; - const { isQX, isLoon, isSurge } = ENV(); - const headers = DEFAULT_HEADERS; - const STATUS_CODE_MAP = { - 200: "HTTP/1.1 200 OK", - 201: "HTTP/1.1 201 Created", - 404: "HTTP/1.1 404 Not Found", - 500: "HTTP/1.1 500 Internal Server Error", - }; - return new (class { - status(code) { - statusCode = code; - return this; - } - - send(body = "") { - const response = { - status: isQX ? STATUS_CODE_MAP[statusCode] : statusCode, - body, - headers, + function Response() { + let statusCode = 200; + const {isQX, isLoon, isSurge} = ENV(); + const headers = DEFAULT_HEADERS; + const STATUS_CODE_MAP = { + 200: "HTTP/1.1 200 OK", + 201: "HTTP/1.1 201 Created", + 404: "HTTP/1.1 404 Not Found", + 500: "HTTP/1.1 500 Internal Server Error", }; - if (isQX) { - $done(response); - } else if (isLoon || isSurge) { - $done({ - response, - }); - } - } + return new (class { + status(code) { + statusCode = code; + return this; + } - end() { - this.send(); - } + send(body = "") { + const response = { + status: isQX ? STATUS_CODE_MAP[statusCode] : statusCode, + body, + headers, + }; + if (isQX) { + $done(response); + } else if (isLoon || isSurge) { + $done({ + response, + }); + } + } - html(data) { - this.set("Content-Type", "text/html;charset=UTF-8"); - this.send(data); - } + end() { + this.send(); + } - json(data) { - this.set("Content-Type", "application/json;charset=UTF-8"); - this.send(JSON.stringify(data)); - } + html(data) { + this.set("Content-Type", "text/html;charset=UTF-8"); + this.send(data); + } - set(key, val) { - headers[key] = val; - return this; - } - })(); - } + json(data) { + this.set("Content-Type", "application/json;charset=UTF-8"); + this.send(JSON.stringify(data)); + } - function patternMatched(pattern, path) { - if (pattern instanceof RegExp && pattern.test(path)) { - return true; - } else { - // root pattern, match all - if (pattern === "/") return true; - // normal string pattern - if (pattern.indexOf(":") === -1) { - const spath = path.split("/"); - const spattern = pattern.split("/"); - for (let i = 0; i < spattern.length; i++) { - if (spath[i] !== spattern[i]) { - return false; - } - } - return true; - } - // string pattern with path parameters - else if (extractPathParams(pattern, path)) { - return true; - } + set(key, val) { + headers[key] = val; + return this; + } + })(); } - return false; - } - function extractURL(url) { - // extract path - const match = url.match(/https?:\/\/[^\/]+(\/[^?]*)/) || []; - const path = match[1] || "/"; - - // extract query string - const split = url.indexOf("?"); - const query = {}; - if (split !== -1) { - let hashes = url.slice(url.indexOf("?") + 1).split("&"); - for (let i = 0; i < hashes.length; i++) { - hash = hashes[i].split("="); - query[hash[0]] = hash[1]; - } - } - return { - path, - query, - }; - } - - function extractPathParams(pattern, path) { - if (pattern.indexOf(":") === -1) { - return null; - } else { - const params = {}; - for (let i = 0, j = 0; i < pattern.length; i++, j++) { - if (pattern[i] === ":") { - let key = []; - let val = []; - while (pattern[++i] !== "/" && i < pattern.length) { - key.push(pattern[i]); - } - while (path[j] !== "/" && j < path.length) { - val.push(path[j++]); - } - params[key.join("")] = val.join(""); + function patternMatched(pattern, path) { + if (pattern instanceof RegExp && pattern.test(path)) { + return true; } else { - if (pattern[i] !== path[j]) { - return null; - } + // root pattern, match all + if (pattern === "/") return true; + // normal string pattern + if (pattern.indexOf(":") === -1) { + const spath = path.split("/"); + const spattern = pattern.split("/"); + for (let i = 0; i < spattern.length; i++) { + if (spath[i] !== spattern[i]) { + return false; + } + } + return true; + } + // string pattern with path parameters + else if (extractPathParams(pattern, path)) { + return true; + } + } + return false; + } + + function extractURL(url) { + // extract path + const match = url.match(/https?:\/\/[^\/]+(\/[^?]*)/) || []; + const path = match[1] || "/"; + + // extract query string + const split = url.indexOf("?"); + const query = {}; + if (split !== -1) { + let hashes = url.slice(url.indexOf("?") + 1).split("&"); + for (let i = 0; i < hashes.length; i++) { + hash = hashes[i].split("="); + query[hash[0]] = hash[1]; + } + } + return { + path, + query, + }; + } + + function extractPathParams(pattern, path) { + if (pattern.indexOf(":") === -1) { + return null; + } else { + const params = {}; + for (let i = 0, j = 0; i < pattern.length; i++, j++) { + if (pattern[i] === ":") { + let key = []; + let val = []; + while (pattern[++i] !== "/" && i < pattern.length) { + key.push(pattern[i]); + } + while (path[j] !== "/" && j < path.length) { + val.push(path[j++]); + } + params[key.join("")] = val.join(""); + } else { + if (pattern[i] !== path[j]) { + return null; + } + } + } + return params; } - } - return params; } - } } function Gist(backupKey, token) { - const FILE_NAME = "Sub-Store"; - const http = HTTP({ - baseURL: "https://api.github.com", - headers: { - Authorization: `token ${token}`, - "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: (resp) => { - if (String(resp.statusCode).startsWith("4")) { - return Promise.reject(`ERROR: ${JSON.parse(resp.body).message}`); - } else { - return resp; - } - }, - }, - }); - - async function locate() { - return http.get("/gists").then((response) => { - const gists = JSON.parse(response.body); - for (let g of gists) { - if (g.description === backupKey) { - return g.id; - } - } - return -1; + const FILE_NAME = "Sub-Store"; + const http = HTTP({ + baseURL: "https://api.github.com", + headers: { + Authorization: `token ${token}`, + "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: (resp) => { + if (String(resp.statusCode).startsWith("4")) { + return Promise.reject(`ERROR: ${JSON.parse(resp.body).message}`); + } else { + return resp; + } + }, + }, }); - } - this.upload = async function(content) { - const id = await locate(); - const files = { - [FILE_NAME]: { content } + async function locate() { + return http.get("/gists").then((response) => { + const gists = JSON.parse(response.body); + for (let g of gists) { + if (g.description === backupKey) { + return g.id; + } + } + return -1; + }); + } + + this.upload = async function (content) { + const id = await locate(); + const files = { + [FILE_NAME]: {content} + }; + + if (id === -1) { + // create a new gist for backup + return http.post({ + url: "/gists", + body: JSON.stringify({ + description: backupKey, + public: false, + files + }) + }); + } else { + // update an existing gist + return http.patch({ + url: `/gists/${id}`, + body: JSON.stringify({files}) + }); + } }; - if (id === -1) { - // create a new gist for backup - return http.post({ - url: "/gists", - body: JSON.stringify({ - description: backupKey, - public: false, - files - }) - }); - } else { - // update an existing gist - return http.patch({ - url: `/gists/${id}`, - body: JSON.stringify({ files }) - }); - } - }; - - this.download = async function() { - const id = await locate(); - if (id === -1) { - return Promise.reject("未找到Gist备份!"); - } else { - try { - const { files } = await http - .get(`/gists/${id}`) - .then(resp => JSON.parse(resp.body)); - const url = files[FILE_NAME].raw_url; - return await HTTP().get(url).then(resp => resp.body); - } catch (err) { - return Promise.reject(err); - } - } - }; + this.download = async function () { + const id = await locate(); + if (id === -1) { + return Promise.reject("未找到Gist备份!"); + } else { + try { + const {files} = await http + .get(`/gists/${id}`) + .then(resp => JSON.parse(resp.body)); + const url = files[FILE_NAME].raw_url; + return await HTTP().get(url).then(resp => resp.body); + } catch (err) { + return Promise.reject(err); + } + } + }; } + /******************************** Base 64 *********************************************/ // Base64 Coding Library // https://github.com/dankogai/js-base64#readme // Under BSD License function Base64Code() { - // constants - const b64chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - const b64tab = (function (bin) { - const t = {}; - let i = 0; - const l = bin.length; - for (; i < l; i++) t[bin.charAt(i)] = i; - return t; - })(b64chars); - const fromCharCode = String.fromCharCode; - // encoder stuff - const cb_utob = function (c) { - let cc; - if (c.length < 2) { - cc = c.charCodeAt(0); - return cc < 0x80 - ? c - : cc < 0x800 - ? fromCharCode(0xc0 | (cc >>> 6)) + fromCharCode(0x80 | (cc & 0x3f)) - : fromCharCode(0xe0 | ((cc >>> 12) & 0x0f)) + - fromCharCode(0x80 | ((cc >>> 6) & 0x3f)) + - fromCharCode(0x80 | (cc & 0x3f)); - } else { - cc = - 0x10000 + - (c.charCodeAt(0) - 0xd800) * 0x400 + - (c.charCodeAt(1) - 0xdc00); - return ( - fromCharCode(0xf0 | ((cc >>> 18) & 0x07)) + - fromCharCode(0x80 | ((cc >>> 12) & 0x3f)) + - fromCharCode(0x80 | ((cc >>> 6) & 0x3f)) + - fromCharCode(0x80 | (cc & 0x3f)) - ); - } - }; - const re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g; - const utob = function (u) { - return u.replace(re_utob, cb_utob); - }; - const cb_encode = function (ccc) { - const padlen = [0, 2, 1][ccc.length % 3], - ord = - (ccc.charCodeAt(0) << 16) | - ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8) | - (ccc.length > 2 ? ccc.charCodeAt(2) : 0), - chars = [ - b64chars.charAt(ord >>> 18), - b64chars.charAt((ord >>> 12) & 63), - padlen >= 2 ? "=" : b64chars.charAt((ord >>> 6) & 63), - padlen >= 1 ? "=" : b64chars.charAt(ord & 63), - ]; - return chars.join(""); - }; - const btoa = function (b) { - return b.replace(/[\s\S]{1,3}/g, cb_encode); - }; - this.encode = function (u) { - const isUint8Array = - Object.prototype.toString.call(u) === "[object Uint8Array]"; - return isUint8Array ? u.toString("base64") : btoa(utob(String(u))); - }; - const uriencode = function (u, urisafe) { - return !urisafe - ? _encode(u) - : _encode(String(u)) - .replace(/[+\/]/g, function (m0) { - return m0 === "+" ? "-" : "_"; - }) - .replace(/=/g, ""); - }; - const encodeURI = function (u) { - return uriencode(u, true); - }; - // decoder stuff - const re_btou = /[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3}/g; - const cb_btou = function (cccc) { - switch (cccc.length) { - case 4: - const cp = - ((0x07 & cccc.charCodeAt(0)) << 18) | - ((0x3f & cccc.charCodeAt(1)) << 12) | - ((0x3f & cccc.charCodeAt(2)) << 6) | - (0x3f & cccc.charCodeAt(3)), - offset = cp - 0x10000; - return ( - fromCharCode((offset >>> 10) + 0xd800) + - fromCharCode((offset & 0x3ff) + 0xdc00) - ); - case 3: - return fromCharCode( - ((0x0f & cccc.charCodeAt(0)) << 12) | - ((0x3f & cccc.charCodeAt(1)) << 6) | - (0x3f & cccc.charCodeAt(2)) - ); - default: - return fromCharCode( - ((0x1f & cccc.charCodeAt(0)) << 6) | (0x3f & cccc.charCodeAt(1)) - ); - } - }; - const btou = function (b) { - return b.replace(re_btou, cb_btou); - }; - const cb_decode = function (cccc) { - const len = cccc.length, - padlen = len % 4, - n = - (len > 0 ? b64tab[cccc.charAt(0)] << 18 : 0) | - (len > 1 ? b64tab[cccc.charAt(1)] << 12 : 0) | - (len > 2 ? b64tab[cccc.charAt(2)] << 6 : 0) | - (len > 3 ? b64tab[cccc.charAt(3)] : 0), - chars = [ - fromCharCode(n >>> 16), - fromCharCode((n >>> 8) & 0xff), - fromCharCode(n & 0xff), - ]; - chars.length -= [0, 0, 2, 1][padlen]; - return chars.join(""); - }; - const _atob = function (a) { - return a.replace(/\S{1,4}/g, cb_decode); - }; - const atob = function (a) { - return _atob(String(a).replace(/[^A-Za-z0-9\+\/]/g, "")); - }; - const _decode = function (u) { - return btou(_atob(u)); - }; - this.decode = function (a) { - return _decode( - String(a) - .replace(/[-_]/g, function (m0) { - return m0 === "-" ? "+" : "/"; - }) - .replace(/[^A-Za-z0-9\+\/]/g, "") - ) - .replace(/>/g, ">") - .replace(/</g, "<"); - }; - this.safeEncode = function (a) { - return this.encode(a.replace(/\+/g, "-").replace(/\//g, "_")); - }; - this.safeDecode = function (a) { - return this.decode(a.replace(/-/g, "+").replace(/_/g, "/")); - }; + // constants + const b64chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + const b64tab = (function (bin) { + const t = {}; + let i = 0; + const l = bin.length; + for (; i < l; i++) t[bin.charAt(i)] = i; + return t; + })(b64chars); + const fromCharCode = String.fromCharCode; + // encoder stuff + const cb_utob = function (c) { + let cc; + if (c.length < 2) { + cc = c.charCodeAt(0); + return cc < 0x80 + ? c + : cc < 0x800 + ? fromCharCode(0xc0 | (cc >>> 6)) + fromCharCode(0x80 | (cc & 0x3f)) + : fromCharCode(0xe0 | ((cc >>> 12) & 0x0f)) + + fromCharCode(0x80 | ((cc >>> 6) & 0x3f)) + + fromCharCode(0x80 | (cc & 0x3f)); + } else { + cc = + 0x10000 + + (c.charCodeAt(0) - 0xd800) * 0x400 + + (c.charCodeAt(1) - 0xdc00); + return ( + fromCharCode(0xf0 | ((cc >>> 18) & 0x07)) + + fromCharCode(0x80 | ((cc >>> 12) & 0x3f)) + + fromCharCode(0x80 | ((cc >>> 6) & 0x3f)) + + fromCharCode(0x80 | (cc & 0x3f)) + ); + } + }; + const re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g; + const utob = function (u) { + return u.replace(re_utob, cb_utob); + }; + const cb_encode = function (ccc) { + const padlen = [0, 2, 1][ccc.length % 3], + ord = + (ccc.charCodeAt(0) << 16) | + ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8) | + (ccc.length > 2 ? ccc.charCodeAt(2) : 0), + chars = [ + b64chars.charAt(ord >>> 18), + b64chars.charAt((ord >>> 12) & 63), + padlen >= 2 ? "=" : b64chars.charAt((ord >>> 6) & 63), + padlen >= 1 ? "=" : b64chars.charAt(ord & 63), + ]; + return chars.join(""); + }; + const btoa = function (b) { + return b.replace(/[\s\S]{1,3}/g, cb_encode); + }; + this.encode = function (u) { + const isUint8Array = + Object.prototype.toString.call(u) === "[object Uint8Array]"; + return isUint8Array ? u.toString("base64") : btoa(utob(String(u))); + }; + const uriencode = function (u, urisafe) { + return !urisafe + ? _encode(u) + : _encode(String(u)) + .replace(/[+\/]/g, function (m0) { + return m0 === "+" ? "-" : "_"; + }) + .replace(/=/g, ""); + }; + const encodeURI = function (u) { + return uriencode(u, true); + }; + // decoder stuff + const re_btou = /[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3}/g; + const cb_btou = function (cccc) { + switch (cccc.length) { + case 4: + const cp = + ((0x07 & cccc.charCodeAt(0)) << 18) | + ((0x3f & cccc.charCodeAt(1)) << 12) | + ((0x3f & cccc.charCodeAt(2)) << 6) | + (0x3f & cccc.charCodeAt(3)), + offset = cp - 0x10000; + return ( + fromCharCode((offset >>> 10) + 0xd800) + + fromCharCode((offset & 0x3ff) + 0xdc00) + ); + case 3: + return fromCharCode( + ((0x0f & cccc.charCodeAt(0)) << 12) | + ((0x3f & cccc.charCodeAt(1)) << 6) | + (0x3f & cccc.charCodeAt(2)) + ); + default: + return fromCharCode( + ((0x1f & cccc.charCodeAt(0)) << 6) | (0x3f & cccc.charCodeAt(1)) + ); + } + }; + const btou = function (b) { + return b.replace(re_btou, cb_btou); + }; + const cb_decode = function (cccc) { + const len = cccc.length, + padlen = len % 4, + n = + (len > 0 ? b64tab[cccc.charAt(0)] << 18 : 0) | + (len > 1 ? b64tab[cccc.charAt(1)] << 12 : 0) | + (len > 2 ? b64tab[cccc.charAt(2)] << 6 : 0) | + (len > 3 ? b64tab[cccc.charAt(3)] : 0), + chars = [ + fromCharCode(n >>> 16), + fromCharCode((n >>> 8) & 0xff), + fromCharCode(n & 0xff), + ]; + chars.length -= [0, 0, 2, 1][padlen]; + return chars.join(""); + }; + const _atob = function (a) { + return a.replace(/\S{1,4}/g, cb_decode); + }; + const atob = function (a) { + return _atob(String(a).replace(/[^A-Za-z0-9\+\/]/g, "")); + }; + const _decode = function (u) { + return btou(_atob(u)); + }; + this.decode = function (a) { + return _decode( + String(a) + .replace(/[-_]/g, function (m0) { + return m0 === "-" ? "+" : "/"; + }) + .replace(/[^A-Za-z0-9\+\/]/g, "") + ) + .replace(/>/g, ">") + .replace(/</g, "<"); + }; + this.safeEncode = function (a) { + return this.encode(a.replace(/\+/g, "-").replace(/\//g, "_")); + }; + this.safeDecode = function (a) { + return this.decode(a.replace(/-/g, "+").replace(/_/g, "/")); + }; } /* @@ -3262,382 +3296,382 @@ Author: Diogo Costa This program is released under the MIT License */ var YAML = (function () { - var errors = [], - reference_blocks = [], - processing_time = 0, - regex = { - 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( - "([^\\'\\\"#]+([\\'\\\"][^\\'\\\"]*[\\'\\\"])*)*(#.*)?" - ), - }; + var errors = [], + reference_blocks = [], + processing_time = 0, + regex = { + 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( + "([^\\'\\\"#]+([\\'\\\"][^\\'\\\"]*[\\'\\\"])*)*(#.*)?" + ), + }; - /** - * @class A block of lines of a given level. - * @param {int} lvl The block's level. - * @private - */ - function Block(lvl) { - return { - /* The block's parent */ - parent: null, - /* Number of children */ - length: 0, - /* Block's level */ - level: lvl, - /* Lines of code to process */ - lines: [], - /* Blocks with greater level */ - children: [], - /* Add a block to the children collection */ - addChild: function (obj) { - this.children.push(obj); - obj.parent = this; - ++this.length; - }, - }; - } + /** + * @class A block of lines of a given level. + * @param {int} lvl The block's level. + * @private + */ + function Block(lvl) { + return { + /* The block's parent */ + parent: null, + /* Number of children */ + length: 0, + /* Block's level */ + level: lvl, + /* Lines of code to process */ + lines: [], + /* Blocks with greater level */ + children: [], + /* Add a block to the children collection */ + addChild: function (obj) { + this.children.push(obj); + obj.parent = this; + ++this.length; + }, + }; + } - function parser(str) { - var regLevel = regex["regLevel"]; - var invalidLine = regex["invalidLine"]; - var lines = str.split("\n"); - var m; - var level = 0, - curLevel = 0; + function parser(str) { + var regLevel = regex["regLevel"]; + var invalidLine = regex["invalidLine"]; + var lines = str.split("\n"); + var m; + var level = 0, + curLevel = 0; - var blocks = []; + var blocks = []; - var result = new Block(-1); - var currentBlock = new Block(0); - result.addChild(currentBlock); - var levels = []; - var line = ""; + var result = new Block(-1); + var currentBlock = new Block(0); + result.addChild(currentBlock); + var levels = []; + var line = ""; - blocks.push(currentBlock); - levels.push(level); - - for (var i = 0, len = lines.length; i < len; ++i) { - line = lines[i]; - - if (line.match(invalidLine)) { - continue; - } - - if ((m = regLevel.exec(line))) { - level = m[1].length; - } else level = 0; - - if (level > curLevel) { - var oldBlock = currentBlock; - currentBlock = new Block(level); - oldBlock.addChild(currentBlock); blocks.push(currentBlock); levels.push(level); - } else if (level < curLevel) { - var added = false; - var k = levels.length - 1; - for (; k >= 0; --k) { - if (levels[k] == level) { - currentBlock = new Block(level); - blocks.push(currentBlock); - levels.push(level); - if (blocks[k].parent != null) - blocks[k].parent.addChild(currentBlock); - added = true; - break; - } - } + for (var i = 0, len = lines.length; i < len; ++i) { + line = lines[i]; - if (!added) { - errors.push("Error: Invalid indentation at line " + i + ": " + line); - return; - } - } - - currentBlock.lines.push(line.replace(regex["trim"], "")); - curLevel = level; - } - - return result; - } - - function processValue(val) { - val = val.replace(regex["trim"], ""); - var m = null; - - if (val == "true") { - return true; - } else if (val == "false") { - return false; - } else if (val == ".NaN") { - return Number.NaN; - } else if (val == "null") { - return null; - } else if (val == ".inf") { - return Number.POSITIVE_INFINITY; - } else if (val == "-.inf") { - return Number.NEGATIVE_INFINITY; - } else if ((m = val.match(regex["dashesString"]))) { - return m[1]; - } else if ((m = val.match(regex["quotesString"]))) { - return m[1]; - } else if ((m = val.match(regex["float"]))) { - return parseFloat(m[0]); - } else if ((m = val.match(regex["integer"]))) { - return parseInt(m[0]); - } else if (!isNaN((m = Date.parse(val)))) { - return new Date(m); - } else if ((m = val.match(regex["single_key_value"]))) { - var res = {}; - res[m[1]] = processValue(m[2]); - return res; - } else if ((m = val.match(regex["array"]))) { - var count = 0, - c = " "; - var res = []; - var content = ""; - var str = false; - for (var j = 0, lenJ = m[1].length; j < lenJ; ++j) { - c = m[1][j]; - if (c == "'" || c == '"') { - if (str === false) { - str = c; - content += c; - continue; - } else if ((c == "'" && str == "'") || (c == '"' && str == '"')) { - str = false; - content += c; - continue; - } - } else if (str === false && (c == "[" || c == "{")) { - ++count; - } else if (str === false && (c == "]" || c == "}")) { - --count; - } else if (str === false && count == 0 && c == ",") { - res.push(processValue(content)); - content = ""; - continue; - } - - content += c; - } - - if (content.length > 0) res.push(processValue(content)); - return res; - } else if ((m = val.match(regex["map"]))) { - var count = 0, - c = " "; - var res = []; - var content = ""; - var str = false; - for (var j = 0, lenJ = m[1].length; j < lenJ; ++j) { - c = m[1][j]; - if (c == "'" || c == '"') { - if (str === false) { - str = c; - content += c; - continue; - } else if ((c == "'" && str == "'") || (c == '"' && str == '"')) { - str = false; - content += c; - continue; - } - } else if (str === false && (c == "[" || c == "{")) { - ++count; - } else if (str === false && (c == "]" || c == "}")) { - --count; - } else if (str === false && count == 0 && c == ",") { - res.push(content); - content = ""; - continue; - } - - content += c; - } - - if (content.length > 0) res.push(content); - - var newRes = {}; - for (var j = 0, lenJ = res.length; j < lenJ; ++j) { - if ((m = res[j].match(regex["key_value"]))) { - newRes[m[1]] = processValue(m[2]); - } - } - - return newRes; - } else return val; - } - - function processFoldedBlock(block) { - var lines = block.lines; - var children = block.children; - var str = lines.join(" "); - var chunks = [str]; - for (var i = 0, len = children.length; i < len; ++i) { - chunks.push(processFoldedBlock(children[i])); - } - return chunks.join("\n"); - } - - function processLiteralBlock(block) { - var lines = block.lines; - var children = block.children; - var str = lines.join("\n"); - for (var i = 0, len = children.length; i < len; ++i) { - str += processLiteralBlock(children[i]); - } - return str; - } - - function processBlock(blocks) { - var m = null; - var res = {}; - var lines = null; - var children = null; - var currentObj = null; - - var level = -1; - - var processedBlocks = []; - - var isMap = true; - - for (var j = 0, lenJ = blocks.length; j < lenJ; ++j) { - if (level != -1 && level != blocks[j].level) continue; - - processedBlocks.push(j); - - level = blocks[j].level; - lines = blocks[j].lines; - children = blocks[j].children; - currentObj = null; - - for (var i = 0, len = lines.length; i < len; ++i) { - var line = lines[i]; - - if ((m = line.match(regex["key"]))) { - var key = m[1]; - - if (key[0] == "-") { - key = key.replace(regex["item"], ""); - if (isMap) { - isMap = false; - if (typeof res.length === "undefined") { - res = []; - } + if (line.match(invalidLine)) { + continue; } - if (currentObj != null) res.push(currentObj); - currentObj = {}; - isMap = true; - } - if (typeof m[2] != "undefined") { - var value = m[2].replace(regex["trim"], ""); - if (value[0] == "&") { - var nb = processBlock(children); - if (currentObj != null) currentObj[key] = nb; - else res[key] = nb; - reference_blocks[value.substr(1)] = nb; - } else if (value[0] == "|") { - if (currentObj != null) - currentObj[key] = processLiteralBlock(children.shift()); - else res[key] = processLiteralBlock(children.shift()); - } else if (value[0] == "*") { - var v = value.substr(1); - var no = {}; + if ((m = regLevel.exec(line))) { + level = m[1].length; + } else level = 0; - if (typeof reference_blocks[v] == "undefined") { - errors.push("Reference '" + v + "' not found!"); - } else { - for (var k in reference_blocks[v]) { - no[k] = reference_blocks[v][k]; + if (level > curLevel) { + var oldBlock = currentBlock; + currentBlock = new Block(level); + oldBlock.addChild(currentBlock); + blocks.push(currentBlock); + levels.push(level); + } else if (level < curLevel) { + var added = false; + + var k = levels.length - 1; + for (; k >= 0; --k) { + if (levels[k] == level) { + currentBlock = new Block(level); + blocks.push(currentBlock); + levels.push(level); + if (blocks[k].parent != null) + blocks[k].parent.addChild(currentBlock); + added = true; + break; + } } - if (currentObj != null) currentObj[key] = no; - else res[key] = no; - } - } else if (value[0] == ">") { - if (currentObj != null) - currentObj[key] = processFoldedBlock(children.shift()); - else res[key] = processFoldedBlock(children.shift()); - } else { - if (currentObj != null) currentObj[key] = processValue(value); - else res[key] = processValue(value); + if (!added) { + errors.push("Error: Invalid indentation at line " + i + ": " + line); + return; + } } - } else { - if (currentObj != null) currentObj[key] = processBlock(children); - else res[key] = processBlock(children); - } - } else if (line.match(/^-\s*$/)) { - if (isMap) { - isMap = false; - if (typeof res.length === "undefined") { - res = []; - } - } - if (currentObj != null) res.push(currentObj); - currentObj = {}; - isMap = true; - } else if ((m = line.match(/^-\s*(.*)/))) { - if (currentObj != null) currentObj.push(processValue(m[1])); - else { - if (isMap) { - isMap = false; - if (typeof res.length === "undefined") { - res = []; - } - } - res.push(processValue(m[1])); - } - } - } - if (currentObj != null) { - if (isMap) { - isMap = false; - if (typeof res.length === "undefined") { - res = []; - } + currentBlock.lines.push(line.replace(regex["trim"], "")); + curLevel = level; } - res.push(currentObj); - } + + return result; } - for (var j = processedBlocks.length - 1; j >= 0; --j) { - blocks.splice.call(blocks, processedBlocks[j], 1); + function processValue(val) { + val = val.replace(regex["trim"], ""); + var m = null; + + if (val == "true") { + return true; + } else if (val == "false") { + return false; + } else if (val == ".NaN") { + return Number.NaN; + } else if (val == "null") { + return null; + } else if (val == ".inf") { + return Number.POSITIVE_INFINITY; + } else if (val == "-.inf") { + return Number.NEGATIVE_INFINITY; + } else if ((m = val.match(regex["dashesString"]))) { + return m[1]; + } else if ((m = val.match(regex["quotesString"]))) { + return m[1]; + } else if ((m = val.match(regex["float"]))) { + return parseFloat(m[0]); + } else if ((m = val.match(regex["integer"]))) { + return parseInt(m[0]); + } else if (!isNaN((m = Date.parse(val)))) { + return new Date(m); + } else if ((m = val.match(regex["single_key_value"]))) { + var res = {}; + res[m[1]] = processValue(m[2]); + return res; + } else if ((m = val.match(regex["array"]))) { + var count = 0, + c = " "; + var res = []; + var content = ""; + var str = false; + for (var j = 0, lenJ = m[1].length; j < lenJ; ++j) { + c = m[1][j]; + if (c == "'" || c == '"') { + if (str === false) { + str = c; + content += c; + continue; + } else if ((c == "'" && str == "'") || (c == '"' && str == '"')) { + str = false; + content += c; + continue; + } + } else if (str === false && (c == "[" || c == "{")) { + ++count; + } else if (str === false && (c == "]" || c == "}")) { + --count; + } else if (str === false && count == 0 && c == ",") { + res.push(processValue(content)); + content = ""; + continue; + } + + content += c; + } + + if (content.length > 0) res.push(processValue(content)); + return res; + } else if ((m = val.match(regex["map"]))) { + var count = 0, + c = " "; + var res = []; + var content = ""; + var str = false; + for (var j = 0, lenJ = m[1].length; j < lenJ; ++j) { + c = m[1][j]; + if (c == "'" || c == '"') { + if (str === false) { + str = c; + content += c; + continue; + } else if ((c == "'" && str == "'") || (c == '"' && str == '"')) { + str = false; + content += c; + continue; + } + } else if (str === false && (c == "[" || c == "{")) { + ++count; + } else if (str === false && (c == "]" || c == "}")) { + --count; + } else if (str === false && count == 0 && c == ",") { + res.push(content); + content = ""; + continue; + } + + content += c; + } + + if (content.length > 0) res.push(content); + + var newRes = {}; + for (var j = 0, lenJ = res.length; j < lenJ; ++j) { + if ((m = res[j].match(regex["key_value"]))) { + newRes[m[1]] = processValue(m[2]); + } + } + + return newRes; + } else return val; } - return res; - } + function processFoldedBlock(block) { + var lines = block.lines; + var children = block.children; + var str = lines.join(" "); + var chunks = [str]; + for (var i = 0, len = children.length; i < len; ++i) { + chunks.push(processFoldedBlock(children[i])); + } + return chunks.join("\n"); + } - function semanticAnalysis(blocks) { - var res = processBlock(blocks.children); - return res; - } + function processLiteralBlock(block) { + var lines = block.lines; + var children = block.children; + var str = lines.join("\n"); + for (var i = 0, len = children.length; i < len; ++i) { + str += processLiteralBlock(children[i]); + } + return str; + } - function preProcess(src) { - var m; - var lines = src.split("\n"); + function processBlock(blocks) { + var m = null; + var res = {}; + var lines = null; + var children = null; + var currentObj = null; - var r = regex["comment"]; + var level = -1; - for (var i in lines) { - if ((m = typeof lines[i] === "string" && lines[i].match(r))) { - /* var cmt = ""; + var processedBlocks = []; + + var isMap = true; + + for (var j = 0, lenJ = blocks.length; j < lenJ; ++j) { + if (level != -1 && level != blocks[j].level) continue; + + processedBlocks.push(j); + + level = blocks[j].level; + lines = blocks[j].lines; + children = blocks[j].children; + currentObj = null; + + for (var i = 0, len = lines.length; i < len; ++i) { + var line = lines[i]; + + if ((m = line.match(regex["key"]))) { + var key = m[1]; + + if (key[0] == "-") { + key = key.replace(regex["item"], ""); + if (isMap) { + isMap = false; + if (typeof res.length === "undefined") { + res = []; + } + } + if (currentObj != null) res.push(currentObj); + currentObj = {}; + isMap = true; + } + + if (typeof m[2] != "undefined") { + var value = m[2].replace(regex["trim"], ""); + if (value[0] == "&") { + var nb = processBlock(children); + if (currentObj != null) currentObj[key] = nb; + else res[key] = nb; + reference_blocks[value.substr(1)] = nb; + } else if (value[0] == "|") { + if (currentObj != null) + currentObj[key] = processLiteralBlock(children.shift()); + else res[key] = processLiteralBlock(children.shift()); + } else if (value[0] == "*") { + var v = value.substr(1); + var no = {}; + + if (typeof reference_blocks[v] == "undefined") { + errors.push("Reference '" + v + "' not found!"); + } else { + for (var k in reference_blocks[v]) { + no[k] = reference_blocks[v][k]; + } + + if (currentObj != null) currentObj[key] = no; + else res[key] = no; + } + } else if (value[0] == ">") { + if (currentObj != null) + currentObj[key] = processFoldedBlock(children.shift()); + else res[key] = processFoldedBlock(children.shift()); + } else { + if (currentObj != null) currentObj[key] = processValue(value); + else res[key] = processValue(value); + } + } else { + if (currentObj != null) currentObj[key] = processBlock(children); + else res[key] = processBlock(children); + } + } else if (line.match(/^-\s*$/)) { + if (isMap) { + isMap = false; + if (typeof res.length === "undefined") { + res = []; + } + } + if (currentObj != null) res.push(currentObj); + currentObj = {}; + isMap = true; + } else if ((m = line.match(/^-\s*(.*)/))) { + if (currentObj != null) currentObj.push(processValue(m[1])); + else { + if (isMap) { + isMap = false; + if (typeof res.length === "undefined") { + res = []; + } + } + res.push(processValue(m[1])); + } + } + } + + if (currentObj != null) { + if (isMap) { + isMap = false; + if (typeof res.length === "undefined") { + res = []; + } + } + res.push(currentObj); + } + } + + for (var j = processedBlocks.length - 1; j >= 0; --j) { + blocks.splice.call(blocks, processedBlocks[j], 1); + } + + return res; + } + + function semanticAnalysis(blocks) { + var res = processBlock(blocks.children); + return res; + } + + function preProcess(src) { + var m; + var lines = src.split("\n"); + + var r = regex["comment"]; + + for (var i in lines) { + if ((m = typeof lines[i] === "string" && lines[i].match(r))) { + /* var cmt = ""; if(typeof m[3] != "undefined") lines[i] = m[1]; else if(typeof m[3] != "undefined") @@ -3645,51 +3679,51 @@ var YAML = (function () { else lines[i] = ""; */ - if (typeof m[3] !== "undefined") { - lines[i] = m[0].substr(0, m[0].length - m[3].length); + if (typeof m[3] !== "undefined") { + lines[i] = m[0].substr(0, m[0].length - m[3].length); + } + } } - } + + return lines.join("\n"); } - return lines.join("\n"); - } + function eval(str) { + errors = []; + reference_blocks = []; + processing_time = new Date().getTime(); + var pre = preProcess(str); + var doc = parser(pre); + var res = semanticAnalysis(doc); + processing_time = new Date().getTime() - processing_time; - function eval(str) { - errors = []; - reference_blocks = []; - processing_time = new Date().getTime(); - var pre = preProcess(str); - var doc = parser(pre); - var res = semanticAnalysis(doc); - processing_time = new Date().getTime() - processing_time; + return res; + } - return res; - } + return { + /** + * Parse a YAML file from a string. + * @param {String} str String with the YAML file contents. + * @function + */ + eval: eval, - return { - /** - * Parse a YAML file from a string. - * @param {String} str String with the YAML file contents. - * @function - */ - eval: eval, + /** + * Get errors found when parsing the last file. + * @function + * @returns Errors found when parsing the last file. + */ + getErrors: function () { + return errors; + }, - /** - * Get errors found when parsing the last file. - * @function - * @returns Errors found when parsing the last file. - */ - getErrors: function () { - return errors; - }, - - /** - * Get the time it took to parse the last file. - * @function - * @returns Time in milliseconds. - */ - getProcessingTime: function () { - return processing_time; - }, - }; + /** + * Get the time it took to parse the last file. + * @function + * @returns Time in milliseconds. + */ + getProcessingTime: function () { + return processing_time; + }, + }; })(); diff --git a/backend/sub-store.min.js b/backend/sub-store.min.js index b71e706..a6867b1 100644 --- a/backend/sub-store.min.js +++ b/backend/sub-store.min.js @@ -1 +1 @@ -const $=API("sub-store"),$app=express();$.http=HTTP({headers:{"User-Agent":"Quantumult%20X"}});const SETTINGS_KEY="settings",SUBS_KEY="subs",COLLECTIONS_KEY="collections",AVAILABLE_FILTERS={"Keyword Filter":KeywordFilter,"Useless Filter":UselessFilter,"Region Filter":RegionFilter,"Regex Filter":RegexFilter,"Type Filter":TypeFilter,"Script Filter":ScriptFilter},AVAILABLE_OPERATORS={"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};async function IP_API(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)}async function downloadResource(e){let t=await $.http.get(e).then(e=>e.body);const s=t.indexOf("proxies:");if(-1!==s){const e=t.lastIndexOf("}")+1;t=t.substring(s,e)}return t}async function gistBackup(e,t){const{action:s}=e.query,{gistToken:r}=$.read(SETTINGS_KEY);if(r){const e=new Gist("Auto Generated Sub-Store Backup",r);try{let r;switch(s){case"upload":r=$.read("#sub-store"),await e.upload(JSON.stringify(r)),$.info("上传备份中...");break;case"download":r=await e.download(),$.write(r,"#sub-store"),$.info("还原备份中...")}t.json({status:"success"})}catch(e){const r=`${"upload"===s?"上传":"下载"}备份失败!${e}`;$.error(r),t.status(500).json({status:"failed",message:r})}}else t.status(500).json({status:"failed",message:"未找到Gist备份Token!"})}async function getSettings(e,t){const s=$.read(SETTINGS_KEY);t.json(s)}async function updateSettings(e,t){const s=e.body,r=$.read(SETTINGS_KEY);$.write({...r,...s},SETTINGS_KEY),t.json({status:"success"})}async function refreshResource(e,t){const s=new Base64Code,{url:r}=e.body,n=await downloadResource(r);$.write(n,`#${s.safeEncode(r)}`),t.json({status:"success"})}async function downloadSub(e,t){const{name:s}=e.params,r=e.query.target||getPlatformFromHeaders(e.headers),n=$.read(SUBS_KEY);if(n[s]){const e=n[s];try{const s=await parseSub(e,r);t.send(s)}catch(e){t.status(500).json({status:"failed",message:e})}}else t.status(404).json({status:"failed",message:`订阅${s}不存在!`})}async function parseSub(e,t){let s;const r=(new Base64Code).safeEncode(e.url);if("Raw"===t||"URI"===t){const t=$.read(`#${r}`);t?(await $.wait(10),s=t):(s=await downloadResource(e.url),$.write(s,`#${r}`))}else s=await downloadResource(e.url),$.write(s,`#${e.url}`);$.info("======================================================================="),$.info(`Processing subscription: ${e.name}, target platform ==> ${t}.`),$.info("Initializing parsers...");const n=ProxyParser(t);n.addParsers([Clash_All,URI_SS,URI_SSR,URI_VMess,URI_Trojan,QX_SS,QX_SSR,QX_VMess,QX_Trojan,QX_Http,Loon_SS,Loon_SSR,Loon_VMess,Loon_Trojan,Loon_Http,Surge_SS,Surge_VMess,Surge_Trojan,Surge_Http]),$.info("Parsers initialized.");let o=n.parse(s);for(const t of e.process||[]){let e;if(-1!==t.type.indexOf("Script")){const{mode:s,content:r}=t.args;e="link"===s?await $.http.get(r).then(e=>e.body).catch(e=>{throw new Error(`Error when downloading remote script: ${t.args.content}.\n Reason: ${e}`)}):r}if(-1!==t.type.indexOf("Filter")){const s=AVAILABLE_FILTERS[t.type];if(s){$.info(`Applying filter "${t.type}" with arguments:\n >>> ${JSON.stringify(t.args)||"None"}`);try{o=-1!==t.type.indexOf("Script")?processFilter(s(e),o):processFilter(s(t.args),o)}catch(e){$.error(`Failed to apply filter "${t.type}"!\n REASON: ${e}`)}}}else if(-1!==t.type.indexOf("Operator")){const s=AVAILABLE_OPERATORS[t.type];if(s){$.info(`Applying operator "${t.type}" with arguments: \n >>> ${JSON.stringify(t.args)||"None"}`);try{o=-1!==t.type.indexOf("Script")?processOperator(s(e),o):processOperator(s(t.args),o)}catch(e){t.type}}}}return n.addProducers([QX_Producer,Loon_Producer,Surge_Producer,Raw_Producer,URI_Producer]),n.produce(o)}function getFlowHeaders(e,t){const s=Object.keys(e).filter(e=>/SUBSCRIPTION-USERINFO/i.test(e))[0];if(s){const t=e[s],r=Number(t.match(/upload=(\d+)/)[1]),n=Number(t.match(/download=(\d+)/)[1]),o=Number(t.match(/total=(\d+)/)[1]),a=(t.match(/expire=(\d+)/)||[])[1];return{key:"Subscription-Userinfo",value:`${r?"upload="+r+";":""}${n?"download="+n+";":""}${o?"total="+o+";":""}${a?"expire="+a+";":""}`}}{let e,s;for(const r of t)-1!==r.name.indexOf("剩余流量")&&(e=r.name),-1!==r.name.indexOf("过期时间")&&(s=r.name);return{key:"Raw-Subscription-Userinfo",value:`${e?"remains="+e+";":""}${s?"expire="+s+";":""}`}}}async function getSub(e,t){const{name:s}=e.params,r=$.read(SUBS_KEY)[s];r?t.json({status:"success",data:r}):t.status(404).json({status:"failed",message:`未找到订阅:${s}!`})}async function newSub(e,t){const s=e.body,r=$.read(SUBS_KEY);r[s.name]&&t.status(500).json({status:"failed",message:`订阅${s.name}已存在!`}),/^[\w-_]*$/.test(s.name)?(r[s.name]=s,$.write(r,SUBS_KEY),t.status(201).json({status:"success",data:s})):t.status(500).json({status:"failed",message:`订阅名称 ${s.name} 中含有非法字符!名称中只能包含英文字母、数字、下划线、横杠。`})}async function updateSub(e,t){const{name:s}=e.params;$.info(`Updating subscription: ${s}`);let r=e.body;const n=$.read(SUBS_KEY);if(n[s]){const e={...n[s],...r};if(s!==r.name){const t=$.read(COLLECTIONS_KEY);for(const e of Object.keys(t)){const n=t[e].subscriptions.indexOf(s);-1!==n&&(t[e].subscriptions[n]=r.name)}delete n[s],n[r.name]=e}else n[s]=e;$.write(n,SUBS_KEY),t.json({status:"success",data:e})}else t.status(500).json({status:"failed",message:`订阅${s}不存在,无法更新!`})}async function deleteSub(e,t){const{name:s}=e.params;let r=$.read(SUBS_KEY);delete r[s],$.write(r,SUBS_KEY);let n=$.read(COLLECTIONS_KEY);for(const e of Object.keys(n))n[e].subscriptions=n[e].subscriptions.filter(e=>e!==s);$.write(n,COLLECTIONS_KEY),t.json({status:"success"})}async function getAllSubs(e,t){const s=$.read(SUBS_KEY);t.json({status:"success",data:s})}async function deleteAllSubs(e,t){$.write({},SUBS_KEY),t.json({status:"success"})}async function downloadCollection(e,t){const{name:s}=e.params,r=$.read(COLLECTIONS_KEY)[s],n=getPlatformFromHeaders(e.headers);if(r){const e=r.subscriptions||[],s=await Promise.all(e.map(async e=>{const t=$.read(SUBS_KEY)[e];try{return parseSub(t,n)}catch(t){return console.log(`ERROR when process subscription: ${e}`),""}}));t.send(s.join("\n"))}else $.notify("[Sub-Store]",`❌ 未找到订阅集:${s}!`),t.status(404).json({status:"failed",message:`❌ 未找到订阅集:${s}!`})}async function getCollection(e,t){const{name:s}=e.params,r=$.read(COLLECTIONS_KEY)[s];r?t.json({status:"success",data:r}):t.status(404).json({status:"failed",message:`未找到订阅集:${s}!`})}async function newCollection(e,t){const s=e.body,r=$.read(COLLECTIONS_KEY);r[s.name]&&t.status(500).json({status:"failed",message:`订阅集${s.name}已存在!`}),/^[\w-_]*$/.test(s.name)?(r[s.name]=s,$.write(r,COLLECTIONS_KEY),t.status(201).json({status:"success",data:s})):t.status(500).json({status:"failed",message:`订阅集名称 ${s.name} 中含有非法字符!名称中只能包含英文字母、数字、下划线、横杠。`})}async function updateCollection(e,t){const{name:s}=e.params;let r=e.body;const n=$.read(COLLECTIONS_KEY);if(n[s]){const e={...n[s],...r};delete n[s],n[r.name||s]=e,$.write(n,COLLECTIONS_KEY),t.json({status:"success",data:e})}else t.status(500).json({status:"failed",message:`订阅集${s}不存在,无法更新!`})}async function deleteCollection(e,t){const{name:s}=e.params;let r=$.read(COLLECTIONS_KEY);delete r[s],$.write(r,COLLECTIONS_KEY),t.json({status:"success"})}async function getAllCollections(e,t){const s=$.read(COLLECTIONS_KEY);t.json({status:"success",data:s})}async function deleteAllCollections(e,t){$.write({},COLLECTIONS_KEY),t.json({status:"success"})}function ProxyParser(e){const t=[],s=[];return{parse:function(s){const r=(s=function(e){let t;if(-1!==e.indexOf("DOCTYPE html"))throw new Error("Invalid format HTML!");if(-1!==e.indexOf("proxies")){console.log("Preprocessing Clash config..."),-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=e.replace(/ -\n.*name/g," - name").replace(/\$|\`/g,"").split("proxy-providers:")[0].split("proxy-groups:")[0].replace(/\"(name|type|server|port|cipher|password|)\"/g,"$1");const s=YAML.eval(e).proxies;t=s.map(e=>JSON.stringify(e))}else if(0===e.indexOf("ssd://")){t=[];const s=new Base64Code;let r=JSON.parse(s.safeDecode(e.split("ssd://")[1]));r.traffic_used,r.traffic_total,r.expiry,r.airport;let n=r.port,o=r.encryption,a=r.password,i=r.servers;for(let e=0;e-1!==e.indexOf(t))?s.safeDecode(e):e).split("\n");for(let e=0;e{try{return r.output(e)}catch(t){return console.log(`ERROR: cannot produce proxy: ${JSON.stringify(e)}\nReason: ${t}`),""}}).filter(e=>e.length>0).join("\n");throw new Error(`Cannot find any producer for target platform: ${e}`)},addParsers:function(e){e.forEach(e=>t.push(e())),$.info(`${e.length} parser added.`)},addProducers:function(e){e.forEach(e=>s.push(e()))}}}function processFilter(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 processOperator(e,t){let s=objClone(t);try{const t=e.func(s);t&&(s=t)}catch(e){console.log(`ERROR: cannot apply operator ${op.name}! Reason: ${e}`)}return s}function URI_SS(){const e=new Base64Code,t={QX:!0,Loon:!0,Surge:!0,Raw:!0};return{patternTest:e=>/^ss:\/\//.test(e),func:s=>{let r=s.split("ss://")[1];const n={name:decodeURIComponent(s.split("#")[1]),type:"ss",supported:t},o=(r=r.split("#")[0]).match(/@([^\/]*)(\/|$)/)[1],a=o.lastIndexOf(":");n.server=o.substring(0,a),n.port=o.substring(a+1);const i=e.safeDecode(r.split("@")[0]).split(":");if(n.cipher=i[0],n.password=i[1],-1!==r.indexOf("?plugin=")){const e=("plugin="+decodeURIComponent(r.split("?plugin=")[1].split("&")[0])).split(";"),s={};for(const t of e){const[e,r]=t.split("=");e&&(s[e]=r||!0)}switch(s.plugin){case"obfs-local":case"simple-obfs":n.plugin="obfs",n["plugin-opts"]={mode:s.obfs,host:s["obfs-host"]};break;case"v2ray-plugin":n.supported={...t,Loon:!1,Surge:!1},n.obfs="v2ray-plugin",n["plugin-opts"]={mode:"websocket",host:s["obfs-host"],path:s.path||"",tls:s.tls};break;default:throw new Error(`Unsupported plugin option: ${s.plugin}`)}}return n}}}function URI_SSR(){const e=new Base64Code,t={QX:!0,Loon:!0,Surge:!1,Raw:!0};return{patternTest:e=>/^ssr:\/\//.test(e),func:s=>{let r=(s=e.safeDecode(s.split("ssr://")[1])).indexOf(":origin");-1===r&&(r=s.indexOf(":auth_"));const n=s.substring(0,r),o=n.substring(0,n.lastIndexOf(":")),a=n.substring(n.lastIndexOf(":")+1);let i=s.substring(r+1).split("/?")[0].split(":"),p={type:"ssr",server:o,port:a,protocol:i[0],cipher:i[1],obfs:i[2],password:e.safeDecode(i[3]),supported:t};if(i={},(s=s.split("/?")[1].split("&")).length>1)for(const e of s){const[t,s]=e.split("=");i[t]=s}return p={...p,name:e.safeDecode(i.remarks),"protocol-param":e.safeDecode(i.protoparam).replace(/\s/g,"")||"","obfs-param":e.safeDecode(i.obfsparam).replace(/\s/g,"")||""}}}}function URI_VMess(){const e=new Base64Code,t={QX:!0,Loon:!0,Surge:!0,Raw:!0};return{patternTest:e=>/^vmess:\/\//.test(e),func:s=>{s=s.split("vmess://")[1];const r=e.safeDecode(s);if(/=\s*vmess/.test(r)){const e=r.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 s={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||(s.network="ws",s["ws-path"]=t["obfs-uri"],s["ws-headers"]={Host:t["obfs-host"]||s.server}),s.tls&&"false"===t['"tls-verification"']&&(s.scert=!0),s.tls&&t["obfs-host"]&&(s.sni=t["obfs-host"]),s}{const e=JSON.parse(r),s={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&&(s.network="ws",s["ws-path"]=e.path,s["ws-headers"]={Host:e.host||e.add},s.tls&&e.host&&(s.sni=e.host)),!1===e.verify_cert&&(s.scert=!0),s}}}}function URI_Trojan(){const e={QX:!0,Loon:!0,Surge:!0,Raw:!0};return{patternTest:e=>/^trojan:\/\//.test(e),func:t=>{if(-1===t.indexOf(":443"))throw new Error("Trojan port should always be 443!");const s=(t=t.split("trojan://")[1]).split("@")[1].split(":443")[0];return{name:decodeURIComponent(t.split("#")[1].trim())||`[Trojan] ${s}`,type:"trojan",server:s,port:443,password:t.split("@")[0],supported:e}}}}function Clash_All(){return{patternTest:e=>-1!==e.indexOf("{"),func:e=>JSON.parse(e)}}function QX_SS(){const e={QX:!0,Loon:!0,Surge:!0,Raw:!0};return{patternTest:e=>/^shadowsocks\s*=/.test(e.split(",")[0].trim())&&-1===e.indexOf("ssr-protocol"),func:t=>{const s=getQXParams(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:e};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="v2ray-plugin",r.supported.Surge=!1,r.supported.Loon=!1}return r}}}function QX_SSR(){const e={QX:!0,Loon:!0,Surge:!1,Raw:!0};return{patternTest:e=>/^shadowsocks\s*=/.test(e.split(",")[0].trim())&&-1!==e.indexOf("ssr-protocol"),func:t=>{const s=getQXParams(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:e};return s.obfs&&(r.obfs=s.obfs,r["obfs-param"]=s["obfs-host"]),r}}}function QX_VMess(){return{patternTest:e=>/^vmess\s*=/.test(e.split(",")[0].trim()),func:e=>{const t=getQXParams(e),s={type:"vmess",name:t.tag,server:t.server,port:t.port,cipher:t.method||"none",uuid:t.password,alterId:0,tls:"over-tls"===t.obfs||"wss"===t.obfs,udp:JSON.parse(t["udp-relay"]||"false"),tfo:JSON.parse(t["fast-open"]||"false")};return s.tls&&(s.sni=t["obfs-host"]||t.server,s.scert=!JSON.parse(t["tls-verification"]||"true")),"ws"!==t.obfs&&"wss"!==t.obfs||(s.network="ws",s["ws-path"]=t["obfs-uri"],s["ws-headers"]={Host:t["obfs-host"]||t.server}),s}}}function QX_Trojan(){return{patternTest:e=>/^trojan\s*=/.test(e.split(",")[0].trim()),func:e=>{const t=getQXParams(e),s={type:"trojan",name:t.tag,server:t.server,port:t.port,password:t.password,sni:t["tls-host"]||t.server,udp:JSON.parse(t["udp-relay"]||"false"),tfo:JSON.parse(t["fast-open"]||"false")};return s.scert=!JSON.parse(t["tls-verification"]||"true"),s}}}function QX_Http(){return{patternTest:e=>/^http\s*=/.test(e.split(",")[0].trim()),func:e=>{const t=getQXParams(e),s={type:"http",name:t.tag,server:t.server,port:t.port,username:t.username,password:t.password,tls:JSON.parse(t["over-tls"]||"false"),udp:JSON.parse(t["udp-relay"]||"false"),tfo:JSON.parse(t["fast-open"]||"false")};return s.tls&&(s.sni=t["tls-host"]||s.server,s.scert=!JSON.parse(t["tls-verification"]||"true")),s}}}function getQXParams(e){const t=e.split(","),s={},r=["shadowsocks","vmess","http","trojan"];return t.forEach(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 Loon_SS(){return{patternTest:e=>"shadowsocks"===e.split(",")[0].split("=")[1].trim().toLowerCase(),func: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:s.obfs,host:t[6]}),s}}}function Loon_SSR(){return{patternTest:e=>"shadowsocksr"===e.split(",")[0].split("=")[1].trim().toLowerCase(),func: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:{QX:!0,Loon:!0,Surge:!1,Raw:!0},obfs:t[7],"obfs-param":t[8].match(/{(.*)}/)[1]}}}}function Loon_VMess(){return{patternTest:e=>/^.*=\s*vmess/i.test(e.split(",")[0])&&-1===e.indexOf("username"),func: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.scert=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.scert=JSON.parse(t["skip-cert-verify"]||"false")),s}}}function Loon_Trojan(){return{patternTest:e=>/^.*=\s*trojan/i.test(e.split(",")[0])&&-1===e.indexOf("password"),func: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],scert:JSON.parse(t["skip-cert-verify"]||"false")};if(t.length>4){const[r,n]=t[4].split(":");if("tls-name"!==r)throw new Error(`ERROR: unknown option ${r} for line: \n${e}`);s.sni=n}return s}}}function Loon_Http(){return{patternTest:e=>/^.*=\s*http/i.test(e.split(",")[0])&&5===e.split(",").length&&-1===e.indexOf("username")&&-1===e.indexOf("password"),func: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],username:(t[3]||"").replace(/"/g,""),password:(t[4]||"").replace(/"/g,"")};return s.tls&&(s.sni=t["tls-name"]||s.server,s.scert=JSON.parse(t["skip-cert-verify"]||"false")),s}}}function Surge_SS(){return{patternTest:e=>/^.*=\s*ss/.test(e.split(",")[0]),func:e=>{const t=getSurgeParams(e),s={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&&(s.plugin="obfs",s["plugin-opts"]={mode:t.obfs,host:t["obfs-host"]}),s}}}function Surge_VMess(){return{patternTest:e=>/^.*=\s*vmess/.test(e.split(",")[0])&&-1!==e.indexOf("username"),func:e=>{const t=getSurgeParams(e),s={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 s.tls&&(s.scert=JSON.parse(t["skip-cert-verify"]||"false"),s.sni=t.sni||t.server),JSON.parse(t.ws||"false")&&(s.network="ws",s["ws-path"]=t["ws-path"],s["ws-headers"]={Host:t.sni}),s}}}function Surge_Trojan(){return{patternTest:e=>/^.*=\s*trojan/.test(e.split(",")[0])&&-1!==e.indexOf("sni"),func:e=>{const t=getSurgeParams(e);return{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"),scert:JSON.parse(t["skip-cert-verify"]||"false")}}}}function Surge_Http(){return{patternTest:e=>/^.*=\s*http/.test(e.split(",")[0])&&!Loon_Http().patternTest(e),func:e=>{const t=getSurgeParams(e),s={name:t.name,type:"http",server:t.server,port:t.port,tls:JSON.parse(t.tls||"false"),tfo:JSON.parse(t.tfo||"false")};return s.tls&&(s.scert=JSON.parse(t["skip-cert-verify"]||"false"),s.sni=t.sni||t.server),"none"!==t.username&&(s.username=t.username),"none"!==t.password&&(s.password=t.password),s}}}function getSurgeParams(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{let t,s;switch(e.type){case"ss":if(t="","obfs"===e.plugin&&(t=`,obfs=${e["plugin-opts"].mode},obfs-host=${e["plugin-opts"].host}`),"v2ray-plugin"===e.plugin){const{tls:s,host:r,path:n}=e["plugin-opts"];t=`,obfs=${s?"wss":"ws"},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,obfs-host=${e.sni}${e["ws-path"]?",obfs-uri="+e["ws-path"]:""},tls-verification=${e.scert?"false":"true"}`:`,obfs=ws,obfs-host=${e["ws-headers"].Host}${e["ws-path"]?",obfs-uri="+e["ws-path"]:""}`:e.tls&&(t=`,obfs=over-tls,obfs-host=${e.sni},tls-verification=${e.scert?"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:""},tls-verification=${e.scert?"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.scert?"false":"true"},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}`)}}}function Loon_Producer(){return{targetPlatform:"Loon",output: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},path:${e["ws-path"]||"/"}`:",transport:tcp",e.tls&&(t+=`,tls-name=${e.sni},skip-cert-verify:${e.scert}`),`${e.name}=vmess,${e.server},${e.port},${"auto"===e.cipher?"none":e.cipher},"${e.uuid}",over-tls:${e.tls}${t}`;case"trojan":return`${e.name}=trojan,${e.server},${e.port},"${e.password}"${e.sni?",tls-name:"+e.sni:""},skip-cert-verify:${e.scert||"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=`,skip-cert-verify:${e.scert},tls-name:${e.sni}`):r}throw new Error(`Platform Loon does not support proxy type: ${e.type}`)}}}function Surge_Producer(){return{targetPlatform:"Surge",output:e=>{let t,s;switch(e.type){case"ss":if(t="",e.plugin){if("obfs"!==e.plugin)throw new Error(`Platform Surge does not support obfs option: ${e.obfs}`);t=`,obfs=${e["plugin-opts"].mode},obfs-host=${e["plugin-opts"].host}`}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},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.scert?",skip-cert-verify="+e.scert:""}`,r+=e.sni?`,sni=${e.sni}`:""),r;case"trojan":return`${e.name}=trojan,${e.server},${e.port},password=${e.password}${void 0!==e.scert?",skip-cert-verify="+e.scert:""}${e.sni?",sni="+e.sni:""},tfo=${e.tfo||"false"}`;case"http":return s=",tls=false",e.tls&&(s=`,tls=true,skip-cert-verify=${e.scert},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}`)}}}function Raw_Producer(){return{targetPlatform:"Raw",output:e=>JSON.stringify(e)}}function URI_Producer(){const e=new Base64Code;return{targetPlatform:"URI",output:t=>{let s="";switch(t.type){case"ss":const r=`${t.cipher}:${t.password}`;if(s=`ss://${e.safeEncode(r)}@${t.server}:${t.port}/`,t.plugin){s+="?plugin=";const e=t["plugin-opts"];switch(t.plugin){case"obfs":s+=encodeURIComponent(`simple-obfs;obfs=${e.mode}${e.host?";obfs-host="+e.host:""}`);break;case"v2ray-plugin":s+=encodeURIComponent(`v2ray-plugin;obfs=${e.mode}${e.host?";obfs-host"+e.host:""}${e.tls?";tls":""}`);break;default:throw new Error(`Unsupported plugin option: ${t.plugin}`)}}s+=`#${encodeURIComponent(t.name)}`;break;case"ssr":s=`${t.server}:${t.port}:${t.protocol}:${t.cipher}:${t.obfs}:${e.safeEncode(t.password)}/`,s+=`?remarks=${t.name}${t["obfs-param"]?"&obfsparam="+e.safeEncode(t["obfs-param"]):""}${t["protocol-param"]?"&protocolparam="+e.safeEncode(t["protocol-param"]):""}`,s="vmess://"+e.safeEncode(s);break;case"vmess":s={ps:t.name,add:t.server,port:t.port,id:t.uuid,type:"",aid:0,net:t.network||"tcp",tls:t.tls?"tls":""},"ws"===t.network&&(s.path=t["ws-path"]||"/",s.host=t["ws-headers"].Host||t.server),s=e.safeEncode(JSON.stringify(s));break;case"trojan":s=`trojan://${t.password}@${t.server}:${t.port}#${t.name}`;break;default:throw new Error(`Cannot handle proxy type: ${t.type}`)}return s}}}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(AVAILABLE_OPERATORS[e]||AVAILABLE_FILTERS[e])(t)},$process=(e,t)=>-1!==e.name.indexOf("Filter")?processOperator(e,t):-1!==e.name.indexOf("Operator")?processFilter(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={"🏳️‍🌈":["流量","时间","应急","过期","Bandwidth","expire"],"🇦🇨":["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"]};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 objClone(e){return JSON.parse(JSON.stringify(e))}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 UUID(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){const t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)})}function getPlatformFromHeaders(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":"Raw"}function ENV(){const e="undefined"!=typeof $task,t="undefined"!=typeof $loon,s="undefined"!=typeof $httpClient&&!this.isLoon,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}}function HTTP(e={}){const{isQX:t,isLoon:s,isSurge:r}=ENV();const n={};return["GET","POST","PUT","DELETE","HEAD","OPTIONS","PATCH"].forEach(o=>n[o.toLowerCase()]=(n=>(function(n,o){(o=o.hasOwnProperty("url")?o:{url:o}).url=e.baseURL?e.baseURL+o.url:o.url;const a=(o={...e,...o}).timeout,i={onRequest:()=>{},onResponse:e=>e,onTimeout:()=>{},...o.events};let p,l;i.onRequest(n,o),p=t?$task.fetch({method:n,...o}):new Promise((e,t)=>{(r||s?$httpClient:require("request"))[n.toLowerCase()](o,(s,r,n)=>{s?t(s):e({statusCode:r.status||r.statusCode,headers:r.headers,body:n})})});const u=a?new Promise((e,t)=>{l=setTimeout(()=>(i.onTimeout(),t(`${n} URL: ${o.url} exceeds the timeout ${a} ms`)),a)}):null;return(u?Promise.race([u,p]).then(e=>(clearTimeout(l),e)):p).then(e=>i.onResponse(e))})(o,n))),n}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);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),{flag:"w"},e=>console.log(e)))}write(t,a){this.log(`SET ${a}`),-1!==a.indexOf("#")?((a=a.substr(1))===e&&(this.cache=JSON.parse(t)),(n||r)&&$persistentStore.write(t,a),s&&$prefs.setValueForKey(t,a),o&&(this.root[a]=t)):this.cache[a]=t,this.persistCache()}read(t){return this.log(`READ ${t}`),-1===t.indexOf("#")?this.cache[t]:(t=t.substr(1))===e?this.cache:n||r?$persistentStore.read(t):s?$prefs.valueForKey(t):o?this.root[t]:void 0}delete(e){this.log(`DELETE ${e}`),-1!==e.indexOf("#")?(e=e.substr(1),(n||r)&&$persistentStore.write(null,e),s&&$prefs.removeValueForKey(e),o&&delete this.root[e]):delete this.cache[e],this.persistCache()}notify(e,t="",p="",l={}){const u=l["open-url"],c=l["media-url"],f=p+(u?`\n点击跳转: ${u}`:"")+(c?`\n多媒体: ${c}`:"");if(s&&$notify(e,t,p,l),n&&$notification.post(e,t,f),r){let s={};u&&(s.openUrl=u),c&&(s.mediaUrl=c),"{}"===JSON.stringify(s)?$notification.post(e,t,p):$notification.post(e,t,p,s)}if(o||i)if(a){require("push").schedule({title:e,body:(t?t+"\n":"")+f})}else console.log(`${e}\n${t}\n${f}\n\n`)}log(e){this.debug&&console.log(e)}info(e){console.log(e)}error(e){console.log("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 express(e=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:c,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;e{o(s,n,d)},t={method:s,url:n,path:c,query:f,params:u(h.pattern,c),headers:a,body:i},r=p();h.callback(t,r,e).catch(e=>{r.status(500).json({status:"failed",message:`Internal Server Error: ${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",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;eString(e.statusCode).startsWith("4")?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 HTTP().get(r).then(e=>e.body)}catch(e){return Promise.reject(e)}}}function Base64Code(){const e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",t=function(e){const t={};let s=0;const r=e.length;for(;s>>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)},u=function(e){return l(e).replace(a,i)};this.decode=function(e){return u(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,"/"))}}$.read(SUBS_KEY)||$.write({},SUBS_KEY),$.read(COLLECTIONS_KEY)||$.write({},COLLECTIONS_KEY),$.read(SETTINGS_KEY)||$.write({},SETTINGS_KEY),$.info("Initializing Express..."),$app.get("/download/collection/:name",downloadCollection),$app.get("/download/:name",downloadSub),$app.get("/api/IP_API/:server",IP_API),$app.route("/api/sub/:name").get(getSub).patch(updateSub).delete(deleteSub),$app.route("/api/sub").get(getAllSubs).post(newSub).delete(deleteAllSubs),$app.post("/api/refresh",refreshResource),$app.route("/api/collection/:name").get(getCollection).patch(updateCollection).delete(deleteCollection),$app.route("/api/collection").get(getAllCollections).post(newCollection).delete(deleteAllCollections),$app.route("/api/settings").get(getSettings).patch(updateSettings),$app.get("/api/backup",gistBackup),$app.all("/",async(e,t)=>{t.send("Hello from Sub-Store! Made with ❤️ by Peng-YM.")}),$.info("Express initialized"),$app.start();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,u=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,u=t[1].length;l0&&a.push(i);var c={};for(l=0,u=a.length;l"==b[0]?null!=f?f[O]=a(c.shift()):l[O]=a(c.shift()):null!=f?f[O]=o(b):l[O]=o(b)}else null!=f?f[O]=s(c):l[O]=s(c)}else y.match(/^-\s*$/)?(m&&(m=!1,void 0===l.length&&(l=[])),null!=f&&l.push(f),f={},m=!0):(p=y.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(g=h.length-1;g>=0;--g)n.splice.call(n,h[g],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,u=[],c=new n(-1),f=new n(0);c.addChild(f);var d=[],h="";u.push(f),d.push(p);for(var m=0,g=i.length;ml){var $=f;f=new n(p),$.addChild(f),u.push(f),d.push(p)}else if(p=0;--S)if(d[S]==p){f=new n(p),u.push(f),d.push(p),null!=u[S].parent&&u[S].parent.addChild(f),w=!0;break}if(!w)return void e.push("Error: Invalid indentation at line "+m+": "+h)}f.lines.push(h.replace(r.trim,"")),l=p}return c}(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 +const $=API("sub-store"),$app=express();$.http=HTTP({headers:{"User-Agent":"Quantumult%20X"}});const SETTINGS_KEY="settings",SUBS_KEY="subs",COLLECTIONS_KEY="collections",AVAILABLE_FILTERS={"Keyword Filter":KeywordFilter,"Useless Filter":UselessFilter,"Region Filter":RegionFilter,"Regex Filter":RegexFilter,"Type Filter":TypeFilter,"Script Filter":ScriptFilter},AVAILABLE_OPERATORS={"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},AVAILABLE_PRODUCERS=[Raw_Producer,URI_Producer,Surge_Producer,Loon_Producer,QX_Producer,Clash_Producer,Sub_Producer];async function IP_API(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)}async function downloadResource(e){let t=await $.http.get(e).then(e=>e.body);const s=t.indexOf("proxies:");if(-1!==s){const e=t.lastIndexOf("}")+1;t=t.substring(s,e)}return t}async function gistBackup(e,t){const{action:s}=e.query,{gistToken:r}=$.read(SETTINGS_KEY);if(r){const e=new Gist("Auto Generated Sub-Store Backup",r);try{let r;switch(s){case"upload":r=$.read("#sub-store"),await e.upload(JSON.stringify(r)),$.info("上传备份中...");break;case"download":r=await e.download(),$.write(r,"#sub-store"),$.info("还原备份中...")}t.json({status:"success"})}catch(e){const r=`${"upload"===s?"上传":"下载"}备份失败!${e}`;$.error(r),t.status(500).json({status:"failed",message:r})}}else t.status(500).json({status:"failed",message:"未找到Gist备份Token!"})}async function getSettings(e,t){const s=$.read(SETTINGS_KEY);t.json(s)}async function updateSettings(e,t){const s=e.body,r=$.read(SETTINGS_KEY);$.write({...r,...s},SETTINGS_KEY),t.json({status:"success"})}async function refreshResource(e,t){const s=new Base64Code,{url:r}=e.body,n=await downloadResource(r);$.write(n,`#${s.safeEncode(r)}`),t.json({status:"success"})}async function downloadSub(e,t){const{name:s}=e.params,r=e.query.target||getPlatformFromHeaders(e.headers),n=$.read(SUBS_KEY);if(n[s]){const e=n[s];try{const s=await parseSub(e,r);t.send(s)}catch(e){t.status(500).json({status:"failed",message:e})}}else t.status(404).json({status:"failed",message:`订阅${s}不存在!`})}async function parseSub(e,t){let s;const r=(new Base64Code).safeEncode(e.url);if("Raw"===t||"URI"===t){const t=$.read(`#${r}`);t?(await $.wait(10),s=t):(s=await downloadResource(e.url),$.write(s,`#${r}`))}else s=await downloadResource(e.url),$.write(s,`#${e.url}`);$.info("======================================================================="),$.info(`Processing subscription: ${e.name}, target platform ==> ${t}.`),$.info("Initializing parsers...");const n=ProxyParser(t);n.addParsers([Clash_All,URI_SS,URI_SSR,URI_VMess,URI_Trojan,QX_SS,QX_SSR,QX_VMess,QX_Trojan,QX_Http,Loon_SS,Loon_SSR,Loon_VMess,Loon_Trojan,Loon_Http,Surge_SS,Surge_VMess,Surge_Trojan,Surge_Http]),$.info("Parsers initialized.");let o=n.parse(s);for(const t of e.process||[]){let e;if(-1!==t.type.indexOf("Script")){const{mode:s,content:r}=t.args;e="link"===s?await $.http.get(r).then(e=>e.body).catch(e=>{throw new Error(`Error when downloading remote script: ${t.args.content}.\n Reason: ${e}`)}):r}if(-1!==t.type.indexOf("Filter")){const s=AVAILABLE_FILTERS[t.type];if(s){$.info(`Applying filter "${t.type}" with arguments:\n >>> ${JSON.stringify(t.args)||"None"}`);try{o=-1!==t.type.indexOf("Script")?processFilter(s(e),o):processFilter(s(t.args),o)}catch(e){$.error(`Failed to apply filter "${t.type}"!\n REASON: ${e}`)}}}else if(-1!==t.type.indexOf("Operator")){const s=AVAILABLE_OPERATORS[t.type];if(s){$.info(`Applying operator "${t.type}" with arguments: \n >>> ${JSON.stringify(t.args)||"None"}`);try{o=-1!==t.type.indexOf("Script")?processOperator(s(e),o):processOperator(s(t.args),o)}catch(e){t.type}}}}return n.addProducers(AVAILABLE_PRODUCERS),n.produce(o)}function getFlowHeaders(e,t){const s=Object.keys(e).filter(e=>/SUBSCRIPTION-USERINFO/i.test(e))[0];if(s){const t=e[s],r=Number(t.match(/upload=(\d+)/)[1]),n=Number(t.match(/download=(\d+)/)[1]),o=Number(t.match(/total=(\d+)/)[1]),a=(t.match(/expire=(\d+)/)||[])[1];return{key:"Subscription-Userinfo",value:`${r?"upload="+r+";":""}${n?"download="+n+";":""}${o?"total="+o+";":""}${a?"expire="+a+";":""}`}}{let e,s;for(const r of t)-1!==r.name.indexOf("剩余流量")&&(e=r.name),-1!==r.name.indexOf("过期时间")&&(s=r.name);return{key:"Raw-Subscription-Userinfo",value:`${e?"remains="+e+";":""}${s?"expire="+s+";":""}`}}}async function getSub(e,t){const{name:s}=e.params,r=$.read(SUBS_KEY)[s];r?t.json({status:"success",data:r}):t.status(404).json({status:"failed",message:`未找到订阅:${s}!`})}async function newSub(e,t){const s=e.body,r=$.read(SUBS_KEY);r[s.name]&&t.status(500).json({status:"failed",message:`订阅${s.name}已存在!`}),/^[\w-_]*$/.test(s.name)?(r[s.name]=s,$.write(r,SUBS_KEY),t.status(201).json({status:"success",data:s})):t.status(500).json({status:"failed",message:`订阅名称 ${s.name} 中含有非法字符!名称中只能包含英文字母、数字、下划线、横杠。`})}async function updateSub(e,t){const{name:s}=e.params;$.info(`Updating subscription: ${s}`);let r=e.body;const n=$.read(SUBS_KEY);if(n[s]){const e={...n[s],...r};if(s!==r.name){const t=$.read(COLLECTIONS_KEY);for(const e of Object.keys(t)){const n=t[e].subscriptions.indexOf(s);-1!==n&&(t[e].subscriptions[n]=r.name)}delete n[s],n[r.name]=e}else n[s]=e;$.write(n,SUBS_KEY),t.json({status:"success",data:e})}else t.status(500).json({status:"failed",message:`订阅${s}不存在,无法更新!`})}async function deleteSub(e,t){const{name:s}=e.params;let r=$.read(SUBS_KEY);delete r[s],$.write(r,SUBS_KEY);let n=$.read(COLLECTIONS_KEY);for(const e of Object.keys(n))n[e].subscriptions=n[e].subscriptions.filter(e=>e!==s);$.write(n,COLLECTIONS_KEY),t.json({status:"success"})}async function getAllSubs(e,t){const s=$.read(SUBS_KEY);t.json({status:"success",data:s})}async function deleteAllSubs(e,t){$.write({},SUBS_KEY),t.json({status:"success"})}async function downloadCollection(e,t){const{name:s}=e.params,r=$.read(COLLECTIONS_KEY)[s],n=getPlatformFromHeaders(e.headers);if(r){const e=r.subscriptions||[],s=await Promise.all(e.map(async e=>{const t=$.read(SUBS_KEY)[e];try{return parseSub(t,n)}catch(t){return console.log(`ERROR when process subscription: ${e}`),""}}));t.send(s.join("\n"))}else $.notify("[Sub-Store]",`❌ 未找到订阅集:${s}!`),t.status(404).json({status:"failed",message:`❌ 未找到订阅集:${s}!`})}async function getCollection(e,t){const{name:s}=e.params,r=$.read(COLLECTIONS_KEY)[s];r?t.json({status:"success",data:r}):t.status(404).json({status:"failed",message:`未找到订阅集:${s}!`})}async function newCollection(e,t){const s=e.body,r=$.read(COLLECTIONS_KEY);r[s.name]&&t.status(500).json({status:"failed",message:`订阅集${s.name}已存在!`}),/^[\w-_]*$/.test(s.name)?(r[s.name]=s,$.write(r,COLLECTIONS_KEY),t.status(201).json({status:"success",data:s})):t.status(500).json({status:"failed",message:`订阅集名称 ${s.name} 中含有非法字符!名称中只能包含英文字母、数字、下划线、横杠。`})}async function updateCollection(e,t){const{name:s}=e.params;let r=e.body;const n=$.read(COLLECTIONS_KEY);if(n[s]){const e={...n[s],...r};delete n[s],n[r.name||s]=e,$.write(n,COLLECTIONS_KEY),t.json({status:"success",data:e})}else t.status(500).json({status:"failed",message:`订阅集${s}不存在,无法更新!`})}async function deleteCollection(e,t){const{name:s}=e.params;let r=$.read(COLLECTIONS_KEY);delete r[s],$.write(r,COLLECTIONS_KEY),t.json({status:"success"})}async function getAllCollections(e,t){const s=$.read(COLLECTIONS_KEY);t.json({status:"success",data:s})}async function deleteAllCollections(e,t){$.write({},COLLECTIONS_KEY),t.json({status:"success"})}function ProxyParser(e){const t=[],s=[];return{parse:function(s){const r=(s=function(e){let t;if(-1!==e.indexOf("DOCTYPE html"))throw new Error("Invalid format HTML!");if(-1!==e.indexOf("proxies")){console.log("Preprocessing Clash config..."),-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=e.replace(/ -\n.*name/g," - name").replace(/\$|\`/g,"").split("proxy-providers:")[0].split("proxy-groups:")[0].replace(/\"(name|type|server|port|cipher|password|)\"/g,"$1");const s=YAML.eval(e).proxies;t=s.map(e=>JSON.stringify(e))}else if(0===e.indexOf("ssd://")){t=[];const s=new Base64Code;let r=JSON.parse(s.safeDecode(e.split("ssd://")[1]));r.traffic_used,r.traffic_total,r.expiry,r.airport;let n=r.port,o=r.encryption,a=r.password,i=r.servers;for(let e=0;e-1!==e.indexOf(t))?s.safeDecode(e):e).split("\n");for(let e=0;e{try{return r.output(e)}catch(t){return console.log(`ERROR: cannot produce proxy: ${JSON.stringify(e)}\nReason: ${t}`),""}}).filter(e=>e.length>0).join("\n");if("ALL"===r.type)return r.output(t)}throw new Error(`Cannot find any producer for target platform: ${e}`)},addParsers:function(e){e.forEach(e=>t.push(e())),$.info(`${e.length} parser added.`)},addProducers:function(e){e.forEach(e=>s.push(e()))}}}function processFilter(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 processOperator(e,t){let s=objClone(t);try{const t=e.func(s);t&&(s=t)}catch(e){console.log(`ERROR: cannot apply operator ${op.name}! Reason: ${e}`)}return s}function URI_SS(){const e=new Base64Code,t={QX:!0,Loon:!0,Surge:!0,Raw:!0};return{patternTest:e=>/^ss:\/\//.test(e),func:s=>{let r=s.split("ss://")[1];const n={name:decodeURIComponent(s.split("#")[1]),type:"ss",supported:t},o=(r=r.split("#")[0]).match(/@([^\/]*)(\/|$)/)[1],a=o.lastIndexOf(":");n.server=o.substring(0,a),n.port=o.substring(a+1);const i=e.safeDecode(r.split("@")[0]).split(":");if(n.cipher=i[0],n.password=i[1],-1!==r.indexOf("?plugin=")){const e=("plugin="+decodeURIComponent(r.split("?plugin=")[1].split("&")[0])).split(";"),s={};for(const t of e){const[e,r]=t.split("=");e&&(s[e]=r||!0)}switch(s.plugin){case"obfs-local":case"simple-obfs":n.plugin="obfs",n["plugin-opts"]={mode:s.obfs,host:s["obfs-host"]};break;case"v2ray-plugin":n.supported={...t,Loon:!1,Surge:!1},n.obfs="v2ray-plugin",n["plugin-opts"]={mode:"websocket",host:s["obfs-host"],path:s.path||"",tls:s.tls};break;default:throw new Error(`Unsupported plugin option: ${s.plugin}`)}}return n}}}function URI_SSR(){const e=new Base64Code,t={QX:!0,Loon:!0,Surge:!1,Raw:!0};return{patternTest:e=>/^ssr:\/\//.test(e),func:s=>{let r=(s=e.safeDecode(s.split("ssr://")[1])).indexOf(":origin");-1===r&&(r=s.indexOf(":auth_"));const n=s.substring(0,r),o=n.substring(0,n.lastIndexOf(":")),a=n.substring(n.lastIndexOf(":")+1);let i=s.substring(r+1).split("/?")[0].split(":"),p={type:"ssr",server:o,port:a,protocol:i[0],cipher:i[1],obfs:i[2],password:e.safeDecode(i[3]),supported:t};if(i={},(s=s.split("/?")[1].split("&")).length>1)for(const e of s){const[t,s]=e.split("=");i[t]=s}return p={...p,name:e.safeDecode(i.remarks),"protocol-param":e.safeDecode(i.protoparam).replace(/\s/g,"")||"","obfs-param":e.safeDecode(i.obfsparam).replace(/\s/g,"")||""}}}}function URI_VMess(){const e=new Base64Code,t={QX:!0,Loon:!0,Surge:!0,Raw:!0};return{patternTest:e=>/^vmess:\/\//.test(e),func:s=>{s=s.split("vmess://")[1];const r=e.safeDecode(s);if(/=\s*vmess/.test(r)){const e=r.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 s={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||(s.network="ws",s["ws-path"]=t["obfs-uri"],s["ws-headers"]={Host:t["obfs-host"]||s.server}),s.tls&&"false"===t['"tls-verification"']&&(s.scert=!0),s.tls&&t["obfs-host"]&&(s.sni=t["obfs-host"]),s}{const e=JSON.parse(r),s={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&&(s.network="ws",s["ws-path"]=e.path,s["ws-headers"]={Host:e.host||e.add},s.tls&&e.host&&(s.sni=e.host)),!1===e.verify_cert&&(s.scert=!0),s}}}}function URI_Trojan(){const e={QX:!0,Loon:!0,Surge:!0,Raw:!0};return{patternTest:e=>/^trojan:\/\//.test(e),func:t=>{if(-1===t.indexOf(":443"))throw new Error("Trojan port should always be 443!");const s=(t=t.split("trojan://")[1]).split("@")[1].split(":443")[0];return{name:decodeURIComponent(t.split("#")[1].trim())||`[Trojan] ${s}`,type:"trojan",server:s,port:443,password:t.split("@")[0],supported:e}}}}function Clash_All(){return{patternTest:e=>-1!==e.indexOf("{"),func:e=>JSON.parse(e)}}function QX_SS(){const e={QX:!0,Loon:!0,Surge:!0,Raw:!0};return{patternTest:e=>/^shadowsocks\s*=/.test(e.split(",")[0].trim())&&-1===e.indexOf("ssr-protocol"),func:t=>{const s=getQXParams(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:e};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="v2ray-plugin",r.supported.Surge=!1,r.supported.Loon=!1}return r}}}function QX_SSR(){const e={QX:!0,Loon:!0,Surge:!1,Raw:!0};return{patternTest:e=>/^shadowsocks\s*=/.test(e.split(",")[0].trim())&&-1!==e.indexOf("ssr-protocol"),func:t=>{const s=getQXParams(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:e};return s.obfs&&(r.obfs=s.obfs,r["obfs-param"]=s["obfs-host"]),r}}}function QX_VMess(){return{patternTest:e=>/^vmess\s*=/.test(e.split(",")[0].trim()),func:e=>{const t=getQXParams(e),s={type:"vmess",name:t.tag,server:t.server,port:t.port,cipher:t.method||"none",uuid:t.password,alterId:0,tls:"over-tls"===t.obfs||"wss"===t.obfs,udp:JSON.parse(t["udp-relay"]||"false"),tfo:JSON.parse(t["fast-open"]||"false")};return s.tls&&(s.sni=t["obfs-host"]||t.server,s.scert=!JSON.parse(t["tls-verification"]||"true")),"ws"!==t.obfs&&"wss"!==t.obfs||(s.network="ws",s["ws-path"]=t["obfs-uri"],s["ws-headers"]={Host:t["obfs-host"]||t.server}),s}}}function QX_Trojan(){return{patternTest:e=>/^trojan\s*=/.test(e.split(",")[0].trim()),func:e=>{const t=getQXParams(e),s={type:"trojan",name:t.tag,server:t.server,port:t.port,password:t.password,sni:t["tls-host"]||t.server,udp:JSON.parse(t["udp-relay"]||"false"),tfo:JSON.parse(t["fast-open"]||"false")};return s.scert=!JSON.parse(t["tls-verification"]||"true"),s}}}function QX_Http(){return{patternTest:e=>/^http\s*=/.test(e.split(",")[0].trim()),func:e=>{const t=getQXParams(e),s={type:"http",name:t.tag,server:t.server,port:t.port,username:t.username,password:t.password,tls:JSON.parse(t["over-tls"]||"false"),udp:JSON.parse(t["udp-relay"]||"false"),tfo:JSON.parse(t["fast-open"]||"false")};return s.tls&&(s.sni=t["tls-host"]||s.server,s.scert=!JSON.parse(t["tls-verification"]||"true")),s}}}function getQXParams(e){const t=e.split(","),s={},r=["shadowsocks","vmess","http","trojan"];return t.forEach(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 Loon_SS(){return{patternTest:e=>"shadowsocks"===e.split(",")[0].split("=")[1].trim().toLowerCase(),func: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:s.obfs,host:t[6]}),s}}}function Loon_SSR(){return{patternTest:e=>"shadowsocksr"===e.split(",")[0].split("=")[1].trim().toLowerCase(),func: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:{QX:!0,Loon:!0,Surge:!1,Raw:!0},obfs:t[7],"obfs-param":t[8].match(/{(.*)}/)[1]}}}}function Loon_VMess(){return{patternTest:e=>/^.*=\s*vmess/i.test(e.split(",")[0])&&-1===e.indexOf("username"),func: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.scert=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.scert=JSON.parse(t["skip-cert-verify"]||"false")),s}}}function Loon_Trojan(){return{patternTest:e=>/^.*=\s*trojan/i.test(e.split(",")[0])&&-1===e.indexOf("password"),func: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],scert:JSON.parse(t["skip-cert-verify"]||"false")};if(t.length>4){const[r,n]=t[4].split(":");if("tls-name"!==r)throw new Error(`ERROR: unknown option ${r} for line: \n${e}`);s.sni=n}return s}}}function Loon_Http(){return{patternTest:e=>/^.*=\s*http/i.test(e.split(",")[0])&&5===e.split(",").length&&-1===e.indexOf("username")&&-1===e.indexOf("password"),func: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],username:(t[3]||"").replace(/"/g,""),password:(t[4]||"").replace(/"/g,"")};return s.tls&&(s.sni=t["tls-name"]||s.server,s.scert=JSON.parse(t["skip-cert-verify"]||"false")),s}}}function Surge_SS(){return{patternTest:e=>/^.*=\s*ss/.test(e.split(",")[0]),func:e=>{const t=getSurgeParams(e),s={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&&(s.plugin="obfs",s["plugin-opts"]={mode:t.obfs,host:t["obfs-host"]}),s}}}function Surge_VMess(){return{patternTest:e=>/^.*=\s*vmess/.test(e.split(",")[0])&&-1!==e.indexOf("username"),func:e=>{const t=getSurgeParams(e),s={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 s.tls&&(s.scert=JSON.parse(t["skip-cert-verify"]||"false"),s.sni=t.sni||t.server),JSON.parse(t.ws||"false")&&(s.network="ws",s["ws-path"]=t["ws-path"],s["ws-headers"]={Host:t.sni}),s}}}function Surge_Trojan(){return{patternTest:e=>/^.*=\s*trojan/.test(e.split(",")[0])&&-1!==e.indexOf("sni"),func:e=>{const t=getSurgeParams(e);return{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"),scert:JSON.parse(t["skip-cert-verify"]||"false")}}}}function Surge_Http(){return{patternTest:e=>/^.*=\s*http/.test(e.split(",")[0])&&!Loon_Http().patternTest(e),func:e=>{const t=getSurgeParams(e),s={name:t.name,type:"http",server:t.server,port:t.port,tls:JSON.parse(t.tls||"false"),tfo:JSON.parse(t.tfo||"false")};return s.tls&&(s.scert=JSON.parse(t["skip-cert-verify"]||"false"),s.sni=t.sni||t.server),"none"!==t.username&&(s.username=t.username),"none"!==t.password&&(s.password=t.password),s}}}function getSurgeParams(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{let t,s;switch(e.type){case"ss":if(t="","obfs"===e.plugin&&(t=`,obfs=${e["plugin-opts"].mode},obfs-host=${e["plugin-opts"].host}`),"v2ray-plugin"===e.plugin){const{tls:s,host:r,path:n}=e["plugin-opts"];t=`,obfs=${s?"wss":"ws"},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,obfs-host=${e.sni}${e["ws-path"]?",obfs-uri="+e["ws-path"]:""},tls-verification=${e.scert?"false":"true"}`:`,obfs=ws,obfs-host=${e["ws-headers"].Host}${e["ws-path"]?",obfs-uri="+e["ws-path"]:""}`:e.tls&&(t=`,obfs=over-tls,obfs-host=${e.sni},tls-verification=${e.scert?"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:""},tls-verification=${e.scert?"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.scert?"false":"true"},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}`)}}}function Loon_Producer(){return{targetPlatform:"Loon",output: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},path:${e["ws-path"]||"/"}`:",transport:tcp",e.tls&&(t+=`,tls-name=${e.sni},skip-cert-verify:${e.scert}`),`${e.name}=vmess,${e.server},${e.port},${"auto"===e.cipher?"none":e.cipher},"${e.uuid}",over-tls:${e.tls}${t}`;case"trojan":return`${e.name}=trojan,${e.server},${e.port},"${e.password}"${e.sni?",tls-name:"+e.sni:""},skip-cert-verify:${e.scert||"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=`,skip-cert-verify:${e.scert},tls-name:${e.sni}`):r}throw new Error(`Platform Loon does not support proxy type: ${e.type}`)}}}function Surge_Producer(){return{targetPlatform:"Surge",output:e=>{let t,s;switch(e.type){case"ss":if(t="",e.plugin){if("obfs"!==e.plugin)throw new Error(`Platform Surge does not support obfs option: ${e.obfs}`);t=`,obfs=${e["plugin-opts"].mode},obfs-host=${e["plugin-opts"].host}`}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},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.scert?",skip-cert-verify="+e.scert:""}`,r+=e.sni?`,sni=${e.sni}`:""),r;case"trojan":return`${e.name}=trojan,${e.server},${e.port},password=${e.password}${void 0!==e.scert?",skip-cert-verify="+e.scert:""}${e.sni?",sni="+e.sni:""},tfo=${e.tfo||"false"}`;case"http":return s=",tls=false",e.tls&&(s=`,tls=true,skip-cert-verify=${e.scert},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}`)}}}function Raw_Producer(){return{targetPlatform:"Raw",output:e=>JSON.stringify(e)}}function URI_Producer(){const e=new Base64Code;return{targetPlatform:"URI",output:t=>{let s="";switch(t.type){case"ss":const r=`${t.cipher}:${t.password}`;if(s=`ss://${e.safeEncode(r)}@${t.server}:${t.port}/`,t.plugin){s+="?plugin=";const e=t["plugin-opts"];switch(t.plugin){case"obfs":s+=encodeURIComponent(`simple-obfs;obfs=${e.mode}${e.host?";obfs-host="+e.host:""}`);break;case"v2ray-plugin":s+=encodeURIComponent(`v2ray-plugin;obfs=${e.mode}${e.host?";obfs-host"+e.host:""}${e.tls?";tls":""}`);break;default:throw new Error(`Unsupported plugin option: ${t.plugin}`)}}s+=`#${encodeURIComponent(t.name)}`;break;case"ssr":s=`ssr://${t.server}:${t.port}:${t.protocol}:${t.cipher}:${t.obfs}:${e.safeEncode(t.password)}/`,s+=`?remarks=${t.name}${t["obfs-param"]?"&obfsparam="+e.safeEncode(t["obfs-param"]):""}${t["protocol-param"]?"&protocolparam="+e.safeEncode(t["protocol-param"]):""}`;break;case"vmess":s={ps:t.name,add:t.server,port:t.port,id:t.uuid,type:"",aid:0,net:t.network||"tcp",tls:t.tls?"tls":""},"ws"===t.network&&(s.path=t["ws-path"]||"/",s.host=t["ws-headers"].Host||t.server),s="vmess://"+e.safeEncode(JSON.stringify(s));break;case"trojan":s=`trojan://${t.password}@${t.server}:${t.port}#${t.name}`;break;default:throw new Error(`Cannot handle proxy type: ${t.type}`)}return s}}}function Clash_Producer(){return{targetPlatform:"Clash",type:"ALL",output:e=>"proxies:\n"+e.map(e=>(delete e.supported," - "+JSON.stringify(e)+"\n")).join("")}}function Sub_Producer(){return{targetPlatform:"Sub",type:"ALL",output:e=>{const t=e.map(e=>{try{return URI_Producer().output(e)+"\n"}catch(e){return""}}).join("");return(new Base64Code).encode(t)}}}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(AVAILABLE_OPERATORS[e]||AVAILABLE_FILTERS[e])(t)},$process=(e,t)=>-1!==e.name.indexOf("Filter")?processOperator(e,t):-1!==e.name.indexOf("Operator")?processFilter(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={"🏳️‍🌈":["流量","时间","应急","过期","Bandwidth","expire"],"🇦🇨":["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"]};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 objClone(e){return JSON.parse(JSON.stringify(e))}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 UUID(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){const t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)})}function getPlatformFromHeaders(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":"Raw"}function ENV(){const e="undefined"!=typeof $task,t="undefined"!=typeof $loon,s="undefined"!=typeof $httpClient&&!this.isLoon,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}}function HTTP(e={}){const{isQX:t,isLoon:s,isSurge:r}=ENV();const n={};return["GET","POST","PUT","DELETE","HEAD","OPTIONS","PATCH"].forEach(o=>n[o.toLowerCase()]=(n=>(function(n,o){(o=o.hasOwnProperty("url")?o:{url:o}).url=e.baseURL?e.baseURL+o.url:o.url;const a=(o={...e,...o}).timeout,i={onRequest:()=>{},onResponse:e=>e,onTimeout:()=>{},...o.events};let p,u;i.onRequest(n,o),p=t?$task.fetch({method:n,...o}):new Promise((e,t)=>{(r||s?$httpClient:require("request"))[n.toLowerCase()](o,(s,r,n)=>{s?t(s):e({statusCode:r.status||r.statusCode,headers:r.headers,body:n})})});const l=a?new Promise((e,t)=>{u=setTimeout(()=>(i.onTimeout(),t(`${n} URL: ${o.url} exceeds the timeout ${a} ms`)),a)}):null;return(l?Promise.race([l,p]).then(e=>(clearTimeout(u),e)):p).then(e=>i.onResponse(e))})(o,n))),n}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);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),{flag:"w"},e=>console.log(e)))}write(t,a){this.log(`SET ${a}`),-1!==a.indexOf("#")?((a=a.substr(1))===e&&(this.cache=JSON.parse(t)),(n||r)&&$persistentStore.write(t,a),s&&$prefs.setValueForKey(t,a),o&&(this.root[a]=t)):this.cache[a]=t,this.persistCache()}read(t){return this.log(`READ ${t}`),-1===t.indexOf("#")?this.cache[t]:(t=t.substr(1))===e?this.cache:n||r?$persistentStore.read(t):s?$prefs.valueForKey(t):o?this.root[t]:void 0}delete(e){this.log(`DELETE ${e}`),-1!==e.indexOf("#")?(e=e.substr(1),(n||r)&&$persistentStore.write(null,e),s&&$prefs.removeValueForKey(e),o&&delete this.root[e]):delete this.cache[e],this.persistCache()}notify(e,t="",p="",u={}){const l=u["open-url"],c=u["media-url"],f=p+(l?`\n点击跳转: ${l}`:"")+(c?`\n多媒体: ${c}`:"");if(s&&$notify(e,t,p,u),n&&$notification.post(e,t,f),r){let s={};l&&(s.openUrl=l),c&&(s.mediaUrl=c),"{}"===JSON.stringify(s)?$notification.post(e,t,p):$notification.post(e,t,p,s)}if(o||i)if(a){require("push").schedule({title:e,body:(t?t+"\n":"")+f})}else console.log(`${e}\n${t}\n${f}\n\n`)}log(e){this.debug&&console.log(e)}info(e){console.log(e)}error(e){console.log("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 express(e=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:c,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;e{o(s,n,d)},t={method:s,url:n,path:c,query:f,params:l(h.pattern,c),headers:a,body:i},r=p();h.callback(t,r,e).catch(e=>{r.status(500).json({status:"failed",message:`Internal Server Error: ${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",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 u(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;eString(e.statusCode).startsWith("4")?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 HTTP().get(r).then(e=>e.body)}catch(e){return Promise.reject(e)}}}function Base64Code(){const e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",t=function(e){const t={};let s=0;const r=e.length;for(;s>>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("")},u=function(e){return e.replace(/\S{1,4}/g,p)},l=function(e){return u(e).replace(a,i)};this.decode=function(e){return l(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,"/"))}}$.read(SUBS_KEY)||$.write({},SUBS_KEY),$.read(COLLECTIONS_KEY)||$.write({},COLLECTIONS_KEY),$.read(SETTINGS_KEY)||$.write({},SETTINGS_KEY),$app.get("/download/collection/:name",downloadCollection),$app.get("/download/:name",downloadSub),$app.get("/api/IP_API/:server",IP_API),$app.route("/api/sub/:name").get(getSub).patch(updateSub).delete(deleteSub),$app.route("/api/sub").get(getAllSubs).post(newSub).delete(deleteAllSubs),$app.post("/api/refresh",refreshResource),$app.route("/api/collection/:name").get(getCollection).patch(updateCollection).delete(deleteCollection),$app.route("/api/collection").get(getAllCollections).post(newCollection).delete(deleteAllCollections),$app.route("/api/settings").get(getSettings).patch(updateSettings),$app.get("/api/backup",gistBackup),$app.all("/",async(e,t)=>{t.send("Hello from Sub-Store! Made with ❤️ by Peng-YM.")}),$app.start();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,u=0,l=t[1].length;u0&&a.push(o(i)),a}if(t=e.match(r.map)){for(s=0,n=" ",a=[],i="",p=!1,u=0,l=t[1].length;u0&&a.push(i);var c={};for(u=0,l=a.length;u"==b[0]?null!=f?f[O]=a(c.shift()):u[O]=a(c.shift()):null!=f?f[O]=o(b):u[O]=o(b)}else null!=f?f[O]=s(c):u[O]=s(c)}else y.match(/^-\s*$/)?(m&&(m=!1,void 0===u.length&&(u=[])),null!=f&&u.push(f),f={},m=!0):(p=y.match(/^-\s*(.*)/))&&(null!=f?f.push(o(p[1])):(m&&(m=!1,void 0===u.length&&(u=[])),u.push(o(p[1]))))}null!=f&&(m&&(m=!1,void 0===u.length&&(u=[])),u.push(f))}for(g=h.length-1;g>=0;--g)n.splice.call(n,h[g],1);return u}(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,u=0,l=[],c=new n(-1),f=new n(0);c.addChild(f);var d=[],h="";l.push(f),d.push(p);for(var m=0,g=i.length;mu){var $=f;f=new n(p),$.addChild(f),l.push(f),d.push(p)}else if(p=0;--S)if(d[S]==p){f=new n(p),l.push(f),d.push(p),null!=l[S].parent&&l[S].parent.addChild(f),w=!0;break}if(!w)return void e.push("Error: Invalid indentation at line "+m+": "+h)}f.lines.push(h.replace(r.trim,"")),u=p}return c}(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