mirror of
https://git.mirrors.martin98.com/https://github.com/sub-store-org/Sub-Store.git
synced 2025-06-04 11:13:59 +08:00
增加 Loon UDP relay 的支持
This commit is contained in:
parent
e72745fbce
commit
c225751b6f
@ -2432,6 +2432,7 @@ var ProxyUtils = (function () {
|
|||||||
const targetPlatform = "Loon";
|
const targetPlatform = "Loon";
|
||||||
const produce = (proxy) => {
|
const produce = (proxy) => {
|
||||||
let obfs_opts, tls_opts;
|
let obfs_opts, tls_opts;
|
||||||
|
const udp_opts = proxy.udp ? ",udp=true" : "";
|
||||||
switch (proxy.type) {
|
switch (proxy.type) {
|
||||||
case "ss":
|
case "ss":
|
||||||
obfs_opts = ",,";
|
obfs_opts = ",,";
|
||||||
@ -2446,7 +2447,7 @@ var ProxyUtils = (function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${proxy.name}=shadowsocks,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"${obfs_opts}`;
|
return `${proxy.name}=shadowsocks,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"${obfs_opts}${udp_opts}`;
|
||||||
case "ssr":
|
case "ssr":
|
||||||
return `${proxy.name}=shadowsocksr,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}",${proxy.protocol},{${proxy["protocol-param"] || ""}},${proxy.obfs},{${proxy["obfs-param"] || ""}}`;
|
return `${proxy.name}=shadowsocksr,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}",${proxy.protocol},{${proxy["protocol-param"] || ""}},${proxy.obfs},{${proxy["obfs-param"] || ""}}`;
|
||||||
case "vmess":
|
case "vmess":
|
||||||
|
4
backend/sub-store.min.js
vendored
4
backend/sub-store.min.js
vendored
File diff suppressed because one or more lines are too long
@ -2,7 +2,7 @@
|
|||||||
Sub-Store 自动同步配置到gist
|
Sub-Store 自动同步配置到gist
|
||||||
由于机制特殊,注意需要按照说明进行配置!
|
由于机制特殊,注意需要按照说明进行配置!
|
||||||
|
|
||||||
「QX配置」
|
「QX配置」
|
||||||
[http_backend]
|
[http_backend]
|
||||||
https://raw.githubusercontent.com/Peng-YM/Sub-Store/master/backend/sub-store.min.js, tag=Sub-Store, path=/, enabled=true
|
https://raw.githubusercontent.com/Peng-YM/Sub-Store/master/backend/sub-store.min.js, tag=Sub-Store, path=/, enabled=true
|
||||||
[task_local]
|
[task_local]
|
||||||
|
@ -45,7 +45,9 @@ var ProxyUtils = (function () {
|
|||||||
function Base64Encoded() {
|
function Base64Encoded() {
|
||||||
const name = "Base64 Pre-processor";
|
const name = "Base64 Pre-processor";
|
||||||
|
|
||||||
const keys = ["dm1lc3M", "c3NyOi8v", "dHJvamFu", "c3M6Ly", "c3NkOi8v"];
|
const keys = ["dm1lc3M", "c3NyOi8v", "dHJvamFu", "c3M6Ly", "c3NkOi8v",
|
||||||
|
"c2hhZG93", "aHR0c"
|
||||||
|
];
|
||||||
|
|
||||||
const test = function (raw) {
|
const test = function (raw) {
|
||||||
return keys.some(k => raw.indexOf(k) !== -1);
|
return keys.some(k => raw.indexOf(k) !== -1);
|
||||||
@ -244,21 +246,21 @@ var ProxyUtils = (function () {
|
|||||||
supported,
|
supported,
|
||||||
};
|
};
|
||||||
// get other params
|
// get other params
|
||||||
params = {};
|
const other_params = {};
|
||||||
line = line.split("/?")[1].split("&");
|
line = line.split("/?")[1].split("&");
|
||||||
if (line.length > 1) {
|
if (line.length > 1) {
|
||||||
for (const item of line) {
|
for (const item of line) {
|
||||||
const [key, val] = item.split("=");
|
const [key, val] = item.split("=");
|
||||||
params[key] = val;
|
other_params[key] = val.trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
proxy = {
|
proxy = {
|
||||||
...proxy,
|
...proxy,
|
||||||
name: Base64.safeDecode(params.remarks),
|
name: other_params.remarks ? Base64.safeDecode(other_params.remarks) : proxy.server,
|
||||||
"protocol-param":
|
"protocol-param":
|
||||||
Base64.safeDecode(params.protoparam).replace(/\s/g, "") || "",
|
Base64.safeDecode(other_params.protoparam || "").replace(/\s/g, ""),
|
||||||
"obfs-param":
|
"obfs-param":
|
||||||
Base64.safeDecode(params.obfsparam).replace(/\s/g, "") || "",
|
Base64.safeDecode(other_params.obfsparam || "").replace(/\s/g, ""),
|
||||||
};
|
};
|
||||||
return proxy;
|
return proxy;
|
||||||
};
|
};
|
||||||
@ -280,8 +282,8 @@ var ProxyUtils = (function () {
|
|||||||
line = line.split("vmess://")[1];
|
line = line.split("vmess://")[1];
|
||||||
const content = Base64.safeDecode(line);
|
const content = Base64.safeDecode(line);
|
||||||
if (/=\s*vmess/.test(content)) {
|
if (/=\s*vmess/.test(content)) {
|
||||||
const partitions = content.split(",").map((p) => p.trim());
|
|
||||||
// Quantumult VMess URI format
|
// Quantumult VMess URI format
|
||||||
|
const partitions = content.split(",").map((p) => p.trim());
|
||||||
// get keyword params
|
// get keyword params
|
||||||
const params = {};
|
const params = {};
|
||||||
for (const part of partitions) {
|
for (const part of partitions) {
|
||||||
@ -299,16 +301,21 @@ var ProxyUtils = (function () {
|
|||||||
cipher: partitions[3],
|
cipher: partitions[3],
|
||||||
uuid: partitions[4].match(/^"(.*)"$/)[1],
|
uuid: partitions[4].match(/^"(.*)"$/)[1],
|
||||||
tls: params.obfs === "over-tls" || params.obfs === "wss",
|
tls: params.obfs === "over-tls" || params.obfs === "wss",
|
||||||
udp: JSON.parse(params["udp-relay"] || "false"),
|
|
||||||
tfo: JSON.parse(params["fast-open"] || "false"),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (typeof params['udp-relay'] !== "undefined") proxy.udp = JSON.parse(params["udp-relay"]);
|
||||||
|
if (typeof params['fast-open'] !== "undefined") proxy.udp = JSON.parse(params["fast-open"]);
|
||||||
|
|
||||||
// handle ws headers
|
// handle ws headers
|
||||||
if (params.obfs === "ws" || params.obfs === "wss") {
|
if (params.obfs === "ws" || params.obfs === "wss") {
|
||||||
proxy.network = "ws";
|
proxy.network = "ws";
|
||||||
proxy["ws-path"] = params["obfs-uri"];
|
proxy["ws-path"] = (params["obfs-path"] || '"/"').match(/^"(.*)"$/)[1];
|
||||||
|
let obfs_host = params["obfs-header"];
|
||||||
|
if (obfs_host && obfs_host.indexOf("Host") !== -1) {
|
||||||
|
obfs_host = obfs_host.match(/Host:\s*([a-zA-Z0-9-.]*)/)[1];
|
||||||
|
}
|
||||||
proxy["ws-headers"] = {
|
proxy["ws-headers"] = {
|
||||||
Host: params["obfs-host"] || proxy.server, // if no host provided, use the same as server
|
Host: obfs_host || proxy.server, // if no host provided, use the same as server
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,19 +374,15 @@ var ProxyUtils = (function () {
|
|||||||
|
|
||||||
const parse = (line) => {
|
const parse = (line) => {
|
||||||
const supported = {};
|
const supported = {};
|
||||||
// trojan forces to use 443 port
|
|
||||||
if (line.indexOf(":443") === -1) {
|
|
||||||
throw new Error("Trojan port should always be 443!");
|
|
||||||
}
|
|
||||||
line = line.split("trojan://")[1];
|
line = line.split("trojan://")[1];
|
||||||
const server = line.split("@")[1].split(":443")[0];
|
const [server, port] = line.split("@")[1].split("?")[0].split(":");
|
||||||
const name = decodeURIComponent(line.split("#")[1].trim());
|
const name = decodeURIComponent(line.split("#")[1].trim());
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: name || `[Trojan] ${server}`, // trojan uri may have no server tag!
|
name: name || `[Trojan] ${server}`, // trojan uri may have no server tag!
|
||||||
type: "trojan",
|
type: "trojan",
|
||||||
server,
|
server,
|
||||||
port: 443,
|
port,
|
||||||
password: line.split("@")[0],
|
password: line.split("@")[0],
|
||||||
supported,
|
supported,
|
||||||
};
|
};
|
||||||
@ -836,8 +839,10 @@ var ProxyUtils = (function () {
|
|||||||
if (JSON.parse(params.ws || "false")) {
|
if (JSON.parse(params.ws || "false")) {
|
||||||
proxy.network = "ws";
|
proxy.network = "ws";
|
||||||
proxy["ws-path"] = params["ws-path"];
|
proxy["ws-path"] = params["ws-path"];
|
||||||
|
const res = params["ws-headers"].match(/(,|^|\s)*HOST:\s*(.*?)(,|$)/);
|
||||||
|
const host = res ? res[2] : proxy.server;
|
||||||
proxy["ws-headers"] = {
|
proxy["ws-headers"] = {
|
||||||
Host: params.sni,
|
Host: host || params.server,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return proxy;
|
return proxy;
|
||||||
@ -985,27 +990,30 @@ var ProxyUtils = (function () {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort by keywords
|
// sort by regex
|
||||||
function KeywordSortOperator(keywords) {
|
function RegexSortOperator(expressions) {
|
||||||
return {
|
return {
|
||||||
name: "Keyword Sort Operator",
|
name: "Regex Sort Operator",
|
||||||
func: (proxies) =>
|
func: (proxies) => {
|
||||||
proxies.sort((a, b) => {
|
expressions = expressions.map(expr => buildRegex(expr));
|
||||||
const oA = getKeywordOrder(keywords, a.name);
|
return proxies.sort((a, b) => {
|
||||||
const oB = getKeywordOrder(keywords, b.name);
|
const oA = getRegexOrder(expressions, a.name);
|
||||||
|
const oB = getRegexOrder(expressions, b.name);
|
||||||
if (oA && !oB) return -1;
|
if (oA && !oB) return -1;
|
||||||
if (oB && !oA) return 1;
|
if (oB && !oA) return 1;
|
||||||
if (oA && oB) return oA < oB ? -1 : 1;
|
if (oA && oB) return oA < oB ? -1 : 1;
|
||||||
if ((!oA && !oB) || (oA && oB && oA === oB))
|
if ((!oA && !oB) || (oA && oB && oA === oB))
|
||||||
return a.name < b.name ? -1 : 1; // fallback to normal sort
|
return a.name < b.name ? -1 : 1; // fallback to normal sort
|
||||||
}),
|
})
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getKeywordOrder(keywords, str) {
|
function getRegexOrder(expressions, str) {
|
||||||
let order = null;
|
let order = null;
|
||||||
for (let i = 0; i < keywords.length; i++) {
|
for (let i = 0; i < expressions.length; i++) {
|
||||||
if (str.indexOf(keywords[i]) !== -1) {
|
if (expressions[i].test(str)) {
|
||||||
order = i + 1; // plus 1 is important! 0 will be treated as false!!!
|
order = i + 1; // plus 1 is important! 0 will be treated as false!!!
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1013,22 +1021,6 @@ var ProxyUtils = (function () {
|
|||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
// rename by keywords
|
|
||||||
// keywords: [{old: "old", now: "now"}]
|
|
||||||
function KeywordRenameOperator(keywords) {
|
|
||||||
return {
|
|
||||||
name: "Keyword Rename Operator",
|
|
||||||
func: (proxies) => {
|
|
||||||
return proxies.map((proxy) => {
|
|
||||||
for (const {old, now} of keywords) {
|
|
||||||
proxy.name = proxy.name.replaceAll(old, now).trim();
|
|
||||||
}
|
|
||||||
return proxy;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// rename by regex
|
// rename by regex
|
||||||
// keywords: [{expr: "string format regex", now: "now"}]
|
// keywords: [{expr: "string format regex", now: "now"}]
|
||||||
function RegexRenameOperator(regex) {
|
function RegexRenameOperator(regex) {
|
||||||
@ -1037,7 +1029,7 @@ var ProxyUtils = (function () {
|
|||||||
func: (proxies) => {
|
func: (proxies) => {
|
||||||
return proxies.map((proxy) => {
|
return proxies.map((proxy) => {
|
||||||
for (const {expr, now} of regex) {
|
for (const {expr, now} of regex) {
|
||||||
proxy.name = proxy.name.replace(new RegExp(expr, "g"), now).trim();
|
proxy.name = proxy.name.replace(buildRegex(expr, "g"), now).trim();
|
||||||
}
|
}
|
||||||
return proxy;
|
return proxy;
|
||||||
});
|
});
|
||||||
@ -1045,21 +1037,6 @@ var ProxyUtils = (function () {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete keywords operator
|
|
||||||
// keywords: ['a', 'b', 'c']
|
|
||||||
function KeywordDeleteOperator(keywords) {
|
|
||||||
const keywords_ = keywords.map((k) => {
|
|
||||||
return {
|
|
||||||
old: k,
|
|
||||||
now: "",
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
name: "Keyword Delete Operator",
|
|
||||||
func: KeywordRenameOperator(keywords_).func,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete regex operator
|
// delete regex operator
|
||||||
// regex: ['a', 'b', 'c']
|
// regex: ['a', 'b', 'c']
|
||||||
function RegexDeleteOperator(regex) {
|
function RegexDeleteOperator(regex) {
|
||||||
@ -1094,16 +1071,10 @@ var ProxyUtils = (function () {
|
|||||||
(function () {
|
(function () {
|
||||||
// interface to get internal operators
|
// interface to get internal operators
|
||||||
const $get = (name, args) => {
|
const $get = (name, args) => {
|
||||||
const item = AVAILABLE_OPERATORS[name];
|
const item = PROXY_PROCESSORS[name];
|
||||||
return item(args);
|
return item(args);
|
||||||
};
|
};
|
||||||
const $process = (item, proxies) => {
|
const $process = ApplyProcessor;
|
||||||
if (item.name.indexOf("Filter") !== -1) {
|
|
||||||
return ApplyOperator(item, proxies);
|
|
||||||
} else if (item.name.indexOf("Operator") !== -1) {
|
|
||||||
return ApplyFilter(item, proxies);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
eval(script);
|
eval(script);
|
||||||
output = operator(proxies);
|
output = operator(proxies);
|
||||||
})();
|
})();
|
||||||
@ -1113,19 +1084,6 @@ var ProxyUtils = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**************************** Filters ***************************************/
|
/**************************** Filters ***************************************/
|
||||||
// filter by keywords
|
|
||||||
function KeywordFilter({keywords = [], keep = true}) {
|
|
||||||
return {
|
|
||||||
name: "Keyword Filter",
|
|
||||||
func: (proxies) => {
|
|
||||||
return proxies.map((proxy) => {
|
|
||||||
const selected = keywords.some((k) => proxy.name.indexOf(k) !== -1);
|
|
||||||
return keep ? selected : !selected;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter useless proxies
|
// filter useless proxies
|
||||||
function UselessFilter() {
|
function UselessFilter() {
|
||||||
const KEYWORDS = [
|
const KEYWORDS = [
|
||||||
@ -1139,8 +1097,8 @@ var ProxyUtils = (function () {
|
|||||||
];
|
];
|
||||||
return {
|
return {
|
||||||
name: "Useless Filter",
|
name: "Useless Filter",
|
||||||
func: KeywordFilter({
|
func: RegexFilter({
|
||||||
keywords: KEYWORDS,
|
regex: KEYWORDS,
|
||||||
keep: false,
|
keep: false,
|
||||||
}).func,
|
}).func,
|
||||||
};
|
};
|
||||||
@ -1175,8 +1133,7 @@ var ProxyUtils = (function () {
|
|||||||
func: (proxies) => {
|
func: (proxies) => {
|
||||||
return proxies.map((proxy) => {
|
return proxies.map((proxy) => {
|
||||||
const selected = regex.some((r) => {
|
const selected = regex.some((r) => {
|
||||||
r = new RegExp(r);
|
return buildRegex(r).test(proxy.name);
|
||||||
return r.test(proxy.name);
|
|
||||||
});
|
});
|
||||||
return keep ? selected : !selected;
|
return keep ? selected : !selected;
|
||||||
});
|
});
|
||||||
@ -1432,7 +1389,6 @@ var ProxyUtils = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"Keyword Filter": KeywordFilter,
|
|
||||||
"Useless Filter": UselessFilter,
|
"Useless Filter": UselessFilter,
|
||||||
"Region Filter": RegionFilter,
|
"Region Filter": RegionFilter,
|
||||||
"Regex Filter": RegexFilter,
|
"Regex Filter": RegexFilter,
|
||||||
@ -1442,9 +1398,7 @@ var ProxyUtils = (function () {
|
|||||||
"Set Property Operator": SetPropertyOperator,
|
"Set Property Operator": SetPropertyOperator,
|
||||||
"Flag Operator": FlagOperator,
|
"Flag Operator": FlagOperator,
|
||||||
"Sort Operator": SortOperator,
|
"Sort Operator": SortOperator,
|
||||||
"Keyword Sort Operator": KeywordSortOperator,
|
"Regex Sort Operator": RegexSortOperator,
|
||||||
"Keyword Rename Operator": KeywordRenameOperator,
|
|
||||||
"Keyword Delete Operator": KeywordDeleteOperator,
|
|
||||||
"Regex Rename Operator": RegexRenameOperator,
|
"Regex Rename Operator": RegexRenameOperator,
|
||||||
"Regex Delete Operator": RegexDeleteOperator,
|
"Regex Delete Operator": RegexDeleteOperator,
|
||||||
"Script Operator": ScriptOperator,
|
"Script Operator": ScriptOperator,
|
||||||
@ -1560,6 +1514,7 @@ var ProxyUtils = (function () {
|
|||||||
const targetPlatform = "Loon";
|
const targetPlatform = "Loon";
|
||||||
const produce = (proxy) => {
|
const produce = (proxy) => {
|
||||||
let obfs_opts, tls_opts;
|
let obfs_opts, tls_opts;
|
||||||
|
const udp_opts = proxy.udp ? ",udp=true" : "";
|
||||||
switch (proxy.type) {
|
switch (proxy.type) {
|
||||||
case "ss":
|
case "ss":
|
||||||
obfs_opts = ",,";
|
obfs_opts = ",,";
|
||||||
@ -1574,7 +1529,7 @@ var ProxyUtils = (function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${proxy.name}=shadowsocks,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"${obfs_opts}`;
|
return `${proxy.name}=shadowsocks,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"${obfs_opts}${udp_opts}`;
|
||||||
case "ssr":
|
case "ssr":
|
||||||
return `${proxy.name}=shadowsocksr,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}",${proxy.protocol},{${proxy["protocol-param"] || ""}},${proxy.obfs},{${proxy["obfs-param"] || ""}}`;
|
return `${proxy.name}=shadowsocksr,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}",${proxy.protocol},{${proxy["protocol-param"] || ""}},${proxy.obfs},{${proxy["obfs-param"] || ""}}`;
|
||||||
case "vmess":
|
case "vmess":
|
||||||
@ -1805,7 +1760,7 @@ var ProxyUtils = (function () {
|
|||||||
for (const processor of PROXY_PREPROCESSORS) {
|
for (const processor of PROXY_PREPROCESSORS) {
|
||||||
try {
|
try {
|
||||||
if (processor.test(raw)) {
|
if (processor.test(raw)) {
|
||||||
$.log(`Pre-processor [${processor.name}] activated`);
|
$.info(`Pre-processor [${processor.name}] activated`);
|
||||||
return processor.parse(raw);
|
return processor.parse(raw);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -1841,7 +1796,7 @@ var ProxyUtils = (function () {
|
|||||||
if (safeMatch(parser, line)) {
|
if (safeMatch(parser, line)) {
|
||||||
lastParser = parser;
|
lastParser = parser;
|
||||||
matched = true;
|
matched = true;
|
||||||
$.log(`Proxy parser: ${parser.name} is activated`);
|
$.info(`Proxy parser: ${parser.name} is activated`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1874,14 +1829,18 @@ var ProxyUtils = (function () {
|
|||||||
const {mode, content} = item.args;
|
const {mode, content} = item.args;
|
||||||
if (mode === "link") {
|
if (mode === "link") {
|
||||||
// if this is remote script, download it
|
// if this is remote script, download it
|
||||||
script = await $.http
|
try {
|
||||||
.get(content)
|
script = await $.http
|
||||||
.then((resp) => resp.body)
|
.get(content)
|
||||||
.catch((err) => {
|
.then((resp) => resp.body);
|
||||||
throw new Error(
|
} catch (err) {
|
||||||
`Error when downloading remote script: ${item.args.content}.\n Reason: ${err}`
|
$.error(
|
||||||
);
|
`Error when downloading remote script: ${item.args.content}.\n Reason: ${err}`
|
||||||
});
|
);
|
||||||
|
// skip the script if download failed.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
script = content;
|
script = content;
|
||||||
}
|
}
|
||||||
@ -1892,7 +1851,7 @@ var ProxyUtils = (function () {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$.log(
|
$.info(
|
||||||
`Applying "${item.type}" with arguments:\n >>> ${
|
`Applying "${item.type}" with arguments:\n >>> ${
|
||||||
JSON.stringify(item.args, null, 2) || "None"
|
JSON.stringify(item.args, null, 2) || "None"
|
||||||
}`
|
}`
|
||||||
@ -1917,7 +1876,7 @@ var ProxyUtils = (function () {
|
|||||||
// filter unsupported proxies
|
// filter unsupported proxies
|
||||||
proxies = proxies.filter(proxy => !(proxy.supported && proxy.supported[targetPlatform] === false));
|
proxies = proxies.filter(proxy => !(proxy.supported && proxy.supported[targetPlatform] === false));
|
||||||
|
|
||||||
$.log(`Producing proxies for target: ${targetPlatform}`);
|
$.info(`Producing proxies for target: ${targetPlatform}`);
|
||||||
if (typeof producer.type === "undefined" || producer.type === 'SINGLE') {
|
if (typeof producer.type === "undefined" || producer.type === 'SINGLE') {
|
||||||
return proxies
|
return proxies
|
||||||
.map(proxy => {
|
.map(proxy => {
|
||||||
@ -2287,14 +2246,6 @@ 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
function NOT(array) {
|
|
||||||
return array.map((c) => !c);
|
|
||||||
}
|
|
||||||
|
|
||||||
function FULL(length, bool) {
|
function FULL(length, bool) {
|
||||||
return [...Array(length).keys()].map(() => bool);
|
return [...Array(length).keys()].map(() => bool);
|
||||||
}
|
}
|
||||||
@ -2635,370 +2586,6 @@ function API(name = "untitled", debug = false) {
|
|||||||
})(name, debug);
|
})(name, debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gist backup
|
|
||||||
*/
|
|
||||||
function Gist(backupKey, token) {
|
|
||||||
const FILE_NAME = "Sub-Store";
|
|
||||||
const http = HTTP({
|
|
||||||
baseURL: "https://api.github.com",
|
|
||||||
headers: {
|
|
||||||
Authorization: `token ${token}`,
|
|
||||||
"User-Agent":
|
|
||||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.141 Safari/537.36",
|
|
||||||
},
|
|
||||||
events: {
|
|
||||||
onResponse: (resp) => {
|
|
||||||
if (/^[45]/.test(String(resp.statusCode))) {
|
|
||||||
return Promise.reject(`ERROR: ${JSON.parse(resp.body).message}`);
|
|
||||||
} else {
|
|
||||||
return resp;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
async function locate() {
|
|
||||||
return http.get("/gists").then((response) => {
|
|
||||||
const gists = JSON.parse(response.body);
|
|
||||||
for (let g of gists) {
|
|
||||||
if (g.description === backupKey) {
|
|
||||||
return g.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.upload = async function (content) {
|
|
||||||
const id = await locate();
|
|
||||||
const files = {
|
|
||||||
[FILE_NAME]: {content}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (id === -1) {
|
|
||||||
// create a new gist for backup
|
|
||||||
return http.post({
|
|
||||||
url: "/gists",
|
|
||||||
body: JSON.stringify({
|
|
||||||
description: backupKey,
|
|
||||||
public: false,
|
|
||||||
files
|
|
||||||
})
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// update an existing gist
|
|
||||||
return http.patch({
|
|
||||||
url: `/gists/${id}`,
|
|
||||||
body: JSON.stringify({files})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.download = async function () {
|
|
||||||
const id = await locate();
|
|
||||||
if (id === -1) {
|
|
||||||
return Promise.reject("未找到Gist备份!");
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
const {files} = await http
|
|
||||||
.get(`/gists/${id}`)
|
|
||||||
.then(resp => JSON.parse(resp.body));
|
|
||||||
const url = files[FILE_NAME].raw_url;
|
|
||||||
return await http.get(url).then(resp => resp.body);
|
|
||||||
} catch (err) {
|
|
||||||
return Promise.reject(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mini Express Framework
|
|
||||||
* https://github.com/Peng-YM/QuanX/blob/master/Tools/OpenAPI/Express.js
|
|
||||||
*/
|
|
||||||
function express({port} = {port: 3000}) {
|
|
||||||
const {isNode} = ENV();
|
|
||||||
const DEFAULT_HEADERS = {
|
|
||||||
"Content-Type": "text/plain;charset=UTF-8",
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Methods": "POST,GET,OPTIONS,PATCH,PUT,DELETE",
|
|
||||||
"Access-Control-Allow-Headers":
|
|
||||||
"Origin, X-Requested-With, Content-Type, Accept",
|
|
||||||
};
|
|
||||||
|
|
||||||
// node support
|
|
||||||
if (isNode) {
|
|
||||||
const express_ = require("express");
|
|
||||||
const bodyParser = require("body-parser");
|
|
||||||
const app = express_();
|
|
||||||
app.use(bodyParser.json({verify: rawBodySaver}));
|
|
||||||
app.use(bodyParser.urlencoded({verify: rawBodySaver, extended: true}));
|
|
||||||
app.use(bodyParser.raw({verify: rawBodySaver, type: "*/*"}));
|
|
||||||
app.use((req, res, next) => {
|
|
||||||
res.set(DEFAULT_HEADERS);
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
// adapter
|
|
||||||
app.start = () => {
|
|
||||||
app.listen(port, () => {
|
|
||||||
$.info(`Express started on port: ${port}`);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
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);
|
|
||||||
|
|
||||||
// pattern match
|
|
||||||
let handler = null;
|
|
||||||
let i;
|
|
||||||
let longestMatchedPattern = 0;
|
|
||||||
for (i = start; i < handlers.length; i++) {
|
|
||||||
if (handlers[i].method === "ALL" || method === handlers[i].method) {
|
|
||||||
const {pattern} = handlers[i];
|
|
||||||
if (patternMatched(pattern, path)) {
|
|
||||||
if (pattern.split("/").length > longestMatchedPattern) {
|
|
||||||
handler = handlers[i];
|
|
||||||
longestMatchedPattern = pattern.split("/").length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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();
|
|
||||||
const cb = handler.callback;
|
|
||||||
|
|
||||||
const errFunc = err => {
|
|
||||||
res.status(500).json({
|
|
||||||
status: "failed",
|
|
||||||
message: `Internal Server Error: ${err}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cb.constructor.name === 'AsyncFunction') {
|
|
||||||
cb(req, res, next).catch(errFunc);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
cb(req, res, next);
|
|
||||||
} catch (err) {
|
|
||||||
errFunc(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 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",
|
|
||||||
302: "HTTP/1.1 302 Found",
|
|
||||||
307: "HTTP/1.1 307 Temporary Redirect",
|
|
||||||
308: "HTTP/1.1 308 Permanent Redirect",
|
|
||||||
404: "HTTP/1.1 404 Not Found",
|
|
||||||
500: "HTTP/1.1 500 Internal Server Error",
|
|
||||||
};
|
|
||||||
return new (class {
|
|
||||||
status(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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/****************************************** Third Party Libraries **********************************************/
|
/****************************************** Third Party Libraries **********************************************/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user