mirror of
https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-10 00:52:40 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c27c589024 | ||
|
|
0efed4f1a0 | ||
|
|
e3a514d1fb | ||
|
|
64478c7a27 | ||
|
|
dc8f19f350 | ||
|
|
b4ccfc7e07 | ||
|
|
3f1940630a | ||
|
|
5a0bdb1276 | ||
|
|
a1b86e26a2 | ||
|
|
6ec8c29f6a | ||
|
|
bbb9602f9f | ||
|
|
6db6153672 | ||
|
|
b66189948a | ||
|
|
2611dccc73 | ||
|
|
25d3cf6ca4 | ||
|
|
3637c5eb74 | ||
|
|
80d46597b4 | ||
|
|
ca65e4209e | ||
|
|
53bb4866e7 | ||
|
|
09495fa607 | ||
|
|
4b27d40602 | ||
|
|
518de2e919 | ||
|
|
078bf228de | ||
|
|
aaef97cf5d | ||
|
|
7beff4013f | ||
|
|
23cf81d0a5 | ||
|
|
572f2f5533 |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sub-store",
|
"name": "sub-store",
|
||||||
"version": "2.14.240",
|
"version": "2.14.264",
|
||||||
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
|
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
|
||||||
"main": "src/main.js",
|
"main": "src/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ async function processFn(proxies, operators = [], targetPlatform, source) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$.info(
|
$.log(
|
||||||
`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'
|
||||||
}`,
|
}`,
|
||||||
@@ -199,7 +199,7 @@ function produce(proxies, targetPlatform, type, opts = {}) {
|
|||||||
return proxy;
|
return proxy;
|
||||||
});
|
});
|
||||||
|
|
||||||
$.info(`Producing proxies for target: ${targetPlatform}`);
|
$.log(`Producing proxies for target: ${targetPlatform}`);
|
||||||
if (typeof producer.type === 'undefined' || producer.type === 'SINGLE') {
|
if (typeof producer.type === 'undefined' || producer.type === 'SINGLE') {
|
||||||
let localPort = 10000;
|
let localPort = 10000;
|
||||||
const list = proxies
|
const list = proxies
|
||||||
@@ -370,6 +370,10 @@ function lastParse(proxy) {
|
|||||||
delete proxy.ports;
|
delete proxy.ports;
|
||||||
}
|
}
|
||||||
if (['vless'].includes(proxy.type)) {
|
if (['vless'].includes(proxy.type)) {
|
||||||
|
// 非 reality, 空 flow 没有意义
|
||||||
|
if (!proxy['reality-opts'] && !proxy.flow) {
|
||||||
|
delete proxy.flow;
|
||||||
|
}
|
||||||
if (['http'].includes(proxy.network)) {
|
if (['http'].includes(proxy.network)) {
|
||||||
let transportPath = proxy[`${proxy.network}-opts`]?.path;
|
let transportPath = proxy[`${proxy.network}-opts`]?.path;
|
||||||
if (!transportPath) {
|
if (!transportPath) {
|
||||||
|
|||||||
@@ -444,6 +444,7 @@ function URI_VLESS() {
|
|||||||
proxy[`${params.security}-opts`] = opts;
|
proxy[`${params.security}-opts`] = opts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy.network = params.type;
|
proxy.network = params.type;
|
||||||
if (proxy.network === 'tcp' && params.headerType === 'http') {
|
if (proxy.network === 'tcp' && params.headerType === 'http') {
|
||||||
proxy.network = 'http';
|
proxy.network = 'http';
|
||||||
@@ -546,6 +547,7 @@ function URI_Hysteria2() {
|
|||||||
proxy.obfs = params.obfs;
|
proxy.obfs = params.obfs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proxy.ports = params.mport;
|
||||||
proxy['obfs-password'] = params['obfs-password'];
|
proxy['obfs-password'] = params['obfs-password'];
|
||||||
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(params.insecure);
|
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(params.insecure);
|
||||||
proxy.tfo = /(TRUE)|1/i.test(params.fastopen);
|
proxy.tfo = /(TRUE)|1/i.test(params.fastopen);
|
||||||
@@ -722,6 +724,7 @@ function Clash_All() {
|
|||||||
'hysteria',
|
'hysteria',
|
||||||
'hysteria2',
|
'hysteria2',
|
||||||
'wireguard',
|
'wireguard',
|
||||||
|
'ssh',
|
||||||
].includes(proxy.type)
|
].includes(proxy.type)
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ http = tag equals "http" address (username password)? (usernamek passwordk)? (ip
|
|||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
ssh = tag equals "ssh" address (username password)? (usernamek passwordk)? (server_fingerprint/idle_timeout/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
ssh = tag equals "ssh" address (username password)? (usernamek passwordk)? (server_fingerprint/idle_timeout/private_key/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "ssh";
|
proxy.type = "ssh";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
@@ -229,6 +229,7 @@ interface = comma "interface" equals match:[^,]+ { proxy.interface = match.join(
|
|||||||
allow_other_interface = comma "allow-other-interface" equals flag:bool { proxy["allow-other-interface"] = flag; }
|
allow_other_interface = comma "allow-other-interface" equals flag:bool { proxy["allow-other-interface"] = flag; }
|
||||||
hybrid = comma "hybrid" equals flag:bool { proxy.hybrid = flag; }
|
hybrid = comma "hybrid" equals flag:bool { proxy.hybrid = flag; }
|
||||||
idle_timeout = comma "idle-timeout" equals match:$[0-9]+ { proxy["idle-timeout"] = parseInt(match.trim()); }
|
idle_timeout = comma "idle-timeout" equals match:$[0-9]+ { proxy["idle-timeout"] = parseInt(match.trim()); }
|
||||||
|
private_key = comma "private-key" equals match:[^,]+ { proxy["keystore-private-key"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
server_fingerprint = comma "server-fingerprint" equals match:[^,]+ { proxy["server-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
server_fingerprint = comma "server-fingerprint" equals match:[^,]+ { proxy["server-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
block_quic = comma "block-quic" equals match:[^,]+ { proxy["block-quic"] = match.join(""); }
|
block_quic = comma "block-quic" equals match:[^,]+ { proxy["block-quic"] = match.join(""); }
|
||||||
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
|
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ http = tag equals "http" address (username password)? (usernamek passwordk)? (ip
|
|||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
ssh = tag equals "ssh" address (username password)? (usernamek passwordk)? (server_fingerprint/idle_timeout/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
ssh = tag equals "ssh" address (username password)? (usernamek passwordk)? (server_fingerprint/idle_timeout/private_key/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "ssh";
|
proxy.type = "ssh";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
@@ -227,6 +227,7 @@ interface = comma "interface" equals match:[^,]+ { proxy.interface = match.join(
|
|||||||
allow_other_interface = comma "allow-other-interface" equals flag:bool { proxy["allow-other-interface"] = flag; }
|
allow_other_interface = comma "allow-other-interface" equals flag:bool { proxy["allow-other-interface"] = flag; }
|
||||||
hybrid = comma "hybrid" equals flag:bool { proxy.hybrid = flag; }
|
hybrid = comma "hybrid" equals flag:bool { proxy.hybrid = flag; }
|
||||||
idle_timeout = comma "idle-timeout" equals match:$[0-9]+ { proxy["idle-timeout"] = parseInt(match.trim()); }
|
idle_timeout = comma "idle-timeout" equals match:$[0-9]+ { proxy["idle-timeout"] = parseInt(match.trim()); }
|
||||||
|
private_key = comma "private-key" equals match:[^,]+ { proxy["keystore-private-key"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
server_fingerprint = comma "server-fingerprint" equals match:[^,]+ { proxy["server-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
server_fingerprint = comma "server-fingerprint" equals match:[^,]+ { proxy["server-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
block_quic = comma "block-quic" equals match:[^,]+ { proxy["block-quic"] = match.join(""); }
|
block_quic = comma "block-quic" equals match:[^,]+ { proxy["block-quic"] = match.join(""); }
|
||||||
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
|
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
parseFlowHeaders,
|
parseFlowHeaders,
|
||||||
validCheck,
|
validCheck,
|
||||||
flowTransfer,
|
flowTransfer,
|
||||||
|
getRmainingDays,
|
||||||
} from '@/utils/flow';
|
} from '@/utils/flow';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -863,6 +864,7 @@ function createDynamicFunction(name, script, $arguments) {
|
|||||||
parseFlowHeaders,
|
parseFlowHeaders,
|
||||||
flowTransfer,
|
flowTransfer,
|
||||||
validCheck,
|
validCheck,
|
||||||
|
getRmainingDays,
|
||||||
};
|
};
|
||||||
if ($.env.isLoon) {
|
if ($.env.isLoon) {
|
||||||
return new Function(
|
return new Function(
|
||||||
|
|||||||
@@ -228,6 +228,13 @@ const sshParser = (proxy = {}) => {
|
|||||||
throw 'invalid port';
|
throw 'invalid port';
|
||||||
if (proxy.username) parsedProxy.user = proxy.username;
|
if (proxy.username) parsedProxy.user = proxy.username;
|
||||||
if (proxy.password) parsedProxy.password = proxy.password;
|
if (proxy.password) parsedProxy.password = proxy.password;
|
||||||
|
// https://wiki.metacubex.one/config/proxies/ssh
|
||||||
|
// https://sing-box.sagernet.org/zh/configuration/outbound/ssh
|
||||||
|
if (proxy['privateKey']) parsedProxy.private_key_path = proxy['privateKey'];
|
||||||
|
if (proxy['private-key'])
|
||||||
|
parsedProxy.private_key_path = proxy['private-key'];
|
||||||
|
if (proxy['private-key-passphrase'])
|
||||||
|
parsedProxy.private_key_passphrase = proxy['private-key-passphrase'];
|
||||||
if (proxy['server-fingerprint']) {
|
if (proxy['server-fingerprint']) {
|
||||||
parsedProxy.host_key = [proxy['server-fingerprint']];
|
parsedProxy.host_key = [proxy['server-fingerprint']];
|
||||||
// https://manual.nssurge.com/policy/ssh.html
|
// https://manual.nssurge.com/policy/ssh.html
|
||||||
@@ -237,6 +244,9 @@ const sshParser = (proxy = {}) => {
|
|||||||
proxy['server-fingerprint'].split(' ')[0],
|
proxy['server-fingerprint'].split(' ')[0],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
if (proxy['host-key']) parsedProxy.host_key = proxy['host-key'];
|
||||||
|
if (proxy['host-key-algorithms'])
|
||||||
|
parsedProxy.host_key_algorithms = proxy['host-key-algorithms'];
|
||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
|
|||||||
@@ -356,8 +356,15 @@ function ssh(proxy) {
|
|||||||
const result = new Result(proxy);
|
const result = new Result(proxy);
|
||||||
result.append(`${proxy.name}=ssh,${proxy.server},${proxy.port}`);
|
result.append(`${proxy.name}=ssh,${proxy.server},${proxy.port}`);
|
||||||
result.appendIfPresent(`,${proxy.username}`, 'username');
|
result.appendIfPresent(`,${proxy.username}`, 'username');
|
||||||
|
// 所有的类似的字段都有双引号的问题 暂不处理
|
||||||
result.appendIfPresent(`,${proxy.password}`, 'password');
|
result.appendIfPresent(`,${proxy.password}`, 'password');
|
||||||
|
|
||||||
|
// https://manual.nssurge.com/policy/ssh.html
|
||||||
|
// 需配合 Keystore
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,private-key=${proxy['keystore-private-key']}`,
|
||||||
|
'keystore-private-key',
|
||||||
|
);
|
||||||
result.appendIfPresent(
|
result.appendIfPresent(
|
||||||
`,idle-timeout=${proxy['idle-timeout']}`,
|
`,idle-timeout=${proxy['idle-timeout']}`,
|
||||||
'idle-timeout',
|
'idle-timeout',
|
||||||
|
|||||||
@@ -274,6 +274,9 @@ export default function URI_Producer() {
|
|||||||
`sni=${encodeURIComponent(proxy.sni)}`,
|
`sni=${encodeURIComponent(proxy.sni)}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (proxy.ports) {
|
||||||
|
hysteria2params.push(`mport=${proxy.ports}`);
|
||||||
|
}
|
||||||
if (proxy['tls-fingerprint']) {
|
if (proxy['tls-fingerprint']) {
|
||||||
hysteria2params.push(
|
hysteria2params.push(
|
||||||
`pinSHA256=${encodeURIComponent(
|
`pinSHA256=${encodeURIComponent(
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ const RULE_TYPES_MAPPING = [
|
|||||||
[/^PROTOCOL$/, 'PROTOCOL'],
|
[/^PROTOCOL$/, 'PROTOCOL'],
|
||||||
[/^IP-CIDR$/i, 'IP-CIDR'],
|
[/^IP-CIDR$/i, 'IP-CIDR'],
|
||||||
[/^(IP-CIDR6|ip6-cidr|IP6-CIDR)$/, 'IP-CIDR6'],
|
[/^(IP-CIDR6|ip6-cidr|IP6-CIDR)$/, 'IP-CIDR6'],
|
||||||
|
[/^GEOIP$/i, 'GEOIP'],
|
||||||
|
[/^GEOSITE$/i, 'GEOSITE'],
|
||||||
];
|
];
|
||||||
|
|
||||||
function AllRuleParser() {
|
function AllRuleParser() {
|
||||||
@@ -37,8 +39,7 @@ function AllRuleParser() {
|
|||||||
content: params[1],
|
content: params[1],
|
||||||
};
|
};
|
||||||
if (
|
if (
|
||||||
rule.type === 'IP-CIDR' ||
|
['IP-CIDR', 'IP-CIDR6', 'GEOIP'].includes(rule.type)
|
||||||
rule.type === 'IP-CIDR6'
|
|
||||||
) {
|
) {
|
||||||
rule.options = params.slice(2);
|
rule.options = params.slice(2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ function QXFilter() {
|
|||||||
'SRC-IP',
|
'SRC-IP',
|
||||||
'IN-PORT',
|
'IN-PORT',
|
||||||
'PROTOCOL',
|
'PROTOCOL',
|
||||||
|
'GEOSITE',
|
||||||
|
'GEOIP',
|
||||||
];
|
];
|
||||||
if (UNSUPPORTED.indexOf(rule.type) !== -1) return null;
|
if (UNSUPPORTED.indexOf(rule.type) !== -1) return null;
|
||||||
|
|
||||||
@@ -29,6 +31,8 @@ function QXFilter() {
|
|||||||
function SurgeRuleSet() {
|
function SurgeRuleSet() {
|
||||||
const type = 'SINGLE';
|
const type = 'SINGLE';
|
||||||
const func = (rule) => {
|
const func = (rule) => {
|
||||||
|
const UNSUPPORTED = ['GEOSITE', 'GEOIP'];
|
||||||
|
if (UNSUPPORTED.indexOf(rule.type) !== -1) return null;
|
||||||
let output = `${rule.type},${rule.content}`;
|
let output = `${rule.type},${rule.content}`;
|
||||||
if (['IP-CIDR', 'IP-CIDR6'].includes(rule.type)) {
|
if (['IP-CIDR', 'IP-CIDR6'].includes(rule.type)) {
|
||||||
output +=
|
output +=
|
||||||
@@ -43,7 +47,7 @@ function LoonRules() {
|
|||||||
const type = 'SINGLE';
|
const type = 'SINGLE';
|
||||||
const func = (rule) => {
|
const func = (rule) => {
|
||||||
// skip unsupported rules
|
// skip unsupported rules
|
||||||
const UNSUPPORTED = ['DEST-PORT', 'SRC-IP', 'IN-PORT', 'PROTOCOL'];
|
const UNSUPPORTED = ['SRC-IP', 'GEOSITE', 'GEOIP'];
|
||||||
if (UNSUPPORTED.indexOf(rule.type) !== -1) return null;
|
if (UNSUPPORTED.indexOf(rule.type) !== -1) return null;
|
||||||
if (['IP-CIDR', 'IP-CIDR6'].includes(rule.type) && rule.options) {
|
if (['IP-CIDR', 'IP-CIDR6'].includes(rule.type) && rule.options) {
|
||||||
// Loon only supports the no-resolve option
|
// Loon only supports the no-resolve option
|
||||||
@@ -69,7 +73,7 @@ function ClashRuleProvider() {
|
|||||||
let output = `${TRANSFORM[rule.type] || rule.type},${
|
let output = `${TRANSFORM[rule.type] || rule.type},${
|
||||||
rule.content
|
rule.content
|
||||||
}`;
|
}`;
|
||||||
if (['IP-CIDR', 'IP-CIDR6'].includes(rule.type)) {
|
if (['IP-CIDR', 'IP-CIDR6', 'GEOIP'].includes(rule.type)) {
|
||||||
if (rule.options) {
|
if (rule.options) {
|
||||||
// Clash only supports the no-resolve option
|
// Clash only supports the no-resolve option
|
||||||
rule.options = rule.options.filter((option) =>
|
rule.options = rule.options.filter((option) =>
|
||||||
|
|||||||
@@ -128,10 +128,19 @@ async function doSync() {
|
|||||||
files.map((item) => [item.path, item]),
|
files.map((item) => [item.path, item]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const url = files[encodeURIComponent(artifact.name)]?.raw_url;
|
const raw_url =
|
||||||
artifact.url = isGitLab
|
files[encodeURIComponent(artifact.name)]?.raw_url;
|
||||||
? url
|
const new_url = isGitLab
|
||||||
: url?.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
|
? raw_url
|
||||||
|
: raw_url?.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
|
||||||
|
$.info(
|
||||||
|
`上传配置完成\n文件列表: ${Object.keys(files).join(
|
||||||
|
', ',
|
||||||
|
)}\n当前文件: ${encodeURIComponent(
|
||||||
|
artifact.name,
|
||||||
|
)}\n响应返回的原始链接: ${raw_url}\n处理完的新链接: ${new_url}`,
|
||||||
|
);
|
||||||
|
artifact.url = new_url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,12 +77,48 @@ async function downloadSubscription(req, res) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (sub.source !== 'local' || url) {
|
if (
|
||||||
|
sub.source !== 'local' ||
|
||||||
|
['localFirst', 'remoteFirst'].includes(sub.mergeSources)
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
// forward flow headers
|
url = `${url || sub.url}`
|
||||||
const flowInfo = await getFlowHeaders(url || sub.url);
|
.split(/[\r\n]+/)
|
||||||
if (flowInfo) {
|
.map((i) => i.trim())
|
||||||
res.set('subscription-userinfo', flowInfo);
|
.filter((i) => i.length)?.[0];
|
||||||
|
|
||||||
|
let $arguments = {};
|
||||||
|
const rawArgs = url.split('#');
|
||||||
|
url = url.split('#')[0];
|
||||||
|
if (rawArgs.length > 1) {
|
||||||
|
try {
|
||||||
|
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
|
||||||
|
$arguments = JSON.parse(
|
||||||
|
decodeURIComponent(rawArgs[1]),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
for (const pair of rawArgs[1].split('&')) {
|
||||||
|
const key = pair.split('=')[0];
|
||||||
|
const value = pair.split('=')[1];
|
||||||
|
// 部分兼容之前的逻辑 const value = pair.split('=')[1] || true;
|
||||||
|
$arguments[key] =
|
||||||
|
value == null || value === ''
|
||||||
|
? true
|
||||||
|
: decodeURIComponent(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$arguments.noFlow) {
|
||||||
|
// forward flow headers
|
||||||
|
const flowInfo = await getFlowHeaders(
|
||||||
|
url,
|
||||||
|
$arguments.flowUserAgent,
|
||||||
|
undefined,
|
||||||
|
sub.proxy,
|
||||||
|
);
|
||||||
|
if (flowInfo) {
|
||||||
|
res.set('subscription-userinfo', flowInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
$.error(
|
$.error(
|
||||||
@@ -92,6 +128,9 @@ async function downloadSubscription(req, res) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (sub.subUserinfo) {
|
||||||
|
res.set('subscription-userinfo', sub.subUserinfo);
|
||||||
|
}
|
||||||
|
|
||||||
if (platform === 'JSON') {
|
if (platform === 'JSON') {
|
||||||
res.set('Content-Type', 'application/json;charset=utf-8').send(
|
res.set('Content-Type', 'application/json;charset=utf-8').send(
|
||||||
@@ -176,11 +215,47 @@ async function downloadCollection(req, res) {
|
|||||||
const subnames = collection.subscriptions;
|
const subnames = collection.subscriptions;
|
||||||
if (subnames.length > 0) {
|
if (subnames.length > 0) {
|
||||||
const sub = findByName(allSubs, subnames[0]);
|
const sub = findByName(allSubs, subnames[0]);
|
||||||
if (sub.source !== 'local') {
|
if (
|
||||||
|
sub.source !== 'local' ||
|
||||||
|
['localFirst', 'remoteFirst'].includes(sub.mergeSources)
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const flowInfo = await getFlowHeaders(sub.url);
|
let url = `${sub.url}`
|
||||||
if (flowInfo) {
|
.split(/[\r\n]+/)
|
||||||
res.set('subscription-userinfo', flowInfo);
|
.map((i) => i.trim())
|
||||||
|
.filter((i) => i.length)?.[0];
|
||||||
|
|
||||||
|
let $arguments = {};
|
||||||
|
const rawArgs = url.split('#');
|
||||||
|
url = url.split('#')[0];
|
||||||
|
if (rawArgs.length > 1) {
|
||||||
|
try {
|
||||||
|
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
|
||||||
|
$arguments = JSON.parse(
|
||||||
|
decodeURIComponent(rawArgs[1]),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
for (const pair of rawArgs[1].split('&')) {
|
||||||
|
const key = pair.split('=')[0];
|
||||||
|
const value = pair.split('=')[1];
|
||||||
|
// 部分兼容之前的逻辑 const value = pair.split('=')[1] || true;
|
||||||
|
$arguments[key] =
|
||||||
|
value == null || value === ''
|
||||||
|
? true
|
||||||
|
: decodeURIComponent(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$arguments.noFlow) {
|
||||||
|
const flowInfo = await getFlowHeaders(
|
||||||
|
url,
|
||||||
|
$arguments.flowUserAgent,
|
||||||
|
undefined,
|
||||||
|
sub.proxy,
|
||||||
|
);
|
||||||
|
if (flowInfo) {
|
||||||
|
res.set('subscription-userinfo', flowInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
$.error(
|
$.error(
|
||||||
@@ -190,6 +265,9 @@ async function downloadCollection(req, res) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (sub.subUserinfo) {
|
||||||
|
res.set('subscription-userinfo', sub.subUserinfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (platform === 'JSON') {
|
if (platform === 'JSON') {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { ENV } from '@/vendor/open-api';
|
|||||||
import { failed, success } from '@/restful/response';
|
import { failed, success } from '@/restful/response';
|
||||||
import { updateArtifactStore, updateAvatar } from '@/restful/settings';
|
import { updateArtifactStore, updateAvatar } from '@/restful/settings';
|
||||||
import resourceCache from '@/utils/resource-cache';
|
import resourceCache from '@/utils/resource-cache';
|
||||||
|
import scriptResourceCache from '@/utils/script-resource-cache';
|
||||||
|
import headersResourceCache from '@/utils/headers-resource-cache';
|
||||||
import {
|
import {
|
||||||
GIST_BACKUP_FILE_NAME,
|
GIST_BACKUP_FILE_NAME,
|
||||||
GIST_BACKUP_KEY,
|
GIST_BACKUP_KEY,
|
||||||
@@ -73,6 +75,8 @@ async function refresh(_, res) {
|
|||||||
|
|
||||||
// 2. clear resource cache
|
// 2. clear resource cache
|
||||||
resourceCache.revokeAll();
|
resourceCache.revokeAll();
|
||||||
|
scriptResourceCache.revokeAll();
|
||||||
|
headersResourceCache.revokeAll();
|
||||||
success(res);
|
success(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,11 +157,14 @@ async function gistBackup(req, res) {
|
|||||||
}
|
}
|
||||||
success(res);
|
success(res);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
$.error(
|
||||||
|
`Failed to ${action} gist data.\nReason: ${err.message ?? err}`,
|
||||||
|
);
|
||||||
failed(
|
failed(
|
||||||
res,
|
res,
|
||||||
new InternalServerError(
|
new InternalServerError(
|
||||||
'BACKUP_FAILED',
|
'BACKUP_FAILED',
|
||||||
`Failed to ${action} data to gist!`,
|
`Failed to ${action} gist data!`,
|
||||||
`Reason: ${err.message ?? err}`,
|
`Reason: ${err.message ?? err}`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -109,7 +109,12 @@ async function compareSub(req, res) {
|
|||||||
.filter((i) => i.length)
|
.filter((i) => i.length)
|
||||||
.map(async (url) => {
|
.map(async (url) => {
|
||||||
try {
|
try {
|
||||||
return await download(url, sub.ua);
|
return await download(
|
||||||
|
url,
|
||||||
|
sub.ua,
|
||||||
|
undefined,
|
||||||
|
sub.proxy,
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errors[url] = err;
|
errors[url] = err;
|
||||||
$.error(
|
$.error(
|
||||||
@@ -195,7 +200,12 @@ async function compareCollection(req, res) {
|
|||||||
.filter((i) => i.length)
|
.filter((i) => i.length)
|
||||||
.map(async (url) => {
|
.map(async (url) => {
|
||||||
try {
|
try {
|
||||||
return await download(url, sub.ua);
|
return await download(
|
||||||
|
url,
|
||||||
|
sub.ua,
|
||||||
|
undefined,
|
||||||
|
sub.proxy,
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errors[url] = err;
|
errors[url] = err;
|
||||||
$.error(
|
$.error(
|
||||||
|
|||||||
@@ -6,7 +6,11 @@ import {
|
|||||||
} from './errors';
|
} from './errors';
|
||||||
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
||||||
import { SUBS_KEY, COLLECTIONS_KEY, ARTIFACTS_KEY } from '@/constants';
|
import { SUBS_KEY, COLLECTIONS_KEY, ARTIFACTS_KEY } from '@/constants';
|
||||||
import { getFlowHeaders, parseFlowHeaders } from '@/utils/flow';
|
import {
|
||||||
|
getFlowHeaders,
|
||||||
|
parseFlowHeaders,
|
||||||
|
getRmainingDays,
|
||||||
|
} from '@/utils/flow';
|
||||||
import { success, failed } from './response';
|
import { success, failed } from './response';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
|
|
||||||
@@ -43,32 +47,98 @@ async function getFlowInfo(req, res) {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (sub.source === 'local') {
|
if (
|
||||||
failed(
|
sub.source === 'local' &&
|
||||||
res,
|
!['localFirst', 'remoteFirst'].includes(sub.mergeSources)
|
||||||
new RequestInvalidError(
|
) {
|
||||||
'NO_FLOW_INFO',
|
if (sub.subUserinfo) {
|
||||||
'N/A',
|
success(res, {
|
||||||
`Local subscription ${name} has no flow information!`,
|
...parseFlowHeaders(sub.subUserinfo),
|
||||||
),
|
});
|
||||||
);
|
} else {
|
||||||
|
failed(
|
||||||
|
res,
|
||||||
|
new RequestInvalidError(
|
||||||
|
'NO_FLOW_INFO',
|
||||||
|
'N/A',
|
||||||
|
`Local subscription ${name} has no flow information!`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const flowHeaders = await getFlowHeaders(sub.url);
|
let url = `${sub.url}`
|
||||||
if (!flowHeaders) {
|
.split(/[\r\n]+/)
|
||||||
|
.map((i) => i.trim())
|
||||||
|
.filter((i) => i.length)?.[0];
|
||||||
|
|
||||||
|
let $arguments = {};
|
||||||
|
const rawArgs = url.split('#');
|
||||||
|
url = url.split('#')[0];
|
||||||
|
if (rawArgs.length > 1) {
|
||||||
|
try {
|
||||||
|
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
|
||||||
|
$arguments = JSON.parse(decodeURIComponent(rawArgs[1]));
|
||||||
|
} catch (e) {
|
||||||
|
for (const pair of rawArgs[1].split('&')) {
|
||||||
|
const key = pair.split('=')[0];
|
||||||
|
const value = pair.split('=')[1];
|
||||||
|
// 部分兼容之前的逻辑 const value = pair.split('=')[1] || true;
|
||||||
|
$arguments[key] =
|
||||||
|
value == null || value === ''
|
||||||
|
? true
|
||||||
|
: decodeURIComponent(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($arguments.noFlow) {
|
||||||
failed(
|
failed(
|
||||||
res,
|
res,
|
||||||
new InternalServerError(
|
new RequestInvalidError(
|
||||||
'NO_FLOW_INFO',
|
'NO_FLOW_INFO',
|
||||||
'No flow info',
|
'N/A',
|
||||||
`Failed to fetch flow headers`,
|
`Subscription ${name}: noFlow`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (sub.subUserinfo) {
|
||||||
success(res, parseFlowHeaders(flowHeaders));
|
success(res, {
|
||||||
|
...parseFlowHeaders(sub.subUserinfo),
|
||||||
|
remainingDays: getRmainingDays({
|
||||||
|
resetDay: $arguments.resetDay,
|
||||||
|
startDate: $arguments.startDate,
|
||||||
|
cycleDays: $arguments.cycleDays,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const flowHeaders = await getFlowHeaders(
|
||||||
|
url,
|
||||||
|
$arguments.flowUserAgent,
|
||||||
|
undefined,
|
||||||
|
sub.proxy,
|
||||||
|
);
|
||||||
|
if (!flowHeaders) {
|
||||||
|
failed(
|
||||||
|
res,
|
||||||
|
new InternalServerError(
|
||||||
|
'NO_FLOW_INFO',
|
||||||
|
'No flow info',
|
||||||
|
`Failed to fetch flow headers`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
success(res, {
|
||||||
|
...parseFlowHeaders(flowHeaders),
|
||||||
|
remainingDays: getRmainingDays({
|
||||||
|
resetDay: $arguments.resetDay,
|
||||||
|
startDate: $arguments.startDate,
|
||||||
|
cycleDays: $arguments.cycleDays,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
failed(
|
failed(
|
||||||
res,
|
res,
|
||||||
|
|||||||
@@ -35,13 +35,21 @@ async function produceArtifact({
|
|||||||
ignoreFailedRemoteFile,
|
ignoreFailedRemoteFile,
|
||||||
produceType,
|
produceType,
|
||||||
produceOpts = {},
|
produceOpts = {},
|
||||||
|
subscription,
|
||||||
}) {
|
}) {
|
||||||
platform = platform || 'JSON';
|
platform = platform || 'JSON';
|
||||||
|
|
||||||
if (type === 'subscription') {
|
if (type === 'subscription') {
|
||||||
const allSubs = $.read(SUBS_KEY);
|
let sub;
|
||||||
const sub = findByName(allSubs, name);
|
if (name) {
|
||||||
if (!sub) throw new Error(`找不到订阅 ${name}`);
|
const allSubs = $.read(SUBS_KEY);
|
||||||
|
sub = findByName(allSubs, name);
|
||||||
|
if (!sub) throw new Error(`找不到订阅 ${name}`);
|
||||||
|
} else if (subscription) {
|
||||||
|
sub = subscription;
|
||||||
|
} else {
|
||||||
|
throw new Error('未提供订阅名称或订阅数据');
|
||||||
|
}
|
||||||
let raw;
|
let raw;
|
||||||
if (content && !['localFirst', 'remoteFirst'].includes(mergeSources)) {
|
if (content && !['localFirst', 'remoteFirst'].includes(mergeSources)) {
|
||||||
raw = content;
|
raw = content;
|
||||||
@@ -54,7 +62,12 @@ async function produceArtifact({
|
|||||||
.filter((i) => i.length)
|
.filter((i) => i.length)
|
||||||
.map(async (url) => {
|
.map(async (url) => {
|
||||||
try {
|
try {
|
||||||
return await download(url, ua || sub.ua);
|
return await download(
|
||||||
|
url,
|
||||||
|
ua || sub.ua,
|
||||||
|
undefined,
|
||||||
|
sub.proxy,
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errors[url] = err;
|
errors[url] = err;
|
||||||
$.error(
|
$.error(
|
||||||
@@ -94,7 +107,12 @@ async function produceArtifact({
|
|||||||
.filter((i) => i.length)
|
.filter((i) => i.length)
|
||||||
.map(async (url) => {
|
.map(async (url) => {
|
||||||
try {
|
try {
|
||||||
return await download(url, ua || sub.ua);
|
return await download(
|
||||||
|
url,
|
||||||
|
ua || sub.ua,
|
||||||
|
undefined,
|
||||||
|
sub.proxy,
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errors[url] = err;
|
errors[url] = err;
|
||||||
$.error(
|
$.error(
|
||||||
@@ -190,7 +208,12 @@ async function produceArtifact({
|
|||||||
.filter((i) => i.length)
|
.filter((i) => i.length)
|
||||||
.map(async (url) => {
|
.map(async (url) => {
|
||||||
try {
|
try {
|
||||||
return await download(url, sub.ua);
|
return await download(
|
||||||
|
url,
|
||||||
|
sub.ua,
|
||||||
|
undefined,
|
||||||
|
sub.proxy,
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errors[url] = err;
|
errors[url] = err;
|
||||||
$.error(
|
$.error(
|
||||||
@@ -540,10 +563,19 @@ async function syncArtifacts() {
|
|||||||
files.map((item) => [item.path, item]),
|
files.map((item) => [item.path, item]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const url = files[encodeURIComponent(artifact.name)]?.raw_url;
|
const raw_url =
|
||||||
artifact.url = isGitLab
|
files[encodeURIComponent(artifact.name)]?.raw_url;
|
||||||
? url
|
const new_url = isGitLab
|
||||||
: url?.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
|
? raw_url
|
||||||
|
: raw_url?.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
|
||||||
|
$.info(
|
||||||
|
`上传配置完成\n文件列表: ${Object.keys(files).join(
|
||||||
|
', ',
|
||||||
|
)}\n当前文件: ${encodeURIComponent(
|
||||||
|
artifact.name,
|
||||||
|
)}\n响应返回的原始链接: ${raw_url}\n处理完的新链接: ${new_url}`,
|
||||||
|
);
|
||||||
|
artifact.url = new_url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -637,10 +669,18 @@ async function syncArtifact(req, res) {
|
|||||||
isGitLab = true;
|
isGitLab = true;
|
||||||
files = Object.fromEntries(files.map((item) => [item.path, item]));
|
files = Object.fromEntries(files.map((item) => [item.path, item]));
|
||||||
}
|
}
|
||||||
const url = files[encodeURIComponent(artifact.name)]?.raw_url;
|
const raw_url = files[encodeURIComponent(artifact.name)]?.raw_url;
|
||||||
artifact.url = isGitLab
|
const new_url = isGitLab
|
||||||
? url
|
? raw_url
|
||||||
: url?.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
|
: raw_url?.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
|
||||||
|
$.info(
|
||||||
|
`上传配置完成\n文件列表: ${Object.keys(files).join(
|
||||||
|
', ',
|
||||||
|
)}\n当前文件: ${encodeURIComponent(
|
||||||
|
artifact.name,
|
||||||
|
)}\n响应返回的原始链接: ${raw_url}\n处理完的新链接: ${new_url}`,
|
||||||
|
);
|
||||||
|
artifact.url = new_url;
|
||||||
$.write(allArtifacts, ARTIFACTS_KEY);
|
$.write(allArtifacts, ARTIFACTS_KEY);
|
||||||
success(res, artifact);
|
success(res, artifact);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { SETTINGS_KEY } from '@/constants';
|
import { SETTINGS_KEY } from '@/constants';
|
||||||
import { HTTP, ENV } from '@/vendor/open-api';
|
import { HTTP, ENV } from '@/vendor/open-api';
|
||||||
import { hex_md5 } from '@/vendor/md5';
|
import { hex_md5 } from '@/vendor/md5';
|
||||||
|
import { getPolicyDescriptor } from '@/utils';
|
||||||
import resourceCache from '@/utils/resource-cache';
|
import resourceCache from '@/utils/resource-cache';
|
||||||
import headersResourceCache from '@/utils/headers-resource-cache';
|
import headersResourceCache from '@/utils/headers-resource-cache';
|
||||||
import {
|
import {
|
||||||
@@ -13,7 +14,7 @@ import $ from '@/core/app';
|
|||||||
|
|
||||||
const tasks = new Map();
|
const tasks = new Map();
|
||||||
|
|
||||||
export default async function download(rawUrl, ua, timeout) {
|
export default async function download(rawUrl, ua, timeout, proxy) {
|
||||||
let $arguments = {};
|
let $arguments = {};
|
||||||
let url = rawUrl.replace(/#noFlow$/, '');
|
let url = rawUrl.replace(/#noFlow$/, '');
|
||||||
const rawArgs = url.split('#');
|
const rawArgs = url.split('#');
|
||||||
@@ -52,7 +53,7 @@ export default async function download(rawUrl, ua, timeout) {
|
|||||||
// return item.content;
|
// return item.content;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const { isNode } = ENV();
|
const { isNode, isStash, isLoon, isShadowRocket, isQX } = ENV();
|
||||||
const { defaultUserAgent, defaultTimeout, cacheThreshold } =
|
const { defaultUserAgent, defaultTimeout, cacheThreshold } =
|
||||||
$.read(SETTINGS_KEY);
|
$.read(SETTINGS_KEY);
|
||||||
const userAgent = ua || defaultUserAgent || 'clash.meta';
|
const userAgent = ua || defaultUserAgent || 'clash.meta';
|
||||||
@@ -65,6 +66,10 @@ export default async function download(rawUrl, ua, timeout) {
|
|||||||
const http = HTTP({
|
const http = HTTP({
|
||||||
headers: {
|
headers: {
|
||||||
'User-Agent': userAgent,
|
'User-Agent': userAgent,
|
||||||
|
...(isStash && proxy
|
||||||
|
? { 'X-Stash-Selected-Proxy': encodeURIComponent(proxy) }
|
||||||
|
: {}),
|
||||||
|
...(isShadowRocket && proxy ? { 'X-Surge-Policy': proxy } : {}),
|
||||||
},
|
},
|
||||||
timeout: requestTimeout,
|
timeout: requestTimeout,
|
||||||
});
|
});
|
||||||
@@ -78,10 +83,16 @@ export default async function download(rawUrl, ua, timeout) {
|
|||||||
result = cached;
|
result = cached;
|
||||||
} else {
|
} else {
|
||||||
$.info(
|
$.info(
|
||||||
`Downloading...\nUser-Agent: ${userAgent}\nTimeout: ${requestTimeout}\nURL: ${url}`,
|
`Downloading...\nUser-Agent: ${userAgent}\nTimeout: ${requestTimeout}\nProxy: ${proxy}\nURL: ${url}`,
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
const { body, headers } = await http.get(url);
|
const { body, headers } = await http.get({
|
||||||
|
url,
|
||||||
|
...(proxy ? { proxy } : {}),
|
||||||
|
...(isLoon && proxy ? { node: proxy } : {}),
|
||||||
|
...(isQX && proxy ? { opts: { policy: proxy } } : {}),
|
||||||
|
...(proxy ? getPolicyDescriptor(proxy) : {}),
|
||||||
|
});
|
||||||
|
|
||||||
if (headers) {
|
if (headers) {
|
||||||
const flowInfo = getFlowField(headers);
|
const flowInfo = getFlowField(headers);
|
||||||
@@ -116,7 +127,16 @@ export default async function download(rawUrl, ua, timeout) {
|
|||||||
// 检查订阅有效性
|
// 检查订阅有效性
|
||||||
|
|
||||||
if ($arguments?.validCheck) {
|
if ($arguments?.validCheck) {
|
||||||
await validCheck(parseFlowHeaders(await getFlowHeaders(url)));
|
await validCheck(
|
||||||
|
parseFlowHeaders(
|
||||||
|
await getFlowHeaders(
|
||||||
|
url,
|
||||||
|
$arguments.flowUserAgent,
|
||||||
|
undefined,
|
||||||
|
proxy,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isNode) {
|
if (!isNode) {
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
import { version as substoreVersion } from '../../package.json';
|
import { version as substoreVersion } from '../../package.json';
|
||||||
import { ENV } from '@/vendor/open-api';
|
import { ENV } from '@/vendor/open-api';
|
||||||
|
|
||||||
const { isNode, isQX, isLoon, isSurge, isStash, isShadowRocket } = ENV();
|
const {
|
||||||
|
isNode,
|
||||||
|
isQX,
|
||||||
|
isLoon,
|
||||||
|
isSurge,
|
||||||
|
isStash,
|
||||||
|
isShadowRocket,
|
||||||
|
isLanceX,
|
||||||
|
isEgern,
|
||||||
|
} = ENV();
|
||||||
let backend = 'Node';
|
let backend = 'Node';
|
||||||
if (isNode) backend = 'Node';
|
if (isNode) backend = 'Node';
|
||||||
if (isQX) backend = 'QX';
|
if (isQX) backend = 'QX';
|
||||||
@@ -9,8 +18,44 @@ if (isLoon) backend = 'Loon';
|
|||||||
if (isSurge) backend = 'Surge';
|
if (isSurge) backend = 'Surge';
|
||||||
if (isStash) backend = 'Stash';
|
if (isStash) backend = 'Stash';
|
||||||
if (isShadowRocket) backend = 'ShadowRocket';
|
if (isShadowRocket) backend = 'ShadowRocket';
|
||||||
|
if (isEgern) backend = 'Egern';
|
||||||
|
if (isLanceX) backend = 'LanceX';
|
||||||
|
|
||||||
|
let meta = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (typeof $environment !== 'undefined') {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
meta.env = $environment;
|
||||||
|
}
|
||||||
|
if (typeof $loon !== 'undefined') {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
meta.loon = $loon;
|
||||||
|
}
|
||||||
|
if (typeof $script !== 'undefined') {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
meta.script = $script;
|
||||||
|
}
|
||||||
|
if (isNode) {
|
||||||
|
meta.node = {
|
||||||
|
version: eval('process.version'),
|
||||||
|
argv: eval('process.argv'),
|
||||||
|
filename: eval('__filename'),
|
||||||
|
dirname: eval('__dirname'),
|
||||||
|
env: {},
|
||||||
|
};
|
||||||
|
const env = eval('process.env');
|
||||||
|
for (const key in env) {
|
||||||
|
if (/^SUB_STORE_/.test(key)) {
|
||||||
|
meta.node.env[key] = env[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-empty
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
backend,
|
backend,
|
||||||
version: substoreVersion,
|
version: substoreVersion,
|
||||||
|
meta,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { SETTINGS_KEY } from '@/constants';
|
import { SETTINGS_KEY } from '@/constants';
|
||||||
import { HTTP } from '@/vendor/open-api';
|
import { HTTP, ENV } from '@/vendor/open-api';
|
||||||
|
import { getPolicyDescriptor } from '@/utils';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
import headersResourceCache from '@/utils/headers-resource-cache';
|
import headersResourceCache from '@/utils/headers-resource-cache';
|
||||||
|
|
||||||
@@ -9,7 +10,7 @@ export function getFlowField(headers) {
|
|||||||
)[0];
|
)[0];
|
||||||
return headers[subkey];
|
return headers[subkey];
|
||||||
}
|
}
|
||||||
export async function getFlowHeaders(rawUrl, ua, timeout) {
|
export async function getFlowHeaders(rawUrl, ua, timeout, proxy) {
|
||||||
let url = rawUrl;
|
let url = rawUrl;
|
||||||
let $arguments = {};
|
let $arguments = {};
|
||||||
const rawArgs = url.split('#');
|
const rawArgs = url.split('#');
|
||||||
@@ -33,6 +34,7 @@ export async function getFlowHeaders(rawUrl, ua, timeout) {
|
|||||||
if ($arguments?.noFlow) {
|
if ($arguments?.noFlow) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const { isStash, isLoon, isShadowRocket, isQX } = ENV();
|
||||||
const cached = headersResourceCache.get(url);
|
const cached = headersResourceCache.get(url);
|
||||||
let flowInfo;
|
let flowInfo;
|
||||||
if (!$arguments?.noCache && cached) {
|
if (!$arguments?.noCache && cached) {
|
||||||
@@ -47,7 +49,11 @@ export async function getFlowHeaders(rawUrl, ua, timeout) {
|
|||||||
const requestTimeout = timeout || defaultTimeout;
|
const requestTimeout = timeout || defaultTimeout;
|
||||||
const http = HTTP();
|
const http = HTTP();
|
||||||
try {
|
try {
|
||||||
// $.info(`使用 HEAD 方法获取流量信息: ${url}`);
|
$.info(
|
||||||
|
`使用 HEAD 方法获取流量信息: ${url}, User-Agent: ${
|
||||||
|
userAgent || ''
|
||||||
|
}`,
|
||||||
|
);
|
||||||
const { headers } = await http.head({
|
const { headers } = await http.head({
|
||||||
url: url
|
url: url
|
||||||
.split(/[\r\n]+/)
|
.split(/[\r\n]+/)
|
||||||
@@ -55,17 +61,36 @@ export async function getFlowHeaders(rawUrl, ua, timeout) {
|
|||||||
.filter((i) => i.length)[0],
|
.filter((i) => i.length)[0],
|
||||||
headers: {
|
headers: {
|
||||||
'User-Agent': userAgent,
|
'User-Agent': userAgent,
|
||||||
|
...(isStash && proxy
|
||||||
|
? {
|
||||||
|
'X-Stash-Selected-Proxy':
|
||||||
|
encodeURIComponent(proxy),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
...(isShadowRocket && proxy
|
||||||
|
? { 'X-Surge-Policy': proxy }
|
||||||
|
: {}),
|
||||||
},
|
},
|
||||||
timeout: requestTimeout,
|
timeout: requestTimeout,
|
||||||
|
...(proxy ? { proxy } : {}),
|
||||||
|
...(isLoon && proxy ? { node: proxy } : {}),
|
||||||
|
...(isQX && proxy ? { opts: { policy: proxy } } : {}),
|
||||||
|
...(proxy ? getPolicyDescriptor(proxy) : {}),
|
||||||
});
|
});
|
||||||
flowInfo = getFlowField(headers);
|
flowInfo = getFlowField(headers);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
$.error(
|
$.error(
|
||||||
`使用 HEAD 方法获取流量信息失败: ${url}: ${e.message ?? e}`,
|
`使用 HEAD 方法获取流量信息失败: ${url}, User-Agent: ${
|
||||||
|
userAgent || ''
|
||||||
|
}: ${e.message ?? e}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!flowInfo) {
|
if (!flowInfo) {
|
||||||
$.info(`使用 GET 方法获取流量信息: ${url}`);
|
$.info(
|
||||||
|
`使用 GET 方法获取流量信息: ${url}, User-Agent: ${
|
||||||
|
userAgent || ''
|
||||||
|
}`,
|
||||||
|
);
|
||||||
const { headers } = await http.get({
|
const { headers } = await http.get({
|
||||||
url: url
|
url: url
|
||||||
.split(/[\r\n]+/)
|
.split(/[\r\n]+/)
|
||||||
@@ -143,3 +168,60 @@ export function validCheck(flow) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getRmainingDays(opt = {}) {
|
||||||
|
try {
|
||||||
|
let { resetDay, startDate, cycleDays } = opt;
|
||||||
|
if (['string', 'number'].includes(typeof opt)) {
|
||||||
|
resetDay = opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startDate && cycleDays) {
|
||||||
|
cycleDays = parseInt(cycleDays);
|
||||||
|
if (isNaN(cycleDays) || cycleDays <= 0)
|
||||||
|
throw new Error('重置周期应为正整数');
|
||||||
|
if (!startDate || !Date.parse(startDate))
|
||||||
|
throw new Error('开始日期不合法');
|
||||||
|
|
||||||
|
const start = new Date(startDate);
|
||||||
|
const today = new Date();
|
||||||
|
start.setHours(0, 0, 0, 0);
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
if (start.getTime() > today.getTime())
|
||||||
|
throw new Error('开始日期应早于现在');
|
||||||
|
|
||||||
|
let resetDate = new Date(startDate);
|
||||||
|
resetDate.setDate(resetDate.getDate() + cycleDays);
|
||||||
|
|
||||||
|
while (resetDate < today) {
|
||||||
|
resetDate.setDate(resetDate.getDate() + cycleDays);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetDate.setHours(0, 0, 0, 0);
|
||||||
|
const timeDiff = resetDate.getTime() - today.getTime();
|
||||||
|
const daysDiff = Math.ceil(timeDiff / (1000 * 3600 * 24));
|
||||||
|
|
||||||
|
return daysDiff;
|
||||||
|
} else {
|
||||||
|
if (!resetDay) return;
|
||||||
|
resetDay = parseInt(resetDay);
|
||||||
|
if (isNaN(resetDay) || resetDay <= 0 || resetDay > 31)
|
||||||
|
throw new Error('月重置日应为 1-31 之间的整数');
|
||||||
|
let now = new Date();
|
||||||
|
let today = now.getDate();
|
||||||
|
let month = now.getMonth();
|
||||||
|
let year = now.getFullYear();
|
||||||
|
let daysInMonth;
|
||||||
|
|
||||||
|
if (resetDay > today) {
|
||||||
|
daysInMonth = 0;
|
||||||
|
} else {
|
||||||
|
daysInMonth = new Date(year, month + 1, 0).getDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
return daysInMonth - today + resetDay;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$.error(`getRmainingDays failed: ${e.message ?? e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,6 +35,17 @@ function getIfPresent(obj, defaultValue) {
|
|||||||
return isPresent(obj) ? obj : defaultValue;
|
return isPresent(obj) ? obj : defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPolicyDescriptor(str) {
|
||||||
|
if (!str) return {};
|
||||||
|
return /^.+?\s*?=\s*?.+?\s*?,.+?/.test(str)
|
||||||
|
? {
|
||||||
|
'policy-descriptor': str,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
policy: str,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const utf8ArrayToStr =
|
const utf8ArrayToStr =
|
||||||
typeof TextDecoder !== 'undefined'
|
typeof TextDecoder !== 'undefined'
|
||||||
? (v) => new TextDecoder().decode(new Uint8Array(v))
|
? (v) => new TextDecoder().decode(new Uint8Array(v))
|
||||||
@@ -91,4 +102,5 @@ export {
|
|||||||
isPresent,
|
isPresent,
|
||||||
getIfPresent,
|
getIfPresent,
|
||||||
utf8ArrayToStr,
|
utf8ArrayToStr,
|
||||||
|
getPolicyDescriptor,
|
||||||
};
|
};
|
||||||
|
|||||||
70
backend/src/vendor/open-api.js
vendored
70
backend/src/vendor/open-api.js
vendored
@@ -6,6 +6,8 @@ const isNode = eval(`typeof process !== "undefined"`); // eval is needed in orde
|
|||||||
const isStash =
|
const isStash =
|
||||||
'undefined' !== typeof $environment && $environment['stash-version'];
|
'undefined' !== typeof $environment && $environment['stash-version'];
|
||||||
const isShadowRocket = 'undefined' !== typeof $rocket;
|
const isShadowRocket = 'undefined' !== typeof $rocket;
|
||||||
|
const isEgern = 'object' == typeof egern;
|
||||||
|
const isLanceX = 'undefined' != typeof $native;
|
||||||
|
|
||||||
export class OpenAPI {
|
export class OpenAPI {
|
||||||
constructor(name = 'untitled', debug = false) {
|
constructor(name = 'untitled', debug = false) {
|
||||||
@@ -251,7 +253,16 @@ export class OpenAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ENV() {
|
export function ENV() {
|
||||||
return { isQX, isLoon, isSurge, isNode, isStash, isShadowRocket };
|
return {
|
||||||
|
isQX,
|
||||||
|
isLoon,
|
||||||
|
isSurge,
|
||||||
|
isNode,
|
||||||
|
isStash,
|
||||||
|
isShadowRocket,
|
||||||
|
isEgern,
|
||||||
|
isLanceX,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HTTP(defaultOptions = { baseURL: '' }) {
|
export function HTTP(defaultOptions = { baseURL: '' }) {
|
||||||
@@ -305,42 +316,53 @@ export function HTTP(defaultOptions = { baseURL: '' }) {
|
|||||||
url: options.url,
|
url: options.url,
|
||||||
headers: options.headers,
|
headers: options.headers,
|
||||||
body: options.body,
|
body: options.body,
|
||||||
|
opts: options.opts,
|
||||||
});
|
});
|
||||||
} else if (isLoon || isSurge || isNode) {
|
} else if (isLoon || isSurge || isNode) {
|
||||||
worker = new Promise((resolve, reject) => {
|
worker = new Promise((resolve, reject) => {
|
||||||
const request = isNode
|
const request = isNode
|
||||||
? eval("require('request')")
|
? eval("require('request')")
|
||||||
: $httpClient;
|
: $httpClient;
|
||||||
request[method.toLowerCase()](
|
const opts = JSON.parse(JSON.stringify(options));
|
||||||
JSON.parse(JSON.stringify(options)),
|
if (!isNode && opts.timeout) {
|
||||||
(err, response, body) => {
|
opts.timeout++;
|
||||||
// if (err) {
|
let unit = 'ms';
|
||||||
// console.log(err);
|
// 这些客户端单位为 s
|
||||||
// } else {
|
if (isSurge || isStash || isShadowRocket) {
|
||||||
// console.log({
|
opts.timeout = Math.ceil(opts.timeout / 1000);
|
||||||
// statusCode:
|
unit = 's';
|
||||||
// response.status || response.statusCode,
|
}
|
||||||
// headers: response.headers,
|
// Loon 为 ms
|
||||||
// body,
|
// console.log(`[httpClient timeout] ${opts.timeout}${unit}`);
|
||||||
// });
|
}
|
||||||
// }
|
request[method.toLowerCase()](opts, (err, response, body) => {
|
||||||
|
// if (err) {
|
||||||
|
// console.log(err);
|
||||||
|
// } else {
|
||||||
|
// console.log({
|
||||||
|
// statusCode:
|
||||||
|
// response.status || response.statusCode,
|
||||||
|
// headers: response.headers,
|
||||||
|
// body,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
if (err) reject(err);
|
if (err) reject(err);
|
||||||
else
|
else
|
||||||
resolve({
|
resolve({
|
||||||
statusCode:
|
statusCode: response.status || response.statusCode,
|
||||||
response.status || response.statusCode,
|
headers: response.headers,
|
||||||
headers: response.headers,
|
body,
|
||||||
body,
|
});
|
||||||
});
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let timeoutid;
|
let timeoutid;
|
||||||
|
|
||||||
const timer = timeout
|
const timer = timeout
|
||||||
? new Promise((_, reject) => {
|
? new Promise((_, reject) => {
|
||||||
|
// console.log(`[request timeout] ${timeout}ms`);
|
||||||
timeoutid = setTimeout(() => {
|
timeoutid = setTimeout(() => {
|
||||||
events.onTimeout();
|
events.onTimeout();
|
||||||
return reject(
|
return reject(
|
||||||
|
|||||||
@@ -17,4 +17,4 @@ hostname=sub.store
|
|||||||
http-request ^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))) script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js, requires-body=true, timeout=120, tag=Sub-Store Core
|
http-request ^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))) script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js, requires-body=true, timeout=120, tag=Sub-Store Core
|
||||||
http-request ^https?:\/\/sub\.store script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js, requires-body=true, timeout=120, tag=Sub-Store Simple
|
http-request ^https?:\/\/sub\.store script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js, requires-body=true, timeout=120, tag=Sub-Store Simple
|
||||||
|
|
||||||
cron "55 23 * * *" script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js, tag=Sub-Store Sync
|
cron "55 23 * * *" script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js, timeout=120, tag=Sub-Store Sync
|
||||||
@@ -10,6 +10,6 @@ hostname = %APPEND% sub.store
|
|||||||
[Script]
|
[Script]
|
||||||
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout=120,ability="{{{ability}}}"
|
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout=120,ability="{{{ability}}}"
|
||||||
|
|
||||||
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true
|
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true,timeout=120
|
||||||
|
|
||||||
{{{sync}}}=type=cron,cronexp="{{{cronexp}}}",wake-system=1,timeout=120,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js
|
{{{sync}}}=type=cron,cronexp="{{{cronexp}}}",wake-system=1,timeout=120,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ hostname = %APPEND% sub.store
|
|||||||
[Script]
|
[Script]
|
||||||
# 主程序 已经去掉 Sub-Store Core 的参数 [,ability=http-client-policy] 不会爆内存,这个参数在 Surge 非常占用内存; 如果不需要使用指定节点功能 例如[加旗帜脚本或者cname脚本] 则可以使用此脚本
|
# 主程序 已经去掉 Sub-Store Core 的参数 [,ability=http-client-policy] 不会爆内存,这个参数在 Surge 非常占用内存; 如果不需要使用指定节点功能 例如[加旗帜脚本或者cname脚本] 则可以使用此脚本
|
||||||
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout=120
|
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout=120
|
||||||
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true
|
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true,timeout=120
|
||||||
|
|
||||||
Sub-Store Sync=type=cron,cronexp=55 23 * * *,wake-system=1,timeout=120,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js
|
Sub-Store Sync=type=cron,cronexp=55 23 * * *,wake-system=1,timeout=120,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ hostname = %APPEND% sub.store
|
|||||||
|
|
||||||
[Script]
|
[Script]
|
||||||
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout=120,ability=http-client-policy
|
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout=120,ability=http-client-policy
|
||||||
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true
|
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true,timeout=120
|
||||||
|
|
||||||
Sub-Store Sync=type=cron,cronexp=55 23 * * *,wake-system=1,timeout=120,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js
|
Sub-Store Sync=type=cron,cronexp=55 23 * * *,wake-system=1,timeout=120,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ hostname = %APPEND% sub.store
|
|||||||
|
|
||||||
[Script]
|
[Script]
|
||||||
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout=120
|
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout=120
|
||||||
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true
|
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true,timeout=120
|
||||||
|
|
||||||
Sub-Store Sync=type=cron,cronexp=55 23 * * *,wake-system=1,timeout=120,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js
|
Sub-Store Sync=type=cron,cronexp=55 23 * * *,wake-system=1,timeout=120,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
|
|
||||||
// scriptResourceCache 缓存
|
// scriptResourceCache 缓存
|
||||||
// 可参考 https://t.me/zhetengsha/1003
|
// 可参考 https://t.me/zhetengsha/1003
|
||||||
|
// const cache = scriptResourceCache
|
||||||
|
// cache.set(id, data)
|
||||||
|
// cache.get(id)
|
||||||
|
|
||||||
// ProxyUtils 为节点处理工具
|
// ProxyUtils 为节点处理工具
|
||||||
// 可参考 https://t.me/zhetengsha/1066
|
// 可参考 https://t.me/zhetengsha/1066
|
||||||
@@ -35,6 +38,13 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
// yaml, // yaml 解析和生成
|
// yaml, // yaml 解析和生成
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// 示例: 从 sni 文件中读取内容并进行节点操作
|
||||||
|
// const sni = await produceArtifact({
|
||||||
|
// type: 'file',
|
||||||
|
// name: 'sni' // 文件名
|
||||||
|
// });
|
||||||
|
// $server.sni = sni
|
||||||
|
|
||||||
// 1. Surge 输出 WireGuard 完整配置
|
// 1. Surge 输出 WireGuard 完整配置
|
||||||
|
|
||||||
// let proxies = await produceArtifact({
|
// let proxies = await produceArtifact({
|
||||||
@@ -49,7 +59,10 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
|
|
||||||
// 2. sing-box
|
// 2. sing-box
|
||||||
|
|
||||||
// 但是一般不需要这样用, 可参考 1. https://t.me/zhetengsha/1111 和 2. https://t.me/zhetengsha/1070
|
// 但是一般不需要这样用, 可参考
|
||||||
|
// 1. https://t.me/zhetengsha/1111
|
||||||
|
// 2. https://t.me/zhetengsha/1070
|
||||||
|
// 3. https://t.me/zhetengsha/1241
|
||||||
|
|
||||||
// let singboxProxies = await produceArtifact({
|
// let singboxProxies = await produceArtifact({
|
||||||
// type: 'subscription', // type: 'subscription' 或 'collection'
|
// type: 'subscription', // type: 'subscription' 或 'collection'
|
||||||
@@ -63,24 +76,42 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
|
|
||||||
// 3. clash.meta
|
// 3. clash.meta
|
||||||
|
|
||||||
// 但是一般不需要这样用, 可参考 1. https://t.me/zhetengsha/1111 和 2. https://t.me/zhetengsha/1070
|
// 但是一般不需要这样用, 可参考
|
||||||
|
// 1. https://t.me/zhetengsha/1111
|
||||||
|
// 2. https://t.me/zhetengsha/1070
|
||||||
|
// 3. https://t.me/zhetengsha/1234
|
||||||
|
|
||||||
// let clashMetaProxies = await produceArtifact({
|
// let clashMetaProxies = await produceArtifact({
|
||||||
// type: 'subscription',
|
// type: 'subscription',
|
||||||
// name: 'sub',
|
// name: 'sub',
|
||||||
// platform: 'ClashMeta',
|
// platform: 'ClashMeta',
|
||||||
// produceType: 'internal' // 'internal' produces an Array, otherwise produces a String( ProxyUtils.yaml.safeLoad('YAML String').proxies )
|
// produceType: 'internal' // 'internal' produces an Array, otherwise produces a String( ProxyUtils.yaml.safeLoad('YAML String').proxies )
|
||||||
// }))
|
// })
|
||||||
|
|
||||||
// // YAML
|
// // YAML
|
||||||
|
// ProxyUtils.yaml.load('YAML String')
|
||||||
|
// ProxyUtils.yaml.safeLoad('YAML String')
|
||||||
// $content = ProxyUtils.yaml.safeDump({})
|
// $content = ProxyUtils.yaml.safeDump({})
|
||||||
|
// $content = ProxyUtils.yaml.dump({})
|
||||||
|
|
||||||
|
// 一个往文件里插入本地节点的例子:
|
||||||
|
// const yaml = ProxyUtils.yaml.safeLoad($content ?? $files[0])
|
||||||
|
// let clashMetaProxies = await produceArtifact({
|
||||||
|
// type: 'collection',
|
||||||
|
// name: '机场',
|
||||||
|
// platform: 'ClashMeta',
|
||||||
|
// produceType: 'internal'
|
||||||
|
// })
|
||||||
|
// yaml.proxies.unshift(...clashMetaProxies)
|
||||||
|
// $content = ProxyUtils.yaml.dump(yaml)
|
||||||
|
|
||||||
|
|
||||||
// { $content, $files } will be passed to the next operator
|
// { $content, $files } will be passed to the next operator
|
||||||
// $content is the final content of the file
|
// $content is the final content of the file
|
||||||
|
|
||||||
// flowUtils 为机场订阅流量信息处理工具
|
// flowUtils 为机场订阅流量信息处理工具
|
||||||
// 可参考 https://t.me/zhetengsha/948
|
// 可参考:
|
||||||
// https://github.com/sub-store-org/Sub-Store/blob/31b6dd0507a9286d6ab834ec94ad3050f6bdc86b/backend/src/utils/download.js#L104
|
// 1. https://t.me/zhetengsha/948
|
||||||
|
|
||||||
// context 为传入的上下文
|
// context 为传入的上下文
|
||||||
// 有三种情况, 按需判断
|
// 有三种情况, 按需判断
|
||||||
|
|||||||
Reference in New Issue
Block a user