mirror of
https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-10 00:52:40 +00:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4790bf47d1 | ||
|
|
56fd495fb6 | ||
|
|
f4639d9a34 | ||
|
|
cc58a5541e | ||
|
|
772f431887 | ||
|
|
2b60c515cd | ||
|
|
c8c22c3901 | ||
|
|
d8f9466b84 | ||
|
|
d12ccad382 | ||
|
|
b4358663cc | ||
|
|
aba6264988 | ||
|
|
2320ab3838 | ||
|
|
542957d34a | ||
|
|
07e50175f9 | ||
|
|
e09d66060d | ||
|
|
b048ecdfff | ||
|
|
aac72fb9a3 | ||
|
|
baec193e5c | ||
|
|
8fe818f826 | ||
|
|
72286984ec | ||
|
|
27e693c308 | ||
|
|
6cf8080cd3 | ||
|
|
839fcacf63 | ||
|
|
a2e45bcb10 | ||
|
|
ea0eb91691 | ||
|
|
1f0ddf2d28 | ||
|
|
a660c6ff90 | ||
|
|
71d9adbc07 | ||
|
|
97bec9183a | ||
|
|
ef85b6d0e9 | ||
|
|
8ffb060cb4 | ||
|
|
6d43961e96 | ||
|
|
f3200aea8c | ||
|
|
e2346d16a2 | ||
|
|
dc320eaa6c | ||
|
|
02031019f7 |
@@ -26,6 +26,8 @@ Core functionalities:
|
|||||||
|
|
||||||
### Supported Input Formats
|
### Supported Input Formats
|
||||||
|
|
||||||
|
> ⚠️ Do not use `Shadowrocket` to export URI and then import it as input. It is not a standard URI.
|
||||||
|
|
||||||
- [x] URI(SS, SSR, VMess, VLESS, Trojan, Hysteria, Hysteria 2, TUIC v5, WireGuard)
|
- [x] URI(SS, SSR, VMess, VLESS, Trojan, Hysteria, Hysteria 2, TUIC v5, WireGuard)
|
||||||
- [x] Clash Proxies YAML
|
- [x] Clash Proxies YAML
|
||||||
- [x] Clash Proxy JSON(single line)
|
- [x] Clash Proxy JSON(single line)
|
||||||
@@ -33,7 +35,6 @@ Core functionalities:
|
|||||||
- [x] Loon (SS, SSR, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, WireGuard, VLESS, Hysteria 2)
|
- [x] Loon (SS, SSR, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, WireGuard, VLESS, Hysteria 2)
|
||||||
- [x] Surge (SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, TUIC, Snell, Hysteria 2, SSH(Password authentication only), External Proxy Program(only for macOS), WireGuard(Surge to Surge))
|
- [x] Surge (SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, TUIC, Snell, Hysteria 2, SSH(Password authentication only), External Proxy Program(only for macOS), WireGuard(Surge to Surge))
|
||||||
- [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] Shadowrocket (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC)
|
|
||||||
- [x] Clash.Meta (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC)
|
- [x] Clash.Meta (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC)
|
||||||
- [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)
|
||||||
- [x] Clash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard)
|
- [x] Clash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sub-store",
|
"name": "sub-store",
|
||||||
"version": "2.14.385",
|
"version": "2.14.418",
|
||||||
"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": {
|
||||||
@@ -30,11 +30,10 @@
|
|||||||
"js-base64": "^3.7.2",
|
"js-base64": "^3.7.2",
|
||||||
"jsrsasign": "^11.1.0",
|
"jsrsasign": "^11.1.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"ms": "^2.1.3",
|
||||||
|
"nanoid": "^3.3.3",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
"requests": "^0.3.0",
|
"static-js-yaml": "^1.0.0"
|
||||||
"semver": "^7.3.7",
|
|
||||||
"static-js-yaml": "^1.0.0",
|
|
||||||
"uuid": "^8.3.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.18.0",
|
"@babel/core": "^7.18.0",
|
||||||
|
|||||||
14661
backend/pnpm-lock.yaml
generated
14661
backend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@ export const FILES_KEY = 'files';
|
|||||||
export const MODULES_KEY = 'modules';
|
export const MODULES_KEY = 'modules';
|
||||||
export const ARTIFACTS_KEY = 'artifacts';
|
export const ARTIFACTS_KEY = 'artifacts';
|
||||||
export const RULES_KEY = 'rules';
|
export const RULES_KEY = 'rules';
|
||||||
|
export const TOKENS_KEY = 'tokens';
|
||||||
export const GIST_BACKUP_KEY = 'Auto Generated Sub-Store Backup';
|
export const GIST_BACKUP_KEY = 'Auto Generated Sub-Store Backup';
|
||||||
export const GIST_BACKUP_FILE_NAME = 'Sub-Store';
|
export const GIST_BACKUP_FILE_NAME = 'Sub-Store';
|
||||||
export const ARTIFACT_REPOSITORY_KEY = 'Sub-Store Artifacts Repository';
|
export const ARTIFACT_REPOSITORY_KEY = 'Sub-Store Artifacts Repository';
|
||||||
|
|||||||
@@ -292,6 +292,7 @@ export const ProxyUtils = {
|
|||||||
getISO,
|
getISO,
|
||||||
MMDB,
|
MMDB,
|
||||||
Gist,
|
Gist,
|
||||||
|
download,
|
||||||
};
|
};
|
||||||
|
|
||||||
function tryParse(parser, line) {
|
function tryParse(parser, line) {
|
||||||
@@ -326,6 +327,9 @@ function formatTransportPath(path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function lastParse(proxy) {
|
function lastParse(proxy) {
|
||||||
|
if (typeof proxy.cipher === 'string') {
|
||||||
|
proxy.cipher = proxy.cipher.toLowerCase();
|
||||||
|
}
|
||||||
if (typeof proxy.password === 'number') {
|
if (typeof proxy.password === 'number') {
|
||||||
proxy.password = numberToString(proxy.password);
|
proxy.password = numberToString(proxy.password);
|
||||||
}
|
}
|
||||||
@@ -407,20 +411,7 @@ function lastParse(proxy) {
|
|||||||
proxy['h2-opts'].path = path[0];
|
proxy['h2-opts'].path = path[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (proxy.tls && !proxy.sni) {
|
|
||||||
if (proxy.network) {
|
|
||||||
let transportHost = proxy[`${proxy.network}-opts`]?.headers?.Host;
|
|
||||||
transportHost = Array.isArray(transportHost)
|
|
||||||
? transportHost[0]
|
|
||||||
: transportHost;
|
|
||||||
if (transportHost) {
|
|
||||||
proxy.sni = transportHost;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!proxy.sni && !isIP(proxy.server)) {
|
|
||||||
proxy.sni = proxy.server;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 非 tls, 有 ws/http 传输层, 使用域名的节点, 将设置传输层 Host 防止之后域名解析后丢失域名(不覆盖现有的 Host)
|
// 非 tls, 有 ws/http 传输层, 使用域名的节点, 将设置传输层 Host 防止之后域名解析后丢失域名(不覆盖现有的 Host)
|
||||||
if (
|
if (
|
||||||
!proxy.tls &&
|
!proxy.tls &&
|
||||||
@@ -447,6 +438,20 @@ function lastParse(proxy) {
|
|||||||
proxy[`${proxy.network}-opts`].path = [transportPath];
|
proxy[`${proxy.network}-opts`].path = [transportPath];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (proxy.tls && !proxy.sni) {
|
||||||
|
if (!isIP(proxy.server)) {
|
||||||
|
proxy.sni = proxy.server;
|
||||||
|
}
|
||||||
|
if (!proxy.sni && proxy.network) {
|
||||||
|
let transportHost = proxy[`${proxy.network}-opts`]?.headers?.Host;
|
||||||
|
transportHost = Array.isArray(transportHost)
|
||||||
|
? transportHost[0]
|
||||||
|
: transportHost;
|
||||||
|
if (transportHost) {
|
||||||
|
proxy.sni = transportHost;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// if (['hysteria', 'hysteria2', 'tuic'].includes(proxy.type)) {
|
// if (['hysteria', 'hysteria2', 'tuic'].includes(proxy.type)) {
|
||||||
if (proxy.ports) {
|
if (proxy.ports) {
|
||||||
proxy.ports = String(proxy.ports).replace(/\//g, ',');
|
proxy.ports = String(proxy.ports).replace(/\//g, ',');
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ function URI_VMess() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
// https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2)
|
// https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2)
|
||||||
if (proxy.tls && proxy.sni) {
|
if (proxy.tls && params.sni && params.sni !== '') {
|
||||||
proxy.sni = params.sni;
|
proxy.sni = params.sni;
|
||||||
}
|
}
|
||||||
let httpupgrade = false;
|
let httpupgrade = false;
|
||||||
@@ -391,12 +391,6 @@ function URI_VMess() {
|
|||||||
} else {
|
} else {
|
||||||
delete proxy.network;
|
delete proxy.network;
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml#L413
|
|
||||||
// sni 优先级应高于 host
|
|
||||||
if (proxy.tls && !proxy.sni && transportHost) {
|
|
||||||
proxy.sni = transportHost;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
@@ -537,14 +531,13 @@ function URI_VLESS() {
|
|||||||
if (Object.keys(opts).length > 0) {
|
if (Object.keys(opts).length > 0) {
|
||||||
proxy[`${proxy.network}-opts`] = opts;
|
proxy[`${proxy.network}-opts`] = opts;
|
||||||
}
|
}
|
||||||
}
|
if (proxy.network === 'kcp') {
|
||||||
|
// mKCP 种子。省略时不使用种子,但不可以为空字符串。建议 mKCP 用户使用 seed。
|
||||||
if (proxy.tls && !proxy.sni) {
|
if (params.seed) {
|
||||||
if (proxy.network === 'ws') {
|
proxy.seed = params.seed;
|
||||||
proxy.sni = proxy['ws-opts']?.headers?.Host;
|
}
|
||||||
} else if (proxy.network === 'http') {
|
// mKCP 的伪装头部类型。当前可选值有 none / srtp / utp / wechat-video / dtls / wireguard。省略时默认值为 none,即不使用伪装头部,但不可以为空字符串。
|
||||||
let httpHost = proxy['http-opts']?.headers?.Host;
|
proxy.headerType = params.headerType || 'none';
|
||||||
proxy.sni = Array.isArray(httpHost) ? httpHost[0] : httpHost;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -898,18 +891,7 @@ function Clash_All() {
|
|||||||
if (['vmess', 'vless'].includes(proxy.type)) {
|
if (['vmess', 'vless'].includes(proxy.type)) {
|
||||||
proxy.sni = proxy.servername;
|
proxy.sni = proxy.servername;
|
||||||
delete proxy.servername;
|
delete proxy.servername;
|
||||||
if (proxy.tls && !proxy.sni) {
|
|
||||||
if (proxy.network === 'ws') {
|
|
||||||
proxy.sni = proxy['ws-opts']?.headers?.Host;
|
|
||||||
} else if (proxy.network === 'http') {
|
|
||||||
let httpHost = proxy['http-opts']?.headers?.Host;
|
|
||||||
proxy.sni = Array.isArray(httpHost)
|
|
||||||
? httpHost[0]
|
|
||||||
: httpHost;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (proxy['server-cert-fingerprint']) {
|
if (proxy['server-cert-fingerprint']) {
|
||||||
proxy['tls-fingerprint'] = proxy['server-cert-fingerprint'];
|
proxy['tls-fingerprint'] = proxy['server-cert-fingerprint'];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v
|
|||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/udp_port/others)* {
|
||||||
proxy.type = "ss";
|
proxy.type = "ss";
|
||||||
// handle obfs
|
// handle obfs
|
||||||
if (obfs.type == "http" || obfs.type === "tls") {
|
if (obfs.type == "http" || obfs.type === "tls") {
|
||||||
@@ -193,14 +193,14 @@ snell_psk = comma "psk" equals match:[^,]+ { proxy.psk = match.join(""); }
|
|||||||
snell_version = comma "version" equals match:$[0-9]+ { proxy.version = parseInt(match.trim()); }
|
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(""); }
|
||||||
passwordk = comma "password" equals match:[^,]+ { proxy.password = match.join(""); }
|
passwordk = comma "password" equals match:[^,]+ { proxy.password = match.join("").replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
|
||||||
vmess_uuid = comma "username" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
vmess_uuid = comma "username" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
||||||
vmess_aead = comma "vmess-aead" equals flag:bool { proxy.aead = flag; }
|
vmess_aead = comma "vmess-aead" equals flag:bool { proxy.aead = flag; }
|
||||||
|
|
||||||
method = comma "encrypt-method" equals cipher:cipher {
|
method = comma "encrypt-method" equals cipher:cipher {
|
||||||
proxy.cipher = cipher;
|
proxy.cipher = cipher;
|
||||||
}
|
}
|
||||||
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"camellia-128-cfb"/"camellia-192-cfb"/"camellia-256-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"idea-cfb"/"none"/"rc2-cfb"/"rc4-md5"/"rc4"/"salsa20"/"seed-cfb"/"xchacha20-ietf-poly1305");
|
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"camellia-128-cfb"/"camellia-192-cfb"/"camellia-256-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"idea-cfb"/"none"/"rc2-cfb"/"rc4-md5"/"rc4"/"salsa20"/"seed-cfb"/"xchacha20-ietf-poly1305"/"2022-blake3-aes-128-gcm"/"2022-blake3-aes-256-gcm");
|
||||||
|
|
||||||
ws = comma "ws" equals flag:bool { obfs.type = "ws"; }
|
ws = comma "ws" equals flag:bool { obfs.type = "ws"; }
|
||||||
ws_headers = comma "ws-headers" equals headers:$[^,]+ {
|
ws_headers = comma "ws-headers" equals headers:$[^,]+ {
|
||||||
@@ -219,7 +219,7 @@ obfs_host = comma "obfs-host" equals host:domain { obfs.host = host; };
|
|||||||
obfs_uri = comma "obfs-uri" equals path:uri { obfs.path = path }
|
obfs_uri = comma "obfs-uri" equals path:uri { obfs.path = path }
|
||||||
uri = $[^,]+
|
uri = $[^,]+
|
||||||
|
|
||||||
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
udp_relay = comma "udp-relay" equals flag:bool { proxy.udp = flag; }
|
||||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||||
reuse = comma "reuse" equals flag:bool { proxy.reuse = flag; }
|
reuse = comma "reuse" equals flag:bool { proxy.reuse = flag; }
|
||||||
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
||||||
@@ -240,6 +240,7 @@ idle_timeout = comma "idle-timeout" equals match:$[0-9]+ { proxy["idle-timeout"]
|
|||||||
private_key = comma "private-key" equals match:[^,]+ { proxy["keystore-private-key"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
private_key = comma "private-key" equals match:[^,]+ { proxy["keystore-private-key"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
server_fingerprint = comma "server-fingerprint" equals match:[^,]+ { proxy["server-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
server_fingerprint = comma "server-fingerprint" equals match:[^,]+ { proxy["server-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
block_quic = comma "block-quic" equals match:[^,]+ { proxy["block-quic"] = match.join(""); }
|
block_quic = comma "block-quic" equals match:[^,]+ { proxy["block-quic"] = match.join(""); }
|
||||||
|
udp_port = comma "udp-port" equals match:$[0-9]+ { proxy["udp-port"] = parseInt(match.trim()); }
|
||||||
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
|
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
|
||||||
shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-sni"] = match.join(""); }
|
shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-sni"] = match.join(""); }
|
||||||
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join(""); }
|
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join(""); }
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v
|
|||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/udp_port/others)* {
|
||||||
proxy.type = "ss";
|
proxy.type = "ss";
|
||||||
// handle obfs
|
// handle obfs
|
||||||
if (obfs.type == "http" || obfs.type === "tls") {
|
if (obfs.type == "http" || obfs.type === "tls") {
|
||||||
@@ -191,14 +191,14 @@ snell_psk = comma "psk" equals match:[^,]+ { proxy.psk = match.join(""); }
|
|||||||
snell_version = comma "version" equals match:$[0-9]+ { proxy.version = parseInt(match.trim()); }
|
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(""); }
|
||||||
passwordk = comma "password" equals match:[^,]+ { proxy.password = match.join(""); }
|
passwordk = comma "password" equals match:[^,]+ { proxy.password = match.join("").replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
|
||||||
vmess_uuid = comma "username" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
vmess_uuid = comma "username" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
||||||
vmess_aead = comma "vmess-aead" equals flag:bool { proxy.aead = flag; }
|
vmess_aead = comma "vmess-aead" equals flag:bool { proxy.aead = flag; }
|
||||||
|
|
||||||
method = comma "encrypt-method" equals cipher:cipher {
|
method = comma "encrypt-method" equals cipher:cipher {
|
||||||
proxy.cipher = cipher;
|
proxy.cipher = cipher;
|
||||||
}
|
}
|
||||||
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"camellia-128-cfb"/"camellia-192-cfb"/"camellia-256-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"idea-cfb"/"none"/"rc2-cfb"/"rc4-md5"/"rc4"/"salsa20"/"seed-cfb"/"xchacha20-ietf-poly1305");
|
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"camellia-128-cfb"/"camellia-192-cfb"/"camellia-256-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"idea-cfb"/"none"/"rc2-cfb"/"rc4-md5"/"rc4"/"salsa20"/"seed-cfb"/"xchacha20-ietf-poly1305"/"2022-blake3-aes-128-gcm"/"2022-blake3-aes-256-gcm");
|
||||||
|
|
||||||
ws = comma "ws" equals flag:bool { obfs.type = "ws"; }
|
ws = comma "ws" equals flag:bool { obfs.type = "ws"; }
|
||||||
ws_headers = comma "ws-headers" equals headers:$[^,]+ {
|
ws_headers = comma "ws-headers" equals headers:$[^,]+ {
|
||||||
@@ -217,7 +217,7 @@ obfs_host = comma "obfs-host" equals host:domain { obfs.host = host; };
|
|||||||
obfs_uri = comma "obfs-uri" equals path:uri { obfs.path = path }
|
obfs_uri = comma "obfs-uri" equals path:uri { obfs.path = path }
|
||||||
uri = $[^,]+
|
uri = $[^,]+
|
||||||
|
|
||||||
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
udp_relay = comma "udp-relay" equals flag:bool { proxy.udp = flag; }
|
||||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||||
reuse = comma "reuse" equals flag:bool { proxy.reuse = flag; }
|
reuse = comma "reuse" equals flag:bool { proxy.reuse = flag; }
|
||||||
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
||||||
@@ -238,6 +238,7 @@ idle_timeout = comma "idle-timeout" equals match:$[0-9]+ { proxy["idle-timeout"]
|
|||||||
private_key = comma "private-key" equals match:[^,]+ { proxy["keystore-private-key"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
private_key = comma "private-key" equals match:[^,]+ { proxy["keystore-private-key"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
server_fingerprint = comma "server-fingerprint" equals match:[^,]+ { proxy["server-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
server_fingerprint = comma "server-fingerprint" equals match:[^,]+ { proxy["server-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
block_quic = comma "block-quic" equals match:[^,]+ { proxy["block-quic"] = match.join(""); }
|
block_quic = comma "block-quic" equals match:[^,]+ { proxy["block-quic"] = match.join(""); }
|
||||||
|
udp_port = comma "udp-port" equals match:$[0-9]+ { proxy["udp-port"] = parseInt(match.trim()); }
|
||||||
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
|
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
|
||||||
shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-sni"] = match.join(""); }
|
shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-sni"] = match.join(""); }
|
||||||
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join(""); }
|
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join(""); }
|
||||||
|
|||||||
@@ -50,10 +50,32 @@ function Clash() {
|
|||||||
};
|
};
|
||||||
const parse = function (raw) {
|
const parse = function (raw) {
|
||||||
// Clash YAML format
|
// Clash YAML format
|
||||||
|
|
||||||
|
// 防止 VLESS节点 reality-opts 选项中的 short-id 被解析成 Infinity
|
||||||
|
// 匹配 short-id 冒号后面的值(包含空格和引号)
|
||||||
|
const afterReplace = raw.replace(
|
||||||
|
/short-id:([ ]*[^,\n}]*)/g,
|
||||||
|
(matched, value) => {
|
||||||
|
const afterTrim = value.trim();
|
||||||
|
|
||||||
|
// 为空
|
||||||
|
if (!afterTrim || afterTrim === '') {
|
||||||
|
return 'short-id: ""'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否被引号包裹
|
||||||
|
if (/^(['"]).*\1$/.test(afterTrim)) {
|
||||||
|
return `short-id: ${afterTrim}`;
|
||||||
|
} else {
|
||||||
|
return `short-id: "${afterTrim}"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
proxies,
|
proxies,
|
||||||
'global-client-fingerprint': globalClientFingerprint,
|
'global-client-fingerprint': globalClientFingerprint,
|
||||||
} = safeLoad(raw);
|
} = safeLoad(afterReplace);
|
||||||
return proxies
|
return proxies
|
||||||
.map((p) => {
|
.map((p) => {
|
||||||
// https://github.com/MetaCubeX/mihomo/blob/Alpha/docs/config.yaml#L73C1-L73C26
|
// https://github.com/MetaCubeX/mihomo/blob/Alpha/docs/config.yaml#L73C1-L73C26
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export default function Surge_Producer() {
|
|||||||
}
|
}
|
||||||
switch (proxy.type) {
|
switch (proxy.type) {
|
||||||
case 'ss':
|
case 'ss':
|
||||||
return shadowsocks(proxy);
|
return shadowsocks(proxy, opts['include-unsupported-proxy']);
|
||||||
case 'trojan':
|
case 'trojan':
|
||||||
return trojan(proxy);
|
return trojan(proxy);
|
||||||
case 'vmess':
|
case 'vmess':
|
||||||
@@ -51,7 +51,7 @@ export default function Surge_Producer() {
|
|||||||
return { produce };
|
return { produce };
|
||||||
}
|
}
|
||||||
|
|
||||||
function shadowsocks(proxy) {
|
function shadowsocks(proxy, includeUnsupportedProxy) {
|
||||||
const result = new Result(proxy);
|
const result = new Result(proxy);
|
||||||
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
|
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
|
||||||
if (!proxy.cipher) {
|
if (!proxy.cipher) {
|
||||||
@@ -85,12 +85,15 @@ function shadowsocks(proxy) {
|
|||||||
'chacha20',
|
'chacha20',
|
||||||
'chacha20-ietf',
|
'chacha20-ietf',
|
||||||
'none',
|
'none',
|
||||||
|
...(includeUnsupportedProxy
|
||||||
|
? ['2022-blake3-aes-128-gcm', '2022-blake3-aes-256-gcm']
|
||||||
|
: []),
|
||||||
].includes(proxy.cipher)
|
].includes(proxy.cipher)
|
||||||
) {
|
) {
|
||||||
throw new Error(`cipher ${proxy.cipher} is not supported`);
|
throw new Error(`cipher ${proxy.cipher} is not supported`);
|
||||||
}
|
}
|
||||||
result.append(`,encrypt-method=${proxy.cipher}`);
|
result.append(`,encrypt-method=${proxy.cipher}`);
|
||||||
result.appendIfPresent(`,password=${proxy.password}`, 'password');
|
result.appendIfPresent(`,password="${proxy.password}"`, 'password');
|
||||||
|
|
||||||
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
||||||
@@ -122,6 +125,8 @@ function shadowsocks(proxy) {
|
|||||||
|
|
||||||
// udp
|
// udp
|
||||||
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
||||||
|
// udp-port
|
||||||
|
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
|
||||||
|
|
||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||||
@@ -188,7 +193,7 @@ function shadowsocks(proxy) {
|
|||||||
function trojan(proxy) {
|
function trojan(proxy) {
|
||||||
const result = new Result(proxy);
|
const result = new Result(proxy);
|
||||||
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
|
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
|
||||||
result.appendIfPresent(`,password=${proxy.password}`, 'password');
|
result.appendIfPresent(`,password="${proxy.password}"`, 'password');
|
||||||
|
|
||||||
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
||||||
@@ -361,7 +366,7 @@ function ssh(proxy) {
|
|||||||
result.append(`${proxy.name}=ssh,${proxy.server},${proxy.port}`);
|
result.append(`${proxy.name}=ssh,${proxy.server},${proxy.port}`);
|
||||||
result.appendIfPresent(`,${proxy.username}`, 'username');
|
result.appendIfPresent(`,${proxy.username}`, 'username');
|
||||||
// 所有的类似的字段都有双引号的问题 暂不处理
|
// 所有的类似的字段都有双引号的问题 暂不处理
|
||||||
result.appendIfPresent(`,${proxy.password}`, 'password');
|
result.appendIfPresent(`,"${proxy.password}"`, 'password');
|
||||||
|
|
||||||
// https://manual.nssurge.com/policy/ssh.html
|
// https://manual.nssurge.com/policy/ssh.html
|
||||||
// 需配合 Keystore
|
// 需配合 Keystore
|
||||||
@@ -426,7 +431,7 @@ function http(proxy) {
|
|||||||
const type = proxy.tls ? 'https' : 'http';
|
const type = proxy.tls ? 'https' : 'http';
|
||||||
result.append(`${proxy.name}=${type},${proxy.server},${proxy.port}`);
|
result.append(`${proxy.name}=${type},${proxy.server},${proxy.port}`);
|
||||||
result.appendIfPresent(`,${proxy.username}`, 'username');
|
result.appendIfPresent(`,${proxy.username}`, 'username');
|
||||||
result.appendIfPresent(`,${proxy.password}`, 'password');
|
result.appendIfPresent(`,"${proxy.password}"`, 'password');
|
||||||
|
|
||||||
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
||||||
@@ -504,7 +509,7 @@ function socks5(proxy) {
|
|||||||
const type = proxy.tls ? 'socks5-tls' : 'socks5';
|
const type = proxy.tls ? 'socks5-tls' : 'socks5';
|
||||||
result.append(`${proxy.name}=${type},${proxy.server},${proxy.port}`);
|
result.append(`${proxy.name}=${type},${proxy.server},${proxy.port}`);
|
||||||
result.appendIfPresent(`,${proxy.username}`, 'username');
|
result.appendIfPresent(`,${proxy.username}`, 'username');
|
||||||
result.appendIfPresent(`,${proxy.password}`, 'password');
|
result.appendIfPresent(`,"${proxy.password}"`, 'password');
|
||||||
|
|
||||||
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
||||||
@@ -670,7 +675,7 @@ function tuic(proxy) {
|
|||||||
result.append(`${proxy.name}=${type},${proxy.server},${proxy.port}`);
|
result.append(`${proxy.name}=${type},${proxy.server},${proxy.port}`);
|
||||||
|
|
||||||
result.appendIfPresent(`,uuid=${proxy.uuid}`, 'uuid');
|
result.appendIfPresent(`,uuid=${proxy.uuid}`, 'uuid');
|
||||||
result.appendIfPresent(`,password=${proxy.password}`, 'password');
|
result.appendIfPresent(`,password="${proxy.password}"`, 'password');
|
||||||
result.appendIfPresent(`,token=${proxy.token}`, 'token');
|
result.appendIfPresent(`,token=${proxy.token}`, 'token');
|
||||||
|
|
||||||
result.appendIfPresent(
|
result.appendIfPresent(
|
||||||
@@ -945,7 +950,7 @@ function hysteria2(proxy) {
|
|||||||
const result = new Result(proxy);
|
const result = new Result(proxy);
|
||||||
result.append(`${proxy.name}=hysteria2,${proxy.server},${proxy.port}`);
|
result.append(`${proxy.name}=hysteria2,${proxy.server},${proxy.port}`);
|
||||||
|
|
||||||
result.appendIfPresent(`,password=${proxy.password}`, 'password');
|
result.appendIfPresent(`,password="${proxy.password}"`, 'password');
|
||||||
|
|
||||||
if (isPresent(proxy, 'ports')) {
|
if (isPresent(proxy, 'ports')) {
|
||||||
result.append(`,port-hopping="${proxy.ports.replace(/,/g, ';')}"`);
|
result.append(`,port-hopping="${proxy.ports.replace(/,/g, ';')}"`);
|
||||||
|
|||||||
@@ -20,10 +20,16 @@ export default function SurgeMac_Producer() {
|
|||||||
try {
|
try {
|
||||||
return surge_Producer.produce(proxy, type, opts);
|
return surge_Producer.produce(proxy, type, opts);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
$.log(
|
if (opts.useMihomoExternal) {
|
||||||
`${proxy.name} is not supported on ${targetPlatform}, try to use Mihomo(SurgeMac - External Proxy Program) instead`,
|
$.log(
|
||||||
);
|
`${proxy.name} is not supported on ${targetPlatform}, try to use Mihomo(SurgeMac - External Proxy Program) instead`,
|
||||||
return mihomo(proxy, type, opts);
|
);
|
||||||
|
return mihomo(proxy, type, opts);
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
`Surge for macOS 可手动指定链接参数 target=SurgeMac 或在 同步配置 中指定 SurgeMac 来启用 mihomo 支援 Surge 本身不支持的协议`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -224,6 +224,18 @@ export default function URI_Producer() {
|
|||||||
vlessTransportServiceName,
|
vlessTransportServiceName,
|
||||||
)}`;
|
)}`;
|
||||||
}
|
}
|
||||||
|
if (proxy.network === 'kcp') {
|
||||||
|
if (proxy.seed) {
|
||||||
|
vlessTransport += `&seed=${encodeURIComponent(
|
||||||
|
proxy.seed,
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
if (proxy.headerType) {
|
||||||
|
vlessTransport += `&headerType=${encodeURIComponent(
|
||||||
|
proxy.headerType,
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result = `vless://${proxy.uuid}@${proxy.server}:${
|
result = `vless://${proxy.uuid}@${proxy.server}:${
|
||||||
proxy.port
|
proxy.port
|
||||||
|
|||||||
@@ -130,6 +130,15 @@ async function doSync() {
|
|||||||
try {
|
try {
|
||||||
if (artifact.sync && artifact.source) {
|
if (artifact.sync && artifact.source) {
|
||||||
$.info(`正在同步云配置:${artifact.name}...`);
|
$.info(`正在同步云配置:${artifact.name}...`);
|
||||||
|
|
||||||
|
const useMihomoExternal =
|
||||||
|
artifact.platform === 'SurgeMac';
|
||||||
|
|
||||||
|
if (useMihomoExternal) {
|
||||||
|
$.info(
|
||||||
|
`手动指定了 target 为 SurgeMac, 将使用 Mihomo External`,
|
||||||
|
);
|
||||||
|
}
|
||||||
const output = await produceArtifact({
|
const output = await produceArtifact({
|
||||||
type: artifact.type,
|
type: artifact.type,
|
||||||
name: artifact.source,
|
name: artifact.source,
|
||||||
@@ -137,6 +146,7 @@ async function doSync() {
|
|||||||
produceOpts: {
|
produceOpts: {
|
||||||
'include-unsupported-proxy':
|
'include-unsupported-proxy':
|
||||||
artifact.includeUnsupportedProxy,
|
artifact.includeUnsupportedProxy,
|
||||||
|
useMihomoExternal,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import registerSettingRoutes from '@/restful/settings';
|
|||||||
import registerMiscRoutes from '@/restful/miscs';
|
import registerMiscRoutes from '@/restful/miscs';
|
||||||
import registerSortRoutes from '@/restful/sort';
|
import registerSortRoutes from '@/restful/sort';
|
||||||
import registerFileRoutes from '@/restful/file';
|
import registerFileRoutes from '@/restful/file';
|
||||||
|
import registerTokenRoutes from '@/restful/token';
|
||||||
import registerModuleRoutes from '@/restful/module';
|
import registerModuleRoutes from '@/restful/module';
|
||||||
|
|
||||||
migrate();
|
migrate();
|
||||||
@@ -32,6 +33,7 @@ function serve() {
|
|||||||
// register routes
|
// register routes
|
||||||
registerCollectionRoutes($app);
|
registerCollectionRoutes($app);
|
||||||
registerSubscriptionRoutes($app);
|
registerSubscriptionRoutes($app);
|
||||||
|
registerTokenRoutes($app);
|
||||||
registerFileRoutes($app);
|
registerFileRoutes($app);
|
||||||
registerModuleRoutes($app);
|
registerModuleRoutes($app);
|
||||||
registerArtifactRoutes($app);
|
registerArtifactRoutes($app);
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ import { getISO } from '@/utils/geo';
|
|||||||
import env from '@/utils/env';
|
import env from '@/utils/env';
|
||||||
|
|
||||||
export default function register($app) {
|
export default function register($app) {
|
||||||
|
$app.get('/share/col/:name', downloadCollection);
|
||||||
|
$app.get('/share/sub/:name', downloadSubscription);
|
||||||
|
|
||||||
$app.get('/download/collection/:name', downloadCollection);
|
$app.get('/download/collection/:name', downloadCollection);
|
||||||
$app.get('/download/:name', downloadSubscription);
|
$app.get('/download/:name', downloadSubscription);
|
||||||
$app.get(
|
$app.get(
|
||||||
@@ -52,6 +55,8 @@ async function downloadSubscription(req, res) {
|
|||||||
name = decodeURIComponent(name);
|
name = decodeURIComponent(name);
|
||||||
nezhaIndex = decodeURIComponent(nezhaIndex);
|
nezhaIndex = decodeURIComponent(nezhaIndex);
|
||||||
|
|
||||||
|
const useMihomoExternal = req.query.target === 'SurgeMac';
|
||||||
|
|
||||||
const platform =
|
const platform =
|
||||||
req.query.target || getPlatformFromHeaders(req.headers) || 'JSON';
|
req.query.target || getPlatformFromHeaders(req.headers) || 'JSON';
|
||||||
|
|
||||||
@@ -69,6 +74,8 @@ async function downloadSubscription(req, res) {
|
|||||||
produceType,
|
produceType,
|
||||||
includeUnsupportedProxy,
|
includeUnsupportedProxy,
|
||||||
resultFormat,
|
resultFormat,
|
||||||
|
proxy,
|
||||||
|
noCache,
|
||||||
} = req.query;
|
} = req.query;
|
||||||
let $options = {};
|
let $options = {};
|
||||||
if (req.query.$options) {
|
if (req.query.$options) {
|
||||||
@@ -92,6 +99,10 @@ async function downloadSubscription(req, res) {
|
|||||||
url = decodeURIComponent(url);
|
url = decodeURIComponent(url);
|
||||||
$.info(`指定远程订阅 URL: ${url}`);
|
$.info(`指定远程订阅 URL: ${url}`);
|
||||||
}
|
}
|
||||||
|
if (proxy) {
|
||||||
|
proxy = decodeURIComponent(proxy);
|
||||||
|
$.info(`指定远程订阅使用代理/策略 proxy: ${proxy}`);
|
||||||
|
}
|
||||||
if (ua) {
|
if (ua) {
|
||||||
ua = decodeURIComponent(ua);
|
ua = decodeURIComponent(ua);
|
||||||
$.info(`指定远程订阅 User-Agent: ${ua}`);
|
$.info(`指定远程订阅 User-Agent: ${ua}`);
|
||||||
@@ -117,6 +128,14 @@ async function downloadSubscription(req, res) {
|
|||||||
$.info(`包含不支持的节点: ${includeUnsupportedProxy}`);
|
$.info(`包含不支持的节点: ${includeUnsupportedProxy}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (useMihomoExternal) {
|
||||||
|
$.info(`手动指定了 target 为 SurgeMac, 将使用 Mihomo External`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noCache) {
|
||||||
|
$.info(`指定不使用缓存: ${noCache}`);
|
||||||
|
}
|
||||||
|
|
||||||
const allSubs = $.read(SUBS_KEY);
|
const allSubs = $.read(SUBS_KEY);
|
||||||
const sub = findByName(allSubs, name);
|
const sub = findByName(allSubs, name);
|
||||||
if (sub) {
|
if (sub) {
|
||||||
@@ -133,8 +152,11 @@ async function downloadSubscription(req, res) {
|
|||||||
produceType,
|
produceType,
|
||||||
produceOpts: {
|
produceOpts: {
|
||||||
'include-unsupported-proxy': includeUnsupportedProxy,
|
'include-unsupported-proxy': includeUnsupportedProxy,
|
||||||
|
useMihomoExternal,
|
||||||
},
|
},
|
||||||
$options,
|
$options,
|
||||||
|
proxy,
|
||||||
|
noCache,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -172,10 +194,10 @@ async function downloadSubscription(req, res) {
|
|||||||
if (!$arguments.noFlow) {
|
if (!$arguments.noFlow) {
|
||||||
// forward flow headers
|
// forward flow headers
|
||||||
const flowInfo = await getFlowHeaders(
|
const flowInfo = await getFlowHeaders(
|
||||||
url,
|
$arguments?.insecure ? `${url}#insecure` : url,
|
||||||
$arguments.flowUserAgent,
|
$arguments.flowUserAgent,
|
||||||
undefined,
|
undefined,
|
||||||
sub.proxy,
|
proxy || sub.proxy,
|
||||||
$arguments.flowUrl,
|
$arguments.flowUrl,
|
||||||
);
|
);
|
||||||
if (flowInfo) {
|
if (flowInfo) {
|
||||||
@@ -247,6 +269,8 @@ async function downloadCollection(req, res) {
|
|||||||
name = decodeURIComponent(name);
|
name = decodeURIComponent(name);
|
||||||
nezhaIndex = decodeURIComponent(nezhaIndex);
|
nezhaIndex = decodeURIComponent(nezhaIndex);
|
||||||
|
|
||||||
|
const useMihomoExternal = req.query.target === 'SurgeMac';
|
||||||
|
|
||||||
const platform =
|
const platform =
|
||||||
req.query.target || getPlatformFromHeaders(req.headers) || 'JSON';
|
req.query.target || getPlatformFromHeaders(req.headers) || 'JSON';
|
||||||
|
|
||||||
@@ -264,6 +288,8 @@ async function downloadCollection(req, res) {
|
|||||||
produceType,
|
produceType,
|
||||||
includeUnsupportedProxy,
|
includeUnsupportedProxy,
|
||||||
resultFormat,
|
resultFormat,
|
||||||
|
proxy,
|
||||||
|
noCache,
|
||||||
} = req.query;
|
} = req.query;
|
||||||
|
|
||||||
let $options = {};
|
let $options = {};
|
||||||
@@ -285,6 +311,11 @@ async function downloadCollection(req, res) {
|
|||||||
$.info(`传入 $options: ${JSON.stringify($options)}`);
|
$.info(`传入 $options: ${JSON.stringify($options)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (proxy) {
|
||||||
|
proxy = decodeURIComponent(proxy);
|
||||||
|
$.info(`指定远程订阅使用代理/策略 proxy: ${proxy}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
|
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
|
||||||
ignoreFailedRemoteSub = decodeURIComponent(ignoreFailedRemoteSub);
|
ignoreFailedRemoteSub = decodeURIComponent(ignoreFailedRemoteSub);
|
||||||
$.info(`指定忽略失败的远程订阅: ${ignoreFailedRemoteSub}`);
|
$.info(`指定忽略失败的远程订阅: ${ignoreFailedRemoteSub}`);
|
||||||
@@ -298,6 +329,12 @@ async function downloadCollection(req, res) {
|
|||||||
includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy);
|
includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy);
|
||||||
$.info(`包含不支持的节点: ${includeUnsupportedProxy}`);
|
$.info(`包含不支持的节点: ${includeUnsupportedProxy}`);
|
||||||
}
|
}
|
||||||
|
if (useMihomoExternal) {
|
||||||
|
$.info(`手动指定了 target 为 SurgeMac, 将使用 Mihomo External`);
|
||||||
|
}
|
||||||
|
if (noCache) {
|
||||||
|
$.info(`指定不使用缓存: ${noCache}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (collection) {
|
if (collection) {
|
||||||
try {
|
try {
|
||||||
@@ -309,8 +346,11 @@ async function downloadCollection(req, res) {
|
|||||||
produceType,
|
produceType,
|
||||||
produceOpts: {
|
produceOpts: {
|
||||||
'include-unsupported-proxy': includeUnsupportedProxy,
|
'include-unsupported-proxy': includeUnsupportedProxy,
|
||||||
|
useMihomoExternal,
|
||||||
},
|
},
|
||||||
$options,
|
$options,
|
||||||
|
proxy,
|
||||||
|
noCache,
|
||||||
});
|
});
|
||||||
|
|
||||||
// forward flow header from the first subscription in this collection
|
// forward flow header from the first subscription in this collection
|
||||||
@@ -352,10 +392,10 @@ async function downloadCollection(req, res) {
|
|||||||
}
|
}
|
||||||
if (!$arguments.noFlow) {
|
if (!$arguments.noFlow) {
|
||||||
const flowInfo = await getFlowHeaders(
|
const flowInfo = await getFlowHeaders(
|
||||||
url,
|
$arguments?.insecure ? `${url}#insecure` : url,
|
||||||
$arguments.flowUserAgent,
|
$arguments.flowUserAgent,
|
||||||
undefined,
|
undefined,
|
||||||
sub.proxy,
|
proxy || sub.proxy || collection.proxy,
|
||||||
$arguments.flowUrl,
|
$arguments.flowUrl,
|
||||||
);
|
);
|
||||||
if (flowInfo) {
|
if (flowInfo) {
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import { produceArtifact } from '@/restful/sync';
|
|||||||
export default function register($app) {
|
export default function register($app) {
|
||||||
if (!$.read(FILES_KEY)) $.write([], FILES_KEY);
|
if (!$.read(FILES_KEY)) $.write([], FILES_KEY);
|
||||||
|
|
||||||
|
$app.get('/share/file/:name', getFile);
|
||||||
|
|
||||||
$app.route('/api/file/:name')
|
$app.route('/api/file/:name')
|
||||||
.get(getFile)
|
.get(getFile)
|
||||||
.patch(updateFile)
|
.patch(updateFile)
|
||||||
@@ -59,6 +61,7 @@ async function getFile(req, res) {
|
|||||||
content,
|
content,
|
||||||
mergeSources,
|
mergeSources,
|
||||||
ignoreFailedRemoteFile,
|
ignoreFailedRemoteFile,
|
||||||
|
proxy,
|
||||||
} = req.query;
|
} = req.query;
|
||||||
let $options = {};
|
let $options = {};
|
||||||
if (req.query.$options) {
|
if (req.query.$options) {
|
||||||
@@ -82,6 +85,10 @@ async function getFile(req, res) {
|
|||||||
url = decodeURIComponent(url);
|
url = decodeURIComponent(url);
|
||||||
$.info(`指定远程文件 URL: ${url}`);
|
$.info(`指定远程文件 URL: ${url}`);
|
||||||
}
|
}
|
||||||
|
if (proxy) {
|
||||||
|
proxy = decodeURIComponent(proxy);
|
||||||
|
$.info(`指定远程订阅使用代理/策略 proxy: ${proxy}`);
|
||||||
|
}
|
||||||
if (ua) {
|
if (ua) {
|
||||||
ua = decodeURIComponent(ua);
|
ua = decodeURIComponent(ua);
|
||||||
$.info(`指定远程文件 User-Agent: ${ua}`);
|
$.info(`指定远程文件 User-Agent: ${ua}`);
|
||||||
@@ -120,6 +127,7 @@ async function getFile(req, res) {
|
|||||||
mergeSources,
|
mergeSources,
|
||||||
ignoreFailedRemoteFile,
|
ignoreFailedRemoteFile,
|
||||||
$options,
|
$options,
|
||||||
|
proxy,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -129,6 +137,8 @@ async function getFile(req, res) {
|
|||||||
const flowInfo = await getFlowHeaders(
|
const flowInfo = await getFlowHeaders(
|
||||||
subInfoUrl,
|
subInfoUrl,
|
||||||
subInfoUserAgent || file.subInfoUserAgent,
|
subInfoUserAgent || file.subInfoUserAgent,
|
||||||
|
undefined,
|
||||||
|
proxy || file.proxy,
|
||||||
);
|
);
|
||||||
if (flowInfo) {
|
if (flowInfo) {
|
||||||
res.set('subscription-userinfo', flowInfo);
|
res.set('subscription-userinfo', flowInfo);
|
||||||
@@ -141,7 +151,14 @@ async function getFile(req, res) {
|
|||||||
)}`,
|
)}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (file.download) {
|
||||||
|
res.set(
|
||||||
|
'Content-Disposition',
|
||||||
|
`attachment; filename*=UTF-8''${encodeURIComponent(
|
||||||
|
file.displayName || file.name,
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
res.set('Content-Type', 'text/plain; charset=utf-8').send(
|
res.set('Content-Type', 'text/plain; charset=utf-8').send(
|
||||||
output ?? '',
|
output ?? '',
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import migrate from '@/utils/migration';
|
|||||||
import download from '@/utils/download';
|
import download from '@/utils/download';
|
||||||
import { syncArtifacts } from '@/restful/sync';
|
import { syncArtifacts } from '@/restful/sync';
|
||||||
import { gistBackupAction } from '@/restful/miscs';
|
import { gistBackupAction } from '@/restful/miscs';
|
||||||
|
import { TOKENS_KEY } from '@/constants';
|
||||||
|
|
||||||
import registerSubscriptionRoutes from './subscriptions';
|
import registerSubscriptionRoutes from './subscriptions';
|
||||||
import registerCollectionRoutes from './collections';
|
import registerCollectionRoutes from './collections';
|
||||||
import registerArtifactRoutes from './artifacts';
|
import registerArtifactRoutes from './artifacts';
|
||||||
import registerFileRoutes from './file';
|
import registerFileRoutes from './file';
|
||||||
|
import registerTokenRoutes from './token';
|
||||||
import registerModuleRoutes from './module';
|
import registerModuleRoutes from './module';
|
||||||
import registerSyncRoutes from './sync';
|
import registerSyncRoutes from './sync';
|
||||||
import registerDownloadRoutes from './download';
|
import registerDownloadRoutes from './download';
|
||||||
@@ -36,6 +38,7 @@ export default function serve() {
|
|||||||
registerSettingRoutes($app);
|
registerSettingRoutes($app);
|
||||||
registerArtifactRoutes($app);
|
registerArtifactRoutes($app);
|
||||||
registerFileRoutes($app);
|
registerFileRoutes($app);
|
||||||
|
registerTokenRoutes($app);
|
||||||
registerModuleRoutes($app);
|
registerModuleRoutes($app);
|
||||||
registerSyncRoutes($app);
|
registerSyncRoutes($app);
|
||||||
registerNodeInfoRoutes($app);
|
registerNodeInfoRoutes($app);
|
||||||
@@ -143,7 +146,7 @@ export default function serve() {
|
|||||||
try {
|
try {
|
||||||
fs.accessSync(path.join(fe_abs_path, 'index.html'));
|
fs.accessSync(path.join(fe_abs_path, 'index.html'));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(
|
$.error(
|
||||||
`[FRONTEND] index.html file not found in ${fe_abs_path}`,
|
`[FRONTEND] index.html file not found in ${fe_abs_path}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -158,6 +161,7 @@ export default function serve() {
|
|||||||
|
|
||||||
const staticFileMiddleware = express_.static(fe_path);
|
const staticFileMiddleware = express_.static(fe_path);
|
||||||
|
|
||||||
|
let be_share_rewrite = '/share/:type/:name';
|
||||||
let be_api_rewrite = '';
|
let be_api_rewrite = '';
|
||||||
let be_download_rewrite = '';
|
let be_download_rewrite = '';
|
||||||
let be_api = '/api/';
|
let be_api = '/api/';
|
||||||
@@ -174,15 +178,39 @@ export default function serve() {
|
|||||||
be_download_rewrite = `${
|
be_download_rewrite = `${
|
||||||
fe_be_path === '/' ? '' : fe_be_path
|
fe_be_path === '/' ? '' : fe_be_path
|
||||||
}${be_download}`;
|
}${be_download}`;
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
be_share_rewrite,
|
||||||
|
createProxyMiddleware({
|
||||||
|
target: `http://127.0.0.1:${port}`,
|
||||||
|
changeOrigin: true,
|
||||||
|
pathRewrite: (path, req) => {
|
||||||
|
if (req.method.toLowerCase() !== 'get')
|
||||||
|
throw new Error('Method not allowed');
|
||||||
|
const tokens = $.read(TOKENS_KEY) || [];
|
||||||
|
const token = tokens.find(
|
||||||
|
(t) =>
|
||||||
|
t.token === req.query.token &&
|
||||||
|
t.type === req.params.type &&
|
||||||
|
t.name === req.params.name &&
|
||||||
|
(t.exp == null || t.exp > Date.now()),
|
||||||
|
);
|
||||||
|
if (!token) throw new Error('Forbbiden');
|
||||||
|
return path;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
app.use(
|
app.use(
|
||||||
be_api_rewrite,
|
be_api_rewrite,
|
||||||
createProxyMiddleware({
|
createProxyMiddleware({
|
||||||
target: `http://127.0.0.1:${port}`,
|
target: `http://127.0.0.1:${port}`,
|
||||||
changeOrigin: true,
|
|
||||||
pathRewrite: (path) => {
|
pathRewrite: (path) => {
|
||||||
return path.startsWith(be_api_rewrite)
|
const newPath = path.startsWith(be_api_rewrite)
|
||||||
? path.replace(be_api_rewrite, be_api)
|
? path.replace(be_api_rewrite, be_api)
|
||||||
: path;
|
: path;
|
||||||
|
return newPath.includes('?')
|
||||||
|
? `${newPath}&share=true`
|
||||||
|
: `${newPath}?share=true`;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -220,6 +248,9 @@ export default function serve() {
|
|||||||
$.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} -> http://127.0.0.1:${port}${be_download}`,
|
||||||
);
|
);
|
||||||
|
$.info(
|
||||||
|
`[SHARE BACKEND] ${fe_address}:${fe_port}${be_share_rewrite}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,9 @@ export default function register($app) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getEnv(req, res) {
|
function getEnv(req, res) {
|
||||||
|
if (req.query.share) {
|
||||||
|
env.feature.share = true;
|
||||||
|
}
|
||||||
success(res, env);
|
success(res, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -177,7 +177,20 @@ async function compareCollection(req, res) {
|
|||||||
try {
|
try {
|
||||||
const allSubs = $.read(SUBS_KEY);
|
const allSubs = $.read(SUBS_KEY);
|
||||||
const collection = req.body;
|
const collection = req.body;
|
||||||
const subnames = collection.subscriptions;
|
const subnames = [...collection.subscriptions];
|
||||||
|
let subscriptionTags = collection.subscriptionTags;
|
||||||
|
if (Array.isArray(subscriptionTags) && subscriptionTags.length > 0) {
|
||||||
|
allSubs.forEach((sub) => {
|
||||||
|
if (
|
||||||
|
Array.isArray(sub.tag) &&
|
||||||
|
sub.tag.length > 0 &&
|
||||||
|
!subnames.includes(sub.name) &&
|
||||||
|
sub.tag.some((tag) => subscriptionTags.includes(tag))
|
||||||
|
) {
|
||||||
|
subnames.push(sub.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
const results = {};
|
const results = {};
|
||||||
const errors = {};
|
const errors = {};
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
|
|||||||
@@ -57,9 +57,25 @@ async function getFlowInfo(req, res) {
|
|||||||
!['localFirst', 'remoteFirst'].includes(sub.mergeSources)
|
!['localFirst', 'remoteFirst'].includes(sub.mergeSources)
|
||||||
) {
|
) {
|
||||||
if (sub.subUserinfo) {
|
if (sub.subUserinfo) {
|
||||||
success(res, {
|
try {
|
||||||
...parseFlowHeaders(sub.subUserinfo),
|
success(res, {
|
||||||
});
|
...parseFlowHeaders(sub.subUserinfo),
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
$.error(
|
||||||
|
`Failed to parse flow info for local subscription ${name}: ${
|
||||||
|
e.message ?? e
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
failed(
|
||||||
|
res,
|
||||||
|
new RequestInvalidError(
|
||||||
|
'NO_FLOW_INFO',
|
||||||
|
'N/A',
|
||||||
|
`Failed to parse flow info`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
failed(
|
failed(
|
||||||
res,
|
res,
|
||||||
@@ -110,17 +126,37 @@ async function getFlowInfo(req, res) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (sub.subUserinfo) {
|
if (sub.subUserinfo) {
|
||||||
success(res, {
|
try {
|
||||||
...parseFlowHeaders(sub.subUserinfo),
|
const remainingDays = getRmainingDays({
|
||||||
remainingDays: getRmainingDays({
|
|
||||||
resetDay: $arguments.resetDay,
|
resetDay: $arguments.resetDay,
|
||||||
startDate: $arguments.startDate,
|
startDate: $arguments.startDate,
|
||||||
cycleDays: $arguments.cycleDays,
|
cycleDays: $arguments.cycleDays,
|
||||||
}),
|
});
|
||||||
});
|
const result = {
|
||||||
|
...parseFlowHeaders(sub.subUserinfo),
|
||||||
|
};
|
||||||
|
if (remainingDays != null) {
|
||||||
|
result.remainingDays = remainingDays;
|
||||||
|
}
|
||||||
|
success(res, result);
|
||||||
|
} catch (e) {
|
||||||
|
$.error(
|
||||||
|
`Failed to parse flow info for local subscription ${name}: ${
|
||||||
|
e.message ?? e
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
failed(
|
||||||
|
res,
|
||||||
|
new RequestInvalidError(
|
||||||
|
'NO_FLOW_INFO',
|
||||||
|
'N/A',
|
||||||
|
`Failed to parse flow info`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const flowHeaders = await getFlowHeaders(
|
const flowHeaders = await getFlowHeaders(
|
||||||
url,
|
$arguments?.insecure ? `${url}#insecure` : url,
|
||||||
$arguments.flowUserAgent,
|
$arguments.flowUserAgent,
|
||||||
undefined,
|
undefined,
|
||||||
sub.proxy,
|
sub.proxy,
|
||||||
@@ -137,14 +173,18 @@ async function getFlowInfo(req, res) {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
success(res, {
|
const remainingDays = getRmainingDays({
|
||||||
...parseFlowHeaders(flowHeaders),
|
resetDay: $arguments.resetDay,
|
||||||
remainingDays: getRmainingDays({
|
startDate: $arguments.startDate,
|
||||||
resetDay: $arguments.resetDay,
|
cycleDays: $arguments.cycleDays,
|
||||||
startDate: $arguments.startDate,
|
|
||||||
cycleDays: $arguments.cycleDays,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
const result = {
|
||||||
|
...parseFlowHeaders(flowHeaders),
|
||||||
|
};
|
||||||
|
if (remainingDays != null) {
|
||||||
|
result.remainingDays = remainingDays;
|
||||||
|
}
|
||||||
|
success(res, result);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
failed(
|
failed(
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ async function produceArtifact({
|
|||||||
subscription,
|
subscription,
|
||||||
awaitCustomCache,
|
awaitCustomCache,
|
||||||
$options,
|
$options,
|
||||||
|
proxy,
|
||||||
|
noCache,
|
||||||
}) {
|
}) {
|
||||||
platform = platform || 'JSON';
|
platform = platform || 'JSON';
|
||||||
|
|
||||||
@@ -68,9 +70,10 @@ async function produceArtifact({
|
|||||||
url,
|
url,
|
||||||
ua || sub.ua,
|
ua || sub.ua,
|
||||||
undefined,
|
undefined,
|
||||||
sub.proxy,
|
proxy || sub.proxy,
|
||||||
undefined,
|
undefined,
|
||||||
awaitCustomCache,
|
awaitCustomCache,
|
||||||
|
noCache,
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errors[url] = err;
|
errors[url] = err;
|
||||||
@@ -115,9 +118,10 @@ async function produceArtifact({
|
|||||||
url,
|
url,
|
||||||
ua || sub.ua,
|
ua || sub.ua,
|
||||||
undefined,
|
undefined,
|
||||||
sub.proxy,
|
proxy || sub.proxy,
|
||||||
undefined,
|
undefined,
|
||||||
awaitCustomCache,
|
awaitCustomCache,
|
||||||
|
noCache,
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errors[url] = err;
|
errors[url] = err;
|
||||||
@@ -189,7 +193,20 @@ async function produceArtifact({
|
|||||||
const allCols = $.read(COLLECTIONS_KEY);
|
const allCols = $.read(COLLECTIONS_KEY);
|
||||||
const collection = findByName(allCols, name);
|
const collection = findByName(allCols, name);
|
||||||
if (!collection) throw new Error(`找不到组合订阅 ${name}`);
|
if (!collection) throw new Error(`找不到组合订阅 ${name}`);
|
||||||
const subnames = collection.subscriptions;
|
const subnames = [...collection.subscriptions];
|
||||||
|
let subscriptionTags = collection.subscriptionTags;
|
||||||
|
if (Array.isArray(subscriptionTags) && subscriptionTags.length > 0) {
|
||||||
|
allSubs.forEach((sub) => {
|
||||||
|
if (
|
||||||
|
Array.isArray(sub.tag) &&
|
||||||
|
sub.tag.length > 0 &&
|
||||||
|
!subnames.includes(sub.name) &&
|
||||||
|
sub.tag.some((tag) => subscriptionTags.includes(tag))
|
||||||
|
) {
|
||||||
|
subnames.push(sub.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
const results = {};
|
const results = {};
|
||||||
const errors = {};
|
const errors = {};
|
||||||
let processed = 0;
|
let processed = 0;
|
||||||
@@ -220,7 +237,12 @@ async function produceArtifact({
|
|||||||
url,
|
url,
|
||||||
sub.ua,
|
sub.ua,
|
||||||
undefined,
|
undefined,
|
||||||
sub.proxy,
|
proxy ||
|
||||||
|
sub.proxy ||
|
||||||
|
collection.proxy,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
noCache,
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errors[url] = err;
|
errors[url] = err;
|
||||||
@@ -344,7 +366,6 @@ async function produceArtifact({
|
|||||||
}
|
}
|
||||||
exist[proxy.name] = true;
|
exist[proxy.name] = true;
|
||||||
}
|
}
|
||||||
console.log(proxies);
|
|
||||||
return ProxyUtils.produce(proxies, platform, produceType, produceOpts);
|
return ProxyUtils.produce(proxies, platform, produceType, produceOpts);
|
||||||
} else if (type === 'rule') {
|
} else if (type === 'rule') {
|
||||||
const allRules = $.read(RULES_KEY);
|
const allRules = $.read(RULES_KEY);
|
||||||
@@ -390,7 +411,15 @@ async function produceArtifact({
|
|||||||
.filter((i) => i.length)
|
.filter((i) => i.length)
|
||||||
.map(async (url) => {
|
.map(async (url) => {
|
||||||
try {
|
try {
|
||||||
return await download(url, ua || file.ua);
|
return await download(
|
||||||
|
url,
|
||||||
|
ua || file.ua,
|
||||||
|
undefined,
|
||||||
|
file.proxy || proxy,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
noCache,
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errors[url] = err;
|
errors[url] = err;
|
||||||
$.error(
|
$.error(
|
||||||
@@ -433,7 +462,15 @@ async function produceArtifact({
|
|||||||
.filter((i) => i.length)
|
.filter((i) => i.length)
|
||||||
.map(async (url) => {
|
.map(async (url) => {
|
||||||
try {
|
try {
|
||||||
return await download(url, ua || file.ua);
|
return await download(
|
||||||
|
url,
|
||||||
|
ua || file.ua,
|
||||||
|
undefined,
|
||||||
|
file.proxy || proxy,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
noCache,
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errors[url] = err;
|
errors[url] = err;
|
||||||
$.error(
|
$.error(
|
||||||
@@ -534,6 +571,16 @@ async function syncArtifacts() {
|
|||||||
try {
|
try {
|
||||||
if (artifact.sync && artifact.source) {
|
if (artifact.sync && artifact.source) {
|
||||||
$.info(`正在同步云配置:${artifact.name}...`);
|
$.info(`正在同步云配置:${artifact.name}...`);
|
||||||
|
|
||||||
|
const useMihomoExternal =
|
||||||
|
artifact.platform === 'SurgeMac';
|
||||||
|
|
||||||
|
if (useMihomoExternal) {
|
||||||
|
$.info(
|
||||||
|
`手动指定了 target 为 SurgeMac, 将使用 Mihomo External`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const output = await produceArtifact({
|
const output = await produceArtifact({
|
||||||
type: artifact.type,
|
type: artifact.type,
|
||||||
name: artifact.source,
|
name: artifact.source,
|
||||||
@@ -541,6 +588,7 @@ async function syncArtifacts() {
|
|||||||
produceOpts: {
|
produceOpts: {
|
||||||
'include-unsupported-proxy':
|
'include-unsupported-proxy':
|
||||||
artifact.includeUnsupportedProxy,
|
artifact.includeUnsupportedProxy,
|
||||||
|
useMihomoExternal,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -656,12 +704,18 @@ async function syncArtifact(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const useMihomoExternal = artifact.platform === 'SurgeMac';
|
||||||
|
|
||||||
|
if (useMihomoExternal) {
|
||||||
|
$.info(`手动指定了 target 为 SurgeMac, 将使用 Mihomo External`);
|
||||||
|
}
|
||||||
const output = await produceArtifact({
|
const output = await produceArtifact({
|
||||||
type: artifact.type,
|
type: artifact.type,
|
||||||
name: artifact.source,
|
name: artifact.source,
|
||||||
platform: artifact.platform,
|
platform: artifact.platform,
|
||||||
produceOpts: {
|
produceOpts: {
|
||||||
'include-unsupported-proxy': artifact.includeUnsupportedProxy,
|
'include-unsupported-proxy': artifact.includeUnsupportedProxy,
|
||||||
|
useMihomoExternal,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
181
backend/src/restful/token.js
Normal file
181
backend/src/restful/token.js
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
import { deleteByName } from '@/utils/database';
|
||||||
|
import { ENV } from '@/vendor/open-api';
|
||||||
|
import { TOKENS_KEY, SUBS_KEY, FILES_KEY, COLLECTIONS_KEY } from '@/constants';
|
||||||
|
import { failed, success } from '@/restful/response';
|
||||||
|
import $ from '@/core/app';
|
||||||
|
import { RequestInvalidError, InternalServerError } from '@/restful/errors';
|
||||||
|
|
||||||
|
export default function register($app) {
|
||||||
|
if (!$.read(TOKENS_KEY)) $.write([], TOKENS_KEY);
|
||||||
|
|
||||||
|
$app.post('/api/token', signToken);
|
||||||
|
|
||||||
|
$app.route('/api/token/:token').delete(deleteToken);
|
||||||
|
|
||||||
|
$app.route('/api/tokens').get(getAllTokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteToken(req, res) {
|
||||||
|
let { token } = req.params;
|
||||||
|
token = decodeURIComponent(token);
|
||||||
|
$.info(`正在删除:${token}`);
|
||||||
|
let allTokens = $.read(TOKENS_KEY);
|
||||||
|
deleteByName(allTokens, token, 'token');
|
||||||
|
$.write(allTokens, TOKENS_KEY);
|
||||||
|
success(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllTokens(req, res) {
|
||||||
|
const { type, name } = req.query;
|
||||||
|
const allTokens = $.read(TOKENS_KEY) || [];
|
||||||
|
success(
|
||||||
|
res,
|
||||||
|
type || name
|
||||||
|
? allTokens.filter(
|
||||||
|
(item) =>
|
||||||
|
(type ? item.type === type : true) &&
|
||||||
|
(name ? item.name === name : true),
|
||||||
|
)
|
||||||
|
: allTokens,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function signToken(req, res) {
|
||||||
|
if (!ENV().isNode) {
|
||||||
|
return failed(
|
||||||
|
res,
|
||||||
|
new RequestInvalidError(
|
||||||
|
'INVALID_ENV',
|
||||||
|
`This endpoint is only available in Node.js environment`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { payload, options } = req.body;
|
||||||
|
const ms = eval(`require("ms")`);
|
||||||
|
let token = payload?.token;
|
||||||
|
if (token != null) {
|
||||||
|
if (typeof token !== 'string' || token.length < 1) {
|
||||||
|
return failed(
|
||||||
|
res,
|
||||||
|
new RequestInvalidError(
|
||||||
|
'INVALID_CUSTOM_TOKEN',
|
||||||
|
`Invalid custom token: ${token}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const tokens = $.read(TOKENS_KEY) || [];
|
||||||
|
if (tokens.find((t) => t.token === token)) {
|
||||||
|
return failed(
|
||||||
|
res,
|
||||||
|
new RequestInvalidError(
|
||||||
|
'DUPLICATE_TOKEN',
|
||||||
|
`Token ${token} already exists`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const type = payload?.type;
|
||||||
|
const name = payload?.name;
|
||||||
|
if (!type || !name)
|
||||||
|
return failed(
|
||||||
|
res,
|
||||||
|
new RequestInvalidError(
|
||||||
|
'INVALID_PAYLOAD',
|
||||||
|
`payload type and name are required`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (type === 'col') {
|
||||||
|
const collections = $.read(COLLECTIONS_KEY) || [];
|
||||||
|
const collection = collections.find((c) => c.name === name);
|
||||||
|
if (!collection)
|
||||||
|
return failed(
|
||||||
|
res,
|
||||||
|
new RequestInvalidError(
|
||||||
|
'INVALID_COLLECTION',
|
||||||
|
`collection ${name} not found`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (type === 'file') {
|
||||||
|
const files = $.read(FILES_KEY) || [];
|
||||||
|
const file = files.find((f) => f.name === name);
|
||||||
|
if (!file)
|
||||||
|
return failed(
|
||||||
|
res,
|
||||||
|
new RequestInvalidError(
|
||||||
|
'INVALID_FILE',
|
||||||
|
`file ${name} not found`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (type === 'sub') {
|
||||||
|
const subs = $.read(SUBS_KEY) || [];
|
||||||
|
const sub = subs.find((s) => s.name === name);
|
||||||
|
if (!sub)
|
||||||
|
return failed(
|
||||||
|
res,
|
||||||
|
new RequestInvalidError(
|
||||||
|
'INVALID_SUB',
|
||||||
|
`sub ${name} not found`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return failed(
|
||||||
|
res,
|
||||||
|
new RequestInvalidError(
|
||||||
|
'INVALID_TYPE',
|
||||||
|
`type ${name} not supported`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let expiresIn = options?.expiresIn;
|
||||||
|
if (options?.expiresIn != null) {
|
||||||
|
expiresIn = ms(options.expiresIn);
|
||||||
|
if (expiresIn == null || isNaN(expiresIn) || expiresIn <= 0) {
|
||||||
|
return failed(
|
||||||
|
res,
|
||||||
|
new RequestInvalidError(
|
||||||
|
'INVALID_EXPIRES_IN',
|
||||||
|
`Invalid expiresIn option: ${options.expiresIn}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// const secret = eval('process.env.SUB_STORE_FRONTEND_BACKEND_PATH');
|
||||||
|
const nanoid = eval(`require("nanoid")`);
|
||||||
|
const tokens = $.read(TOKENS_KEY) || [];
|
||||||
|
// const now = Date.now();
|
||||||
|
// for (const key in tokens) {
|
||||||
|
// const token = tokens[key];
|
||||||
|
// if (token.exp != null || token.exp < now) {
|
||||||
|
// delete tokens[key];
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
if (!token) {
|
||||||
|
do {
|
||||||
|
token = nanoid.customAlphabet(nanoid.urlAlphabet)();
|
||||||
|
} while (tokens.find((t) => t.token === token));
|
||||||
|
}
|
||||||
|
tokens.push({
|
||||||
|
...payload,
|
||||||
|
token,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
expiresIn: expiresIn > 0 ? options?.expiresIn : undefined,
|
||||||
|
exp: expiresIn > 0 ? Date.now() + expiresIn : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
$.write(tokens, TOKENS_KEY);
|
||||||
|
return success(res, {
|
||||||
|
token,
|
||||||
|
// secret,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return failed(
|
||||||
|
res,
|
||||||
|
new InternalServerError(
|
||||||
|
'TOKEN_SIGN_FAILED',
|
||||||
|
`Failed to sign token`,
|
||||||
|
`Reason: ${e.message ?? e}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
export function findByName(list, name) {
|
export function findByName(list, name, field = 'name') {
|
||||||
return list.find((item) => item.name === name);
|
return list.find((item) => item[field] === name);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findIndexByName(list, name) {
|
export function findIndexByName(list, name, field = 'name') {
|
||||||
return list.findIndex((item) => item.name === name);
|
return list.findIndex((item) => item[field] === name);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteByName(list, name) {
|
export function deleteByName(list, name, field = 'name') {
|
||||||
const idx = findIndexByName(list, name);
|
const idx = findIndexByName(list, name, field);
|
||||||
list.splice(idx, 1);
|
list.splice(idx, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateByName(list, name, newItem) {
|
export function updateByName(list, name, newItem, field = 'name') {
|
||||||
const idx = findIndexByName(list, name);
|
const idx = findIndexByName(list, name, field);
|
||||||
list[idx] = newItem;
|
list[idx] = newItem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,9 +18,10 @@ export default async function download(
|
|||||||
rawUrl = '',
|
rawUrl = '',
|
||||||
ua,
|
ua,
|
||||||
timeout,
|
timeout,
|
||||||
proxy,
|
customProxy,
|
||||||
skipCustomCache,
|
skipCustomCache,
|
||||||
awaitCustomCache,
|
awaitCustomCache,
|
||||||
|
noCache,
|
||||||
) {
|
) {
|
||||||
let $arguments = {};
|
let $arguments = {};
|
||||||
let url = rawUrl.replace(/#noFlow$/, '');
|
let url = rawUrl.replace(/#noFlow$/, '');
|
||||||
@@ -43,8 +44,12 @@ export default async function download(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const { isNode, isStash, isLoon, isShadowRocket, isQX } = ENV();
|
const { isNode, isStash, isLoon, isShadowRocket, isQX } = ENV();
|
||||||
const { defaultUserAgent, defaultTimeout, cacheThreshold } =
|
const { defaultProxy, defaultUserAgent, defaultTimeout, cacheThreshold } =
|
||||||
$.read(SETTINGS_KEY);
|
$.read(SETTINGS_KEY);
|
||||||
|
let proxy = customProxy || defaultProxy;
|
||||||
|
if ($.env.isNode) {
|
||||||
|
proxy = proxy || eval('process.env.SUB_STORE_BACKEND_DEFAULT_PROXY');
|
||||||
|
}
|
||||||
const userAgent = ua || defaultUserAgent || 'clash.meta';
|
const userAgent = ua || defaultUserAgent || 'clash.meta';
|
||||||
const requestTimeout = timeout || defaultTimeout;
|
const requestTimeout = timeout || defaultTimeout;
|
||||||
const id = hex_md5(userAgent + url);
|
const id = hex_md5(userAgent + url);
|
||||||
@@ -61,7 +66,7 @@ export default async function download(
|
|||||||
if (customCacheKey && !skipCustomCache) {
|
if (customCacheKey && !skipCustomCache) {
|
||||||
const customCached = $.read(customCacheKey);
|
const customCached = $.read(customCacheKey);
|
||||||
const cached = resourceCache.get(id);
|
const cached = resourceCache.get(id);
|
||||||
if (!$arguments?.noCache && cached) {
|
if (!noCache && !$arguments?.noCache && cached) {
|
||||||
$.info(
|
$.info(
|
||||||
`乐观缓存: URL ${url}\n存在有效的常规缓存\n使用常规缓存以避免重复请求`,
|
`乐观缓存: URL ${url}\n存在有效的常规缓存\n使用常规缓存以避免重复请求`,
|
||||||
);
|
);
|
||||||
@@ -145,7 +150,7 @@ export default async function download(
|
|||||||
|
|
||||||
// try to find in app cache
|
// try to find in app cache
|
||||||
const cached = resourceCache.get(id);
|
const cached = resourceCache.get(id);
|
||||||
if (!$arguments?.noCache && cached) {
|
if (!noCache && !$arguments?.noCache && cached) {
|
||||||
$.info(`使用缓存: ${url}`);
|
$.info(`使用缓存: ${url}`);
|
||||||
result = cached;
|
result = cached;
|
||||||
if (customCacheKey) {
|
if (customCacheKey) {
|
||||||
@@ -153,8 +158,13 @@ export default async function download(
|
|||||||
$.write(cached, customCacheKey);
|
$.write(cached, customCacheKey);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
const insecure = $arguments?.insecure
|
||||||
|
? isNode
|
||||||
|
? { strictSSL: false }
|
||||||
|
: { insecure: true }
|
||||||
|
: undefined;
|
||||||
$.info(
|
$.info(
|
||||||
`Downloading...\nUser-Agent: ${userAgent}\nTimeout: ${requestTimeout}\nProxy: ${proxy}\nURL: ${url}`,
|
`Downloading...\nUser-Agent: ${userAgent}\nTimeout: ${requestTimeout}\nProxy: ${proxy}\nInsecure: ${!!insecure}\nURL: ${url}`,
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
const { body, headers } = await http.get({
|
const { body, headers } = await http.get({
|
||||||
@@ -163,6 +173,7 @@ export default async function download(
|
|||||||
...(isLoon && proxy ? { node: proxy } : {}),
|
...(isLoon && proxy ? { node: proxy } : {}),
|
||||||
...(isQX && proxy ? { opts: { policy: proxy } } : {}),
|
...(isQX && proxy ? { opts: { policy: proxy } } : {}),
|
||||||
...(proxy ? getPolicyDescriptor(proxy) : {}),
|
...(proxy ? getPolicyDescriptor(proxy) : {}),
|
||||||
|
...(insecure ? insecure : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (headers) {
|
if (headers) {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ if (isLanceX) backend = 'LanceX';
|
|||||||
if (isGUIforCores) backend = 'GUI.for.Cores';
|
if (isGUIforCores) backend = 'GUI.for.Cores';
|
||||||
|
|
||||||
let meta = {};
|
let meta = {};
|
||||||
|
let feature = {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (typeof $environment !== 'undefined') {
|
if (typeof $environment !== 'undefined') {
|
||||||
@@ -63,5 +64,6 @@ try {
|
|||||||
export default {
|
export default {
|
||||||
backend,
|
backend,
|
||||||
version: substoreVersion,
|
version: substoreVersion,
|
||||||
|
feature,
|
||||||
meta,
|
meta,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,13 @@ export function getFlowField(headers) {
|
|||||||
)[0];
|
)[0];
|
||||||
return headers[subkey];
|
return headers[subkey];
|
||||||
}
|
}
|
||||||
export async function getFlowHeaders(rawUrl, ua, timeout, proxy, flowUrl) {
|
export async function getFlowHeaders(
|
||||||
|
rawUrl,
|
||||||
|
ua,
|
||||||
|
timeout,
|
||||||
|
customProxy,
|
||||||
|
flowUrl,
|
||||||
|
) {
|
||||||
let url = flowUrl || rawUrl || '';
|
let url = flowUrl || rawUrl || '';
|
||||||
let $arguments = {};
|
let $arguments = {};
|
||||||
const rawArgs = url.split('#');
|
const rawArgs = url.split('#');
|
||||||
@@ -41,7 +47,18 @@ export async function getFlowHeaders(rawUrl, ua, timeout, proxy, flowUrl) {
|
|||||||
// $.info(`使用缓存的流量信息: ${url}`);
|
// $.info(`使用缓存的流量信息: ${url}`);
|
||||||
flowInfo = cached;
|
flowInfo = cached;
|
||||||
} else {
|
} else {
|
||||||
const { defaultFlowUserAgent, defaultTimeout } = $.read(SETTINGS_KEY);
|
const insecure = $arguments?.insecure
|
||||||
|
? $.env.isNode
|
||||||
|
? { strictSSL: false }
|
||||||
|
: { insecure: true }
|
||||||
|
: undefined;
|
||||||
|
const { defaultProxy, defaultFlowUserAgent, defaultTimeout } =
|
||||||
|
$.read(SETTINGS_KEY);
|
||||||
|
let proxy = customProxy || defaultProxy;
|
||||||
|
if ($.env.isNode) {
|
||||||
|
proxy =
|
||||||
|
proxy || eval('process.env.SUB_STORE_BACKEND_DEFAULT_PROXY');
|
||||||
|
}
|
||||||
const userAgent =
|
const userAgent =
|
||||||
ua ||
|
ua ||
|
||||||
defaultFlowUserAgent ||
|
defaultFlowUserAgent ||
|
||||||
@@ -52,7 +69,7 @@ export async function getFlowHeaders(rawUrl, ua, timeout, proxy, flowUrl) {
|
|||||||
$.info(
|
$.info(
|
||||||
`使用 GET 方法从响应体获取流量信息: ${flowUrl}, User-Agent: ${
|
`使用 GET 方法从响应体获取流量信息: ${flowUrl}, User-Agent: ${
|
||||||
userAgent || ''
|
userAgent || ''
|
||||||
}`,
|
}, Insecure: ${!!insecure}, Proxy: ${proxy}`,
|
||||||
);
|
);
|
||||||
const { body } = await http.get({
|
const { body } = await http.get({
|
||||||
url: flowUrl,
|
url: flowUrl,
|
||||||
@@ -60,6 +77,11 @@ export async function getFlowHeaders(rawUrl, ua, timeout, proxy, flowUrl) {
|
|||||||
'User-Agent': userAgent,
|
'User-Agent': userAgent,
|
||||||
},
|
},
|
||||||
timeout: requestTimeout,
|
timeout: requestTimeout,
|
||||||
|
...(proxy ? { proxy } : {}),
|
||||||
|
...(isLoon && proxy ? { node: proxy } : {}),
|
||||||
|
...(isQX && proxy ? { opts: { policy: proxy } } : {}),
|
||||||
|
...(proxy ? getPolicyDescriptor(proxy) : {}),
|
||||||
|
...(insecure ? insecure : {}),
|
||||||
});
|
});
|
||||||
flowInfo = body;
|
flowInfo = body;
|
||||||
} else {
|
} else {
|
||||||
@@ -67,7 +89,7 @@ export async function getFlowHeaders(rawUrl, ua, timeout, proxy, flowUrl) {
|
|||||||
$.info(
|
$.info(
|
||||||
`使用 HEAD 方法从响应头获取流量信息: ${url}, User-Agent: ${
|
`使用 HEAD 方法从响应头获取流量信息: ${url}, User-Agent: ${
|
||||||
userAgent || ''
|
userAgent || ''
|
||||||
}`,
|
}, Insecure: ${!!insecure}, Proxy: ${proxy}`,
|
||||||
);
|
);
|
||||||
const { headers } = await http.head({
|
const { headers } = await http.head({
|
||||||
url: url
|
url: url
|
||||||
@@ -91,20 +113,23 @@ export async function getFlowHeaders(rawUrl, ua, timeout, proxy, flowUrl) {
|
|||||||
...(isLoon && proxy ? { node: proxy } : {}),
|
...(isLoon && proxy ? { node: proxy } : {}),
|
||||||
...(isQX && proxy ? { opts: { policy: proxy } } : {}),
|
...(isQX && proxy ? { opts: { policy: proxy } } : {}),
|
||||||
...(proxy ? getPolicyDescriptor(proxy) : {}),
|
...(proxy ? getPolicyDescriptor(proxy) : {}),
|
||||||
|
...(insecure ? insecure : {}),
|
||||||
});
|
});
|
||||||
flowInfo = getFlowField(headers);
|
flowInfo = getFlowField(headers);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
$.error(
|
$.error(
|
||||||
`使用 HEAD 方法从响应头获取流量信息失败: ${url}, User-Agent: ${
|
`使用 HEAD 方法从响应头获取流量信息失败: ${url}, User-Agent: ${
|
||||||
userAgent || ''
|
userAgent || ''
|
||||||
}: ${e.message ?? e}`,
|
}, Insecure: ${!!insecure}, Proxy: ${proxy}: ${
|
||||||
|
e.message ?? e
|
||||||
|
}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!flowInfo) {
|
if (!flowInfo) {
|
||||||
$.info(
|
$.info(
|
||||||
`使用 GET 方法获取流量信息: ${url}, User-Agent: ${
|
`使用 GET 方法获取流量信息: ${url}, User-Agent: ${
|
||||||
userAgent || ''
|
userAgent || ''
|
||||||
}`,
|
}, Insecure: ${!!insecure}, Proxy: ${proxy}`,
|
||||||
);
|
);
|
||||||
const { headers } = await http.get({
|
const { headers } = await http.get({
|
||||||
url: url
|
url: url
|
||||||
@@ -113,8 +138,22 @@ export async function getFlowHeaders(rawUrl, ua, timeout, proxy, flowUrl) {
|
|||||||
.filter((i) => i.length)[0],
|
.filter((i) => i.length)[0],
|
||||||
headers: {
|
headers: {
|
||||||
'User-Agent': userAgent,
|
'User-Agent': userAgent,
|
||||||
|
...(isStash && proxy
|
||||||
|
? {
|
||||||
|
'X-Stash-Selected-Proxy':
|
||||||
|
encodeURIComponent(proxy),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
...(isShadowRocket && proxy
|
||||||
|
? { 'X-Surge-Policy': proxy }
|
||||||
|
: {}),
|
||||||
},
|
},
|
||||||
timeout: requestTimeout,
|
timeout: requestTimeout,
|
||||||
|
...(proxy ? { proxy } : {}),
|
||||||
|
...(isLoon && proxy ? { node: proxy } : {}),
|
||||||
|
...(isQX && proxy ? { opts: { policy: proxy } } : {}),
|
||||||
|
...(proxy ? getPolicyDescriptor(proxy) : {}),
|
||||||
|
...(insecure ? insecure : {}),
|
||||||
});
|
});
|
||||||
flowInfo = getFlowField(headers);
|
flowInfo = getFlowField(headers);
|
||||||
}
|
}
|
||||||
@@ -151,8 +190,29 @@ export function parseFlowHeaders(flowHeaders) {
|
|||||||
? Number(expireMatch[1] + expireMatch[2])
|
? Number(expireMatch[1] + expireMatch[2])
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
return { expires, total, usage: { upload, download } };
|
const remainingDaysMatch = flowHeaders.match(/reset_day=([0-9]+)/);
|
||||||
|
const remainingDays = remainingDaysMatch
|
||||||
|
? Number(remainingDaysMatch[1])
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const appUrlMatch = flowHeaders.match(/app_url=(.*?)\s*?(;|$)/);
|
||||||
|
const appUrl = appUrlMatch ? appUrlMatch[1] : undefined;
|
||||||
|
|
||||||
|
const planNameMatch = flowHeaders.match(/plan_name=(.*?)\s*?(;|$)/);
|
||||||
|
const planName = planNameMatch
|
||||||
|
? decodeURIComponent(planNameMatch[1])
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
expires,
|
||||||
|
total,
|
||||||
|
usage: { upload, download },
|
||||||
|
remainingDays,
|
||||||
|
appUrl,
|
||||||
|
planName,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function flowTransfer(flow, unit = 'B') {
|
export function flowTransfer(flow, unit = 'B') {
|
||||||
const unitList = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
const unitList = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||||
let unitIndex = unitList.indexOf(unit);
|
let unitIndex = unitList.indexOf(unit);
|
||||||
|
|||||||
@@ -474,4 +474,7 @@ export class MMDB {
|
|||||||
ipaso(ip) {
|
ipaso(ip) {
|
||||||
return this.asnReader?.asn(ip)?.autonomousSystemOrganization;
|
return this.asnReader?.asn(ip)?.autonomousSystemOrganization;
|
||||||
}
|
}
|
||||||
|
ipasn(ip) {
|
||||||
|
return this.asnReader?.asn(ip)?.autonomousSystemNumber;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,21 @@
|
|||||||
import { HTTP } from '@/vendor/open-api';
|
import { HTTP, ENV } from '@/vendor/open-api';
|
||||||
|
import { getPolicyDescriptor } from '@/utils';
|
||||||
|
import $ from '@/core/app';
|
||||||
|
import { SETTINGS_KEY } from '@/constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gist backup
|
* Gist backup
|
||||||
*/
|
*/
|
||||||
export default class Gist {
|
export default class Gist {
|
||||||
constructor({ token, key, syncPlatform }) {
|
constructor({ token, key, syncPlatform }) {
|
||||||
|
const { isStash, isLoon, isShadowRocket, isQX } = ENV();
|
||||||
|
const { defaultProxy, defaultTimeout: timeout } = $.read(SETTINGS_KEY);
|
||||||
|
let proxy = defaultProxy;
|
||||||
|
if ($.env.isNode) {
|
||||||
|
proxy =
|
||||||
|
proxy || eval('process.env.SUB_STORE_BACKEND_DEFAULT_PROXY');
|
||||||
|
}
|
||||||
|
|
||||||
if (syncPlatform === 'gitlab') {
|
if (syncPlatform === 'gitlab') {
|
||||||
this.headers = {
|
this.headers = {
|
||||||
'PRIVATE-TOKEN': `${token}`,
|
'PRIVATE-TOKEN': `${token}`,
|
||||||
@@ -13,7 +24,25 @@ export default class Gist {
|
|||||||
};
|
};
|
||||||
this.http = HTTP({
|
this.http = HTTP({
|
||||||
baseURL: 'https://gitlab.com/api/v4',
|
baseURL: 'https://gitlab.com/api/v4',
|
||||||
headers: { ...this.headers },
|
headers: {
|
||||||
|
...this.headers,
|
||||||
|
...(isStash && proxy
|
||||||
|
? {
|
||||||
|
'X-Stash-Selected-Proxy':
|
||||||
|
encodeURIComponent(proxy),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
...(isShadowRocket && proxy
|
||||||
|
? { 'X-Surge-Policy': proxy }
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
|
||||||
|
...(proxy ? { proxy } : {}),
|
||||||
|
...(isLoon && proxy ? { node: proxy } : {}),
|
||||||
|
...(isQX && proxy ? { opts: { policy: proxy } } : {}),
|
||||||
|
...(proxy ? getPolicyDescriptor(proxy) : {}),
|
||||||
|
timeout,
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
onResponse: (resp) => {
|
onResponse: (resp) => {
|
||||||
if (/^[45]/.test(String(resp.statusCode))) {
|
if (/^[45]/.test(String(resp.statusCode))) {
|
||||||
@@ -35,7 +64,25 @@ export default class Gist {
|
|||||||
};
|
};
|
||||||
this.http = HTTP({
|
this.http = HTTP({
|
||||||
baseURL: 'https://api.github.com',
|
baseURL: 'https://api.github.com',
|
||||||
headers: { ...this.headers },
|
headers: {
|
||||||
|
...this.headers,
|
||||||
|
...(isStash && proxy
|
||||||
|
? {
|
||||||
|
'X-Stash-Selected-Proxy':
|
||||||
|
encodeURIComponent(proxy),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
...(isShadowRocket && proxy
|
||||||
|
? { 'X-Surge-Policy': proxy }
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
|
||||||
|
...(proxy ? { proxy } : {}),
|
||||||
|
...(isLoon && proxy ? { node: proxy } : {}),
|
||||||
|
...(isQX && proxy ? { opts: { policy: proxy } } : {}),
|
||||||
|
...(proxy ? getPolicyDescriptor(proxy) : {}),
|
||||||
|
timeout,
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
onResponse: (resp) => {
|
onResponse: (resp) => {
|
||||||
if (/^[45]/.test(String(resp.statusCode))) {
|
if (/^[45]/.test(String(resp.statusCode))) {
|
||||||
|
|||||||
@@ -17,16 +17,16 @@ function retry(fn, content, ...args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function safeLoad(content, ...args) {
|
export function safeLoad(content, ...args) {
|
||||||
return retry(YAML.safeLoad, content, ...args);
|
return retry(YAML.safeLoad, JSON.parse(JSON.stringify(content)), ...args);
|
||||||
}
|
}
|
||||||
export function load(content, ...args) {
|
export function load(content, ...args) {
|
||||||
return retry(YAML.load, content, ...args);
|
return retry(YAML.load, JSON.parse(JSON.stringify(content)), ...args);
|
||||||
}
|
}
|
||||||
export function safeDump(...args) {
|
export function safeDump(content, ...args) {
|
||||||
return YAML.safeDump(...args);
|
return YAML.safeDump(JSON.parse(JSON.stringify(content)), ...args);
|
||||||
}
|
}
|
||||||
export function dump(...args) {
|
export function dump(content, ...args) {
|
||||||
return YAML.dump(...args);
|
return YAML.dump(JSON.parse(JSON.stringify(content)), ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
// 10. `sni` 在某些协议里会自动与 `servername` 转换
|
// 10. `sni` 在某些协议里会自动与 `servername` 转换
|
||||||
// 11. 读取节点的 ca-str 和 _ca (后端文件路径) 字段, 自动计算 fingerprint (参考 https://t.me/zhetengsha/1512)
|
// 11. 读取节点的 ca-str 和 _ca (后端文件路径) 字段, 自动计算 fingerprint (参考 https://t.me/zhetengsha/1512)
|
||||||
// 12. 以 Surge 为例, 最新的参数一般我都会跟进, 以 Surge 文档为例, 一些常用的: TUIC/Hysteria 2 的 `ecn`, Snell 的 `reuse` 连接复用, QUIC 策略 block-quic`, Hysteria 2 下载带宽 `down`
|
// 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. 会进行内部转换, 若无法匹配则使用原始值
|
||||||
|
|
||||||
// require 为 Node.js 的 require, 在 Node.js 运行环境下 可以用来引入模块
|
// require 为 Node.js 的 require, 在 Node.js 运行环境下 可以用来引入模块
|
||||||
|
|
||||||
@@ -63,6 +66,8 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
// removeFlag, // 移除 emoji 旗帜
|
// removeFlag, // 移除 emoji 旗帜
|
||||||
// getISO, // 获取 ISO 3166-1 alpha-2 代码
|
// getISO, // 获取 ISO 3166-1 alpha-2 代码
|
||||||
// Gist, // Gist 类
|
// Gist, // Gist 类
|
||||||
|
// download, // 内部的下载方法, 见 backend/src/utils/download.js
|
||||||
|
// MMDB, // Node.js 环境 可用于模拟 Surge/Loon 的 $utils.ipasn, $utils.ipaso, $utils.geoip. 具体见 https://t.me/zhetengsha/1269
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// 如果只是为了快速修改或者筛选 可以参考 脚本操作支持节点快捷脚本 https://t.me/zhetengsha/970 和 脚本筛选支持节点快捷脚本 https://t.me/zhetengsha/1009
|
// 如果只是为了快速修改或者筛选 可以参考 脚本操作支持节点快捷脚本 https://t.me/zhetengsha/970 和 脚本筛选支持节点快捷脚本 https://t.me/zhetengsha/1009
|
||||||
|
|||||||
Reference in New Issue
Block a user