From 8fc5e58276bdd6b74b158aa629cbf0d78e6e1d6e Mon Sep 17 00:00:00 2001
From: Peng-YM <1048217874pengym@gmail.com>
Date: Tue, 1 Sep 2020 16:18:59 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=BC=93=E5=AD=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
backend/.idea/workspace.xml | 111 +-
backend/sub-store.js | 5385 ++++++++++++++-------------
web/src/components/SortOperator.vue | 2 +-
web/src/config.js | 1 -
4 files changed, 2923 insertions(+), 2576 deletions(-)
diff --git a/backend/.idea/workspace.xml b/backend/.idea/workspace.xml
index 1ffbea4..eef3475 100644
--- a/backend/.idea/workspace.xml
+++ b/backend/.idea/workspace.xml
@@ -22,8 +22,8 @@
-
-
+
+
@@ -91,7 +91,8 @@
-
+
+
@@ -111,8 +112,8 @@
-
-
+
+
@@ -120,10 +121,10 @@
-
-
-
-
+
+
+
+
@@ -131,10 +132,10 @@
-
-
-
-
+
+
+
+
@@ -142,10 +143,10 @@
-
-
-
-
+
+
+
+
@@ -153,10 +154,10 @@
-
-
-
-
+
+
+
+
@@ -164,10 +165,10 @@
-
+
-
-
+
+
@@ -175,10 +176,10 @@
-
+
-
-
+
+
@@ -186,10 +187,10 @@
-
+
-
-
+
+
@@ -197,10 +198,10 @@
-
+
-
-
+
+
@@ -208,10 +209,10 @@
-
+
-
-
+
+
@@ -219,10 +220,10 @@
-
+
-
-
+
+
@@ -230,10 +231,10 @@
-
+
-
-
+
+
@@ -241,10 +242,10 @@
-
+
-
-
+
+
@@ -252,10 +253,10 @@
-
+
-
-
+
+
@@ -263,10 +264,10 @@
-
+
-
-
+
+
@@ -274,10 +275,10 @@
-
+
-
-
+
+
@@ -285,7 +286,7 @@
-
+
diff --git a/backend/sub-store.js b/backend/sub-store.js
index fdef294..815126f 100644
--- a/backend/sub-store.js
+++ b/backend/sub-store.js
@@ -1,5 +1,5 @@
/**
- * Sub-Store v0.1 (Backend)
+ * Sub-Store v0.2 (Backend)
* @Author: Peng-YM
* @Description:
* 适用于QX,Loon,Surge的订阅管理工具。
@@ -9,11 +9,12 @@
* 3. 订阅拆分,组合。
*/
const $ = API("sub-store", true);
+const $code = new Base64Code();
$.http = HTTP("", {
- headers: {
- "User-Agent": "Quantumult"
- }
-})
+ headers: {
+ "User-Agent": "Quantumult",
+ },
+});
// Constants
const SUBS_KEY = "subs";
const COLLECTIONS_KEY = "collections";
@@ -23,7 +24,7 @@ if (!$.read(SUBS_KEY)) $.write({}, SUBS_KEY);
if (!$.read(COLLECTIONS_KEY)) $.write({}, COLLECTIONS_KEY);
// BACKEND API
-$.log("Initializing Express...")
+$.log("Initializing Express...");
const $app = express();
// download
@@ -34,1613 +35,1817 @@ $app.get("/download/:name", downloadSub);
$app.post("/api/refresh", refreshResource);
// subscriptions
-$app.route("/api/sub/:name")
- .get(getSub)
- .patch(updateSub)
- .delete(deleteSub);
+$app.route("/api/sub/:name").get(getSub).patch(updateSub).delete(deleteSub);
-$app.route("/api/sub")
- .get(getAllSubs)
- .post(newSub)
- .delete(deleteAllSubs);
+$app.route("/api/sub").get(getAllSubs).post(newSub).delete(deleteAllSubs);
// collections
-$app.route("/api/collection/:name")
- .get(getCollection)
- .patch(updateCollection)
- .delete(deleteCollection);
+$app
+ .route("/api/collection/:name")
+ .get(getCollection)
+ .patch(updateCollection)
+ .delete(deleteCollection);
-$app.route("/api/collection")
- .get(getAllCollections)
- .post(newCollection)
- .delete(deleteAllCollections);
+$app
+ .route("/api/collection")
+ .get(getAllCollections)
+ .post(newCollection)
+ .delete(deleteAllCollections);
$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.");
});
-$app.start()
-$.log("Express initialized")
-
+$app.start();
+$.log("Express initialized");
// SOME CONSTANTS
-const DEFAULT_SUPPORTED_PLATFORMS = {
- QX: true,
- Loon: true,
- Surge: true,
- Raw: true
-}
+var DEFAULT_SUPPORTED_PLATFORMS = {
+ QX: true,
+ Loon: true,
+ Surge: true,
+ Raw: true,
+};
-const AVAILABLE_FILTERS = {
- "Keyword Filter": KeywordFilter,
- "Useless Filter": UselessFilter,
- "Region Filter": RegionFilter,
- "Regex Filter": RegexFilter,
- "Type Filter": TypeFilter,
- "Script Filter": ScriptFilter
-}
+var AVAILABLE_FILTERS = {
+ "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
-}
+var 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,
+};
/**************************** API -- Subscriptions ***************************************/
// refresh resource
async function refreshResource(req, res) {
- const {url} = req.body;
- const raw = await $.http.get(url).then(resp => resp.body).catch(err => {
- res.status(500).json({
- status: "failed",
- message: `Cannot refresh remote resource: ${url}\n Reason: ${err}`
- });
- });
- $.write(raw, `#${url}`);
- res.json({
- status: "success"
+ const { url } = req.body;
+ const raw = await $.http
+ .get(url)
+ .then((resp) => resp.body)
+ .catch((err) => {
+ res.status(500).json({
+ status: "failed",
+ message: `Cannot refresh remote resource: ${url}\n Reason: ${err}`,
+ });
});
+ $.write(raw, `#${$code.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
- });
- }
- } else {
- res.status(404).json({
- status: "failed",
- message: `订阅${name}不存在!`
- });
+ 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}不存在!`,
+ });
+ }
}
async function parseSub(sub, platform) {
- let raw;
- if (platform === 'Raw') {
- const cache = $.read(`#${sub.url}`);
- if (!cache) {
- raw = await $.http.get(sub.url).then(resp => resp.body).catch(err => {
- throw new Error(err);
- });
- $.write(raw, `#${sub.url}`);
- } else {
- raw = cache;
- }
- } else {
- // always download from url
- raw = await $.http.get(sub.url).then(resp => resp.body).catch(err => {
- throw new Error(err);
+ let raw;
+ const key = $code.safeEncode(sub.url);
+
+ if (platform === "Raw") {
+ const cache = $.read(`#${key}`);
+ if (!cache) {
+ raw = await $.http
+ .get(sub.url)
+ .then((resp) => resp.body)
+ .catch((err) => {
+ throw new Error(err);
});
- $.write(raw, `#${sub.url}`);
+ $.write(raw, `#${key}`);
+ } else {
+ raw = cache;
+ console.log(`Cache hit`);
}
+ } else {
+ // always download from url
+ raw = await $.http
+ .get(sub.url)
+ .then((resp) => resp.body)
+ .catch((err) => {
+ throw new Error(err);
+ });
+ $.write(raw, `#${sub.url}`);
+ }
- $.log("=======================================================================");
- $.log(`Processing subscription: ${sub.name}, target platform ==> ${platform}.`);
- $.log(`Initializing parser...`);
- 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
- ]);
+ $.log(
+ "======================================================================="
+ );
+ $.log(
+ `Processing subscription: ${sub.name}, target platform ==> ${platform}.`
+ );
+ $.log(`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,
+ ]);
- $.log(`Parser initialized.`);
- let proxies = $parser.parse(raw);
+ $.log(`Parsers initialized.`);
+ let proxies = $parser.parse(raw);
- // filters
- const $filter = ProxyFilter();
- // operators
- const $operator = ProxyOperator();
+ // filters
+ const $filter = ProxyFilter();
+ // operators
+ const $operator = ProxyOperator();
- for (const item of sub.process || []) {
- // process script
- if (item.type.indexOf("Script") !== -1) {
- if (item.args && item.args[0].indexOf("http") !== -1) {
- // if this is remote script
- item.args[0] = await $.http.get(item.args[0]).then(resp => resp.body).catch(err => {
- throw new Error(`Error when downloading remote script: ${item.args[0]}.\n Reason: ${err}`);
- });
- }
- }
- if (item.type.indexOf("Filter") !== -1) {
- const filter = AVAILABLE_FILTERS[item.type];
- if (filter) {
- $filter.addFilters(filter(item.args));
- proxies = $filter.process(proxies);
- $.log(`Applying filter "${item.type}" with arguments:\n >>> ${JSON.stringify(item.args) || "None"}`);
- }
- } else if (item.type.indexOf("Operator") !== -1) {
- const operator = AVAILABLE_OPERATORS[item.type];
- if (operator) {
- $operator.addOperators(operator(item.args));
- proxies = $operator.process(proxies);
- $.log(`Applying operator "${item.type}" with arguments: \n >>> ${item.args || "None"}`);
- }
- }
+ for (const item of sub.process || []) {
+ // process script
+ if (item.type.indexOf("Script") !== -1) {
+ if (item.args && item.args[0].indexOf("http") !== -1) {
+ // if this is remote script
+ item.args[0] = await $.http
+ .get(item.args[0])
+ .then((resp) => resp.body)
+ .catch((err) => {
+ throw new Error(
+ `Error when downloading remote script: ${item.args[0]}.\n Reason: ${err}`
+ );
+ });
+ }
}
+ if (item.type.indexOf("Filter") !== -1) {
+ const filter = AVAILABLE_FILTERS[item.type];
+ if (filter) {
+ $filter.addFilters(filter(item.args));
+ proxies = $filter.process(proxies);
+ $.log(
+ `Applying filter "${item.type}" with arguments:\n >>> ${
+ JSON.stringify(item.args) || "None"
+ }`
+ );
+ }
+ } else if (item.type.indexOf("Operator") !== -1) {
+ const operator = AVAILABLE_OPERATORS[item.type];
+ if (operator) {
+ $operator.addOperators(operator(item.args));
+ proxies = $operator.process(proxies);
+ $.log(
+ `Applying operator "${item.type}" with arguments: \n >>> ${
+ item.args || "None"
+ }`
+ );
+ }
+ }
+ }
- // Producers
- $parser.addProducers([
- QX_Producer, Loon_Producer, Surge_Producer, Raw_Producer
- ]);
- return $parser.produce(proxies);
+ // Producers
+ $parser.addProducers([
+ QX_Producer,
+ Loon_Producer,
+ Surge_Producer,
+ Raw_Producer,
+ ]);
+ 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;
- }
- }
- return {
- key: "Raw-Subscription-Userinfo",
- value: `${remains ? "remains=" + remains + ";" : ""}${expire ? "expire=" + expire + ";" : ""}`
- }
+ 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 + ";" : ""
+ }`,
+ };
+ }
}
// 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;
- 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;
+ const { name } = req.params;
+ 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;
}
- $.write(allSubs, SUBS_KEY);
- res.json({
- status: "success",
- data: newSub
- })
+ }
+ // update subscriptions
+ delete allSubs[name];
+ allSubs[sub.name] = newSub;
} else {
- res.status(500).json({
- status: "failed",
- message: `订阅${name}不存在,无法更新!`
- });
+ allSubs[name] = newSub;
}
+ $.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()));
- $.log(`${args.length} parser added.`);
- }
+ function addParsers(args) {
+ args.forEach((a) => parsers.push(a()));
+ $.log(`${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;
- }
-
- 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 (typeof proxy.supported === 'undefined' || proxy.supported[targetPlatform]) {
- 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`);
- }
+ // some lines with weird format may produce errors!
+ let patternMatched;
+ try {
+ patternMatched = patternTest(line);
+ } catch (err) {
+ patternMatched = false;
}
- 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 "";
- }
- }).join("\n");
+ 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 (
+ typeof proxy.supported === "undefined" ||
+ proxy.supported[targetPlatform]
+ ) {
+ result.push(proxy);
+ break;
+ }
+ } catch (err) {
+ console.log(
+ `ERROR: Failed to parse line: \n ${line}\n Reason: ${err}`
+ );
+ }
}
- throw new Error(`Cannot find any producer for target platform: ${targetPlatform}`);
+ }
+ if (!matched) {
+ console.log(`ERROR: Failed to find a rule to parse line: \n${line}\n`);
+ }
}
+ return result;
+ }
- // 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) {
- // Clash YAML format
- // codes are modified from @KOP-XIAO
- // https://github.com/KOP-XIAO/QuantumultX
- if (raw.indexOf("{") !== -1) {
- raw = raw.replace(/: {/g, ": {, ")
- .replace(/, (host|path|tls|mux|skip)/g, ", $1")
- .replace(/{name: /g, "{name: \"")
- .replace(/, server:/g, "\", server:")
- .replace(/{|}/g, "")
- .replace(/,/g, "\n ")
+ 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 "";
}
- raw = raw.replace(/ -\n.*name/g, " - name");
- const proxies = YAML.eval(raw).proxies;
- output = proxies.map(p => JSON.stringify(p));
- } 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");
+ })
+ .join("\n");
+ }
}
+ throw new Error(
+ `Cannot find any producer for target platform: ${targetPlatform}`
+ );
+ }
- 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) {
+ // Clash YAML format
+ // codes are modified from @KOP-XIAO
+ // https://github.com/KOP-XIAO/QuantumultX
+ if (raw.indexOf("{") !== -1) {
+ raw = raw
+ .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");
+ const proxies = YAML.eval(raw).proxies;
+ output = proxies.map((p) => JSON.stringify(p));
+ } 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 ProxyFilter() {
- const filters = [];
+ const filters = [];
- function addFilters(...args) {
- args.forEach(a => filters.push(a));
- }
+ function addFilters(...args) {
+ args.forEach((a) => filters.push(a));
+ }
- // select proxies
- function process(proxies) {
- let selected = FULL(proxies.length, true);
- for (const filter of filters) {
- 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
+ function process(proxies) {
+ let selected = FULL(proxies.length, true);
+ for (const filter of filters) {
+ 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]);
+ }
- return {
- process, addFilters
- }
+ return {
+ process,
+ addFilters,
+ };
}
function ProxyOperator() {
- const operators = [];
+ const operators = [];
- function addOperators(...args) {
- args.forEach(a => operators.push(a));
+ function addOperators(...args) {
+ args.forEach((a) => operators.push(a));
+ }
+
+ // run all operators
+ function process(proxies) {
+ let output = objClone(proxies);
+ for (const op of operators) {
+ try {
+ const output_ = op.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;
+ }
- // run all operators
- function process(proxies) {
- let output = clone(proxies);
- for (const op of operators) {
- try {
- const output_ = op.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 {addOperators, process}
+ return { addOperators, process };
}
/**************************** 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 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,
+ };
+ content = content.split("#")[0]; // strip proxy name
+ // handle IPV4 and IPV6
+ const serverAndPort = content.match(/@([^\/]*)(\/|$)/)[1];
+ const portIdx = serverAndPort.lastIndexOf(":");
+ proxy.server = serverAndPort.substring(0, portIdx);
+ proxy.port = serverAndPort.substring(portIdx + 1);
+
+ const userInfo = Base64.safeDecode(content.split("@")[0]).split(":");
+ proxy.cipher = userInfo[0];
+ proxy.password = userInfo[1];
+
+ // handle obfs
+ const idx = content.indexOf("?plugin=");
+ if (idx !== -1) {
+ const pluginInfo = (
+ "plugin=" +
+ decodeURIComponent(content.split("?plugin=")[1].split("&")[0])
+ ).split(";");
+ const params = {};
+ for (const item of pluginInfo) {
+ const [key, val] = item.split("=");
+ if (key) params[key] = val || true; // some options like "tls" will not have value
+ }
+ switch (params.plugin) {
+ case "obfs-local":
+ case "simple-obfs":
+ proxy.plugin = "obfs";
+ proxy["plugin-opts"] = {
+ mode: params.obfs,
+ host: params["obfs-host"],
+ };
+ break;
+ case "v2ray-plugin":
+ proxy.supported = {
+ ...supported,
+ Loon: false,
+ Surge: false,
+ };
+ proxy.obfs = "v2ray-plugin";
+ proxy["plugin-opts"] = {
+ mode: "websocket",
+ host: params["obfs-host"],
+ path: params.path || "",
+ };
+ break;
+ default:
+ throw new Error(`Unsupported plugin option: ${params.plugin}`);
+ }
}
- const Base64 = new Base64Code();
- const supported = clone(DEFAULT_SUPPORTED_PLATFORMS);
- const func = (line) => {
- // parse url
- let content = line.split("ss://")[1];
-
- const proxy = {
- name: decodeURIComponent(line.split("#")[1]),
- type: "ss",
- supported
- }
- content = content.split("#")[0]; // strip proxy name
- // handle IPV4 and IPV6
- const serverAndPort = content.match(/@([^\/]*)(\/|$)/)[1];
- const portIdx = serverAndPort.lastIndexOf(":");
- proxy.server = serverAndPort.substring(0, portIdx);
- proxy.port = serverAndPort.substring(portIdx + 1);
-
- const userInfo = Base64.safeDecode(content.split("@")[0]).split(":");
- proxy.cipher = userInfo[0];
- proxy.password = userInfo[1];
-
- // handle obfs
- const idx = content.indexOf("?plugin=");
- if (idx !== -1) {
- const pluginInfo = ("plugin=" + decodeURIComponent(content.split("?plugin=")[1].split("&")[0])).split(";");
- const params = {};
- for (const item of pluginInfo) {
- const [key, val] = item.split("=");
- if (key) params[key] = val || true; // some options like "tls" will not have value
- }
- switch (params.plugin) {
- case 'obfs-local':
- case 'simple-obfs':
- proxy.plugin = 'obfs'
- proxy['plugin-opts'] = {
- mode: params.obfs,
- host: params['obfs-host']
- }
- break
- case 'v2ray-plugin':
- proxy.supported = {
- ...DEFAULT_SUPPORTED_PLATFORMS,
- Loon: false,
- Surge: false
- }
- proxy.obfs = 'v2ray-plugin'
- proxy['plugin-opts'] = {
- mode: "websocket",
- host: params['obfs-host'],
- path: params.path || ""
- }
- break
- default:
- throw new Error(`Unsupported plugin option: ${params.plugin}`)
- }
- }
- return proxy;
- }
- return {patternTest, func};
+ return proxy;
+ };
+ return { patternTest, func };
}
// Parse URI SSR format, such as ssr://xxx
function URI_SSR() {
- const patternTest = (line) => {
- return /^ssr:\/\//.test(line);
+ 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 Base64 = new Base64Code();
- const supported = {
- ...DEFAULT_SUPPORTED_PLATFORMS,
- Surge: false
+ 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;
+ };
- 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};
+ return { patternTest, func };
}
// V2rayN URI VMess format
// reference: https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2)
function URI_VMess() {
- const patternTest = (line) => {
- return /^vmess:\/\//.test(line);
- }
- const Base64 = new Base64Code();
- const supported = clone(DEFAULT_SUPPORTED_PLATFORMS);
- 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"),
- };
-
- if (proxy.tls) {
- proxy.sni = params['obfs-host'] || proxy.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'] || proxy.server // if no host provided, use the same as server
- }
- }
-
- 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
- }
- }
- return proxy
+ 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"),
+ };
+
+ if (proxy.tls) {
+ proxy.sni = params["obfs-host"] || proxy.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"] || proxy.server, // if no host provided, use the same as server
+ };
+ }
+
+ 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,
+ };
+ }
+ return proxy;
}
- return {patternTest, func};
+ };
+ return { patternTest, func };
}
// Trojan URI format
function URI_Trojan() {
- const patternTest = (line) => {
- return /^trojan:\/\//.test(line);
+ 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!");
}
- const supported = clone(DEFAULT_SUPPORTED_PLATFORMS);
- 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];
+ line = line.split("trojan://")[1];
+ const server = line.split("@")[1].split(":443")[0];
- return {
- name: `[Trojan] ${server}`, // trojan uri has no server tag!
- type: "trojan",
- server,
- port: 443,
- password: line.split("@")[0],
- supported
- }
- }
- return {patternTest, func};
+ return {
+ name: `[Trojan] ${server}`, // trojan uri has 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 patternTest = (line) => {
- return /^shadowsocks\s*=/.test(line.split(",")[0].trim()) && line.indexOf("ssr-protocol") === -1;
+ 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 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: clone(DEFAULT_SUPPORTED_PLATFORMS)
- };
- // 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};
+ // 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 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 supported = {
- ...DEFAULT_SUPPORTED_PLATFORMS,
- Surge: false
+ // handle obfs options
+ if (params.obfs) {
+ proxy.obfs = params.obfs;
+ proxy["obfs-param"] = params["obfs-host"];
}
- 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};
+ return proxy;
+ };
+ return { patternTest, func };
}
function QX_VMess() {
- const patternTest = (line) => {
- return /^vmess\s*=/.test(line.split(",")[0].trim());
+ 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 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 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 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}
+ 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 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 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) => {
- const [key, value] = g.split("=");
- 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) => {
+ const [key, value] = g.split("=");
+ 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 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, ""),
+ };
+ // handle obfs
+ if (params.length > 5) {
+ proxy.plugin = "obfs";
+ proxy["plugin-opts"] = {
+ mode: proxy.obfs,
+ host: params[6],
+ };
}
- 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};
+ 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 = clone(DEFAULT_SUPPORTED_PLATFORMS);
- supported.Surge = false;
- return {
- name: line.split("=")[0].trim(),
- type: "ssr",
- server: params[1],
- port: params[2],
- cipher: params[3],
- password: params[4].replace(/"/g, ""),
- protocol: params[5],
- "protocol-param": params[6].match(/{(.*)}/)[1],
- supported,
- obfs: params[7],
- 'obfs-param': params[8].match(/{(.*)}/)[1]
- }
- }
- return {patternTest, func};
+ 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,
+ };
+ 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 };
}
function Loon_VMess() {
- const patternTest = (line) => {
- // distinguish between surge vmess
- return /^.*=\s*vmess/i.test(line.split(",")[0]) && line.indexOf("username") === -1;
+ 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,
+ };
+ // get transport options
+ params = params.splice(5);
+ for (const item of params) {
+ const [key, val] = item.split(":");
+ params[key] = val;
}
- 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,
- }
- // 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;
+ 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");
}
- return {patternTest, func};
+ 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 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')
- }
- // 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;
+ 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}
+ 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 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, ""),
+ };
+ if (proxy.tls) {
+ proxy.sni = params["tls-name"] || proxy.server;
+ proxy.scert = JSON.parse(params["skip-cert-verify"] || "false");
}
- 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}
+ return proxy;
+ };
+ return { patternTest, func };
}
/**************************** Surge ***************************************/
function Surge_SS() {
- const patternTest = (line) => {
- return /^.*=\s*ss/.test(line.split(",")[0]);
+ 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"),
+ };
+ // handle obfs
+ if (params.obfs) {
+ proxy.plugin = "obfs";
+ proxy["plugin-opts"] = {
+ mode: params.obfs,
+ host: params["obfs-host"],
+ };
}
- 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}
+ return proxy;
+ };
+ return { patternTest, func };
}
function Surge_VMess() {
- const patternTest = (line) => {
- return /^.*=\s*vmess/.test(line.split(",")[0]) && line.indexOf("username") !== -1;
+ 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"),
+ };
+ if (proxy.tls) {
+ proxy.scert = JSON.parse(params["skip-cert-verify"] || "false");
+ proxy.sni = params["sni"] || params.server;
}
- 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;
+ // use websocket
+ if (JSON.parse(params.ws || "false")) {
+ proxy.network = "ws";
+ proxy["ws-path"] = params["ws-path"];
+ proxy["ws-headers"] = {
+ Host: params.sni,
+ };
}
- return {patternTest, func};
+ 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 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"),
+ };
+ if (proxy.tls) {
+ proxy.scert = JSON.parse(params["skip-cert-verify"] || "false");
+ proxy.sni = params.sni || params.server;
}
- 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}
+ 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}`;
- }
- 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},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}`;
+ 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}`;
}
- throw new Error(`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`);
+ 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
+ },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}`;
}
- 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}`);
- }
- }
-
- 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},tls-name:${proxy.sni},skip-cert-verify:${proxy.scert}`;
- 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;
+ 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}`
+ );
+ }
}
- throw new Error(`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`);
+
+ 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},tls-name:${proxy.sni},skip-cert-verify:${proxy.scert}`;
+ 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;
}
- 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}`);
- }
- }
- 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 += `,skip-cert-verify=${proxy.scert},sni=${proxy.sni}`;
- }
- return config;
- case 'trojan':
- return `${proxy.name}=trojan,${proxy.server},${proxy.port},password=${proxy.password},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'}`;
+ 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}`
+ );
+ }
}
- throw new Error(`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`);
+ 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 += `,skip-cert-verify=${proxy.scert},sni=${proxy.sni}`;
+ }
+ return config;
+ case "trojan":
+ return `${proxy.name}=trojan,${proxy.server},${proxy.port},password=${
+ proxy.password
+ },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 {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 };
}
/**************************** Operators ***************************************/
// force to set some properties (e.g., scert, udp, tfo, etc.)
function SetPropertyOperator(key, val) {
- return {
- name: "Set Property Operator",
- func: proxies => {
- return proxies.map(p => {
- p[key] = val;
- return p;
- })
- }
- }
+ return {
+ name: "Set Property Operator",
+ func: (proxies) => {
+ return proxies.map((p) => {
+ p[key] = val;
+ 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);
- }
- }
- }
+function SortOperator(order = "asc") {
+ return {
+ name: "Sort Operator",
+ func: (proxies) => {
+ switch (order) {
+ case "asc":
+ case "desc":
+ return proxies.sort((a, b) => {
+ let res = a.name > b.name ? 1 : -1;
+ res *= order === "desc" ? -1 : 1;
+ return res;
+ });
+ case "random":
+ return shuffle(proxies);
+ default:
+ throw new Error("Unknown sort option: " + order);
+ }
+ },
+ };
}
// sort by keywords
function KeywordSortOperator(keywords) {
- return {
- name: "Keyword Sort Operator",
- func: proxies => proxies.sort((a, b) => {
- const oA = getKeywordOrder(keywords, a.name);
- const oB = getKeywordOrder(keywords, b.name);
- if (oA && !oB) return -1;
- if (oB && !oA) return 1;
- if (oA && oB) return oA < oB ? -1 : 1;
- if ((!oA && !oB) || (oA && oB && oA === oB)) return a.name < b.name ? -1 : 1; // fallback to normal sort
- })
- }
+ 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.replace(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.replace(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 => {
- return {
- old: k,
- now: ""
- }
- })
+ const keywords_ = keywords.map((k) => {
return {
- name: "Keyword Delete Operator",
- func: KeywordRenameOperator(keywords_).func
- }
+ old: k,
+ now: "",
+ };
+ });
+ return {
+ name: "Keyword Delete Operator",
+ func: KeywordRenameOperator(keywords_).func,
+ };
}
// delete regex operator
// regex: ['a', 'b', 'c']
function RegexDeleteOperator(regex) {
- const regex_ = regex.map(r => {
- return {
- expr: r,
- now: ""
- }
- });
+ const regex_ = regex.map((r) => {
return {
- name: "Regex Delete Operator",
- func: RegexRenameOperator(regex_).func
- }
+ expr: r,
+ now: "",
+ };
+ });
+ return {
+ name: "Regex Delete Operator",
+ func: RegexRenameOperator(regex_).func,
+ };
}
// use base64 encoded script to rename
@@ -1655,98 +1860,106 @@ function RegexDeleteOperator(regex) {
2. Always declare variable before using it!
*/
function ScriptOperator(script) {
- return {
- name: "Script Operator",
- func: (proxies) => {
- ;(function () {
- // interface to get internal operators
- const $get = (name, args) => {
- const operator = AVAILABLE_OPERATORS[name];
- if (operator) {
- return operator(args).func;
- } else {
- throw new Error(`No operator named ${name} is found!`);
- }
- };
- eval(script);
- return func(proxies);
- })();
- }
- }
+ return {
+ name: "Script Operator",
+ func: (proxies) => {
+ (function () {
+ // interface to get internal operators
+ const $get = (name, args) => {
+ const operator = AVAILABLE_OPERATORS[name];
+ if (operator) {
+ return operator(args).func;
+ } else {
+ throw new Error(`No operator named ${name} is found!`);
+ }
+ };
+ eval(script);
+ return func(proxies);
+ })();
+ },
+ };
}
/**************************** 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
@@ -1761,164 +1974,288 @@ function TypeFilter(types) {
2. Always declare variables before using them!
*/
function ScriptFilter(script) {
- return {
- name: "Script Filter",
- func: (proxies) => {
- !(function () {
- // interface to get internal filters
- const $get = (name, args) => {
- const filter = AVAILABLE_FILTERS[name];
- if (filter) {
- return filter(args).func;
- } else {
- throw new Error(`No filter named ${name} is found!`);
- }
- };
- eval(script);
- return func(proxies);
- })();
- }
- }
+ return {
+ name: "Script Filter",
+ func: (proxies) => {
+ !(function () {
+ // interface to get internal filters
+ const $get = (name, args) => {
+ const filter = AVAILABLE_FILTERS[name];
+ if (filter) {
+ return filter(args).func;
+ } else {
+ throw new Error(`No filter named ${name} is found!`);
+ }
+ };
+ eval(script);
+ return func(proxies);
+ })();
+ },
+ };
}
/******************************** 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", "巴西", "圣保罗"],
- "🇨🇦": ["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", "巴西", "圣保罗"],
+ "🇨🇦": [
+ "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();
}
-// clone an object
-function clone(obj) {
- return JSON.parse(JSON.stringify(obj))
+// objClone an object
+function objobjClone(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) {
+ // While there remain elements to shuffle...
+ while (0 !== currentIndex) {
+ // Pick a remaining element...
+ randomIndex = Math.floor(Math.random() * currentIndex);
+ currentIndex -= 1;
- // 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;
- }
- }
- if (UA.indexOf("Quantumult%20X") !== -1) {
- return "QX";
- } else if (UA.indexOf("Surge") !== -1) {
- return "Surge";
- } else if (UA.indexOf("Decar") !== -1) {
- return "Loon";
- } else {
- // browser
- return "Raw";
+ 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) {
+ return "Loon";
+ } else {
+ // browser
+ return "Raw";
+ }
}
/*********************************** OpenAPI *************************************/
@@ -1935,528 +2272,529 @@ function ENV() {
}
function HTTP(baseURL, 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 = typeof options === "string" ? {url: options} : options;
- options.url = baseURL ? 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 = typeof options === "string" ? { url: options } : options;
+ options.url = baseURL ? 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))
+ });
+ });
}
- const http = {};
- methods.forEach(
- (method) =>
- (http[method.toLowerCase()] = (options) => send(method, options))
- );
- return http;
+ 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;
}
function API(name = "untitled", debug = false) {
- const {isQX, isLoon, isSurge, isNode, isJSBox} = ENV();
- return new (class {
- constructor(name, debug) {
- this.name = name;
- this.debug = debug;
+ const { isQX, isLoon, isSurge, isNode, isJSBox } = 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;
- }
- })();
- 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);
- });
- };
+ return {
+ fs,
+ };
+ } else {
+ return null;
}
+ })();
+ this.initCache();
- // persistance
+ const delay = (t, v) =>
+ new Promise(function (resolve) {
+ setTimeout(resolve.bind(null, v), t);
+ });
- // 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 = {};
+ // persistance
- // 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`)
- );
- }
- }
+ // 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)
+ );
}
+ this.root = {};
- // 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)
- );
- }
+ // 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`)
+ );
}
+ }
+ }
- write(data, key) {
- this.log(`SET ${key}`);
- if (key.indexOf("#") !== -1) {
- key = key.substr(1);
- 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();
+ // 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 (isSurge || isLoon) {
+ $persistentStore.write(data, key);
}
-
- read(key) {
- this.log(`READ ${key}`);
- if (key.indexOf("#") !== -1) {
- key = key.substr(1);
- 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 (isQX) {
+ $prefs.setValueForKey(data, 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 (isNode) {
+ this.root[key] = data;
}
+ } else {
+ this.cache[key] = data;
+ }
+ 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) $notification.post(title, subtitle, content, openURL);
- if (isNode) {
- 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`);
- }
- }
+ read(key) {
+ this.log(`READ ${key}`);
+ if (key.indexOf("#") !== -1) {
+ key = key.substr(1);
+ if (isSurge || isLoon) {
+ return $persistentStore.read(key);
}
-
- // other helper functions
- log(msg) {
- if (this.debug) console.log(msg);
+ if (isQX) {
+ return $prefs.valueForKey(key);
}
-
- info(msg) {
- console.log(msg);
+ if (isNode) {
+ return this.root[key];
}
+ } else {
+ return this.cache[key];
+ }
+ }
- error(msg) {
- console.log("ERROR: " + msg);
+ delete(key) {
+ this.log(`DELETE ${key}`);
+ if (key.indexOf("#") !== -1) {
+ key = key.substr(1);
+ if (isSurge & isLoon) {
+ $persistentStore.write(null, key);
}
- })(name, debug);
+ if (isQX) {
+ $prefs.removeValueForKey(key);
+ }
+ if (isNode) {
+ delete this.root[key];
+ }
+ } 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) $notification.post(title, subtitle, content, openURL);
+ if (isNode) {
+ 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);
+ }
+ })(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"
- };
+ 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, () => {
- $.log(`Express started on port: ${port}`);
- })
- }
- 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();
- if (typeof handler.callback === 'undefined') {
- $.notify("FUCK")
- }
- 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"
- });
- }
- };
-
- const app = {};
-
- // attach http methods
- METHODS_NAMES.forEach((method) => {
- app[method.toLowerCase()] = (pattern, callback) => {
- // add handler
- handlers.push({method, pattern, callback});
- };
+ // 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();
});
- // 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;
- };
-
- // start service
+ // adapter
app.start = () => {
- dispatch($request);
+ app.listen(port, () => {
+ $.log(`Express started on port: ${port}`);
+ });
};
-
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();
+ if (typeof handler.callback === "undefined") {
+ $.notify("FUCK");
+ }
+ 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",
+ });
+ }
+ };
+
+ const app = {};
+
+ // attach http methods
+ METHODS_NAMES.forEach((method) => {
+ app[method.toLowerCase()] = (pattern, callback) => {
+ // add handler
+ handlers.push({ method, pattern, callback });
+ };
+ });
+
+ // 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;
+ };
+
+ // 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;
- }
+ 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
- };
- if (isQX) {
- $done(response);
- } else if (isLoon || isSurge) {
- $done({
- response,
- });
- }
- }
-
- end() {
- this.send();
- }
-
- html(data) {
- this.set("Content-Type", "text/html;charset=UTF-8");
- this.send(data);
- }
-
- json(data) {
- this.set("Content-Type", "application/json;charset=UTF-8");
- this.send(JSON.stringify(data));
- }
-
- set(key, val) {
- headers[key] = val;
- return this;
- }
- })();
- }
-
- 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;
- }
- }
- 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,
+ send(body = "") {
+ const response = {
+ status: isQX ? STATUS_CODE_MAP[statusCode] : statusCode,
+ body,
+ headers,
};
- }
-
- 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;
+ if (isQX) {
+ $done(response);
+ } else if (isLoon || isSurge) {
+ $done({
+ response,
+ });
}
+ }
+
+ end() {
+ this.send();
+ }
+
+ html(data) {
+ this.set("Content-Type", "text/html;charset=UTF-8");
+ this.send(data);
+ }
+
+ json(data) {
+ this.set("Content-Type", "application/json;charset=UTF-8");
+ this.send(JSON.stringify(data));
+ }
+
+ set(key, val) {
+ headers[key] = val;
+ return this;
+ }
+ })();
+ }
+
+ 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;
+ }
}
+ 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;
+ }
+ }
}
/******************************** Base 64 *********************************************/
@@ -2464,139 +2802,152 @@ function express(port = 3000) {
// 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)));
+ // 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 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, "/"));
+ };
+ 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, "/"));
+ };
}
/*
@@ -2606,387 +2957,383 @@ 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 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(
+ "([^\\'\\\"#]+([\\'\\\"][^\\'\\\"]*[\\'\\\"])*)*(#.*)?"
+ ),
+ };
- /**
- * @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;
+ }
+ }
- if (line.match(invalidLine)) {
- continue;
- }
+ currentBlock.lines.push(line.replace(regex["trim"], ""));
+ curLevel = level;
+ }
- if (m = regLevel.exec(line)) {
- level = m[1].length;
- } else
- level = 0;
+ return result;
+ }
- 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;
+ function processValue(val) {
+ val = val.replace(regex["trim"], "");
+ var m = null;
- 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 (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;
+ }
- if (!added) {
- errors.push("Error: Invalid indentation at line " + i + ": " + line);
- return;
- }
- }
+ content += c;
+ }
- currentBlock.lines.push(line.replace(regex["trim"], ""));
- curLevel = level;
+ 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 (currentObj != null) res.push(currentObj);
+ currentObj = {};
+ isMap = true;
+ }
- return result;
- }
+ 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 = {};
- 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 (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 (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]));
+ 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);
}
- 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]);
+ } 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 = [];
}
- 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 (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);
- }
+ }
+ 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 = [];
+ }
}
-
- for (var j = processedBlocks.length - 1; j >= 0; --j) {
- blocks.splice.call(blocks, processedBlocks[j], 1);
- }
-
- return res;
+ res.push(processValue(m[1]));
+ }
}
+ }
- function semanticAnalysis(blocks) {
- var res = processBlock(blocks.children);
- return res;
+ if (currentObj != null) {
+ if (isMap) {
+ isMap = false;
+ if (typeof res.length === "undefined") {
+ res = [];
+ }
}
+ res.push(currentObj);
+ }
+ }
- function preProcess(src) {
- var m;
- var lines = src.split("\n");
+ for (var j = processedBlocks.length - 1; j >= 0; --j) {
+ blocks.splice.call(blocks, processedBlocks[j], 1);
+ }
- var r = regex["comment"];
+ return res;
+ }
- for (var i in lines) {
- if (m = (typeof lines[i] === 'string' && lines[i].match(r))) {
- /* var cmt = "";
+ 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")
@@ -2994,51 +3341,51 @@ var YAML =
else
lines[i] = "";
*/
- if (typeof m[3] !== "undefined") {
- lines[i] = m[0].substr(0, m[0].length - m[3].length);
- }
- }
- }
-
- return lines.join("\n");
+ if (typeof m[3] !== "undefined") {
+ lines[i] = m[0].substr(0, m[0].length - m[3].length);
}
+ }
+ }
- 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 lines.join("\n");
+ }
- return res;
- }
+ 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 {
- /**
- * Parse a YAML file from a string.
- * @param {String} str String with the YAML file contents.
- * @function
- */
- eval: eval,
+ return res;
+ }
- /**
- * Get errors found when parsing the last file.
- * @function
- * @returns Errors found when parsing the last file.
- */
- getErrors: function () {
- return errors;
- },
+ return {
+ /**
+ * Parse a YAML file from a string.
+ * @param {String} str String with the YAML file contents.
+ * @function
+ */
+ eval: eval,
- /**
- * Get the time it took to parse the last file.
- * @function
- * @returns Time in milliseconds.
- */
- getProcessingTime: function () {
- return processing_time;
- }
- }
- })();
\ No newline at end of file
+ /**
+ * 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;
+ },
+ };
+})();
diff --git a/web/src/components/SortOperator.vue b/web/src/components/SortOperator.vue
index 11ea471..d4d3b54 100644
--- a/web/src/components/SortOperator.vue
+++ b/web/src/components/SortOperator.vue
@@ -10,7 +10,7 @@
keyboard_arrow_down
-
+
mdi-delete
diff --git a/web/src/config.js b/web/src/config.js
index 33c10f1..0655575 100644
--- a/web/src/config.js
+++ b/web/src/config.js
@@ -1,3 +1,2 @@
const DEBUG = process.env.NODE_ENV === "development";
-
export const BACKEND_BASE = DEBUG ? `http://192.168.1.134:3000` : `https://sub.store`;
\ No newline at end of file