mirror of
https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-10 00:52:40 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a417d9852 | ||
|
|
ebff520499 | ||
|
|
876d2e92ca | ||
|
|
63064bc596 | ||
|
|
e81245a5bb | ||
|
|
217fdae7f1 | ||
|
|
ef4d0a228b | ||
|
|
e816e5b3c0 | ||
|
|
005051c4ac | ||
|
|
61078b10f3 | ||
|
|
bfa1b11a0e | ||
|
|
7d60aed50d | ||
|
|
e20d0c1dc9 | ||
|
|
c5660024fb | ||
|
|
76e12bd6a0 | ||
|
|
3a33446422 | ||
|
|
17b12711b4 | ||
|
|
c266635ba1 | ||
|
|
f34eac9568 | ||
|
|
aa5b51a3cc | ||
|
|
b8897dd94a | ||
|
|
71958e6bb1 | ||
|
|
fa5f88ae85 | ||
|
|
212aa7730d | ||
|
|
4c5c9baa3e | ||
|
|
25dcbdc4dd | ||
|
|
282780b791 | ||
|
|
cde09541cf | ||
|
|
6731c42edb |
@@ -26,13 +26,14 @@ Core functionalities:
|
||||
|
||||
### Supported Input Formats
|
||||
|
||||
> ⚠️ Do not use `Shadowrocket` or `NekoBox` to export URI and then import it as input. The URIs exported in this way may not be standard URIs.
|
||||
> ⚠️ Do not use `Shadowrocket` or `NekoBox` to export URI and then import it as input. The URIs exported in this way may not be standard URIs. However, we have already supported some very common non-standard URIs (such as VMess, VLESS).
|
||||
|
||||
- [x] Proxy URI Scheme(`socks5`, `socks5+tls`, `http`, `https`(it's ok))
|
||||
|
||||
example: `socks5+tls://user:pass@ip:port#name`
|
||||
|
||||
- [x] URI(AnyTLS, SOCKS, SS, SSR, VMess, VLESS, Trojan, Hysteria, Hysteria 2, TUIC v5, WireGuard)
|
||||
> Please note, HTTP(s) does not have a standard URI format, so it is not supported. Please use other formats.
|
||||
- [x] Clash Proxies YAML
|
||||
- [x] Clash Proxy JSON(single line)
|
||||
- [x] QX (SS, SSR, VMess, Trojan, HTTP, SOCKS5, VLESS)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sub-store",
|
||||
"version": "2.19.63",
|
||||
"version": "2.19.89",
|
||||
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket.",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
@@ -32,6 +32,7 @@
|
||||
"dns-packet": "^5.6.1",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.17.1",
|
||||
"fetch-socks": "^1.3.2",
|
||||
"http-proxy-middleware": "^3.0.3",
|
||||
"ip-address": "^9.0.5",
|
||||
"js-base64": "^3.7.2",
|
||||
|
||||
26
backend/pnpm-lock.yaml
generated
26
backend/pnpm-lock.yaml
generated
@@ -40,6 +40,9 @@ importers:
|
||||
express:
|
||||
specifier: ^4.17.1
|
||||
version: 4.21.2
|
||||
fetch-socks:
|
||||
specifier: ^1.3.2
|
||||
version: 1.3.2
|
||||
http-proxy-middleware:
|
||||
specifier: ^3.0.3
|
||||
version: 3.0.3
|
||||
@@ -1974,6 +1977,9 @@ packages:
|
||||
fastq@1.18.0:
|
||||
resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==}
|
||||
|
||||
fetch-socks@1.3.2:
|
||||
resolution: {integrity: sha512-vkH5+Zgj2yEbU57Cei0iyLgTZ4OkEKJj56Xu3ViB5dpsl599JgEooQ3x6NVagIFRHWnWJ+7K0MO0aIV1TMgvnw==}
|
||||
|
||||
file-entry-cache@6.0.1:
|
||||
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
|
||||
engines: {node: ^10.12.0 || >=12.0.0}
|
||||
@@ -3636,6 +3642,10 @@ packages:
|
||||
resolution: {integrity: sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
smart-buffer@4.2.0:
|
||||
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
|
||||
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
|
||||
|
||||
snapdragon-node@2.1.1:
|
||||
resolution: {integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -3648,6 +3658,10 @@ packages:
|
||||
resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
socks@2.8.6:
|
||||
resolution: {integrity: sha512-pe4Y2yzru68lXCb38aAqRf5gvN8YdjP1lok5o0J7BOHljkyCGKVz7H3vpVIXKD27rj2giOJ7DwVyk/GWrPHDWA==}
|
||||
engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
|
||||
|
||||
source-map-generator@0.8.0:
|
||||
resolution: {integrity: sha512-psgxdGMwl5MZM9S3FWee4EgsEaIjahYV5AzGnwUvPhWeITz/j6rKpysQHlQ4USdxvINlb8lKfWGIXwfkrgtqkA==}
|
||||
engines: {node: '>= 10'}
|
||||
@@ -6553,6 +6567,11 @@ snapshots:
|
||||
dependencies:
|
||||
reusify: 1.0.4
|
||||
|
||||
fetch-socks@1.3.2:
|
||||
dependencies:
|
||||
socks: 2.8.6
|
||||
undici: 7.4.0
|
||||
|
||||
file-entry-cache@6.0.1:
|
||||
dependencies:
|
||||
flat-cache: 3.2.0
|
||||
@@ -8424,6 +8443,8 @@ snapshots:
|
||||
|
||||
slash@1.0.0: {}
|
||||
|
||||
smart-buffer@4.2.0: {}
|
||||
|
||||
snapdragon-node@2.1.1:
|
||||
dependencies:
|
||||
define-property: 1.0.0
|
||||
@@ -8447,6 +8468,11 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
socks@2.8.6:
|
||||
dependencies:
|
||||
ip-address: 9.0.5
|
||||
smart-buffer: 4.2.0
|
||||
|
||||
source-map-generator@0.8.0: {}
|
||||
|
||||
source-map-resolve@0.5.3:
|
||||
|
||||
@@ -12,6 +12,7 @@ import getLoonParser from './peggy/loon';
|
||||
import getQXParser from './peggy/qx';
|
||||
import getTrojanURIParser from './peggy/trojan-uri';
|
||||
import $ from '@/core/app';
|
||||
import JSON5 from 'json5';
|
||||
|
||||
import { Base64 } from 'js-base64';
|
||||
|
||||
@@ -1130,14 +1131,14 @@ function Clash_All() {
|
||||
const name = 'Clash Parser';
|
||||
const test = (line) => {
|
||||
try {
|
||||
JSON.parse(line);
|
||||
JSON5.parse(line);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const parse = (line) => {
|
||||
const proxy = JSON.parse(line);
|
||||
const proxy = JSON5.parse(line);
|
||||
if (
|
||||
![
|
||||
'anytls',
|
||||
|
||||
@@ -105,11 +105,11 @@ wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/under
|
||||
proxy.type = "wireguard-surge";
|
||||
handleShadowTLS();
|
||||
}
|
||||
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/fast_open/tfo/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/port_hopping_interval/others)* {
|
||||
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/port_hopping_interval/others)* {
|
||||
proxy.type = "hysteria2";
|
||||
handleShadowTLS();
|
||||
}
|
||||
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||
proxy.type = "socks5";
|
||||
handleShadowTLS();
|
||||
}
|
||||
@@ -121,7 +121,6 @@ socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek pas
|
||||
direct = tag equals "direct" (udp_relay/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/block_quic/others)* {
|
||||
proxy.type = "direct";
|
||||
}
|
||||
|
||||
address = comma server:server comma port:port {
|
||||
proxy.server = server;
|
||||
proxy.port = port;
|
||||
@@ -179,8 +178,8 @@ username = & {
|
||||
peg$currPos = end;
|
||||
return true;
|
||||
}
|
||||
} { proxy.username = $.username; }
|
||||
password = comma match:[^,]+ { proxy.password = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||
} { proxy.username = $.username.trim().replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
|
||||
password = comma match:[^,]+ { proxy.password = match.join("").replace(/^"(.*)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
|
||||
|
||||
tls = comma "tls" equals flag:bool { proxy.tls = flag; }
|
||||
sni = comma "sni" equals sni:("off"/domain) {
|
||||
@@ -196,7 +195,7 @@ tls_fingerprint = comma "server-cert-fingerprint-sha256" equals tls_fingerprint:
|
||||
snell_psk = comma "psk" equals match:[^,]+ { proxy.psk = match.join(""); }
|
||||
snell_version = comma "version" equals match:$[0-9]+ { proxy.version = parseInt(match.trim()); }
|
||||
|
||||
usernamek = comma "username" equals match:[^,]+ { proxy.username = match.join(""); }
|
||||
usernamek = comma "username" equals match:[^,]+ { proxy.username = match.join("").replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
|
||||
passwordk = comma "password" equals match:[^,]+ { proxy.password = match.join("").replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
|
||||
vmess_uuid = comma "username" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
||||
vmess_aead = comma "vmess-aead" equals flag:bool { proxy.aead = flag; }
|
||||
@@ -212,11 +211,11 @@ ws_headers = comma "ws-headers" equals headers:$[^,]+ {
|
||||
const result = {};
|
||||
pairs.forEach(pair => {
|
||||
const [key, value] = pair.trim().split(":");
|
||||
result[key.trim()] = value.trim();
|
||||
result[key.trim()] = value.trim().replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1');
|
||||
})
|
||||
obfs["ws-headers"] = result;
|
||||
}
|
||||
ws_path = comma "ws-path" equals path:uri { obfs.path = path; }
|
||||
ws_path = comma "ws-path" equals path:uri { obfs.path = path.trim().replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
|
||||
|
||||
obfs = comma "obfs" equals type:("http"/"tls") { obfs.type = type; }
|
||||
obfs_host = comma "obfs-host" equals host:domain { obfs.host = host; };
|
||||
|
||||
@@ -176,8 +176,8 @@ username = & {
|
||||
peg$currPos = end;
|
||||
return true;
|
||||
}
|
||||
} { proxy.username = $.username; }
|
||||
password = comma match:[^,]+ { proxy.password = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||
} { proxy.username = $.username.trim().replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
|
||||
password = comma match:[^,]+ { proxy.password = match.join("").replace(/^"(.*)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
|
||||
|
||||
tls = comma "tls" equals flag:bool { proxy.tls = flag; }
|
||||
sni = comma "sni" equals sni:("off"/domain) {
|
||||
@@ -193,7 +193,7 @@ tls_fingerprint = comma "server-cert-fingerprint-sha256" equals tls_fingerprint:
|
||||
snell_psk = comma "psk" equals match:[^,]+ { proxy.psk = match.join(""); }
|
||||
snell_version = comma "version" equals match:$[0-9]+ { proxy.version = parseInt(match.trim()); }
|
||||
|
||||
usernamek = comma "username" equals match:[^,]+ { proxy.username = match.join(""); }
|
||||
usernamek = comma "username" equals match:[^,]+ { proxy.username = match.join("").replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
|
||||
passwordk = comma "password" equals match:[^,]+ { proxy.password = match.join("").replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
|
||||
vmess_uuid = comma "username" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
||||
vmess_aead = comma "vmess-aead" equals flag:bool { proxy.aead = flag; }
|
||||
@@ -209,11 +209,11 @@ ws_headers = comma "ws-headers" equals headers:$[^,]+ {
|
||||
const result = {};
|
||||
pairs.forEach(pair => {
|
||||
const [key, value] = pair.trim().split(":");
|
||||
result[key.trim()] = value.trim();
|
||||
result[key.trim()] = value.trim().replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1');
|
||||
})
|
||||
obfs["ws-headers"] = result;
|
||||
}
|
||||
ws_path = comma "ws-path" equals path:uri { obfs.path = path; }
|
||||
ws_path = comma "ws-path" equals path:uri { obfs.path = path.trim().replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
|
||||
|
||||
obfs = comma "obfs" equals type:("http"/"tls") { obfs.type = type; }
|
||||
obfs_host = comma "obfs-host" equals host:domain { obfs.host = host; };
|
||||
|
||||
@@ -623,9 +623,11 @@ const DOMAIN_RESOLVERS = {
|
||||
const cached = resourceCache.get(id);
|
||||
if (!noCache && cached) return cached;
|
||||
const resp = await $.http.get({
|
||||
url: `http://223.6.6.6/resolve?edns_client_subnet=${edns}/24&name=${encodeURIComponent(
|
||||
domain,
|
||||
)}&type=${type === 'IPv6' ? 'AAAA' : 'A'}&short=1`,
|
||||
url: `http://223.6.6.6/resolve?edns_client_subnet=${edns}/${
|
||||
isIPv4(edns) ? 24 : 56
|
||||
}&name=${encodeURIComponent(domain)}&type=${
|
||||
type === 'IPv6' ? 'AAAA' : 'A'
|
||||
}&short=1`,
|
||||
headers: {
|
||||
accept: 'application/dns-json',
|
||||
},
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function Clash_Producer() {
|
||||
'chacha20-ietf-poly1305',
|
||||
'xchacha20-ietf-poly1305',
|
||||
].includes(proxy.cipher)) ||
|
||||
(proxy.type === 'snell' && String(proxy.version) === '4') ||
|
||||
(proxy.type === 'snell' && proxy.version >= 4) ||
|
||||
(proxy.type === 'vless' &&
|
||||
(typeof proxy.flow !== 'undefined' ||
|
||||
proxy['reality-opts']))
|
||||
@@ -134,6 +134,18 @@ export default function Clash_Producer() {
|
||||
proxy['h2-opts'].headers.host = [host];
|
||||
}
|
||||
}
|
||||
if (proxy.network === 'ws') {
|
||||
const wsPath = proxy['ws-opts']?.path;
|
||||
const reg = /^(.*?)(?:\?ed=(\d+))?$/;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [_, path = '', ed = ''] = reg.exec(wsPath);
|
||||
proxy['ws-opts'].path = path;
|
||||
if (ed !== '') {
|
||||
proxy['ws-opts']['early-data-header-name'] =
|
||||
'Sec-WebSocket-Protocol';
|
||||
proxy['ws-opts']['max-early-data'] = parseInt(ed, 10);
|
||||
}
|
||||
}
|
||||
if (proxy['plugin-opts']?.tls) {
|
||||
if (isPresent(proxy, 'skip-cert-verify')) {
|
||||
proxy['plugin-opts']['skip-cert-verify'] =
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
import { isPresent } from '@/core/proxy-utils/producers/utils';
|
||||
|
||||
const ipVersions = {
|
||||
dual: 'dual',
|
||||
'v4-only': 'ipv4',
|
||||
'v6-only': 'ipv6',
|
||||
'prefer-v4': 'ipv4-prefer',
|
||||
'prefer-v6': 'ipv6-prefer',
|
||||
};
|
||||
|
||||
export default function ClashMeta_Producer() {
|
||||
const type = 'ALL';
|
||||
const produce = (proxies, type, opts = {}) => {
|
||||
const list = proxies
|
||||
.filter((proxy) => {
|
||||
if (opts['include-unsupported-proxy']) return true;
|
||||
if (proxy.type === 'snell' && String(proxy.version) === '4') {
|
||||
if (proxy.type === 'snell' && proxy.version >= 4) {
|
||||
return false;
|
||||
} else if (['juicity'].includes(proxy.type)) {
|
||||
return false;
|
||||
@@ -190,6 +198,18 @@ export default function ClashMeta_Producer() {
|
||||
proxy['h2-opts'].headers.host = [host];
|
||||
}
|
||||
}
|
||||
if (proxy.network === 'ws') {
|
||||
const wsPath = proxy['ws-opts']?.path;
|
||||
const reg = /^(.*?)(?:\?ed=(\d+))?$/;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [_, path = '', ed = ''] = reg.exec(wsPath);
|
||||
proxy['ws-opts'].path = path;
|
||||
if (ed !== '') {
|
||||
proxy['ws-opts']['early-data-header-name'] =
|
||||
'Sec-WebSocket-Protocol';
|
||||
proxy['ws-opts']['max-early-data'] = parseInt(ed, 10);
|
||||
}
|
||||
}
|
||||
|
||||
if (proxy['plugin-opts']?.tls) {
|
||||
if (isPresent(proxy, 'skip-cert-verify')) {
|
||||
@@ -242,6 +262,11 @@ export default function ClashMeta_Producer() {
|
||||
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
|
||||
delete proxy[`${proxy.network}-opts`]['_grpc-authority'];
|
||||
}
|
||||
|
||||
if (proxy['ip-version']) {
|
||||
proxy['ip-version'] =
|
||||
ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||
}
|
||||
return proxy;
|
||||
});
|
||||
|
||||
|
||||
@@ -377,6 +377,23 @@ export default function Egern_Producer() {
|
||||
delete proxy.id;
|
||||
delete proxy.resolved;
|
||||
delete proxy['no-resolve'];
|
||||
|
||||
if (proxy.transport) {
|
||||
for (const key in proxy.transport) {
|
||||
if (
|
||||
Object.keys(proxy.transport[key]).length === 0 ||
|
||||
Object.values(proxy.transport[key]).every(
|
||||
(v) => v == null,
|
||||
)
|
||||
) {
|
||||
delete proxy.transport[key];
|
||||
}
|
||||
}
|
||||
if (Object.keys(proxy.transport).length === 0) {
|
||||
delete proxy.transport;
|
||||
}
|
||||
}
|
||||
|
||||
if (type !== 'internal') {
|
||||
for (const key in proxy) {
|
||||
if (proxy[key] == null || /^_/i.test(key)) {
|
||||
|
||||
@@ -7,7 +7,7 @@ export default function Shadowrocket_Producer() {
|
||||
const list = proxies
|
||||
.filter((proxy) => {
|
||||
if (opts['include-unsupported-proxy']) return true;
|
||||
if (proxy.type === 'snell' && String(proxy.version) === '4') {
|
||||
if (proxy.type === 'snell' && proxy.version >= 4) {
|
||||
return false;
|
||||
} else if (['mieru'].includes(proxy.type)) {
|
||||
return false;
|
||||
@@ -166,6 +166,18 @@ export default function Shadowrocket_Producer() {
|
||||
proxy['h2-opts'].headers.host = [host];
|
||||
}
|
||||
}
|
||||
if (proxy.network === 'ws') {
|
||||
const wsPath = proxy['ws-opts']?.path;
|
||||
const reg = /^(.*?)(?:\?ed=(\d+))?$/;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [_, path = '', ed = ''] = reg.exec(wsPath);
|
||||
proxy['ws-opts'].path = path;
|
||||
if (ed !== '') {
|
||||
proxy['ws-opts']['early-data-header-name'] =
|
||||
'Sec-WebSocket-Protocol';
|
||||
proxy['ws-opts']['max-early-data'] = parseInt(ed, 10);
|
||||
}
|
||||
}
|
||||
if (proxy['plugin-opts']?.tls) {
|
||||
if (isPresent(proxy, 'skip-cert-verify')) {
|
||||
proxy['plugin-opts']['skip-cert-verify'] =
|
||||
|
||||
@@ -2,6 +2,26 @@ import ClashMeta_Producer from './clashmeta';
|
||||
import $ from '@/core/app';
|
||||
import { isIPv4, isIPv6 } from '@/utils';
|
||||
|
||||
const ipVersions = {
|
||||
ipv4: 'ipv4_only',
|
||||
ipv6: 'ipv6_only',
|
||||
'v4-only': 'ipv4_only',
|
||||
'v6-only': 'ipv6_only',
|
||||
'ipv4-prefer': 'prefer_ipv4',
|
||||
'ipv6-prefer': 'prefer_ipv6',
|
||||
'prefer-v4': 'prefer_ipv4',
|
||||
'prefer-v6': 'prefer_ipv6',
|
||||
};
|
||||
|
||||
const ipVersionParser = (proxy, parsedProxy) => {
|
||||
const strategy = ipVersions[proxy['ip-version']];
|
||||
if (proxy._dns_server && strategy) {
|
||||
parsedProxy.domain_resolver = {
|
||||
server: proxy._dns_server,
|
||||
strategy,
|
||||
};
|
||||
}
|
||||
};
|
||||
const detourParser = (proxy, parsedProxy) => {
|
||||
parsedProxy.detour = proxy['dialer-proxy'] || proxy.detour;
|
||||
};
|
||||
@@ -51,7 +71,14 @@ const smuxParser = (smux, proxy) => {
|
||||
const wsParser = (proxy, parsedProxy) => {
|
||||
const transport = { type: 'ws', headers: {} };
|
||||
if (proxy['ws-opts']) {
|
||||
const { path: wsPath = '', headers: wsHeaders = {} } = proxy['ws-opts'];
|
||||
const {
|
||||
path: wsPath = '',
|
||||
headers: wsHeaders = {},
|
||||
'max-early-data': max_early_data,
|
||||
'early-data-header-name': early_data_header_name,
|
||||
} = proxy['ws-opts'];
|
||||
transport.early_data_header_name = early_data_header_name;
|
||||
transport.max_early_data = parseInt(max_early_data, 10);
|
||||
if (wsPath !== '') transport.path = `${wsPath}`;
|
||||
if (Object.keys(wsHeaders).length > 0) {
|
||||
const headers = {};
|
||||
@@ -278,6 +305,7 @@ const sshParser = (proxy = {}) => {
|
||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||
tfoParser(proxy, parsedProxy);
|
||||
detourParser(proxy, parsedProxy);
|
||||
ipVersionParser(proxy, parsedProxy);
|
||||
return parsedProxy;
|
||||
};
|
||||
|
||||
@@ -305,6 +333,7 @@ const httpParser = (proxy = {}) => {
|
||||
tfoParser(proxy, parsedProxy);
|
||||
detourParser(proxy, parsedProxy);
|
||||
tlsParser(proxy, parsedProxy);
|
||||
ipVersionParser(proxy, parsedProxy);
|
||||
return parsedProxy;
|
||||
};
|
||||
|
||||
@@ -327,6 +356,7 @@ const socks5Parser = (proxy = {}) => {
|
||||
networkParser(proxy, parsedProxy);
|
||||
tfoParser(proxy, parsedProxy);
|
||||
detourParser(proxy, parsedProxy);
|
||||
ipVersionParser(proxy, parsedProxy);
|
||||
return parsedProxy;
|
||||
};
|
||||
|
||||
@@ -338,6 +368,17 @@ const shadowTLSParser = (proxy = {}) => {
|
||||
password: proxy.password,
|
||||
detour: `${proxy.name}_shadowtls`,
|
||||
};
|
||||
if (proxy.uot) ssPart.udp_over_tcp = true;
|
||||
if (proxy['udp-over-tcp']) {
|
||||
ssPart.udp_over_tcp = {
|
||||
enabled: true,
|
||||
version:
|
||||
!proxy['udp-over-tcp-version'] ||
|
||||
proxy['udp-over-tcp-version'] === 1
|
||||
? 1
|
||||
: 2,
|
||||
};
|
||||
}
|
||||
const stPart = {
|
||||
tag: `${proxy.name}_shadowtls`,
|
||||
type: 'shadowtls',
|
||||
@@ -360,6 +401,7 @@ const shadowTLSParser = (proxy = {}) => {
|
||||
tfoParser(proxy, stPart);
|
||||
detourParser(proxy, stPart);
|
||||
smuxParser(proxy.smux, ssPart);
|
||||
ipVersionParser(proxy, stPart);
|
||||
return { type: 'ss-with-st', ssPart, stPart };
|
||||
};
|
||||
const ssParser = (proxy = {}) => {
|
||||
@@ -389,6 +431,7 @@ const ssParser = (proxy = {}) => {
|
||||
tfoParser(proxy, parsedProxy);
|
||||
detourParser(proxy, parsedProxy);
|
||||
smuxParser(proxy.smux, parsedProxy);
|
||||
ipVersionParser(proxy, parsedProxy);
|
||||
if (proxy.plugin) {
|
||||
const optArr = [];
|
||||
if (proxy.plugin === 'obfs') {
|
||||
@@ -467,6 +510,7 @@ const ssrParser = (proxy = {}) => {
|
||||
tfoParser(proxy, parsedProxy);
|
||||
detourParser(proxy, parsedProxy);
|
||||
smuxParser(proxy.smux, parsedProxy);
|
||||
ipVersionParser(proxy, parsedProxy);
|
||||
return parsedProxy;
|
||||
};
|
||||
|
||||
@@ -505,6 +549,7 @@ const vmessParser = (proxy = {}) => {
|
||||
detourParser(proxy, parsedProxy);
|
||||
tlsParser(proxy, parsedProxy);
|
||||
smuxParser(proxy.smux, parsedProxy);
|
||||
ipVersionParser(proxy, parsedProxy);
|
||||
return parsedProxy;
|
||||
};
|
||||
|
||||
@@ -521,7 +566,8 @@ const vlessParser = (proxy = {}) => {
|
||||
throw 'invalid port';
|
||||
if (proxy.xudp) parsedProxy.packet_encoding = 'xudp';
|
||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||
if (proxy.flow === 'xtls-rprx-vision') parsedProxy.flow = proxy.flow;
|
||||
// if (['xtls-rprx-vision', ''].includes(proxy.flow)) parsedProxy.flow = proxy.flow;
|
||||
if (proxy.flow != null) parsedProxy.flow = proxy.flow;
|
||||
if (proxy.network === 'ws') wsParser(proxy, parsedProxy);
|
||||
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
||||
networkParser(proxy, parsedProxy);
|
||||
@@ -529,6 +575,7 @@ const vlessParser = (proxy = {}) => {
|
||||
detourParser(proxy, parsedProxy);
|
||||
smuxParser(proxy.smux, parsedProxy);
|
||||
tlsParser(proxy, parsedProxy);
|
||||
ipVersionParser(proxy, parsedProxy);
|
||||
return parsedProxy;
|
||||
};
|
||||
const trojanParser = (proxy = {}) => {
|
||||
@@ -550,6 +597,7 @@ const trojanParser = (proxy = {}) => {
|
||||
detourParser(proxy, parsedProxy);
|
||||
tlsParser(proxy, parsedProxy);
|
||||
smuxParser(proxy.smux, parsedProxy);
|
||||
ipVersionParser(proxy, parsedProxy);
|
||||
return parsedProxy;
|
||||
};
|
||||
const hysteriaParser = (proxy = {}) => {
|
||||
@@ -599,6 +647,7 @@ const hysteriaParser = (proxy = {}) => {
|
||||
detourParser(proxy, parsedProxy);
|
||||
tfoParser(proxy, parsedProxy);
|
||||
smuxParser(proxy.smux, parsedProxy);
|
||||
ipVersionParser(proxy, parsedProxy);
|
||||
return parsedProxy;
|
||||
};
|
||||
const hysteria2Parser = (proxy = {}) => {
|
||||
@@ -632,6 +681,7 @@ const hysteria2Parser = (proxy = {}) => {
|
||||
tfoParser(proxy, parsedProxy);
|
||||
detourParser(proxy, parsedProxy);
|
||||
smuxParser(proxy.smux, parsedProxy);
|
||||
ipVersionParser(proxy, parsedProxy);
|
||||
return parsedProxy;
|
||||
};
|
||||
const tuic5Parser = (proxy = {}) => {
|
||||
@@ -663,6 +713,7 @@ const tuic5Parser = (proxy = {}) => {
|
||||
detourParser(proxy, parsedProxy);
|
||||
tlsParser(proxy, parsedProxy);
|
||||
smuxParser(proxy.smux, parsedProxy);
|
||||
ipVersionParser(proxy, parsedProxy);
|
||||
return parsedProxy;
|
||||
};
|
||||
const anytlsParser = (proxy = {}) => {
|
||||
@@ -678,8 +729,14 @@ const anytlsParser = (proxy = {}) => {
|
||||
parsedProxy.idle_session_check_interval = `${proxy['idle-session-check-interval']}s`;
|
||||
if (/^\d+$/.test(proxy['idle-session-timeout']))
|
||||
parsedProxy.idle_session_timeout = `${proxy['idle-session-timeout']}s`;
|
||||
if (/^\d+$/.test(proxy['min-idle-session']))
|
||||
parsedProxy.min_idle_session = parseInt(
|
||||
`${proxy['min-idle-session']}`,
|
||||
10,
|
||||
);
|
||||
detourParser(proxy, parsedProxy);
|
||||
tlsParser(proxy, parsedProxy);
|
||||
ipVersionParser(proxy, parsedProxy);
|
||||
return parsedProxy;
|
||||
};
|
||||
|
||||
@@ -737,6 +794,7 @@ const wireguardParser = (proxy = {}) => {
|
||||
tfoParser(proxy, parsedProxy);
|
||||
detourParser(proxy, parsedProxy);
|
||||
smuxParser(proxy.smux, parsedProxy);
|
||||
ipVersionParser(proxy, parsedProxy);
|
||||
return parsedProxy;
|
||||
};
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ export default function Stash_Producer() {
|
||||
'2022-blake3-aes-128-gcm',
|
||||
'2022-blake3-aes-256-gcm',
|
||||
].includes(proxy.cipher)) ||
|
||||
(proxy.type === 'snell' && String(proxy.version) === '4') ||
|
||||
(proxy.type === 'snell' && proxy.version >= 4) ||
|
||||
(proxy.type === 'vless' &&
|
||||
proxy['reality-opts'] &&
|
||||
!['xtls-rprx-vision'].includes(proxy.flow))
|
||||
@@ -238,6 +238,18 @@ export default function Stash_Producer() {
|
||||
proxy['h2-opts'].headers.host = [host];
|
||||
}
|
||||
}
|
||||
if (proxy.network === 'ws') {
|
||||
const wsPath = proxy['ws-opts']?.path;
|
||||
const reg = /^(.*?)(?:\?ed=(\d+))?$/;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [_, path = '', ed = ''] = reg.exec(wsPath);
|
||||
proxy['ws-opts'].path = path;
|
||||
if (ed !== '') {
|
||||
proxy['ws-opts']['early-data-header-name'] =
|
||||
'Sec-WebSocket-Protocol';
|
||||
proxy['ws-opts']['max-early-data'] = parseInt(ed, 10);
|
||||
}
|
||||
}
|
||||
if (proxy['plugin-opts']?.tls) {
|
||||
if (isPresent(proxy, 'skip-cert-verify')) {
|
||||
proxy['plugin-opts']['skip-cert-verify'] =
|
||||
|
||||
@@ -370,9 +370,9 @@ function vmess(proxy, includeUnsupportedProxy) {
|
||||
function ssh(proxy) {
|
||||
const result = new Result(proxy);
|
||||
result.append(`${proxy.name}=ssh,${proxy.server},${proxy.port}`);
|
||||
result.appendIfPresent(`,${proxy.username}`, 'username');
|
||||
result.appendIfPresent(`,username="${proxy.username}"`, 'username');
|
||||
// 所有的类似的字段都有双引号的问题 暂不处理
|
||||
result.appendIfPresent(`,"${proxy.password}"`, 'password');
|
||||
result.appendIfPresent(`,password="${proxy.password}"`, 'password');
|
||||
|
||||
// https://manual.nssurge.com/policy/ssh.html
|
||||
// 需配合 Keystore
|
||||
@@ -439,8 +439,8 @@ function http(proxy) {
|
||||
const result = new Result(proxy);
|
||||
const type = proxy.tls ? 'https' : 'http';
|
||||
result.append(`${proxy.name}=${type},${proxy.server},${proxy.port}`);
|
||||
result.appendIfPresent(`,${proxy.username}`, 'username');
|
||||
result.appendIfPresent(`,"${proxy.password}"`, 'password');
|
||||
result.appendIfPresent(`,username="${proxy.username}"`, 'username');
|
||||
result.appendIfPresent(`,password="${proxy.password}"`, 'password');
|
||||
|
||||
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
||||
@@ -565,8 +565,8 @@ function socks5(proxy) {
|
||||
const result = new Result(proxy);
|
||||
const type = proxy.tls ? 'socks5-tls' : 'socks5';
|
||||
result.append(`${proxy.name}=${type},${proxy.server},${proxy.port}`);
|
||||
result.appendIfPresent(`,${proxy.username}`, 'username');
|
||||
result.appendIfPresent(`,"${proxy.password}"`, 'password');
|
||||
result.appendIfPresent(`,username="${proxy.username}"`, 'username');
|
||||
result.appendIfPresent(`,password="${proxy.password}"`, 'password');
|
||||
|
||||
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Base64 } from 'js-base64';
|
||||
import _ from 'lodash';
|
||||
import express from '@/vendor/express';
|
||||
import $ from '@/core/app';
|
||||
import migrate from '@/utils/migration';
|
||||
@@ -119,10 +121,11 @@ export default function serve() {
|
||||
$app.start();
|
||||
|
||||
if ($.env.isNode) {
|
||||
// Deprecated: SUB_STORE_BACKEND_CRON
|
||||
const backend_sync_cron =
|
||||
eval('process.env.SUB_STORE_BACKEND_SYNC_CRON') ||
|
||||
eval('process.env.SUB_STORE_BACKEND_CRON');
|
||||
// Deprecated: SUB_STORE_BACKEND_CRON, SUB_STORE_CRON
|
||||
const backend_sync_cron = eval(
|
||||
'process.env.SUB_STORE_BACKEND_SYNC_CRON',
|
||||
);
|
||||
|
||||
if (backend_sync_cron) {
|
||||
$.info(`[SYNC CRON] ${backend_sync_cron} enabled`);
|
||||
const { CronJob } = eval(`require("cron")`);
|
||||
@@ -145,6 +148,17 @@ export default function serve() {
|
||||
true, // start
|
||||
// 'Asia/Shanghai' // timeZone
|
||||
);
|
||||
} else {
|
||||
if (eval('process.env.SUB_STORE_BACKEND_CRON')) {
|
||||
$.error(
|
||||
`[SYNC CRON] SUB_STORE_BACKEND_CRON 已弃用, 请使用 SUB_STORE_BACKEND_SYNC_CRON`,
|
||||
);
|
||||
}
|
||||
if (eval('process.env.SUB_STORE_CRON')) {
|
||||
$.error(
|
||||
`[SYNC CRON] SUB_STORE_CRON 已弃用, 请使用 SUB_STORE_BACKEND_SYNC_CRON`,
|
||||
);
|
||||
}
|
||||
}
|
||||
// 格式: 0 */2 * * *,sub,a;0 */3 * * *,col,b
|
||||
// 每 2 小时处理一次单条订阅 a, 每 3 小时处理一次组合订阅 b
|
||||
@@ -292,6 +306,7 @@ export default function serve() {
|
||||
const path = eval(`require("path")`);
|
||||
const fs = eval(`require("fs")`);
|
||||
const data_url = eval('process.env.SUB_STORE_DATA_URL');
|
||||
const data_url_post = eval('process.env.SUB_STORE_DATA_URL_POST');
|
||||
const fe_be_path = eval('process.env.SUB_STORE_FRONTEND_BACKEND_PATH');
|
||||
const fe_port = eval('process.env.SUB_STORE_FRONTEND_PORT') || 3001;
|
||||
const fe_host =
|
||||
@@ -412,10 +427,39 @@ export default function serve() {
|
||||
if (data_url) {
|
||||
$.info(`[BACKEND] downloading data from ${data_url}`);
|
||||
download(data_url)
|
||||
.then((content) => {
|
||||
$.write(content, '#sub-store');
|
||||
.then(async (content) => {
|
||||
try {
|
||||
content = JSON.parse(Base64.decode(content));
|
||||
if (Object.keys(content.settings).length === 0) {
|
||||
throw new Error(
|
||||
'备份文件应该至少包含 settings 字段',
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
try {
|
||||
content = JSON.parse(content);
|
||||
if (Object.keys(content.settings).length === 0) {
|
||||
throw new Error(
|
||||
'备份文件应该至少包含 settings 字段',
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
$.error(
|
||||
`Gist 备份文件校验失败, 无法还原\nReason: ${
|
||||
err.message ?? err
|
||||
}`,
|
||||
);
|
||||
throw new Error('Gist 备份文件校验失败, 无法还原');
|
||||
}
|
||||
}
|
||||
if (data_url_post) {
|
||||
$.info('[BACKEND] executing post-processing script');
|
||||
eval(data_url_post);
|
||||
}
|
||||
|
||||
$.cache = JSON.parse(content);
|
||||
$.write(JSON.stringify(content, null, ` `), '#sub-store');
|
||||
|
||||
$.cache = content;
|
||||
$.persistCache();
|
||||
|
||||
migrate();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Base64 } from 'js-base64';
|
||||
import _ from 'lodash';
|
||||
import $ from '@/core/app';
|
||||
import { ENV } from '@/vendor/open-api';
|
||||
import { failed, success } from '@/restful/response';
|
||||
@@ -39,10 +41,30 @@ export default function register($app) {
|
||||
);
|
||||
})
|
||||
.post((req, res) => {
|
||||
const { content } = req.body;
|
||||
$.write(content, '#sub-store');
|
||||
let { content } = req.body;
|
||||
try {
|
||||
content = JSON.parse(Base64.decode(content));
|
||||
if (Object.keys(content.settings).length === 0) {
|
||||
throw new Error('备份文件应该至少包含 settings 字段');
|
||||
}
|
||||
} catch (err) {
|
||||
try {
|
||||
content = JSON.parse(content);
|
||||
if (Object.keys(content.settings).length === 0) {
|
||||
throw new Error('备份文件应该至少包含 settings 字段');
|
||||
}
|
||||
} catch (err) {
|
||||
$.error(
|
||||
`备份文件校验失败, 无法还原\nReason: ${
|
||||
err.message ?? err
|
||||
}`,
|
||||
);
|
||||
throw new Error('备份文件校验失败, 无法还原');
|
||||
}
|
||||
}
|
||||
$.write(JSON.stringify(content, null, ` `), '#sub-store');
|
||||
if ($.env.isNode) {
|
||||
$.cache = JSON.parse(content);
|
||||
$.cache = content;
|
||||
$.persistCache();
|
||||
}
|
||||
migrate();
|
||||
@@ -104,7 +126,7 @@ async function refresh(_, res) {
|
||||
success(res);
|
||||
}
|
||||
|
||||
async function gistBackupAction(action) {
|
||||
async function gistBackupAction(action, keep, encode) {
|
||||
// read token
|
||||
const { gistToken, syncPlatform } = $.read(SETTINGS_KEY);
|
||||
if (!gistToken) throw new Error('GitHub Token is required for backup!');
|
||||
@@ -114,6 +136,9 @@ async function gistBackupAction(action) {
|
||||
key: GIST_BACKUP_KEY,
|
||||
syncPlatform,
|
||||
});
|
||||
let currentContent = $.read('#sub-store');
|
||||
currentContent = currentContent ? JSON.parse(currentContent) : {};
|
||||
if ($.env.isNode) currentContent = JSON.parse(JSON.stringify($.cache));
|
||||
let content;
|
||||
const settings = $.read(SETTINGS_KEY);
|
||||
const updated = settings.syncTime;
|
||||
@@ -121,7 +146,18 @@ async function gistBackupAction(action) {
|
||||
case 'upload':
|
||||
try {
|
||||
content = $.read('#sub-store');
|
||||
if ($.env.isNode) content = JSON.stringify($.cache, null, ` `);
|
||||
content = content ? JSON.parse(content) : {};
|
||||
if ($.env.isNode) content = JSON.parse(JSON.stringify($.cache));
|
||||
if (encode === 'plaintext') {
|
||||
content.settings.gistToken =
|
||||
'恢复后请重新设置 GitHub Token';
|
||||
content = JSON.stringify(content, null, ` `);
|
||||
} else {
|
||||
content = Base64.encode(
|
||||
JSON.stringify(content, null, ` `),
|
||||
);
|
||||
}
|
||||
|
||||
$.info(`下载备份, 与本地内容对比...`);
|
||||
const onlineContent = await gist.download(
|
||||
GIST_BACKUP_FILE_NAME,
|
||||
@@ -138,7 +174,14 @@ async function gistBackupAction(action) {
|
||||
settings.syncTime = new Date().getTime();
|
||||
$.write(settings, SETTINGS_KEY);
|
||||
content = $.read('#sub-store');
|
||||
if ($.env.isNode) content = JSON.stringify($.cache, null, ` `);
|
||||
content = content ? JSON.parse(content) : {};
|
||||
if ($.env.isNode) content = JSON.parse(JSON.stringify($.cache));
|
||||
if (encode === 'plaintext') {
|
||||
content.settings.gistToken = '恢复后请重新设置 GitHub Token';
|
||||
content = JSON.stringify(content, null, ` `);
|
||||
} else {
|
||||
content = Base64.encode(JSON.stringify(content, null, ` `));
|
||||
}
|
||||
$.info(`上传备份中...`);
|
||||
try {
|
||||
await gist.upload({
|
||||
@@ -156,21 +199,34 @@ async function gistBackupAction(action) {
|
||||
$.info(`还原备份中...`);
|
||||
content = await gist.download(GIST_BACKUP_FILE_NAME);
|
||||
try {
|
||||
if (Object.keys(JSON.parse(content).settings).length === 0) {
|
||||
content = JSON.parse(Base64.decode(content));
|
||||
if (Object.keys(content.settings).length === 0) {
|
||||
throw new Error('备份文件应该至少包含 settings 字段');
|
||||
}
|
||||
} catch (err) {
|
||||
$.error(
|
||||
`Gist 备份文件校验失败, 无法还原\nReason: ${
|
||||
err.message ?? err
|
||||
}`,
|
||||
);
|
||||
throw new Error('Gist 备份文件校验失败, 无法还原');
|
||||
try {
|
||||
content = JSON.parse(content);
|
||||
if (Object.keys(content.settings).length === 0) {
|
||||
throw new Error('备份文件应该至少包含 settings 字段');
|
||||
}
|
||||
} catch (err) {
|
||||
$.error(
|
||||
`Gist 备份文件校验失败, 无法还原\nReason: ${
|
||||
err.message ?? err
|
||||
}`,
|
||||
);
|
||||
throw new Error('Gist 备份文件校验失败, 无法还原');
|
||||
}
|
||||
}
|
||||
if (keep) {
|
||||
$.info(`保留原有设置 ${keep}`);
|
||||
keep.split(',').forEach((path) => {
|
||||
_.set(content, path, _.get(currentContent, path));
|
||||
});
|
||||
}
|
||||
// restore settings
|
||||
$.write(content, '#sub-store');
|
||||
$.write(JSON.stringify(content, null, ` `), '#sub-store');
|
||||
if ($.env.isNode) {
|
||||
content = JSON.parse(content);
|
||||
$.cache = content;
|
||||
$.persistCache();
|
||||
}
|
||||
@@ -182,7 +238,7 @@ async function gistBackupAction(action) {
|
||||
}
|
||||
}
|
||||
async function gistBackup(req, res) {
|
||||
const { action } = req.query;
|
||||
const { action, keep, encode } = req.query;
|
||||
// read token
|
||||
const { gistToken } = $.read(SETTINGS_KEY);
|
||||
if (!gistToken) {
|
||||
@@ -195,7 +251,7 @@ async function gistBackup(req, res) {
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
await gistBackupAction(action);
|
||||
await gistBackupAction(action, keep, encode);
|
||||
success(res);
|
||||
} catch (err) {
|
||||
$.error(
|
||||
|
||||
@@ -14,6 +14,8 @@ import $ from '@/core/app';
|
||||
import { findByName } from '@/utils/database';
|
||||
import { produceArtifact } from '@/restful/sync';
|
||||
import PROXY_PREPROCESSORS from '@/core/proxy-utils/preprocessors';
|
||||
import { ProxyUtils } from '@/core/proxy-utils';
|
||||
|
||||
const clashPreprocessor = PROXY_PREPROCESSORS.find(
|
||||
(processor) => processor.name === 'Clash Pre-processor',
|
||||
);
|
||||
@@ -261,10 +263,34 @@ export default async function download(
|
||||
if (shouldCache) {
|
||||
resourceCache.set(id, body);
|
||||
if (customCacheKey) {
|
||||
$.info(
|
||||
`URL ${url}\n写入自定义缓存 ${$arguments?.cacheKey}`,
|
||||
);
|
||||
$.write(body, customCacheKey);
|
||||
let shouldWriteCustomCacheKey = true;
|
||||
if (preprocess) {
|
||||
try {
|
||||
const proxies = ProxyUtils.parse(body);
|
||||
if (
|
||||
!Array.isArray(proxies) ||
|
||||
proxies.length === 0
|
||||
) {
|
||||
$.error(
|
||||
`URL ${url} 不包含有效节点\n不写入自定义缓存 ${$arguments?.cacheKey}`,
|
||||
);
|
||||
shouldWriteCustomCacheKey = false;
|
||||
}
|
||||
} catch (e) {
|
||||
$.error(
|
||||
`URL ${url} 尝试解析节点失败 ${
|
||||
e.message ?? e
|
||||
}\n不写入自定义缓存 ${$arguments?.cacheKey}`,
|
||||
);
|
||||
shouldWriteCustomCacheKey = false;
|
||||
}
|
||||
}
|
||||
if (shouldWriteCustomCacheKey) {
|
||||
$.info(
|
||||
`URL ${url}\n写入自定义缓存 ${$arguments?.cacheKey}`,
|
||||
);
|
||||
$.write(body, customCacheKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -334,7 +334,22 @@ export function normalizeFlowHeader(flowHeaders) {
|
||||
if (!kvMap.has(key)) {
|
||||
try {
|
||||
// 解码 URI 组件并保留原始值作为 fallback
|
||||
const decodedValue = decodeURIComponent(encodedValue);
|
||||
let decodedValue = decodeURIComponent(encodedValue);
|
||||
if (
|
||||
['upload', 'download', 'total', 'expire'].includes(
|
||||
key,
|
||||
)
|
||||
) {
|
||||
try {
|
||||
decodedValue = Number(decodedValue).toFixed(0);
|
||||
} catch (e) {
|
||||
$.error(
|
||||
`Failed to convert value for key "${key}=${encodedValue}": ${
|
||||
e.message ?? e
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
kvMap.set(key, decodedValue);
|
||||
} catch (e) {
|
||||
kvMap.set(key, encodedValue);
|
||||
|
||||
75
backend/src/vendor/open-api.js
vendored
75
backend/src/vendor/open-api.js
vendored
@@ -9,6 +9,7 @@ const isShadowRocket = 'undefined' !== typeof $rocket;
|
||||
const isEgern = 'object' == typeof egern;
|
||||
const isLanceX = 'undefined' != typeof $native;
|
||||
const isGUIforCores = typeof $Plugins !== 'undefined';
|
||||
import { Base64 } from 'js-base64';
|
||||
|
||||
function isPlainObject(obj) {
|
||||
return (
|
||||
@@ -18,6 +19,26 @@ function isPlainObject(obj) {
|
||||
);
|
||||
}
|
||||
|
||||
function parseSocks5Uri(uri) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let [__, username, password, server, port, query, name] = uri.match(
|
||||
/^socks5:\/\/(?:(.*?):(.*?)@)?(.*?)(?::(\d+?))?(\?.*?)?(?:#(.*?))?$/,
|
||||
);
|
||||
if (port) {
|
||||
port = parseInt(port, 10);
|
||||
} else {
|
||||
$.error(`port is not present in line: ${uri}`);
|
||||
throw new Error(`port is not present in line: ${uri}`);
|
||||
}
|
||||
return {
|
||||
type: 5,
|
||||
host: server,
|
||||
port,
|
||||
|
||||
userId: username != null ? decodeURIComponent(username) : undefined,
|
||||
password: password != null ? decodeURIComponent(password) : undefined,
|
||||
};
|
||||
}
|
||||
export class OpenAPI {
|
||||
constructor(name = 'untitled', debug = false) {
|
||||
this.name = name;
|
||||
@@ -100,13 +121,23 @@ export class OpenAPI {
|
||||
if (this.node.fs.existsSync(fpath)) {
|
||||
try {
|
||||
this.cache = JSON.parse(
|
||||
this.node.fs.readFileSync(`${fpath}`),
|
||||
this.node.fs.readFileSync(`${fpath}`, 'utf-8'),
|
||||
);
|
||||
} catch (e) {
|
||||
this.node.fs.copyFileSync(fpath, backupPath);
|
||||
this.error(
|
||||
`Failed to parse ${fpath}: ${e.message}. Backup created at ${backupPath}`,
|
||||
);
|
||||
try {
|
||||
const str = Base64.decode(
|
||||
this.node.fs.readFileSync(`${fpath}`, 'utf-8'),
|
||||
);
|
||||
this.cache = JSON.parse(str);
|
||||
this.node.fs.writeFileSync(fpath, str, {
|
||||
flag: 'w',
|
||||
});
|
||||
} catch (e) {
|
||||
this.node.fs.copyFileSync(fpath, backupPath);
|
||||
this.error(
|
||||
`Failed to parse ${fpath}: ${e.message}. Backup created at ${backupPath}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isPlainObject(this.cache)) {
|
||||
@@ -393,6 +424,7 @@ export function HTTP(defaultOptions = { baseURL: '' }) {
|
||||
}
|
||||
if (isNode) {
|
||||
const undici = eval("require('undici')");
|
||||
const { socksDispatcher } = eval("require('fetch-socks')");
|
||||
const {
|
||||
ProxyAgent,
|
||||
EnvHttpProxyAgent,
|
||||
@@ -422,16 +454,34 @@ export function HTTP(defaultOptions = { baseURL: '' }) {
|
||||
).toString('base64')}`,
|
||||
};
|
||||
}
|
||||
let dispatcher;
|
||||
if (!opts.proxy) {
|
||||
const allProxy =
|
||||
eval('process.env.all_proxy') ||
|
||||
eval('process.env.ALL_PROXY');
|
||||
if (allProxy && /^socks5:\/\//.test(allProxy)) {
|
||||
opts.proxy = allProxy;
|
||||
}
|
||||
}
|
||||
if (opts.proxy) {
|
||||
if (/^socks5:\/\//.test(opts.proxy)) {
|
||||
dispatcher = socksDispatcher(
|
||||
parseSocks5Uri(opts.proxy),
|
||||
agentOpts,
|
||||
);
|
||||
} else {
|
||||
dispatcher = new ProxyAgent({
|
||||
...agentOpts,
|
||||
uri: opts.proxy,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
dispatcher = new EnvHttpProxyAgent(agentOpts);
|
||||
}
|
||||
const response = await request(opts.url, {
|
||||
...opts,
|
||||
method: method.toUpperCase(),
|
||||
dispatcher: (opts.proxy
|
||||
? new ProxyAgent({
|
||||
...agentOpts,
|
||||
uri: opts.proxy,
|
||||
})
|
||||
: new EnvHttpProxyAgent(agentOpts)
|
||||
).compose(
|
||||
dispatcher: dispatcher.compose(
|
||||
interceptors.redirect({
|
||||
maxRedirections: 3,
|
||||
throwOnMaxRedirects: true,
|
||||
@@ -484,6 +534,7 @@ export function HTTP(defaultOptions = { baseURL: '' }) {
|
||||
url: options.url,
|
||||
headers: options.headers,
|
||||
body: options.body,
|
||||
autoTransformBody: false,
|
||||
options: {
|
||||
Proxy: options.proxy,
|
||||
Timeout: options.timeout
|
||||
|
||||
1
backend/sub-store_1752902525508.json
Normal file
1
backend/sub-store_1752902525508.json
Normal file
File diff suppressed because one or more lines are too long
1
backend/sub-store_1752902571053.json
Normal file
1
backend/sub-store_1752902571053.json
Normal file
File diff suppressed because one or more lines are too long
1
backend/sub-store_1752902602701.json
Normal file
1
backend/sub-store_1752902602701.json
Normal file
File diff suppressed because one or more lines are too long
1
backend/sub-store_1752902651631.json
Normal file
1
backend/sub-store_1752902651631.json
Normal file
File diff suppressed because one or more lines are too long
@@ -26,7 +26,7 @@ function operator(proxies = [], targetPlatform, context) {
|
||||
// 12. 以 Surge 为例, 最新的参数一般我都会跟进, 以 Surge 文档为例, 一些常用的: TUIC/Hysteria 2 的 `ecn`, Snell 的 `reuse` 连接复用, QUIC 策略 block-quic`, Hysteria 2 下载带宽 `down`
|
||||
// 13. `test-url` 为测延迟链接, `test-timeout` 为测延迟超时
|
||||
// 14. `ports` 为端口跳跃, `hop-interval` 变换端口号的时间间隔
|
||||
// 15. `ip-version` 设置节点使用 IP 版本,可选:dual,ipv4,ipv6,ipv4-prefer,ipv6-prefer. 会进行内部转换, 若无法匹配则使用原始值
|
||||
// 15. `ip-version` 设置节点使用 IP 版本,兼容各家的值. 会进行内部转换. sing-box 以外: 若无法匹配则使用原始值. sing-box: 需有匹配且节点上设置 `_dns_server` 字段, 将自动设置 `domain_resolver.server`
|
||||
// 16. `sing-box` 支持使用 `_network` 来设置 `network`, 例如 `tcp`, `udp`
|
||||
// 17. `block-quic` 支持 `auto`, `on`, `off`. 不同的平台不一定都支持, 会自动转换
|
||||
|
||||
@@ -151,6 +151,18 @@ function operator(proxies = [], targetPlatform, context) {
|
||||
// });
|
||||
// $server.sni = sni
|
||||
|
||||
// 示例: 从 config 文件中读取配置项并进行节点操作
|
||||
// config 的本地内容为
|
||||
// {
|
||||
// "reuse": false
|
||||
// }
|
||||
// 脚本操作为
|
||||
// const config = (ProxyUtils.JSON5 || JSON).parse(await produceArtifact({
|
||||
// type: 'file',
|
||||
// name: 'config' // 文件名
|
||||
// }))
|
||||
// $server.reuse = config.reuse
|
||||
|
||||
// 1. Surge 输出 WireGuard 完整配置
|
||||
|
||||
// let proxies = await produceArtifact({
|
||||
@@ -235,14 +247,14 @@ function operator(proxies = [], targetPlatform, context) {
|
||||
// 这个历史遗留原因, 是有点复杂. 提供一个例子, 用来取当前脚本所在的组合订阅或单条订阅名称
|
||||
|
||||
// let name = ''
|
||||
// for (const [key, value] of Object.entries(env.source)) {
|
||||
// for (const [key, value] of Object.entries(context.source)) {
|
||||
// if (!key.startsWith('_')) {
|
||||
// name = value.displayName || value.name
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// if (!name) {
|
||||
// const collection = env.source._collection
|
||||
// const collection = context.source._collection
|
||||
// name = collection.displayName || collection.name
|
||||
// }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user