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 | |
|---|---|---|---|
|
|
b0a544b879 | ||
|
|
80955aa339 | ||
|
|
4d27e5bdac | ||
|
|
e2011de69e | ||
|
|
9568f4d6d9 | ||
|
|
543641de9d | ||
|
|
2fbc589a8a | ||
|
|
c854614efc | ||
|
|
16a5995d21 | ||
|
|
15b55f6d1a | ||
|
|
8e5ce26e7b | ||
|
|
c5d8aff73c | ||
|
|
5696492dde | ||
|
|
e6d05fd873 | ||
|
|
4111b8fabf | ||
|
|
dfc619a181 | ||
|
|
ff5283a66f | ||
|
|
6c54518e84 | ||
|
|
dd92a26e6c | ||
|
|
bb5c9d43d0 | ||
|
|
e54ac92357 | ||
|
|
507e37021c | ||
|
|
a70dc7b913 | ||
|
|
fc56df7bfd | ||
|
|
1281df59f3 | ||
|
|
1faa3fb793 | ||
|
|
47307716b2 |
@@ -41,6 +41,9 @@ Core functionalities:
|
|||||||
- [x] Surfboard (SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, WireGuard(Surfboard to Surfboard))
|
- [x] Surfboard (SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, WireGuard(Surfboard to Surfboard))
|
||||||
- [x] Clash.Meta (Direct, SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC, SSH, mieru, AnyTLS)
|
- [x] Clash.Meta (Direct, SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC, SSH, mieru, AnyTLS)
|
||||||
- [x] Stash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, TUIC, Juicity, SSH)
|
- [x] Stash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, TUIC, Juicity, SSH)
|
||||||
|
|
||||||
|
Deprecated:
|
||||||
|
|
||||||
- [x] Clash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard)
|
- [x] Clash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard)
|
||||||
|
|
||||||
### Supported Target Platforms
|
### Supported Target Platforms
|
||||||
@@ -48,7 +51,6 @@ Core functionalities:
|
|||||||
- [x] Plain JSON
|
- [x] Plain JSON
|
||||||
- [x] Stash
|
- [x] Stash
|
||||||
- [x] Clash.Meta(mihomo)
|
- [x] Clash.Meta(mihomo)
|
||||||
- [x] Clash
|
|
||||||
- [x] Surfboard
|
- [x] Surfboard
|
||||||
- [x] Surge
|
- [x] Surge
|
||||||
- [x] SurgeMac(Use mihomo to support protocols that are not supported by Surge itself)
|
- [x] SurgeMac(Use mihomo to support protocols that are not supported by Surge itself)
|
||||||
@@ -60,6 +62,10 @@ Core functionalities:
|
|||||||
- [x] V2Ray
|
- [x] V2Ray
|
||||||
- [x] V2Ray URI
|
- [x] V2Ray URI
|
||||||
|
|
||||||
|
Deprecated:
|
||||||
|
|
||||||
|
- [x] Clash
|
||||||
|
|
||||||
## 2. Subscription Formatting
|
## 2. Subscription Formatting
|
||||||
|
|
||||||
### Filtering
|
### Filtering
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sub-store",
|
"name": "sub-store",
|
||||||
"version": "2.18.0",
|
"version": "2.19.11",
|
||||||
"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": {
|
||||||
@@ -27,10 +27,12 @@
|
|||||||
"automerge": "1.0.1-preview.7",
|
"automerge": "1.0.1-preview.7",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
|
"dotenv": "^16.4.7",
|
||||||
"connect-history-api-fallback": "^2.0.0",
|
"connect-history-api-fallback": "^2.0.0",
|
||||||
"cron": "^3.1.6",
|
"cron": "^3.1.6",
|
||||||
"dns-packet": "^5.6.1",
|
"dns-packet": "^5.6.1",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
|
"mime-types": "^2.1.35",
|
||||||
"http-proxy-middleware": "^3.0.3",
|
"http-proxy-middleware": "^3.0.3",
|
||||||
"ip-address": "^9.0.5",
|
"ip-address": "^9.0.5",
|
||||||
"js-base64": "^3.7.2",
|
"js-base64": "^3.7.2",
|
||||||
|
|||||||
@@ -114,12 +114,7 @@ async function processFn(
|
|||||||
if (item.type.indexOf('Script') !== -1) {
|
if (item.type.indexOf('Script') !== -1) {
|
||||||
const { mode, content } = item.args;
|
const { mode, content } = item.args;
|
||||||
if (mode === 'link') {
|
if (mode === 'link') {
|
||||||
let noCache;
|
|
||||||
let url = content || '';
|
let url = content || '';
|
||||||
if (url.endsWith('#noCache')) {
|
|
||||||
url = url.replace(/#noCache$/, '');
|
|
||||||
noCache = true;
|
|
||||||
}
|
|
||||||
// extract link arguments
|
// extract link arguments
|
||||||
const rawArgs = url.split('#');
|
const rawArgs = url.split('#');
|
||||||
if (rawArgs.length > 1) {
|
if (rawArgs.length > 1) {
|
||||||
@@ -138,7 +133,14 @@ async function processFn(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
url = `${url.split('#')[0]}${noCache ? '#noCache' : ''}`;
|
url = `${url.split('#')[0]}${
|
||||||
|
rawArgs[2]
|
||||||
|
? `#${rawArgs[2]}`
|
||||||
|
: $arguments?.noCache != null ||
|
||||||
|
$arguments?.insecure != null
|
||||||
|
? `#${rawArgs[1]}`
|
||||||
|
: ''
|
||||||
|
}`;
|
||||||
const downloadUrlMatch = url.match(
|
const downloadUrlMatch = url.match(
|
||||||
/^\/api\/(file|module)\/(.+)/,
|
/^\/api\/(file|module)\/(.+)/,
|
||||||
);
|
);
|
||||||
@@ -613,7 +615,7 @@ function lastParse(proxy) {
|
|||||||
proxy['tls-fingerprint'] = rs.generateFingerprint(caStr);
|
proxy['tls-fingerprint'] = rs.generateFingerprint(caStr);
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
['shadowsocks'].includes(proxy.type) &&
|
['ss'].includes(proxy.type) &&
|
||||||
isPresent(proxy, 'shadow-tls-password')
|
isPresent(proxy, 'shadow-tls-password')
|
||||||
) {
|
) {
|
||||||
proxy.plugin = 'shadow-tls';
|
proxy.plugin = 'shadow-tls';
|
||||||
|
|||||||
@@ -342,7 +342,7 @@ function URI_VMess() {
|
|||||||
};
|
};
|
||||||
const parse = (line) => {
|
const parse = (line) => {
|
||||||
line = line.split('vmess://')[1];
|
line = line.split('vmess://')[1];
|
||||||
let content = Base64.decode(line);
|
let content = Base64.decode(line.replace(/\?.*?$/, ''));
|
||||||
if (/=\s*vmess/.test(content)) {
|
if (/=\s*vmess/.test(content)) {
|
||||||
// Quantumult VMess URI format
|
// Quantumult VMess URI format
|
||||||
const partitions = content.split(',').map((p) => p.trim());
|
const partitions = content.split(',').map((p) => p.trim());
|
||||||
@@ -634,6 +634,9 @@ function URI_VLESS() {
|
|||||||
}
|
}
|
||||||
if (!proxy.network && isShadowrocket && params.obfs) {
|
if (!proxy.network && isShadowrocket && params.obfs) {
|
||||||
proxy.network = params.obfs;
|
proxy.network = params.obfs;
|
||||||
|
if (['none'].includes(proxy.network)) {
|
||||||
|
proxy.network = 'tcp';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (['websocket'].includes(proxy.network)) {
|
if (['websocket'].includes(proxy.network)) {
|
||||||
proxy.network = 'ws';
|
proxy.network = 'ws';
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ vmess = tag equals "vmess"i address method uuid (transport/transport_host/transp
|
|||||||
proxy.alterId = proxy.alterId || 0;
|
proxy.alterId = proxy.alterId || 0;
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
|
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/flow/public_key/short_id/others)* {
|
||||||
proxy.type = "vless";
|
proxy.type = "vless";
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
@@ -180,6 +180,10 @@ tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-
|
|||||||
tls_cert_sha256 = comma "tls-cert-sha256" equals match:[^,]+ { proxy["tls-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
tls_cert_sha256 = comma "tls-cert-sha256" equals match:[^,]+ { proxy["tls-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pubkey-sha256"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pubkey-sha256"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
|
|
||||||
|
flow = comma "flow" equals match:[^,]+ { proxy["flow"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
|
public_key = comma "public-key" equals match:[^,]+ { proxy["reality-opts"] = proxy["reality-opts"] || {}; proxy["reality-opts"]["public-key"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
|
short_id = comma "short-id" equals match:[^,]+ { proxy["reality-opts"] = proxy["reality-opts"] || {}; proxy["reality-opts"]["short-id"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
|
|
||||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||||
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
||||||
ip_mode = comma "ip-mode" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }
|
ip_mode = comma "ip-mode" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ vmess = tag equals "vmess"i address method uuid (transport/transport_host/transp
|
|||||||
proxy.alterId = proxy.alterId || 0;
|
proxy.alterId = proxy.alterId || 0;
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
|
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/flow/public_key/short_id/others)* {
|
||||||
proxy.type = "vless";
|
proxy.type = "vless";
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
@@ -178,6 +178,10 @@ tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-
|
|||||||
tls_cert_sha256 = comma "tls-cert-sha256" equals match:[^,]+ { proxy["tls-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
tls_cert_sha256 = comma "tls-cert-sha256" equals match:[^,]+ { proxy["tls-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pubkey-sha256"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pubkey-sha256"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
|
|
||||||
|
flow = comma "flow" equals match:[^,]+ { proxy["flow"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
|
public_key = comma "public-key" equals match:[^,]+ { proxy["reality-opts"] = proxy["reality-opts"] || {}; proxy["reality-opts"]["public-key"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
|
short_id = comma "short-id" equals match:[^,]+ { proxy["reality-opts"] = proxy["reality-opts"] || {}; proxy["reality-opts"]["short-id"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
|
|
||||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||||
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
||||||
ip_mode = comma "ip-mode" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }
|
ip_mode = comma "ip-mode" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ function Clash() {
|
|||||||
// 是否被引号包裹
|
// 是否被引号包裹
|
||||||
if (/^(['"]).*\1$/.test(afterTrim)) {
|
if (/^(['"]).*\1$/.test(afterTrim)) {
|
||||||
return `short-id: ${afterTrim}`;
|
return `short-id: ${afterTrim}`;
|
||||||
|
} else if (['null'].includes(afterTrim)) {
|
||||||
|
return `short-id: ${afterTrim}`;
|
||||||
} else {
|
} else {
|
||||||
return `short-id: "${afterTrim}"`;
|
return `short-id: "${afterTrim}"`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ function SortOperator(order = 'asc') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// sort by regex
|
// sort by regex
|
||||||
function RegexSortOperator(expressions) {
|
function RegexSortOperator(expressions, order = 'asc') {
|
||||||
return {
|
return {
|
||||||
name: 'Regex Sort Operator',
|
name: 'Regex Sort Operator',
|
||||||
func: (proxies) => {
|
func: (proxies) => {
|
||||||
@@ -295,8 +295,13 @@ function RegexSortOperator(expressions) {
|
|||||||
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 (order === 'original') {
|
||||||
return a.name < b.name ? -1 : 1; // fallback to normal sort
|
return 0;
|
||||||
|
} else if (order === 'desc') {
|
||||||
|
return a.name < b.name ? 1 : -1;
|
||||||
|
} else {
|
||||||
|
return a.name < b.name ? -1 : 1;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export default function Loon_Producer() {
|
|||||||
case 'vmess':
|
case 'vmess':
|
||||||
return vmess(proxy);
|
return vmess(proxy);
|
||||||
case 'vless':
|
case 'vless':
|
||||||
return vless(proxy);
|
return vless(proxy, opts['include-unsupported-proxy']);
|
||||||
case 'http':
|
case 'http':
|
||||||
return http(proxy);
|
return http(proxy);
|
||||||
case 'socks5':
|
case 'socks5':
|
||||||
@@ -347,10 +347,28 @@ function vmess(proxy) {
|
|||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function vless(proxy) {
|
function vless(proxy, includeUnsupportedProxy) {
|
||||||
if (typeof proxy.flow !== 'undefined' || proxy['reality-opts']) {
|
if (
|
||||||
|
!includeUnsupportedProxy &&
|
||||||
|
(typeof proxy.flow !== 'undefined' || proxy['reality-opts'])
|
||||||
|
) {
|
||||||
throw new Error(`VLESS XTLS/REALITY is not supported`);
|
throw new Error(`VLESS XTLS/REALITY is not supported`);
|
||||||
}
|
}
|
||||||
|
let isReality = false;
|
||||||
|
if (includeUnsupportedProxy) {
|
||||||
|
if (
|
||||||
|
proxy['reality-opts'] &&
|
||||||
|
['xtls-rprx-vision'].includes(proxy.flow)
|
||||||
|
) {
|
||||||
|
isReality = true;
|
||||||
|
} else if (proxy['reality-opts']) {
|
||||||
|
throw new Error(
|
||||||
|
`VLESS REALITY with flow(${proxy.flow}) is not supported`,
|
||||||
|
);
|
||||||
|
} else if (proxy.flow) {
|
||||||
|
throw new Error(`VLESS XTLS is not supported`);
|
||||||
|
}
|
||||||
|
}
|
||||||
const result = new Result(proxy);
|
const result = new Result(proxy);
|
||||||
result.append(
|
result.append(
|
||||||
`${proxy.name}=vless,${proxy.server},${proxy.port},"${proxy.uuid}"`,
|
`${proxy.name}=vless,${proxy.server},${proxy.port},"${proxy.uuid}"`,
|
||||||
@@ -399,7 +417,21 @@ function vless(proxy) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// sni
|
// sni
|
||||||
result.appendIfPresent(`,tls-name=${proxy.sni}`, 'sni');
|
if (isReality) {
|
||||||
|
result.appendIfPresent(`,flow=${proxy.flow}`, 'flow');
|
||||||
|
result.appendIfPresent(`,sni=${proxy.sni}`, 'sni');
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,public-key="${proxy['reality-opts']['public-key']}"`,
|
||||||
|
'reality-opts.public-key',
|
||||||
|
);
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,short-id=${proxy['reality-opts']['short-id']}`,
|
||||||
|
'reality-opts.short-id',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
result.appendIfPresent(`,tls-name=${proxy.sni}`, 'sni');
|
||||||
|
}
|
||||||
|
|
||||||
result.appendIfPresent(
|
result.appendIfPresent(
|
||||||
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
|
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
|
||||||
'tls-fingerprint',
|
'tls-fingerprint',
|
||||||
|
|||||||
@@ -23,7 +23,11 @@ export default function URI_Producer() {
|
|||||||
) {
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
if (proxy.server && isIPv6(proxy.server)) {
|
if (
|
||||||
|
!['vmess'].includes(proxy.type) &&
|
||||||
|
proxy.server &&
|
||||||
|
isIPv6(proxy.server)
|
||||||
|
) {
|
||||||
proxy.server = `[${proxy.server}]`;
|
proxy.server = `[${proxy.server}]`;
|
||||||
}
|
}
|
||||||
switch (proxy.type) {
|
switch (proxy.type) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
* @documentation: https://www.notion.so/Sub-Store-6259586994d34c11a4ced5c406264b46
|
* @documentation: https://www.notion.so/Sub-Store-6259586994d34c11a4ced5c406264b46
|
||||||
*/
|
*/
|
||||||
import { version } from '../package.json';
|
import { version } from '../package.json';
|
||||||
|
import $ from '@/core/app';
|
||||||
console.log(
|
console.log(
|
||||||
`
|
`
|
||||||
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
|
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
|
||||||
@@ -18,7 +19,6 @@ console.log(
|
|||||||
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
|
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
|
|
||||||
import migrate from '@/utils/migration';
|
import migrate from '@/utils/migration';
|
||||||
import serve from '@/restful';
|
import serve from '@/restful';
|
||||||
|
|
||||||
|
|||||||
@@ -89,8 +89,10 @@ async function doSync() {
|
|||||||
const allSubs = $.read(SUBS_KEY);
|
const allSubs = $.read(SUBS_KEY);
|
||||||
const allCols = $.read(COLLECTIONS_KEY);
|
const allCols = $.read(COLLECTIONS_KEY);
|
||||||
const subNames = [];
|
const subNames = [];
|
||||||
|
let enabledCount = 0;
|
||||||
allArtifacts.map((artifact) => {
|
allArtifacts.map((artifact) => {
|
||||||
if (artifact.sync && artifact.source) {
|
if (artifact.sync && artifact.source) {
|
||||||
|
enabledCount++;
|
||||||
if (artifact.type === 'subscription') {
|
if (artifact.type === 'subscription') {
|
||||||
const subName = artifact.source;
|
const subName = artifact.source;
|
||||||
const sub = findByName(allSubs, subName);
|
const sub = findByName(allSubs, subName);
|
||||||
@@ -111,6 +113,13 @@ async function doSync() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (enabledCount === 0) {
|
||||||
|
$.info(
|
||||||
|
`需同步的配置: ${enabledCount}, 总数: ${allArtifacts.length}`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (subNames.length > 0) {
|
if (subNames.length > 0) {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
subNames.map(async (subName) => {
|
subNames.map(async (subName) => {
|
||||||
|
|||||||
@@ -14,10 +14,12 @@ let resourceUrl = typeof $resourceUrl !== 'undefined' ? $resourceUrl : '';
|
|||||||
`
|
`
|
||||||
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
|
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
|
||||||
Sub-Store -- v${version}
|
Sub-Store -- v${version}
|
||||||
|
Loon -- ${$loon}
|
||||||
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
|
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const build = $loon.match(/\((\d+)\)$/)?.[1];
|
||||||
let arg;
|
let arg;
|
||||||
if (typeof $argument != 'undefined') {
|
if (typeof $argument != 'undefined') {
|
||||||
arg = Object.fromEntries(
|
arg = Object.fromEntries(
|
||||||
@@ -26,23 +28,28 @@ let resourceUrl = typeof $resourceUrl !== 'undefined' ? $resourceUrl : '';
|
|||||||
} else {
|
} else {
|
||||||
arg = {};
|
arg = {};
|
||||||
}
|
}
|
||||||
|
console.log(`arg: ${JSON.stringify(arg)}`);
|
||||||
|
|
||||||
const RESOURCE_TYPE = {
|
const RESOURCE_TYPE = {
|
||||||
PROXY: 1,
|
PROXY: 1,
|
||||||
RULE: 2,
|
RULE: 2,
|
||||||
};
|
};
|
||||||
|
if (!arg.resourceUrlOnly) {
|
||||||
result = resource;
|
result = resource;
|
||||||
|
}
|
||||||
|
|
||||||
if (resourceType === RESOURCE_TYPE.PROXY) {
|
if (resourceType === RESOURCE_TYPE.PROXY) {
|
||||||
try {
|
if (!arg.resourceUrlOnly) {
|
||||||
let proxies = ProxyUtils.parse(resource);
|
try {
|
||||||
result = ProxyUtils.produce(proxies, 'Loon', undefined, {
|
let proxies = ProxyUtils.parse(resource);
|
||||||
'include-unsupported-proxy': arg?.includeUnsupportedProxy,
|
result = ProxyUtils.produce(proxies, 'Loon', undefined, {
|
||||||
});
|
'include-unsupported-proxy':
|
||||||
} catch (e) {
|
arg?.includeUnsupportedProxy || build >= 838,
|
||||||
console.log('解析器: 使用 resource 出现错误');
|
});
|
||||||
console.log(e.message ?? e);
|
} catch (e) {
|
||||||
|
console.log('解析器: 使用 resource 出现错误');
|
||||||
|
console.log(e.message ?? e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ((!result || /^\s*$/.test(result)) && resourceUrl) {
|
if ((!result || /^\s*$/.test(result)) && resourceUrl) {
|
||||||
console.log(`解析器: 尝试从 ${resourceUrl} 获取订阅`);
|
console.log(`解析器: 尝试从 ${resourceUrl} 获取订阅`);
|
||||||
@@ -59,18 +66,21 @@ let resourceUrl = typeof $resourceUrl !== 'undefined' ? $resourceUrl : '';
|
|||||||
);
|
);
|
||||||
let proxies = ProxyUtils.parse(raw);
|
let proxies = ProxyUtils.parse(raw);
|
||||||
result = ProxyUtils.produce(proxies, 'Loon', undefined, {
|
result = ProxyUtils.produce(proxies, 'Loon', undefined, {
|
||||||
'include-unsupported-proxy': arg?.includeUnsupportedProxy,
|
'include-unsupported-proxy':
|
||||||
|
arg?.includeUnsupportedProxy || build >= 838,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e.message ?? e);
|
console.log(e.message ?? e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (resourceType === RESOURCE_TYPE.RULE) {
|
} else if (resourceType === RESOURCE_TYPE.RULE) {
|
||||||
try {
|
if (!arg.resourceUrlOnly) {
|
||||||
const rules = RuleUtils.parse(resource);
|
try {
|
||||||
result = RuleUtils.produce(rules, 'Loon');
|
const rules = RuleUtils.parse(resource);
|
||||||
} catch (e) {
|
result = RuleUtils.produce(rules, 'Loon');
|
||||||
console.log(e.message ?? e);
|
} catch (e) {
|
||||||
|
console.log(e.message ?? e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ((!result || /^\s*$/.test(result)) && resourceUrl) {
|
if ((!result || /^\s*$/.test(result)) && resourceUrl) {
|
||||||
console.log(`解析器: 尝试从 ${resourceUrl} 获取规则`);
|
console.log(`解析器: 尝试从 ${resourceUrl} 获取规则`);
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
||||||
import { COLLECTIONS_KEY, ARTIFACTS_KEY } from '@/constants';
|
import { COLLECTIONS_KEY, ARTIFACTS_KEY, FILES_KEY } from '@/constants';
|
||||||
import { failed, success } from '@/restful/response';
|
import { failed, success } from '@/restful/response';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
import { RequestInvalidError, ResourceNotFoundError } from '@/restful/errors';
|
import { RequestInvalidError, ResourceNotFoundError } from '@/restful/errors';
|
||||||
|
import { formatDateTime } from '@/utils';
|
||||||
|
|
||||||
export default function register($app) {
|
export default function register($app) {
|
||||||
if (!$.read(COLLECTIONS_KEY)) $.write({}, COLLECTIONS_KEY);
|
if (!$.read(COLLECTIONS_KEY)) $.write({}, COLLECTIONS_KEY);
|
||||||
@@ -60,16 +61,9 @@ function getCollection(req, res) {
|
|||||||
.set(
|
.set(
|
||||||
'content-disposition',
|
'content-disposition',
|
||||||
`attachment; filename="${encodeURIComponent(
|
`attachment; filename="${encodeURIComponent(
|
||||||
`sub-store_collection_${name}_${new Date()
|
`sub-store_collection_${name}_${formatDateTime(
|
||||||
.toLocaleString('zh-CN', {
|
new Date(),
|
||||||
year: 'numeric',
|
)}.json`,
|
||||||
day: 'numeric',
|
|
||||||
month: 'numeric',
|
|
||||||
hour: 'numeric',
|
|
||||||
minute: 'numeric',
|
|
||||||
second: 'numeric',
|
|
||||||
})
|
|
||||||
.replace(/\D/g, '')}.json`,
|
|
||||||
)}"`,
|
)}"`,
|
||||||
)
|
)
|
||||||
.send(JSON.stringify(collection));
|
.send(JSON.stringify(collection));
|
||||||
@@ -112,7 +106,18 @@ function updateCollection(req, res) {
|
|||||||
artifact.source = newCol.name;
|
artifact.source = newCol.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// update all files referring this collection
|
||||||
|
const allFiles = $.read(FILES_KEY) || [];
|
||||||
|
for (const file of allFiles) {
|
||||||
|
if (
|
||||||
|
file.sourceType === 'collection' &&
|
||||||
|
file.sourceName === oldCol.name
|
||||||
|
) {
|
||||||
|
file.sourceName = newCol.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
$.write(allArtifacts, ARTIFACTS_KEY);
|
$.write(allArtifacts, ARTIFACTS_KEY);
|
||||||
|
$.write(allFiles, FILES_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateByName(allCols, name, newCol);
|
updateByName(allCols, name, newCol);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
||||||
import { getFlowHeaders, normalizeFlowHeader } from '@/utils/flow';
|
import { getFlowHeaders, normalizeFlowHeader } from '@/utils/flow';
|
||||||
import { FILES_KEY } from '@/constants';
|
import { FILES_KEY, ARTIFACTS_KEY } from '@/constants';
|
||||||
import { failed, success } from '@/restful/response';
|
import { failed, success } from '@/restful/response';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
import {
|
import {
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
InternalServerError,
|
InternalServerError,
|
||||||
} from '@/restful/errors';
|
} from '@/restful/errors';
|
||||||
import { produceArtifact } from '@/restful/sync';
|
import { produceArtifact } from '@/restful/sync';
|
||||||
|
import { formatDateTime } from '@/utils';
|
||||||
|
|
||||||
export default function register($app) {
|
export default function register($app) {
|
||||||
if (!$.read(FILES_KEY)) $.write([], FILES_KEY);
|
if (!$.read(FILES_KEY)) $.write([], FILES_KEY);
|
||||||
@@ -210,16 +211,9 @@ function getWholeFile(req, res) {
|
|||||||
.set(
|
.set(
|
||||||
'content-disposition',
|
'content-disposition',
|
||||||
`attachment; filename="${encodeURIComponent(
|
`attachment; filename="${encodeURIComponent(
|
||||||
`sub-store_file_${name}_${new Date()
|
`sub-store_file_${name}_${formatDateTime(
|
||||||
.toLocaleString('zh-CN', {
|
new Date(),
|
||||||
year: 'numeric',
|
)}.json`,
|
||||||
day: 'numeric',
|
|
||||||
month: 'numeric',
|
|
||||||
hour: 'numeric',
|
|
||||||
minute: 'numeric',
|
|
||||||
second: 'numeric',
|
|
||||||
})
|
|
||||||
.replace(/\D/g, '')}.json`,
|
|
||||||
)}"`,
|
)}"`,
|
||||||
)
|
)
|
||||||
.send(JSON.stringify(file));
|
.send(JSON.stringify(file));
|
||||||
@@ -251,6 +245,20 @@ function updateFile(req, res) {
|
|||||||
};
|
};
|
||||||
$.info(`正在更新文件:${name}...`);
|
$.info(`正在更新文件:${name}...`);
|
||||||
|
|
||||||
|
if (name !== newFile.name) {
|
||||||
|
// update all artifacts referring this collection
|
||||||
|
const allArtifacts = $.read(ARTIFACTS_KEY) || [];
|
||||||
|
for (const artifact of allArtifacts) {
|
||||||
|
if (
|
||||||
|
artifact.type === 'file' &&
|
||||||
|
artifact.source === oldFile.name
|
||||||
|
) {
|
||||||
|
artifact.source = newFile.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$.write(allArtifacts, ARTIFACTS_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
updateByName(allFiles, name, newFile);
|
updateByName(allFiles, name, newFile);
|
||||||
$.write(allFiles, FILES_KEY);
|
$.write(allFiles, FILES_KEY);
|
||||||
success(res, newFile);
|
success(res, newFile);
|
||||||
|
|||||||
@@ -29,6 +29,72 @@ export default function serve() {
|
|||||||
host = eval('process.env.SUB_STORE_BACKEND_API_HOST') || '::';
|
host = eval('process.env.SUB_STORE_BACKEND_API_HOST') || '::';
|
||||||
}
|
}
|
||||||
const $app = express({ substore: $, port, host });
|
const $app = express({ substore: $, port, host });
|
||||||
|
if ($.env.isNode) {
|
||||||
|
const be_merge = eval('process.env.SUB_STORE_BACKEND_MERGE');
|
||||||
|
const be_prefix = eval('process.env.SUB_STORE_BACKEND_PREFIX');
|
||||||
|
const fe_be_path = eval('process.env.SUB_STORE_FRONTEND_BACKEND_PATH');
|
||||||
|
const fe_path = eval('process.env.SUB_STORE_FRONTEND_PATH');
|
||||||
|
if (be_prefix || be_merge) {
|
||||||
|
if(!fe_be_path.startsWith('/')){
|
||||||
|
throw new Error(
|
||||||
|
'SUB_STORE_FRONTEND_BACKEND_PATH should start with /',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (be_merge) {
|
||||||
|
$.info(`[BACKEND] MERGE mode is [ON].`);
|
||||||
|
$.info(`[BACKEND && FRONTEND] ${host}:${port}`);
|
||||||
|
}
|
||||||
|
$.info(`[BACKEND PREFIX] ${host}:${port}${fe_be_path}`);
|
||||||
|
$app.use((req, res, next) => {
|
||||||
|
if (req.path.startsWith(fe_be_path)) {
|
||||||
|
req.url = req.url.replace(fe_be_path, '') || '/';
|
||||||
|
if(be_merge && req.url.startsWith('/api/')){
|
||||||
|
req.query['share'] = 'true';
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pathname = decodeURIComponent(req._parsedUrl.pathname) || '/';
|
||||||
|
if(be_merge && req.path.startsWith('/share/') && req.query.token){
|
||||||
|
if (req.method.toLowerCase() !== 'get'){
|
||||||
|
res.status(405).send('Method not allowed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tokens = $.read(TOKENS_KEY) || [];
|
||||||
|
const token = tokens.find(
|
||||||
|
(t) =>
|
||||||
|
t.token === req.query.token &&
|
||||||
|
`/share/${t.type}/${t.name}` === pathname &&
|
||||||
|
(t.exp == null || t.exp > Date.now()),
|
||||||
|
);
|
||||||
|
if (token){
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (be_merge && fe_path && req.path.indexOf('/',1) == -1) {
|
||||||
|
if (req.path.indexOf('.') == -1){
|
||||||
|
req.url = "/index.html"
|
||||||
|
}
|
||||||
|
const express_ = eval(`require("express")`);
|
||||||
|
const mime_ = eval(`require("mime-types")`);
|
||||||
|
const path_ = eval(`require("path")`);
|
||||||
|
const staticFileMiddleware = express_.static(fe_path, {
|
||||||
|
setHeaders: (res, path) => {
|
||||||
|
const type = mime_.contentType(path_.extname(path));
|
||||||
|
if (type) {
|
||||||
|
res.set('Content-Type', type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
staticFileMiddleware(req, res, next);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.status(403).end('Forbbiden');
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
// register routes
|
// register routes
|
||||||
registerCollectionRoutes($app);
|
registerCollectionRoutes($app);
|
||||||
registerSubscriptionRoutes($app);
|
registerSubscriptionRoutes($app);
|
||||||
@@ -175,7 +241,8 @@ export default function serve() {
|
|||||||
const fe_abs_path = path.resolve(
|
const fe_abs_path = path.resolve(
|
||||||
fe_path || path.join(__dirname, 'frontend'),
|
fe_path || path.join(__dirname, 'frontend'),
|
||||||
);
|
);
|
||||||
if (fe_path) {
|
const be_merge = eval('process.env.SUB_STORE_BACKEND_MERGE');
|
||||||
|
if (fe_path && !be_merge) {
|
||||||
try {
|
try {
|
||||||
fs.accessSync(path.join(fe_abs_path, 'index.html'));
|
fs.accessSync(path.join(fe_abs_path, 'index.html'));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -200,6 +267,9 @@ export default function serve() {
|
|||||||
let be_download_rewrite = '';
|
let be_download_rewrite = '';
|
||||||
let be_api_rewrite = '';
|
let be_api_rewrite = '';
|
||||||
let be_share_rewrite = `${be_share}:type/:name`;
|
let be_share_rewrite = `${be_share}:type/:name`;
|
||||||
|
let prefix = eval('process.env.SUB_STORE_BACKEND_PREFIX')
|
||||||
|
? fe_be_path
|
||||||
|
: '';
|
||||||
if (fe_be_path) {
|
if (fe_be_path) {
|
||||||
if (!fe_be_path.startsWith('/')) {
|
if (!fe_be_path.startsWith('/')) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -216,7 +286,7 @@ export default function serve() {
|
|||||||
app.use(
|
app.use(
|
||||||
be_share_rewrite,
|
be_share_rewrite,
|
||||||
createProxyMiddleware({
|
createProxyMiddleware({
|
||||||
target: `http://127.0.0.1:${port}`,
|
target: `http://127.0.0.1:${port}${prefix}`,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
pathRewrite: async (path, req) => {
|
pathRewrite: async (path, req) => {
|
||||||
if (req.method.toLowerCase() !== 'get')
|
if (req.method.toLowerCase() !== 'get')
|
||||||
@@ -237,7 +307,7 @@ export default function serve() {
|
|||||||
app.use(
|
app.use(
|
||||||
be_api_rewrite,
|
be_api_rewrite,
|
||||||
createProxyMiddleware({
|
createProxyMiddleware({
|
||||||
target: `http://127.0.0.1:${port}${be_api}`,
|
target: `http://127.0.0.1:${port}${prefix}${be_api}`,
|
||||||
pathRewrite: async (path) => {
|
pathRewrite: async (path) => {
|
||||||
return path.includes('?')
|
return path.includes('?')
|
||||||
? `${path}&share=true`
|
? `${path}&share=true`
|
||||||
@@ -248,7 +318,7 @@ export default function serve() {
|
|||||||
app.use(
|
app.use(
|
||||||
be_download_rewrite,
|
be_download_rewrite,
|
||||||
createProxyMiddleware({
|
createProxyMiddleware({
|
||||||
target: `http://127.0.0.1:${port}${be_download}`,
|
target: `http://127.0.0.1:${port}${prefix}${be_download}`,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -269,10 +339,10 @@ export default function serve() {
|
|||||||
$.info(`[FRONTEND] ${fe_address}:${fe_port}`);
|
$.info(`[FRONTEND] ${fe_address}:${fe_port}`);
|
||||||
if (fe_be_path) {
|
if (fe_be_path) {
|
||||||
$.info(
|
$.info(
|
||||||
`[FRONTEND -> BACKEND] ${fe_address}:${fe_port}${be_api_rewrite} -> http://127.0.0.1:${port}${be_api}`,
|
`[FRONTEND -> BACKEND] ${fe_address}:${fe_port}${be_api_rewrite} -> ${host}:${port}${prefix}${be_api}`,
|
||||||
);
|
);
|
||||||
$.info(
|
$.info(
|
||||||
`[FRONTEND -> BACKEND] ${fe_address}:${fe_port}${be_download_rewrite} -> http://127.0.0.1:${port}${be_download}`,
|
`[FRONTEND -> BACKEND] ${fe_address}:${fe_port}${be_download_rewrite} -> ${host}:${port}${prefix}${be_download}`,
|
||||||
);
|
);
|
||||||
$.info(
|
$.info(
|
||||||
`[SHARE BACKEND] ${fe_address}:${fe_port}${be_share_rewrite}`,
|
`[SHARE BACKEND] ${fe_address}:${fe_port}${be_share_rewrite}`,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { InternalServerError, RequestInvalidError } from '@/restful/errors';
|
|||||||
import Gist from '@/utils/gist';
|
import Gist from '@/utils/gist';
|
||||||
import migrate from '@/utils/migration';
|
import migrate from '@/utils/migration';
|
||||||
import env from '@/utils/env';
|
import env from '@/utils/env';
|
||||||
|
import { formatDateTime } from '@/utils';
|
||||||
|
|
||||||
export default function register($app) {
|
export default function register($app) {
|
||||||
// utils
|
// utils
|
||||||
@@ -28,16 +29,7 @@ export default function register($app) {
|
|||||||
.set(
|
.set(
|
||||||
'content-disposition',
|
'content-disposition',
|
||||||
`attachment; filename="${encodeURIComponent(
|
`attachment; filename="${encodeURIComponent(
|
||||||
`sub-store_data_${new Date()
|
`sub-store_data_${formatDateTime(new Date())}.json`,
|
||||||
.toLocaleString('zh-CN', {
|
|
||||||
year: 'numeric',
|
|
||||||
day: 'numeric',
|
|
||||||
month: 'numeric',
|
|
||||||
hour: 'numeric',
|
|
||||||
minute: 'numeric',
|
|
||||||
second: 'numeric',
|
|
||||||
})
|
|
||||||
.replace(/\D/g, '')}.json`,
|
|
||||||
)}"`,
|
)}"`,
|
||||||
)
|
)
|
||||||
.send(
|
.send(
|
||||||
|
|||||||
@@ -134,11 +134,15 @@ export async function updateArtifactStore() {
|
|||||||
settings.artifactStore = url;
|
settings.artifactStore = url;
|
||||||
settings.artifactStoreStatus = 'VALID';
|
settings.artifactStoreStatus = 'VALID';
|
||||||
} else {
|
} else {
|
||||||
$.error(`找不到 Sub-Store Gist`);
|
$.error(`找不到 Sub-Store Gist (${ARTIFACT_REPOSITORY_KEY})`);
|
||||||
settings.artifactStoreStatus = 'NOT FOUND';
|
settings.artifactStoreStatus = 'NOT FOUND';
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
$.error(`查找 Sub-Store Gist 时发生错误: ${err.message ?? err}`);
|
$.error(
|
||||||
|
`查找 Sub-Store Gist (${ARTIFACT_REPOSITORY_KEY}) 时发生错误: ${
|
||||||
|
err.message ?? err
|
||||||
|
}`,
|
||||||
|
);
|
||||||
settings.artifactStoreStatus = 'ERROR';
|
settings.artifactStoreStatus = 'ERROR';
|
||||||
}
|
}
|
||||||
$.write(settings, SETTINGS_KEY);
|
$.write(settings, SETTINGS_KEY);
|
||||||
|
|||||||
@@ -5,7 +5,12 @@ import {
|
|||||||
RequestInvalidError,
|
RequestInvalidError,
|
||||||
} 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,
|
||||||
|
FILES_KEY,
|
||||||
|
} from '@/constants';
|
||||||
import {
|
import {
|
||||||
getFlowHeaders,
|
getFlowHeaders,
|
||||||
parseFlowHeaders,
|
parseFlowHeaders,
|
||||||
@@ -13,6 +18,7 @@ import {
|
|||||||
} from '@/utils/flow';
|
} from '@/utils/flow';
|
||||||
import { success, failed } from './response';
|
import { success, failed } from './response';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
|
import { formatDateTime } from '@/utils';
|
||||||
|
|
||||||
if (!$.read(SUBS_KEY)) $.write({}, SUBS_KEY);
|
if (!$.read(SUBS_KEY)) $.write({}, SUBS_KEY);
|
||||||
|
|
||||||
@@ -265,16 +271,9 @@ function getSubscription(req, res) {
|
|||||||
.set(
|
.set(
|
||||||
'content-disposition',
|
'content-disposition',
|
||||||
`attachment; filename="${encodeURIComponent(
|
`attachment; filename="${encodeURIComponent(
|
||||||
`sub-store_subscription_${name}_${new Date()
|
`sub-store_subscription_${name}_${formatDateTime(
|
||||||
.toLocaleString('zh-CN', {
|
new Date(),
|
||||||
year: 'numeric',
|
)}.json`,
|
||||||
day: 'numeric',
|
|
||||||
month: 'numeric',
|
|
||||||
hour: 'numeric',
|
|
||||||
minute: 'numeric',
|
|
||||||
second: 'numeric',
|
|
||||||
})
|
|
||||||
.replace(/\D/g, '')}.json`,
|
|
||||||
)}"`,
|
)}"`,
|
||||||
)
|
)
|
||||||
.send(JSON.stringify(sub));
|
.send(JSON.stringify(sub));
|
||||||
@@ -326,9 +325,20 @@ function updateSubscription(req, res) {
|
|||||||
artifact.source = sub.name;
|
artifact.source = sub.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// update all files referring this subscription
|
||||||
|
const allFiles = $.read(FILES_KEY) || [];
|
||||||
|
for (const file of allFiles) {
|
||||||
|
if (
|
||||||
|
file.sourceType === 'subscription' &&
|
||||||
|
file.sourceName == name
|
||||||
|
) {
|
||||||
|
file.sourceName = sub.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$.write(allCols, COLLECTIONS_KEY);
|
$.write(allCols, COLLECTIONS_KEY);
|
||||||
$.write(allArtifacts, ARTIFACTS_KEY);
|
$.write(allArtifacts, ARTIFACTS_KEY);
|
||||||
|
$.write(allFiles, FILES_KEY);
|
||||||
}
|
}
|
||||||
updateByName(allSubs, name, newSub);
|
updateByName(allSubs, name, newSub);
|
||||||
$.write(allSubs, SUBS_KEY);
|
$.write(allSubs, SUBS_KEY);
|
||||||
|
|||||||
@@ -556,8 +556,10 @@ async function syncArtifacts() {
|
|||||||
const allSubs = $.read(SUBS_KEY);
|
const allSubs = $.read(SUBS_KEY);
|
||||||
const allCols = $.read(COLLECTIONS_KEY);
|
const allCols = $.read(COLLECTIONS_KEY);
|
||||||
const subNames = [];
|
const subNames = [];
|
||||||
|
let enabledCount = 0;
|
||||||
allArtifacts.map((artifact) => {
|
allArtifacts.map((artifact) => {
|
||||||
if (artifact.sync && artifact.source) {
|
if (artifact.sync && artifact.source) {
|
||||||
|
enabledCount++;
|
||||||
if (artifact.type === 'subscription') {
|
if (artifact.type === 'subscription') {
|
||||||
const subName = artifact.source;
|
const subName = artifact.source;
|
||||||
const sub = findByName(allSubs, subName);
|
const sub = findByName(allSubs, subName);
|
||||||
@@ -578,6 +580,13 @@ async function syncArtifacts() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (enabledCount === 0) {
|
||||||
|
$.info(
|
||||||
|
`需同步的配置: ${enabledCount}, 总数: ${allArtifacts.length}`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (subNames.length > 0) {
|
if (subNames.length > 0) {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
subNames.map(async (subName) => {
|
subNames.map(async (subName) => {
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ export default class Gist {
|
|||||||
return Promise.reject(err);
|
return Promise.reject(err);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject('找不到 Sub-Store Gist');
|
return Promise.reject(`找不到 Sub-Store Gist (${this.key})`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,7 +126,32 @@ function isValidUUID(uuid) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatDateTime(date, format = 'YYYY-MM-DD_HH-mm-ss') {
|
||||||
|
const d = date instanceof Date ? date : new Date(date);
|
||||||
|
|
||||||
|
if (isNaN(d.getTime())) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const pad = (num) => String(num).padStart(2, '0');
|
||||||
|
|
||||||
|
const replacements = {
|
||||||
|
YYYY: d.getFullYear(),
|
||||||
|
MM: pad(d.getMonth() + 1),
|
||||||
|
DD: pad(d.getDate()),
|
||||||
|
HH: pad(d.getHours()),
|
||||||
|
mm: pad(d.getMinutes()),
|
||||||
|
ss: pad(d.getSeconds()),
|
||||||
|
};
|
||||||
|
|
||||||
|
return format.replace(
|
||||||
|
/YYYY|MM|DD|HH|mm|ss/g,
|
||||||
|
(match) => replacements[match],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
formatDateTime,
|
||||||
isValidUUID,
|
isValidUUID,
|
||||||
ipAddress,
|
ipAddress,
|
||||||
isIPv4,
|
isIPv4,
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import {
|
|||||||
SCHEMA_VERSION_KEY,
|
SCHEMA_VERSION_KEY,
|
||||||
ARTIFACTS_KEY,
|
ARTIFACTS_KEY,
|
||||||
RULES_KEY,
|
RULES_KEY,
|
||||||
|
FILES_KEY,
|
||||||
|
TOKENS_KEY,
|
||||||
} from '@/constants';
|
} from '@/constants';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
|
|
||||||
@@ -55,7 +57,17 @@ function doMigrationV2() {
|
|||||||
const newRules = Object.values(rules);
|
const newRules = Object.values(rules);
|
||||||
$.write(newRules, RULES_KEY);
|
$.write(newRules, RULES_KEY);
|
||||||
|
|
||||||
// 5. delete builtin rules
|
// 5. migrate files
|
||||||
|
const files = $.read(FILES_KEY) || {};
|
||||||
|
const newFiles = Object.values(files);
|
||||||
|
$.write(newFiles, FILES_KEY);
|
||||||
|
|
||||||
|
// 6. migrate tokens
|
||||||
|
const tokens = $.read(TOKENS_KEY) || {};
|
||||||
|
const newTokens = Object.values(tokens);
|
||||||
|
$.write(newTokens, TOKENS_KEY);
|
||||||
|
|
||||||
|
// 7. delete builtin rules
|
||||||
delete $.cache.builtin;
|
delete $.cache.builtin;
|
||||||
$.info('Migration complete!');
|
$.info('Migration complete!');
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export function getPlatformFromUserAgent({ ua, UA, accept }) {
|
|||||||
return 'Clash';
|
return 'Clash';
|
||||||
} else if (ua.indexOf('v2ray') !== -1) {
|
} else if (ua.indexOf('v2ray') !== -1) {
|
||||||
return 'V2Ray';
|
return 'V2Ray';
|
||||||
} else if (ua.indexOf('sing-box') !== -1) {
|
} else if (ua.indexOf('sing-box') !== -1 || ua.indexOf('singbox') !== -1) {
|
||||||
return 'sing-box';
|
return 'sing-box';
|
||||||
} else if (accept.indexOf('application/json') === 0) {
|
} else if (accept.indexOf('application/json') === 0) {
|
||||||
return 'JSON';
|
return 'JSON';
|
||||||
@@ -66,10 +66,12 @@ export function shouldIncludeUnsupportedProxy(platform, ua) {
|
|||||||
UA: ua,
|
UA: ua,
|
||||||
ua: ua.toLowerCase(),
|
ua: ua.toLowerCase(),
|
||||||
});
|
});
|
||||||
if (!['Stash', 'Egern'].includes(target)) {
|
if (!['Stash', 'Egern', 'Loon'].includes(target)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const version = coerce(ua).version;
|
const coerceVersion = coerce(ua);
|
||||||
|
$.log(JSON.stringify(coerceVersion, null, 2));
|
||||||
|
const { version } = coerceVersion;
|
||||||
if (
|
if (
|
||||||
platform === 'Stash' &&
|
platform === 'Stash' &&
|
||||||
target === 'Stash' &&
|
target === 'Stash' &&
|
||||||
@@ -84,6 +86,14 @@ export function shouldIncludeUnsupportedProxy(platform, ua) {
|
|||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// Loon 的 UA 不规范, version 取出来是 build
|
||||||
|
if (
|
||||||
|
platform === 'Loon' &&
|
||||||
|
target === 'Loon' &&
|
||||||
|
gte(version, '838.0.0')
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
$.error(`获取版本号失败: ${e}`);
|
$.error(`获取版本号失败: ${e}`);
|
||||||
}
|
}
|
||||||
|
|||||||
32
backend/src/vendor/open-api.js
vendored
32
backend/src/vendor/open-api.js
vendored
@@ -18,6 +18,10 @@ export class OpenAPI {
|
|||||||
this.http = HTTP();
|
this.http = HTTP();
|
||||||
this.env = ENV();
|
this.env = ENV();
|
||||||
|
|
||||||
|
if (isNode) {
|
||||||
|
const dotenv = eval(`require("dotenv")`);
|
||||||
|
dotenv.config();
|
||||||
|
}
|
||||||
this.node = (() => {
|
this.node = (() => {
|
||||||
if (isNode) {
|
if (isNode) {
|
||||||
const fs = eval("require('fs')");
|
const fs = eval("require('fs')");
|
||||||
@@ -360,7 +364,12 @@ export function HTTP(defaultOptions = { baseURL: '' }) {
|
|||||||
}
|
}
|
||||||
if (isNode) {
|
if (isNode) {
|
||||||
const undici = eval("require('undici')");
|
const undici = eval("require('undici')");
|
||||||
const { ProxyAgent, EnvHttpProxyAgent, request } = undici;
|
const {
|
||||||
|
ProxyAgent,
|
||||||
|
EnvHttpProxyAgent,
|
||||||
|
request,
|
||||||
|
interceptors,
|
||||||
|
} = undici;
|
||||||
const agentOpts = {
|
const agentOpts = {
|
||||||
connect: {
|
connect: {
|
||||||
rejectUnauthorized:
|
rejectUnauthorized:
|
||||||
@@ -373,15 +382,32 @@ export function HTTP(defaultOptions = { baseURL: '' }) {
|
|||||||
headersTimeout: opts.timeout,
|
headersTimeout: opts.timeout,
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
|
const url = new URL(opts.url);
|
||||||
|
if (url.username || url.password) {
|
||||||
|
opts.headers = {
|
||||||
|
...(opts.headers || {}),
|
||||||
|
Authorization: `Basic ${Buffer.from(
|
||||||
|
`${url.username || ''}:${
|
||||||
|
url.password || ''
|
||||||
|
}`,
|
||||||
|
).toString('base64')}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
const response = await request(opts.url, {
|
const response = await request(opts.url, {
|
||||||
...opts,
|
...opts,
|
||||||
method: method.toUpperCase(),
|
method: method.toUpperCase(),
|
||||||
dispatcher: opts.proxy
|
dispatcher: (opts.proxy
|
||||||
? new ProxyAgent({
|
? new ProxyAgent({
|
||||||
...agentOpts,
|
...agentOpts,
|
||||||
uri: opts.proxy,
|
uri: opts.proxy,
|
||||||
})
|
})
|
||||||
: new EnvHttpProxyAgent(agentOpts),
|
: new EnvHttpProxyAgent(agentOpts)
|
||||||
|
).compose(
|
||||||
|
interceptors.redirect({
|
||||||
|
maxRedirections: 3,
|
||||||
|
throwOnMaxRedirects: true,
|
||||||
|
}),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
resolve({
|
resolve({
|
||||||
statusCode: response.statusCode,
|
statusCode: response.statusCode,
|
||||||
|
|||||||
Reference in New Issue
Block a user