mirror of
https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-10 00:52:40 +00:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1874e510d | ||
|
|
48aaaf5c99 | ||
|
|
7385e17a4c | ||
|
|
c3daea55ab | ||
|
|
fc9ff48b1f | ||
|
|
fb21890b68 | ||
|
|
2155cc9639 | ||
|
|
03e320cbd0 | ||
|
|
e325b9a39a | ||
|
|
87597f6fc2 | ||
|
|
3462d36c35 | ||
|
|
02946ec81c | ||
|
|
c963c872ff | ||
|
|
c4a1bb4ea1 | ||
|
|
f96d9dea74 | ||
|
|
01eb69d8ae | ||
|
|
797ba6f601 | ||
|
|
128353a7f3 | ||
|
|
e6f6d51608 | ||
|
|
589a6bfadb | ||
|
|
75012503f8 | ||
|
|
85a3e2ee54 | ||
|
|
95b7557635 | ||
|
|
14ca62db4a | ||
|
|
a2a754adb7 | ||
|
|
6b23f82953 | ||
|
|
e071a7f253 | ||
|
|
b9bba895e1 | ||
|
|
8090d678ee | ||
|
|
ff4be7ac38 | ||
|
|
7e2109dc68 | ||
|
|
278beae99a | ||
|
|
3aedd5943d | ||
|
|
222551eb20 | ||
|
|
0d5e1ab38b |
19
.github/workflows/main.yml
vendored
19
.github/workflows/main.yml
vendored
@@ -1,5 +1,6 @@
|
|||||||
name: build
|
name: build
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
@@ -26,18 +27,18 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
npm install -g pnpm
|
npm install -g pnpm
|
||||||
cd backend && pnpm i --no-frozen-lockfile
|
cd backend && pnpm i --no-frozen-lockfile
|
||||||
- name: Test
|
# - name: Test
|
||||||
run: |
|
# run: |
|
||||||
cd backend
|
# cd backend
|
||||||
pnpm test
|
# pnpm test
|
||||||
- name: Build
|
# - name: Build
|
||||||
run: |
|
# run: |
|
||||||
cd backend
|
# cd backend
|
||||||
pnpm run build
|
# pnpm run build
|
||||||
- name: Bundle
|
- name: Bundle
|
||||||
run: |
|
run: |
|
||||||
cd backend
|
cd backend
|
||||||
pnpm run bundle
|
pnpm bundle:esbuild
|
||||||
- id: tag
|
- id: tag
|
||||||
name: Generate release tag
|
name: Generate release tag
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ Core functionalities:
|
|||||||
|
|
||||||
> ⚠️ Do not use `Shadowrocket` to export URI and then import it as input. It is not a standard URI.
|
> ⚠️ Do not use `Shadowrocket` to export URI and then import it as input. It is not a standard URI.
|
||||||
|
|
||||||
- [x] Normal Proxy(`socks5`, `socks5+tls`, `http`, `https`(it's ok))
|
- [x] Proxy URI Scheme(`socks5`, `socks5+tls`, `http`, `https`(it's ok))
|
||||||
|
|
||||||
example: `socks5+tls://user:pass@ip:port#name`
|
example: `socks5+tls://user:pass@ip:port#name`
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sub-store",
|
"name": "sub-store",
|
||||||
"version": "2.15.1",
|
"version": "2.16.19",
|
||||||
"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": {
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
"dev:run": "nodemon -w sub-store.min.js sub-store.min.js",
|
"dev:run": "nodemon -w sub-store.min.js sub-store.min.js",
|
||||||
"build": "gulp",
|
"build": "gulp",
|
||||||
"bundle": "node bundle.js",
|
"bundle": "node bundle.js",
|
||||||
|
"bundle:esbuild": "node bundle-esbuild.js",
|
||||||
"changelog": "conventional-changelog -p cli -i CHANGELOG.md -s"
|
"changelog": "conventional-changelog -p cli -i CHANGELOG.md -s"
|
||||||
},
|
},
|
||||||
"author": "Peng-YM",
|
"author": "Peng-YM",
|
||||||
@@ -33,6 +34,7 @@
|
|||||||
"ms": "^2.1.3",
|
"ms": "^2.1.3",
|
||||||
"nanoid": "^3.3.3",
|
"nanoid": "^3.3.3",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
|
"semver": "^7.6.3",
|
||||||
"static-js-yaml": "^1.0.0"
|
"static-js-yaml": "^1.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
13502
backend/pnpm-lock.yaml
generated
13502
backend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -343,6 +343,14 @@ function lastParse(proxy) {
|
|||||||
if (typeof proxy.password === 'number') {
|
if (typeof proxy.password === 'number') {
|
||||||
proxy.password = numberToString(proxy.password);
|
proxy.password = numberToString(proxy.password);
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
['ss'].includes(proxy.type) &&
|
||||||
|
proxy.cipher === 'none' &&
|
||||||
|
!proxy.password
|
||||||
|
) {
|
||||||
|
// https://github.com/MetaCubeX/mihomo/issues/1677
|
||||||
|
proxy.password = '';
|
||||||
|
}
|
||||||
if (proxy.interface) {
|
if (proxy.interface) {
|
||||||
proxy['interface-name'] = proxy.interface;
|
proxy['interface-name'] = proxy.interface;
|
||||||
delete proxy.interface;
|
delete proxy.interface;
|
||||||
|
|||||||
@@ -596,6 +596,9 @@ function URI_VLESS() {
|
|||||||
// mKCP 的伪装头部类型。当前可选值有 none / srtp / utp / wechat-video / dtls / wireguard。省略时默认值为 none,即不使用伪装头部,但不可以为空字符串。
|
// mKCP 的伪装头部类型。当前可选值有 none / srtp / utp / wechat-video / dtls / wireguard。省略时默认值为 none,即不使用伪装头部,但不可以为空字符串。
|
||||||
proxy.headerType = params.headerType || 'none';
|
proxy.headerType = params.headerType || 'none';
|
||||||
}
|
}
|
||||||
|
if (params.extra) {
|
||||||
|
proxy.extra = params.extra;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return proxy;
|
return proxy;
|
||||||
|
|||||||
@@ -39,12 +39,12 @@ start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/socks5/hysteria2
|
|||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/others)*{
|
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/others)*{
|
||||||
proxy.type = "ssr";
|
proxy.type = "ssr";
|
||||||
// handle ssr obfs
|
// handle ssr obfs
|
||||||
proxy.obfs = obfs.type;
|
proxy.obfs = obfs.type;
|
||||||
}
|
}
|
||||||
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/others)* {
|
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/others)* {
|
||||||
proxy.type = "ss";
|
proxy.type = "ss";
|
||||||
// handle ss obfs
|
// handle ss obfs
|
||||||
if (obfs.type == "http" || obfs.type === "tls") {
|
if (obfs.type == "http" || obfs.type === "tls") {
|
||||||
@@ -54,31 +54,31 @@ shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs
|
|||||||
$set(proxy, "plugin-opts.path", obfs.path);
|
$set(proxy, "plugin-opts.path", obfs.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/others)* {
|
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/ip_mode/others)* {
|
||||||
proxy.type = "vmess";
|
proxy.type = "vmess";
|
||||||
proxy.cipher = proxy.cipher || "none";
|
proxy.cipher = proxy.cipher || "none";
|
||||||
proxy.alterId = proxy.alterId || 0;
|
proxy.alterId = proxy.alterId || 0;
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
|
||||||
proxy.type = "vless";
|
proxy.type = "vless";
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
|
||||||
proxy.type = "trojan";
|
proxy.type = "trojan";
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/others)* {
|
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/ip_mode/others)* {
|
||||||
proxy.type = "hysteria2";
|
proxy.type = "hysteria2";
|
||||||
}
|
}
|
||||||
https = tag equals "https"i address (username password)? (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
https = tag equals "https"i address (username password)? (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
}
|
}
|
||||||
http = tag equals "http"i address (username password)? (fast_open/udp_relay/others)* {
|
http = tag equals "http"i address (username password)? (fast_open/udp_relay/ip_mode/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
}
|
}
|
||||||
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
|
||||||
proxy.type = "socks5";
|
proxy.type = "socks5";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,6 +169,11 @@ ssr_protocol_param = comma "protocol-param" equals param:$[^=,]+ { proxy["protoc
|
|||||||
|
|
||||||
vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseInt(alterId); }
|
vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseInt(alterId); }
|
||||||
|
|
||||||
|
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_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(""); }
|
||||||
|
|
||||||
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
||||||
tls_host = comma sni:("tls-name"/"sni") equals host:domain { proxy.sni = host; }
|
tls_host = comma sni:("tls-name"/"sni") equals host:domain { proxy.sni = host; }
|
||||||
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
|
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
|
||||||
@@ -177,6 +182,7 @@ tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pu
|
|||||||
|
|
||||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||||
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
||||||
|
ip_mode = comma "ip-mode" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }
|
||||||
|
|
||||||
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
||||||
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
||||||
|
|||||||
@@ -37,12 +37,12 @@ start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/socks5/hysteria2
|
|||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/others)*{
|
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/others)*{
|
||||||
proxy.type = "ssr";
|
proxy.type = "ssr";
|
||||||
// handle ssr obfs
|
// handle ssr obfs
|
||||||
proxy.obfs = obfs.type;
|
proxy.obfs = obfs.type;
|
||||||
}
|
}
|
||||||
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/others)* {
|
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/others)* {
|
||||||
proxy.type = "ss";
|
proxy.type = "ss";
|
||||||
// handle ss obfs
|
// handle ss obfs
|
||||||
if (obfs.type == "http" || obfs.type === "tls") {
|
if (obfs.type == "http" || obfs.type === "tls") {
|
||||||
@@ -52,31 +52,31 @@ shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs
|
|||||||
$set(proxy, "plugin-opts.path", obfs.path);
|
$set(proxy, "plugin-opts.path", obfs.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/others)* {
|
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/ip_mode/others)* {
|
||||||
proxy.type = "vmess";
|
proxy.type = "vmess";
|
||||||
proxy.cipher = proxy.cipher || "none";
|
proxy.cipher = proxy.cipher || "none";
|
||||||
proxy.alterId = proxy.alterId || 0;
|
proxy.alterId = proxy.alterId || 0;
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
|
||||||
proxy.type = "vless";
|
proxy.type = "vless";
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
|
||||||
proxy.type = "trojan";
|
proxy.type = "trojan";
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/others)* {
|
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/ip_mode/others)* {
|
||||||
proxy.type = "hysteria2";
|
proxy.type = "hysteria2";
|
||||||
}
|
}
|
||||||
https = tag equals "https"i address (username password)? (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
https = tag equals "https"i address (username password)? (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
}
|
}
|
||||||
http = tag equals "http"i address (username password)? (fast_open/udp_relay/others)* {
|
http = tag equals "http"i address (username password)? (fast_open/udp_relay/ip_mode/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
}
|
}
|
||||||
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
|
||||||
proxy.type = "socks5";
|
proxy.type = "socks5";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,6 +167,11 @@ ssr_protocol_param = comma "protocol-param" equals param:$[^=,]+ { proxy["protoc
|
|||||||
|
|
||||||
vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseInt(alterId); }
|
vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseInt(alterId); }
|
||||||
|
|
||||||
|
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_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(""); }
|
||||||
|
|
||||||
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
||||||
tls_host = comma sni:("tls-name"/"sni") equals host:domain { proxy.sni = host; }
|
tls_host = comma sni:("tls-name"/"sni") equals host:domain { proxy.sni = host; }
|
||||||
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
|
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
|
||||||
@@ -175,6 +180,7 @@ tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pu
|
|||||||
|
|
||||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||||
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
||||||
|
ip_mode = comma "ip-mode" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }
|
||||||
|
|
||||||
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
||||||
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { safeLoad } from '@/utils/yaml';
|
import { safeLoad } from '@/utils/yaml';
|
||||||
import { Base64 } from 'js-base64';
|
import { Base64 } from 'js-base64';
|
||||||
|
import $ from '@/core/app';
|
||||||
|
|
||||||
function HTML() {
|
function HTML() {
|
||||||
const name = 'HTML';
|
const name = 'HTML';
|
||||||
@@ -35,8 +36,15 @@ function Base64Encoded() {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
const parse = function (raw) {
|
const parse = function (raw) {
|
||||||
raw = Base64.decode(raw);
|
const decoded = Base64.decode(raw);
|
||||||
return raw;
|
if (!/^\w+(:\/\/|\s*?=\s*?)\w+/m.test(decoded)) {
|
||||||
|
$.error(
|
||||||
|
`Base64 Pre-processor error: decoded line does not start with protocol`,
|
||||||
|
);
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
return decoded;
|
||||||
};
|
};
|
||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
}
|
}
|
||||||
@@ -48,7 +56,7 @@ function Clash() {
|
|||||||
const content = safeLoad(raw);
|
const content = safeLoad(raw);
|
||||||
return content.proxies && Array.isArray(content.proxies);
|
return content.proxies && Array.isArray(content.proxies);
|
||||||
};
|
};
|
||||||
const parse = function (raw) {
|
const parse = function (raw, includeProxies) {
|
||||||
// Clash YAML format
|
// Clash YAML format
|
||||||
|
|
||||||
// 防止 VLESS节点 reality-opts 选项中的 short-id 被解析成 Infinity
|
// 防止 VLESS节点 reality-opts 选项中的 short-id 被解析成 Infinity
|
||||||
@@ -56,35 +64,40 @@ function Clash() {
|
|||||||
const afterReplace = raw.replace(
|
const afterReplace = raw.replace(
|
||||||
/short-id:([ ]*[^,\n}]*)/g,
|
/short-id:([ ]*[^,\n}]*)/g,
|
||||||
(matched, value) => {
|
(matched, value) => {
|
||||||
const afterTrim = value.trim();
|
const afterTrim = value.trim();
|
||||||
|
|
||||||
// 为空
|
// 为空
|
||||||
if (!afterTrim || afterTrim === '') {
|
if (!afterTrim || afterTrim === '') {
|
||||||
return 'short-id: ""'
|
return 'short-id: ""';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 是否被引号包裹
|
// 是否被引号包裹
|
||||||
if (/^(['"]).*\1$/.test(afterTrim)) {
|
if (/^(['"]).*\1$/.test(afterTrim)) {
|
||||||
return `short-id: ${afterTrim}`;
|
return `short-id: ${afterTrim}`;
|
||||||
} else {
|
} else {
|
||||||
return `short-id: "${afterTrim}"`
|
return `short-id: "${afterTrim}"`;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
proxies,
|
proxies,
|
||||||
'global-client-fingerprint': globalClientFingerprint,
|
'global-client-fingerprint': globalClientFingerprint,
|
||||||
} = safeLoad(afterReplace);
|
} = safeLoad(afterReplace);
|
||||||
return proxies
|
return (
|
||||||
.map((p) => {
|
(includeProxies ? 'proxies:\n' : '') +
|
||||||
// https://github.com/MetaCubeX/mihomo/blob/Alpha/docs/config.yaml#L73C1-L73C26
|
proxies
|
||||||
if (globalClientFingerprint && !p['client-fingerprint']) {
|
.map((p) => {
|
||||||
p['client-fingerprint'] = globalClientFingerprint;
|
// https://github.com/MetaCubeX/mihomo/blob/Alpha/docs/config.yaml#L73C1-L73C26
|
||||||
}
|
if (globalClientFingerprint && !p['client-fingerprint']) {
|
||||||
return JSON.stringify(p);
|
p['client-fingerprint'] = globalClientFingerprint;
|
||||||
})
|
}
|
||||||
.join('\n');
|
return `${includeProxies ? ' - ' : ''}${JSON.stringify(
|
||||||
|
p,
|
||||||
|
)}\n`;
|
||||||
|
})
|
||||||
|
.join('')
|
||||||
|
);
|
||||||
};
|
};
|
||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { hex_md5 } from '@/vendor/md5';
|
|||||||
import { ProxyUtils } from '@/core/proxy-utils';
|
import { ProxyUtils } from '@/core/proxy-utils';
|
||||||
import { produceArtifact } from '@/restful/sync';
|
import { produceArtifact } from '@/restful/sync';
|
||||||
import { SETTINGS_KEY } from '@/constants';
|
import { SETTINGS_KEY } from '@/constants';
|
||||||
|
import YAML from '@/utils/yaml';
|
||||||
|
|
||||||
import env from '@/utils/env';
|
import env from '@/utils/env';
|
||||||
import {
|
import {
|
||||||
@@ -21,6 +22,46 @@ import {
|
|||||||
getRmainingDays,
|
getRmainingDays,
|
||||||
} from '@/utils/flow';
|
} from '@/utils/flow';
|
||||||
|
|
||||||
|
function isObject(item) {
|
||||||
|
return item && typeof item === 'object' && !Array.isArray(item);
|
||||||
|
}
|
||||||
|
function trimWrap(str) {
|
||||||
|
if (str.startsWith('<') && str.endsWith('>')) {
|
||||||
|
return str.slice(1, -1);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
function deepMerge(target, _other) {
|
||||||
|
const other = typeof _other === 'string' ? JSON.parse(_other) : _other;
|
||||||
|
for (const key in other) {
|
||||||
|
if (isObject(other[key])) {
|
||||||
|
if (key.endsWith('!')) {
|
||||||
|
const k = trimWrap(key.slice(0, -1));
|
||||||
|
target[k] = other[key];
|
||||||
|
} else {
|
||||||
|
const k = trimWrap(key);
|
||||||
|
if (!target[k]) Object.assign(target, { [k]: {} });
|
||||||
|
deepMerge(target[k], other[k]);
|
||||||
|
}
|
||||||
|
} else if (Array.isArray(other[key])) {
|
||||||
|
if (key.startsWith('+')) {
|
||||||
|
const k = trimWrap(key.slice(1));
|
||||||
|
if (!target[k]) Object.assign(target, { [k]: [] });
|
||||||
|
target[k] = [...other[key], ...target[k]];
|
||||||
|
} else if (key.endsWith('+')) {
|
||||||
|
const k = trimWrap(key.slice(0, -1));
|
||||||
|
if (!target[k]) Object.assign(target, { [k]: [] });
|
||||||
|
target[k] = [...target[k], ...other[key]];
|
||||||
|
} else {
|
||||||
|
const k = trimWrap(key);
|
||||||
|
Object.assign(target, { [k]: other[key] });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Object.assign(target, { [key]: other[key] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
The rule "(name CONTAINS "🇨🇳") AND (port IN [80, 443])" can be expressed as follows:
|
The rule "(name CONTAINS "🇨🇳") AND (port IN [80, 443])" can be expressed as follows:
|
||||||
{
|
{
|
||||||
@@ -321,6 +362,33 @@ function ScriptOperator(script, targetPlatform, $arguments, source, $options) {
|
|||||||
name: 'Script Operator',
|
name: 'Script Operator',
|
||||||
func: async (proxies) => {
|
func: async (proxies) => {
|
||||||
let output = proxies;
|
let output = proxies;
|
||||||
|
if (output?.$file?.type === 'mihomoProfile') {
|
||||||
|
try {
|
||||||
|
let patch = YAML.safeLoad(script);
|
||||||
|
if (typeof patch !== 'object') patch = {};
|
||||||
|
output.$content = ProxyUtils.yaml.safeDump(
|
||||||
|
deepMerge(
|
||||||
|
{
|
||||||
|
proxies: await produceArtifact({
|
||||||
|
type:
|
||||||
|
output?.$file?.sourceType ||
|
||||||
|
'collection',
|
||||||
|
name: output?.$file?.sourceName,
|
||||||
|
platform: 'mihomo',
|
||||||
|
produceType: 'internal',
|
||||||
|
produceOpts: {
|
||||||
|
'delete-underscore-fields': true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
patch,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return output;
|
||||||
|
} catch (e) {
|
||||||
|
// console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
await (async function () {
|
await (async function () {
|
||||||
const operator = createDynamicFunction(
|
const operator = createDynamicFunction(
|
||||||
'operator',
|
'operator',
|
||||||
@@ -339,9 +407,27 @@ function ScriptOperator(script, targetPlatform, $arguments, source, $options) {
|
|||||||
'operator',
|
'operator',
|
||||||
`async function operator(input = []) {
|
`async function operator(input = []) {
|
||||||
if (input && (input.$files || input.$content)) {
|
if (input && (input.$files || input.$content)) {
|
||||||
let { $content, $files, $options } = input
|
let { $content, $files, $options, $file } = input
|
||||||
${script}
|
if($file.type === 'mihomoProfile') {
|
||||||
return { $content, $files, $options }
|
${script}
|
||||||
|
if(typeof main === 'function') {
|
||||||
|
const config = {
|
||||||
|
proxies: await produceArtifact({
|
||||||
|
type: $file.sourceType || 'collection',
|
||||||
|
name: $file.sourceName,
|
||||||
|
platform: 'mihomo',
|
||||||
|
produceType: 'internal',
|
||||||
|
produceOpts: {
|
||||||
|
'delete-underscore-fields': true
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
$content = ProxyUtils.yaml.safeDump(await main(config))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
${script}
|
||||||
|
}
|
||||||
|
return { $content, $files, $options, $file }
|
||||||
} else {
|
} else {
|
||||||
let proxies = input
|
let proxies = input
|
||||||
let list = []
|
let list = []
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ export default function ClashMeta_Producer() {
|
|||||||
delete proxy.id;
|
delete proxy.id;
|
||||||
delete proxy.resolved;
|
delete proxy.resolved;
|
||||||
delete proxy['no-resolve'];
|
delete proxy['no-resolve'];
|
||||||
if (type !== 'internal') {
|
if (type !== 'internal' || opts['delete-underscore-fields']) {
|
||||||
for (const key in proxy) {
|
for (const key in proxy) {
|
||||||
if (proxy[key] == null || /^_/i.test(key)) {
|
if (proxy[key] == null || /^_/i.test(key)) {
|
||||||
delete proxy[key];
|
delete proxy[key];
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export default function Egern_Producer() {
|
|||||||
// https://egernapp.com/zh-CN/docs/configuration/proxies
|
// https://egernapp.com/zh-CN/docs/configuration/proxies
|
||||||
const list = proxies
|
const list = proxies
|
||||||
.filter((proxy) => {
|
.filter((proxy) => {
|
||||||
if (opts['include-unsupported-proxy']) return true;
|
// if (opts['include-unsupported-proxy']) return true;
|
||||||
if (
|
if (
|
||||||
![
|
![
|
||||||
'http',
|
'http',
|
||||||
@@ -47,6 +47,12 @@ export default function Egern_Producer() {
|
|||||||
'salsa20',
|
'salsa20',
|
||||||
'chacha20',
|
'chacha20',
|
||||||
'chacha20-ietf',
|
'chacha20-ietf',
|
||||||
|
...(opts['include-unsupported-proxy']
|
||||||
|
? [
|
||||||
|
'2022-blake3-aes-128-gcm',
|
||||||
|
'2022-blake3-aes-256-gcm',
|
||||||
|
]
|
||||||
|
: []),
|
||||||
].includes(proxy.cipher))) ||
|
].includes(proxy.cipher))) ||
|
||||||
(proxy.type === 'vmess' &&
|
(proxy.type === 'vmess' &&
|
||||||
(![
|
(![
|
||||||
@@ -72,6 +78,15 @@ export default function Egern_Producer() {
|
|||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
.map((proxy) => {
|
.map((proxy) => {
|
||||||
|
if (proxy.tls && !proxy.sni) {
|
||||||
|
proxy.sni = proxy.server;
|
||||||
|
}
|
||||||
|
const prev_hop =
|
||||||
|
proxy.prev_hop ||
|
||||||
|
proxy['underlying-proxy'] ||
|
||||||
|
proxy['dialer-proxy'] ||
|
||||||
|
proxy.detour;
|
||||||
|
|
||||||
if (proxy.type === 'http') {
|
if (proxy.type === 'http') {
|
||||||
proxy = {
|
proxy = {
|
||||||
type: 'http',
|
type: 'http',
|
||||||
@@ -130,6 +145,8 @@ export default function Egern_Producer() {
|
|||||||
next_hop: proxy.next_hop,
|
next_hop: proxy.next_hop,
|
||||||
sni: proxy.sni,
|
sni: proxy.sni,
|
||||||
skip_tls_verify: proxy['skip-cert-verify'],
|
skip_tls_verify: proxy['skip-cert-verify'],
|
||||||
|
port_hopping: proxy.ports,
|
||||||
|
port_hopping_interval: proxy['hop-interval'],
|
||||||
};
|
};
|
||||||
if (proxy['obfs-password'] && proxy.obfs == 'salamander') {
|
if (proxy['obfs-password'] && proxy.obfs == 'salamander') {
|
||||||
proxy.obfs = 'salamander';
|
proxy.obfs = 'salamander';
|
||||||
@@ -284,6 +301,7 @@ export default function Egern_Producer() {
|
|||||||
[proxy.type]: {
|
[proxy.type]: {
|
||||||
...proxy,
|
...proxy,
|
||||||
type: undefined,
|
type: undefined,
|
||||||
|
prev_hop,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,20 +20,37 @@ function JSON_Producer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
qx: QX_Producer(),
|
||||||
QX: QX_Producer(),
|
QX: QX_Producer(),
|
||||||
QuantumultX: QX_Producer(),
|
QuantumultX: QX_Producer(),
|
||||||
|
surge: Surge_Producer(),
|
||||||
Surge: Surge_Producer(),
|
Surge: Surge_Producer(),
|
||||||
SurgeMac: SurgeMac_Producer(),
|
SurgeMac: SurgeMac_Producer(),
|
||||||
Loon: Loon_Producer(),
|
Loon: Loon_Producer(),
|
||||||
Clash: Clash_Producer(),
|
Clash: Clash_Producer(),
|
||||||
|
meta: ClashMeta_Producer(),
|
||||||
|
clashmeta: ClashMeta_Producer(),
|
||||||
|
'clash.meta': ClashMeta_Producer(),
|
||||||
|
'Clash.Meta': ClashMeta_Producer(),
|
||||||
ClashMeta: ClashMeta_Producer(),
|
ClashMeta: ClashMeta_Producer(),
|
||||||
|
mihomo: ClashMeta_Producer(),
|
||||||
|
Mihomo: ClashMeta_Producer(),
|
||||||
|
uri: URI_Producer(),
|
||||||
URI: URI_Producer(),
|
URI: URI_Producer(),
|
||||||
|
v2: V2Ray_Producer(),
|
||||||
|
v2ray: V2Ray_Producer(),
|
||||||
V2Ray: V2Ray_Producer(),
|
V2Ray: V2Ray_Producer(),
|
||||||
|
json: JSON_Producer(),
|
||||||
JSON: JSON_Producer(),
|
JSON: JSON_Producer(),
|
||||||
|
stash: Stash_Producer(),
|
||||||
Stash: Stash_Producer(),
|
Stash: Stash_Producer(),
|
||||||
|
shadowrocket: Shadowrocket_Producer(),
|
||||||
Shadowrocket: Shadowrocket_Producer(),
|
Shadowrocket: Shadowrocket_Producer(),
|
||||||
ShadowRocket: Shadowrocket_Producer(),
|
ShadowRocket: Shadowrocket_Producer(),
|
||||||
|
surfboard: Surfboard_Producer(),
|
||||||
Surfboard: Surfboard_Producer(),
|
Surfboard: Surfboard_Producer(),
|
||||||
|
singbox: singbox_Producer(),
|
||||||
'sing-box': singbox_Producer(),
|
'sing-box': singbox_Producer(),
|
||||||
|
egern: Egern_Producer(),
|
||||||
Egern: Egern_Producer(),
|
Egern: Egern_Producer(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,13 +3,21 @@ const targetPlatform = 'Loon';
|
|||||||
import { isPresent, Result } from './utils';
|
import { isPresent, Result } from './utils';
|
||||||
import { isIPv4, isIPv6 } from '@/utils';
|
import { isIPv4, isIPv6 } from '@/utils';
|
||||||
|
|
||||||
|
const ipVersions = {
|
||||||
|
dual: 'dual',
|
||||||
|
ipv4: 'v4-only',
|
||||||
|
ipv6: 'v6-only',
|
||||||
|
'ipv4-prefer': 'prefer-v4',
|
||||||
|
'ipv6-prefer': 'prefer-v6',
|
||||||
|
};
|
||||||
|
|
||||||
export default function Loon_Producer() {
|
export default function Loon_Producer() {
|
||||||
const produce = (proxy, type, opts = {}) => {
|
const produce = (proxy, type, opts = {}) => {
|
||||||
switch (proxy.type) {
|
switch (proxy.type) {
|
||||||
case 'ss':
|
case 'ss':
|
||||||
return shadowsocks(proxy, opts['include-unsupported-proxy']);
|
return shadowsocks(proxy, opts['include-unsupported-proxy']);
|
||||||
case 'ssr':
|
case 'ssr':
|
||||||
return shadowsocksr(proxy);
|
return shadowsocksr(proxy, opts['include-unsupported-proxy']);
|
||||||
case 'trojan':
|
case 'trojan':
|
||||||
return trojan(proxy);
|
return trojan(proxy);
|
||||||
case 'vmess':
|
case 'vmess':
|
||||||
@@ -56,9 +64,8 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
|
|||||||
'aes-256-gcm',
|
'aes-256-gcm',
|
||||||
'chacha20-ietf-poly1305',
|
'chacha20-ietf-poly1305',
|
||||||
'xchacha20-ietf-poly1305',
|
'xchacha20-ietf-poly1305',
|
||||||
...(includeUnsupportedProxy
|
'2022-blake3-aes-128-gcm',
|
||||||
? ['2022-blake3-aes-128-gcm', '2022-blake3-aes-256-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`);
|
||||||
@@ -67,6 +74,8 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
|
|||||||
`${proxy.name}=shadowsocks,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"`,
|
`${proxy.name}=shadowsocks,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let isShadowTLS;
|
||||||
|
|
||||||
// obfs
|
// obfs
|
||||||
if (isPresent(proxy, 'plugin')) {
|
if (isPresent(proxy, 'plugin')) {
|
||||||
if (proxy.plugin === 'obfs') {
|
if (proxy.plugin === 'obfs') {
|
||||||
@@ -79,11 +88,52 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
|
|||||||
`,obfs-uri=${proxy['plugin-opts'].path}`,
|
`,obfs-uri=${proxy['plugin-opts'].path}`,
|
||||||
'plugin-opts.path',
|
'plugin-opts.path',
|
||||||
);
|
);
|
||||||
} else {
|
} else if (!['shadow-tls'].includes(proxy.plugin)) {
|
||||||
throw new Error(`plugin ${proxy.plugin} is not supported`);
|
throw new Error(`plugin ${proxy.plugin} is not supported`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// shadow-tls
|
||||||
|
if (isPresent(proxy, 'shadow-tls-password')) {
|
||||||
|
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
|
||||||
|
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
|
||||||
|
'shadow-tls-version',
|
||||||
|
);
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
|
||||||
|
'shadow-tls-sni',
|
||||||
|
);
|
||||||
|
// udp-port
|
||||||
|
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
|
||||||
|
isShadowTLS = true;
|
||||||
|
} else if (['shadow-tls'].includes(proxy.plugin) && proxy['plugin-opts']) {
|
||||||
|
const password = proxy['plugin-opts'].password;
|
||||||
|
const host = proxy['plugin-opts'].host;
|
||||||
|
const version = proxy['plugin-opts'].version;
|
||||||
|
if (password) {
|
||||||
|
result.append(`,shadow-tls-password=${password}`);
|
||||||
|
if (host) {
|
||||||
|
result.append(`,shadow-tls-sni=${host}`);
|
||||||
|
}
|
||||||
|
if (version) {
|
||||||
|
if (version < 2) {
|
||||||
|
throw new Error(
|
||||||
|
`shadow-tls version ${version} is not supported`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
result.append(`,shadow-tls-version=${version}`);
|
||||||
|
}
|
||||||
|
// udp-port
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,udp-port=${proxy['udp-port']}`,
|
||||||
|
'udp-port',
|
||||||
|
);
|
||||||
|
isShadowTLS = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// tfo
|
// tfo
|
||||||
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
||||||
|
|
||||||
@@ -92,10 +142,18 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
|
|||||||
result.append(`,udp=true`);
|
result.append(`,udp=true`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!includeUnsupportedProxy && isShadowTLS) {
|
||||||
|
throw new Error(
|
||||||
|
`shadow-tls is not supported(请使用 includeUnsupportedProxy 参数)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
|
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function shadowsocksr(proxy) {
|
function shadowsocksr(proxy, includeUnsupportedProxy) {
|
||||||
const result = new Result(proxy);
|
const result = new Result(proxy);
|
||||||
result.append(
|
result.append(
|
||||||
`${proxy.name}=shadowsocksr,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"`,
|
`${proxy.name}=shadowsocksr,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"`,
|
||||||
@@ -112,6 +170,49 @@ function shadowsocksr(proxy) {
|
|||||||
result.appendIfPresent(`,obfs=${proxy.obfs}`, 'obfs');
|
result.appendIfPresent(`,obfs=${proxy.obfs}`, 'obfs');
|
||||||
result.appendIfPresent(`,obfs-param=${proxy['obfs-param']}`, 'obfs-param');
|
result.appendIfPresent(`,obfs-param=${proxy['obfs-param']}`, 'obfs-param');
|
||||||
|
|
||||||
|
let isShadowTLS;
|
||||||
|
|
||||||
|
// shadow-tls
|
||||||
|
if (isPresent(proxy, 'shadow-tls-password')) {
|
||||||
|
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
|
||||||
|
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
|
||||||
|
'shadow-tls-version',
|
||||||
|
);
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
|
||||||
|
'shadow-tls-sni',
|
||||||
|
);
|
||||||
|
// udp-port
|
||||||
|
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
|
||||||
|
isShadowTLS = true;
|
||||||
|
} else if (['shadow-tls'].includes(proxy.plugin) && proxy['plugin-opts']) {
|
||||||
|
const password = proxy['plugin-opts'].password;
|
||||||
|
const host = proxy['plugin-opts'].host;
|
||||||
|
const version = proxy['plugin-opts'].version;
|
||||||
|
if (password) {
|
||||||
|
result.append(`,shadow-tls-password=${password}`);
|
||||||
|
if (host) {
|
||||||
|
result.append(`,shadow-tls-sni=${host}`);
|
||||||
|
}
|
||||||
|
if (version) {
|
||||||
|
if (version < 2) {
|
||||||
|
throw new Error(
|
||||||
|
`shadow-tls version ${version} is not supported`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
result.append(`,shadow-tls-version=${version}`);
|
||||||
|
}
|
||||||
|
// udp-port
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,udp-port=${proxy['udp-port']}`,
|
||||||
|
'udp-port',
|
||||||
|
);
|
||||||
|
isShadowTLS = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// tfo
|
// tfo
|
||||||
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
||||||
|
|
||||||
@@ -120,6 +221,14 @@ function shadowsocksr(proxy) {
|
|||||||
result.append(`,udp=true`);
|
result.append(`,udp=true`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!includeUnsupportedProxy && isShadowTLS) {
|
||||||
|
throw new Error(
|
||||||
|
`shadow-tls is not supported(请使用 includeUnsupportedProxy 参数)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
|
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,6 +281,8 @@ function trojan(proxy) {
|
|||||||
if (proxy.udp) {
|
if (proxy.udp) {
|
||||||
result.append(`,udp=true`);
|
result.append(`,udp=true`);
|
||||||
}
|
}
|
||||||
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
|
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
@@ -248,13 +359,16 @@ function vmess(proxy) {
|
|||||||
// udp
|
// udp
|
||||||
if (proxy.udp) {
|
if (proxy.udp) {
|
||||||
result.append(`,udp=true`);
|
result.append(`,udp=true`);
|
||||||
|
const ip_version =
|
||||||
|
ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
|
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
||||||
}
|
}
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function vless(proxy) {
|
function vless(proxy) {
|
||||||
if (proxy['reality-opts']) {
|
if (typeof proxy.flow !== 'undefined' || proxy['reality-opts']) {
|
||||||
throw new Error(`VLESS REALITY is unsupported`);
|
throw new Error(`VLESS XTLS/REALITY is not supported`);
|
||||||
}
|
}
|
||||||
const result = new Result(proxy);
|
const result = new Result(proxy);
|
||||||
result.append(
|
result.append(
|
||||||
@@ -320,6 +434,9 @@ function vless(proxy) {
|
|||||||
// udp
|
// udp
|
||||||
if (proxy.udp) {
|
if (proxy.udp) {
|
||||||
result.append(`,udp=true`);
|
result.append(`,udp=true`);
|
||||||
|
const ip_version =
|
||||||
|
ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
|
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
||||||
}
|
}
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
@@ -342,6 +459,8 @@ function http(proxy) {
|
|||||||
|
|
||||||
// tfo
|
// tfo
|
||||||
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
|
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
|
||||||
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
|
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
@@ -370,6 +489,8 @@ function socks5(proxy) {
|
|||||||
if (proxy.udp) {
|
if (proxy.udp) {
|
||||||
result.append(`,udp=true`);
|
result.append(`,udp=true`);
|
||||||
}
|
}
|
||||||
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
|
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
@@ -435,6 +556,8 @@ function wireguard(proxy) {
|
|||||||
presharedKey ?? ''
|
presharedKey ?? ''
|
||||||
}}]`,
|
}}]`,
|
||||||
);
|
);
|
||||||
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
|
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
@@ -482,6 +605,8 @@ function hysteria2(proxy) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
result.appendIfPresent(`,ecn=${proxy.ecn}`, 'ecn');
|
result.appendIfPresent(`,ecn=${proxy.ecn}`, 'ecn');
|
||||||
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
|
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import { isIPv4, isIPv6 } from '@/utils';
|
|||||||
const detourParser = (proxy, parsedProxy) => {
|
const detourParser = (proxy, parsedProxy) => {
|
||||||
parsedProxy.detour = proxy['dialer-proxy'] || proxy.detour;
|
parsedProxy.detour = proxy['dialer-proxy'] || proxy.detour;
|
||||||
};
|
};
|
||||||
|
const networkParser = (proxy, parsedProxy) => {
|
||||||
|
if (['tcp', 'udp'].includes(proxy._network))
|
||||||
|
parsedProxy.network = proxy._network;
|
||||||
|
};
|
||||||
const tfoParser = (proxy, parsedProxy) => {
|
const tfoParser = (proxy, parsedProxy) => {
|
||||||
parsedProxy.tcp_fast_open = false;
|
parsedProxy.tcp_fast_open = false;
|
||||||
if (proxy.tfo) parsedProxy.tcp_fast_open = true;
|
if (proxy.tfo) parsedProxy.tcp_fast_open = true;
|
||||||
@@ -305,6 +309,7 @@ const socks5Parser = (proxy = {}) => {
|
|||||||
if (proxy.uot) parsedProxy.udp_over_tcp = true;
|
if (proxy.uot) parsedProxy.udp_over_tcp = true;
|
||||||
if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true;
|
if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true;
|
||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
|
networkParser(proxy, parsedProxy);
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
detourParser(proxy, parsedProxy);
|
detourParser(proxy, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
@@ -356,6 +361,7 @@ const ssParser = (proxy = {}) => {
|
|||||||
if (proxy.uot) parsedProxy.udp_over_tcp = true;
|
if (proxy.uot) parsedProxy.udp_over_tcp = true;
|
||||||
if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true;
|
if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true;
|
||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
|
networkParser(proxy, parsedProxy);
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
detourParser(proxy, parsedProxy);
|
detourParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
@@ -470,7 +476,7 @@ const vmessParser = (proxy = {}) => {
|
|||||||
if (proxy.network === 'h2') h2Parser(proxy, parsedProxy);
|
if (proxy.network === 'h2') h2Parser(proxy, parsedProxy);
|
||||||
if (proxy.network === 'http') h1Parser(proxy, parsedProxy);
|
if (proxy.network === 'http') h1Parser(proxy, parsedProxy);
|
||||||
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
||||||
|
networkParser(proxy, parsedProxy);
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
detourParser(proxy, parsedProxy);
|
detourParser(proxy, parsedProxy);
|
||||||
tlsParser(proxy, parsedProxy);
|
tlsParser(proxy, parsedProxy);
|
||||||
@@ -493,7 +499,7 @@ const vlessParser = (proxy = {}) => {
|
|||||||
if (proxy.flow === 'xtls-rprx-vision') parsedProxy.flow = proxy.flow;
|
if (proxy.flow === 'xtls-rprx-vision') parsedProxy.flow = proxy.flow;
|
||||||
if (proxy.network === 'ws') wsParser(proxy, parsedProxy);
|
if (proxy.network === 'ws') wsParser(proxy, parsedProxy);
|
||||||
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
||||||
|
networkParser(proxy, parsedProxy);
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
detourParser(proxy, parsedProxy);
|
detourParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
@@ -514,7 +520,7 @@ const trojanParser = (proxy = {}) => {
|
|||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
||||||
if (proxy.network === 'ws') wsParser(proxy, parsedProxy);
|
if (proxy.network === 'ws') wsParser(proxy, parsedProxy);
|
||||||
|
networkParser(proxy, parsedProxy);
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
detourParser(proxy, parsedProxy);
|
detourParser(proxy, parsedProxy);
|
||||||
tlsParser(proxy, parsedProxy);
|
tlsParser(proxy, parsedProxy);
|
||||||
@@ -562,13 +568,14 @@ const hysteriaParser = (proxy = {}) => {
|
|||||||
parsedProxy.disable_mtu_discovery = true;
|
parsedProxy.disable_mtu_discovery = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
networkParser(proxy, parsedProxy);
|
||||||
tlsParser(proxy, parsedProxy);
|
tlsParser(proxy, parsedProxy);
|
||||||
detourParser(proxy, parsedProxy);
|
detourParser(proxy, parsedProxy);
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
};
|
};
|
||||||
const hysteria2Parser = (proxy = {}) => {
|
const hysteria2Parser = (proxy = {}, includeUnsupportedProxy) => {
|
||||||
const parsedProxy = {
|
const parsedProxy = {
|
||||||
tag: proxy.name,
|
tag: proxy.name,
|
||||||
type: 'hysteria2',
|
type: 'hysteria2',
|
||||||
@@ -580,12 +587,23 @@ const hysteria2Parser = (proxy = {}) => {
|
|||||||
};
|
};
|
||||||
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
|
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
|
||||||
throw 'invalid port';
|
throw 'invalid port';
|
||||||
|
if (includeUnsupportedProxy) {
|
||||||
|
if (proxy['hop-interval'])
|
||||||
|
parsedProxy.hop_interval = /^\d+$/.test(proxy['hop-interval'])
|
||||||
|
? `${proxy['hop-interval']}s`
|
||||||
|
: proxy['hop-interval'];
|
||||||
|
if (proxy['ports'])
|
||||||
|
parsedProxy.server_ports = proxy['ports']
|
||||||
|
.split(/\s*,\s*/)
|
||||||
|
.map((p) => p.replace(/\s*-\s*/g, ':'));
|
||||||
|
}
|
||||||
if (proxy.up) parsedProxy.up_mbps = parseInt(`${proxy.up}`, 10);
|
if (proxy.up) parsedProxy.up_mbps = parseInt(`${proxy.up}`, 10);
|
||||||
if (proxy.down) parsedProxy.down_mbps = parseInt(`${proxy.down}`, 10);
|
if (proxy.down) parsedProxy.down_mbps = parseInt(`${proxy.down}`, 10);
|
||||||
if (proxy.obfs === 'salamander') parsedProxy.obfs.type = 'salamander';
|
if (proxy.obfs === 'salamander') parsedProxy.obfs.type = 'salamander';
|
||||||
if (proxy['obfs-password'])
|
if (proxy['obfs-password'])
|
||||||
parsedProxy.obfs.password = proxy['obfs-password'];
|
parsedProxy.obfs.password = proxy['obfs-password'];
|
||||||
if (!parsedProxy.obfs.type) delete parsedProxy.obfs;
|
if (!parsedProxy.obfs.type) delete parsedProxy.obfs;
|
||||||
|
networkParser(proxy, parsedProxy);
|
||||||
tlsParser(proxy, parsedProxy);
|
tlsParser(proxy, parsedProxy);
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
detourParser(proxy, parsedProxy);
|
detourParser(proxy, parsedProxy);
|
||||||
@@ -616,6 +634,7 @@ const tuic5Parser = (proxy = {}) => {
|
|||||||
if (proxy['udp-over-stream']) parsedProxy.udp_over_stream = true;
|
if (proxy['udp-over-stream']) parsedProxy.udp_over_stream = true;
|
||||||
if (proxy['heartbeat-interval'])
|
if (proxy['heartbeat-interval'])
|
||||||
parsedProxy.heartbeat = `${proxy['heartbeat-interval']}ms`;
|
parsedProxy.heartbeat = `${proxy['heartbeat-interval']}ms`;
|
||||||
|
networkParser(proxy, parsedProxy);
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
detourParser(proxy, parsedProxy);
|
detourParser(proxy, parsedProxy);
|
||||||
tlsParser(proxy, parsedProxy);
|
tlsParser(proxy, parsedProxy);
|
||||||
@@ -673,6 +692,7 @@ const wireguardParser = (proxy = {}) => {
|
|||||||
parsedProxy.peers.push(peer);
|
parsedProxy.peers.push(peer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
networkParser(proxy, parsedProxy);
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
detourParser(proxy, parsedProxy);
|
detourParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
@@ -790,7 +810,12 @@ export default function singbox_Producer() {
|
|||||||
list.push(hysteriaParser(proxy));
|
list.push(hysteriaParser(proxy));
|
||||||
break;
|
break;
|
||||||
case 'hysteria2':
|
case 'hysteria2':
|
||||||
list.push(hysteria2Parser(proxy));
|
list.push(
|
||||||
|
hysteria2Parser(
|
||||||
|
proxy,
|
||||||
|
opts['include-unsupported-proxy'],
|
||||||
|
),
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'tuic':
|
case 'tuic':
|
||||||
if (!proxy.token || proxy.token.length === 0) {
|
if (!proxy.token || proxy.token.length === 0) {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export default function Surge_Producer() {
|
|||||||
return { produce };
|
return { produce };
|
||||||
}
|
}
|
||||||
|
|
||||||
function shadowsocks(proxy, includeUnsupportedProxy) {
|
function shadowsocks(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}`);
|
||||||
if (!proxy.cipher) {
|
if (!proxy.cipher) {
|
||||||
@@ -87,9 +87,8 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
|
|||||||
'chacha20',
|
'chacha20',
|
||||||
'chacha20-ietf',
|
'chacha20-ietf',
|
||||||
'none',
|
'none',
|
||||||
...(includeUnsupportedProxy
|
'2022-blake3-aes-128-gcm',
|
||||||
? ['2022-blake3-aes-128-gcm', '2022-blake3-aes-256-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`);
|
||||||
@@ -127,8 +126,6 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
|
|||||||
|
|
||||||
// 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');
|
||||||
@@ -160,6 +157,8 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
|
|||||||
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
|
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
|
||||||
'shadow-tls-sni',
|
'shadow-tls-sni',
|
||||||
);
|
);
|
||||||
|
// udp-port
|
||||||
|
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
|
||||||
} else if (['shadow-tls'].includes(proxy.plugin) && proxy['plugin-opts']) {
|
} else if (['shadow-tls'].includes(proxy.plugin) && proxy['plugin-opts']) {
|
||||||
const password = proxy['plugin-opts'].password;
|
const password = proxy['plugin-opts'].password;
|
||||||
const host = proxy['plugin-opts'].host;
|
const host = proxy['plugin-opts'].host;
|
||||||
@@ -177,6 +176,11 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
|
|||||||
}
|
}
|
||||||
result.append(`,shadow-tls-version=${version}`);
|
result.append(`,shadow-tls-version=${version}`);
|
||||||
}
|
}
|
||||||
|
// udp-port
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,udp-port=${proxy['udp-port']}`,
|
||||||
|
'udp-port',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -188,6 +188,10 @@ export default function URI_Producer() {
|
|||||||
if (proxy.flow) {
|
if (proxy.flow) {
|
||||||
flow = `&flow=${encodeURIComponent(proxy.flow)}`;
|
flow = `&flow=${encodeURIComponent(proxy.flow)}`;
|
||||||
}
|
}
|
||||||
|
let extra = '';
|
||||||
|
if (proxy.extra) {
|
||||||
|
extra = `&extra=${encodeURIComponent(proxy.extra)}`;
|
||||||
|
}
|
||||||
let vlessType = proxy.network;
|
let vlessType = proxy.network;
|
||||||
if (
|
if (
|
||||||
proxy.network === 'ws' &&
|
proxy.network === 'ws' &&
|
||||||
@@ -254,7 +258,7 @@ export default function URI_Producer() {
|
|||||||
proxy.port
|
proxy.port
|
||||||
}?security=${encodeURIComponent(
|
}?security=${encodeURIComponent(
|
||||||
security,
|
security,
|
||||||
)}${vlessTransport}${alpn}${allowInsecure}${sni}${fp}${flow}${sid}${pbk}#${encodeURIComponent(
|
)}${vlessTransport}${alpn}${allowInsecure}${sni}${fp}${flow}${sid}${pbk}${extra}#${encodeURIComponent(
|
||||||
proxy.name,
|
proxy.name,
|
||||||
)}`;
|
)}`;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -174,6 +174,14 @@ async function doSync() {
|
|||||||
|
|
||||||
const resp = await syncToGist(files);
|
const resp = await syncToGist(files);
|
||||||
const body = JSON.parse(resp.body);
|
const body = JSON.parse(resp.body);
|
||||||
|
delete body.history;
|
||||||
|
delete body.forks;
|
||||||
|
delete body.owner;
|
||||||
|
Object.values(body.files).forEach((file) => {
|
||||||
|
delete file.content;
|
||||||
|
});
|
||||||
|
$.info('上传配置响应:');
|
||||||
|
$.info(JSON.stringify(body, null, 2));
|
||||||
|
|
||||||
for (const artifact of allArtifacts) {
|
for (const artifact of allArtifacts) {
|
||||||
if (artifact.sync) {
|
if (artifact.sync) {
|
||||||
|
|||||||
@@ -50,11 +50,21 @@ function createCollection(req, res) {
|
|||||||
|
|
||||||
function getCollection(req, res) {
|
function getCollection(req, res) {
|
||||||
let { name } = req.params;
|
let { name } = req.params;
|
||||||
|
let { raw } = req.query;
|
||||||
name = decodeURIComponent(name);
|
name = decodeURIComponent(name);
|
||||||
const allCols = $.read(COLLECTIONS_KEY);
|
const allCols = $.read(COLLECTIONS_KEY);
|
||||||
const collection = findByName(allCols, name);
|
const collection = findByName(allCols, name);
|
||||||
if (collection) {
|
if (collection) {
|
||||||
success(res, collection);
|
if (raw) {
|
||||||
|
res.set('content-type', 'application/json')
|
||||||
|
.set(
|
||||||
|
'content-disposition',
|
||||||
|
`attachment; filename="${encodeURIComponent(name)}.json"`,
|
||||||
|
)
|
||||||
|
.send(JSON.stringify(collection));
|
||||||
|
} else {
|
||||||
|
success(res, collection);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
failed(
|
failed(
|
||||||
res,
|
res,
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { getPlatformFromHeaders } from '@/utils/user-agent';
|
import {
|
||||||
|
getPlatformFromHeaders,
|
||||||
|
shouldIncludeUnsupportedProxy,
|
||||||
|
} from '@/utils/user-agent';
|
||||||
import { ProxyUtils } from '@/core/proxy-utils';
|
import { ProxyUtils } from '@/core/proxy-utils';
|
||||||
import { COLLECTIONS_KEY, SUBS_KEY } from '@/constants';
|
import { COLLECTIONS_KEY, SUBS_KEY } from '@/constants';
|
||||||
import { findByName } from '@/utils/database';
|
import { findByName } from '@/utils/database';
|
||||||
@@ -13,11 +16,44 @@ 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/:target', async (req, res) => {
|
||||||
|
const { target } = req.params;
|
||||||
|
if (target) {
|
||||||
|
req.query.target = target;
|
||||||
|
$.info(`使用路由指定目标: ${target}`);
|
||||||
|
}
|
||||||
|
await downloadCollection(req, res);
|
||||||
|
});
|
||||||
$app.get('/share/col/:name', downloadCollection);
|
$app.get('/share/col/:name', downloadCollection);
|
||||||
|
$app.get('/share/sub/:name/:target', async (req, res) => {
|
||||||
|
const { target } = req.params;
|
||||||
|
if (target) {
|
||||||
|
req.query.target = target;
|
||||||
|
$.info(`使用路由指定目标: ${target}`);
|
||||||
|
}
|
||||||
|
await downloadSubscription(req, res);
|
||||||
|
});
|
||||||
$app.get('/share/sub/:name', downloadSubscription);
|
$app.get('/share/sub/:name', downloadSubscription);
|
||||||
|
|
||||||
|
$app.get('/download/collection/:name/:target', async (req, res) => {
|
||||||
|
const { target } = req.params;
|
||||||
|
if (target) {
|
||||||
|
req.query.target = target;
|
||||||
|
$.info(`使用路由指定目标: ${target}`);
|
||||||
|
}
|
||||||
|
await downloadCollection(req, res);
|
||||||
|
});
|
||||||
$app.get('/download/collection/:name', downloadCollection);
|
$app.get('/download/collection/:name', downloadCollection);
|
||||||
|
$app.get('/download/:name/:target', async (req, res) => {
|
||||||
|
const { target } = req.params;
|
||||||
|
if (target) {
|
||||||
|
req.query.target = target;
|
||||||
|
$.info(`使用路由指定目标: ${target}`);
|
||||||
|
}
|
||||||
|
await downloadSubscription(req, res);
|
||||||
|
});
|
||||||
$app.get('/download/:name', downloadSubscription);
|
$app.get('/download/:name', downloadSubscription);
|
||||||
|
|
||||||
$app.get(
|
$app.get(
|
||||||
'/download/collection/:name/api/v1/server/details',
|
'/download/collection/:name/api/v1/server/details',
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
@@ -59,11 +95,9 @@ async function downloadSubscription(req, res) {
|
|||||||
|
|
||||||
const platform =
|
const platform =
|
||||||
req.query.target || getPlatformFromHeaders(req.headers) || 'JSON';
|
req.query.target || getPlatformFromHeaders(req.headers) || 'JSON';
|
||||||
|
const reqUA = req.headers['user-agent'] || req.headers['User-Agent'];
|
||||||
$.info(
|
$.info(
|
||||||
`正在下载订阅:${name}\n请求 User-Agent: ${
|
`正在下载订阅:${name}\n请求 User-Agent: ${reqUA}\n请求 target: ${req.query.target}\n实际输出: ${platform}`,
|
||||||
req.headers['user-agent'] || req.headers['User-Agent']
|
|
||||||
}`,
|
|
||||||
);
|
);
|
||||||
let {
|
let {
|
||||||
url,
|
url,
|
||||||
@@ -98,6 +132,14 @@ async function downloadSubscription(req, res) {
|
|||||||
if (url) {
|
if (url) {
|
||||||
url = decodeURIComponent(url);
|
url = decodeURIComponent(url);
|
||||||
$.info(`指定远程订阅 URL: ${url}`);
|
$.info(`指定远程订阅 URL: ${url}`);
|
||||||
|
if (!/^https?:\/\//.test(url)) {
|
||||||
|
content = url;
|
||||||
|
$.info(`URL 不是链接,视为本地订阅`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (content) {
|
||||||
|
content = decodeURIComponent(content);
|
||||||
|
$.info(`指定本地订阅: ${content}`);
|
||||||
}
|
}
|
||||||
if (proxy) {
|
if (proxy) {
|
||||||
proxy = decodeURIComponent(proxy);
|
proxy = decodeURIComponent(proxy);
|
||||||
@@ -107,10 +149,7 @@ async function downloadSubscription(req, res) {
|
|||||||
ua = decodeURIComponent(ua);
|
ua = decodeURIComponent(ua);
|
||||||
$.info(`指定远程订阅 User-Agent: ${ua}`);
|
$.info(`指定远程订阅 User-Agent: ${ua}`);
|
||||||
}
|
}
|
||||||
if (content) {
|
|
||||||
content = decodeURIComponent(content);
|
|
||||||
$.info(`指定本地订阅: ${content}`);
|
|
||||||
}
|
|
||||||
if (mergeSources) {
|
if (mergeSources) {
|
||||||
mergeSources = decodeURIComponent(mergeSources);
|
mergeSources = decodeURIComponent(mergeSources);
|
||||||
$.info(`指定合并来源: ${mergeSources}`);
|
$.info(`指定合并来源: ${mergeSources}`);
|
||||||
@@ -125,7 +164,19 @@ async function downloadSubscription(req, res) {
|
|||||||
}
|
}
|
||||||
if (includeUnsupportedProxy) {
|
if (includeUnsupportedProxy) {
|
||||||
includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy);
|
includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy);
|
||||||
$.info(`包含不支持的节点: ${includeUnsupportedProxy}`);
|
$.info(
|
||||||
|
`包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!includeUnsupportedProxy &&
|
||||||
|
shouldIncludeUnsupportedProxy(platform, reqUA)
|
||||||
|
) {
|
||||||
|
includeUnsupportedProxy = true;
|
||||||
|
$.info(
|
||||||
|
`当前客户端可包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useMihomoExternal) {
|
if (useMihomoExternal) {
|
||||||
@@ -140,6 +191,13 @@ async function downloadSubscription(req, res) {
|
|||||||
const sub = findByName(allSubs, name);
|
const sub = findByName(allSubs, name);
|
||||||
if (sub) {
|
if (sub) {
|
||||||
try {
|
try {
|
||||||
|
const passThroughUA = sub.passThroughUA;
|
||||||
|
if (passThroughUA) {
|
||||||
|
$.info(
|
||||||
|
`订阅开启了透传 User-Agent, 使用请求的 User-Agent: ${reqUA}`,
|
||||||
|
);
|
||||||
|
ua = reqUA;
|
||||||
|
}
|
||||||
let output = await produceArtifact({
|
let output = await produceArtifact({
|
||||||
type: 'subscription',
|
type: 'subscription',
|
||||||
name,
|
name,
|
||||||
@@ -299,11 +357,9 @@ async function downloadCollection(req, res) {
|
|||||||
|
|
||||||
const allCols = $.read(COLLECTIONS_KEY);
|
const allCols = $.read(COLLECTIONS_KEY);
|
||||||
const collection = findByName(allCols, name);
|
const collection = findByName(allCols, name);
|
||||||
|
const reqUA = req.headers['user-agent'] || req.headers['User-Agent'];
|
||||||
$.info(
|
$.info(
|
||||||
`正在下载组合订阅:${name}\n请求 User-Agent: ${
|
`正在下载组合订阅:${name}\n请求 User-Agent: ${reqUA}\n请求 target: ${req.query.target}\n实际输出: ${platform}`,
|
||||||
req.headers['user-agent'] || req.headers['User-Agent']
|
|
||||||
}`,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@@ -350,7 +406,18 @@ async function downloadCollection(req, res) {
|
|||||||
|
|
||||||
if (includeUnsupportedProxy) {
|
if (includeUnsupportedProxy) {
|
||||||
includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy);
|
includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy);
|
||||||
$.info(`包含不支持的节点: ${includeUnsupportedProxy}`);
|
$.info(
|
||||||
|
`包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!includeUnsupportedProxy &&
|
||||||
|
shouldIncludeUnsupportedProxy(platform, reqUA)
|
||||||
|
) {
|
||||||
|
includeUnsupportedProxy = true;
|
||||||
|
$.info(
|
||||||
|
`当前客户端可包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (useMihomoExternal) {
|
if (useMihomoExternal) {
|
||||||
$.info(`手动指定了 target 为 SurgeMac, 将使用 Mihomo External`);
|
$.info(`手动指定了 target 为 SurgeMac, 将使用 Mihomo External`);
|
||||||
@@ -374,6 +441,7 @@ async function downloadCollection(req, res) {
|
|||||||
$options,
|
$options,
|
||||||
proxy,
|
proxy,
|
||||||
noCache,
|
noCache,
|
||||||
|
ua: reqUA,
|
||||||
});
|
});
|
||||||
let subUserInfoOfSub;
|
let subUserInfoOfSub;
|
||||||
// forward flow header from the first subscription in this collection
|
// forward flow header from the first subscription in this collection
|
||||||
@@ -479,13 +547,12 @@ async function downloadCollection(req, res) {
|
|||||||
} else {
|
} else {
|
||||||
subUserInfoOfCol = collection.subUserinfo;
|
subUserInfoOfCol = collection.subUserinfo;
|
||||||
}
|
}
|
||||||
res.set(
|
const subUserInfo = [subUserInfoOfCol, subUserInfoOfSub]
|
||||||
'subscription-userinfo',
|
.filter((i) => i)
|
||||||
[subUserInfoOfCol, subUserInfoOfSub]
|
.join('; ');
|
||||||
.filter((i) => i)
|
if (subUserInfo) {
|
||||||
.join('; '),
|
res.set('subscription-userinfo', subUserInfo);
|
||||||
);
|
}
|
||||||
|
|
||||||
if (platform === 'JSON') {
|
if (platform === 'JSON') {
|
||||||
if (resultFormat === 'nezha') {
|
if (resultFormat === 'nezha') {
|
||||||
output = nezhaTransform(output);
|
output = nezhaTransform(output);
|
||||||
|
|||||||
@@ -197,11 +197,21 @@ async function getFile(req, res) {
|
|||||||
}
|
}
|
||||||
function getWholeFile(req, res) {
|
function getWholeFile(req, res) {
|
||||||
let { name } = req.params;
|
let { name } = req.params;
|
||||||
|
let { raw } = req.query;
|
||||||
name = decodeURIComponent(name);
|
name = decodeURIComponent(name);
|
||||||
const allFiles = $.read(FILES_KEY);
|
const allFiles = $.read(FILES_KEY);
|
||||||
const file = findByName(allFiles, name);
|
const file = findByName(allFiles, name);
|
||||||
if (file) {
|
if (file) {
|
||||||
success(res, file);
|
if (raw) {
|
||||||
|
res.set('content-type', 'application/json')
|
||||||
|
.set(
|
||||||
|
'content-disposition',
|
||||||
|
`attachment; filename="${encodeURIComponent(name)}.json"`,
|
||||||
|
)
|
||||||
|
.send(JSON.stringify(file));
|
||||||
|
} else {
|
||||||
|
success(res, file);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
failed(
|
failed(
|
||||||
res,
|
res,
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ async function previewFile(req, res) {
|
|||||||
const processed =
|
const processed =
|
||||||
Array.isArray(file.process) && file.process.length > 0
|
Array.isArray(file.process) && file.process.length > 0
|
||||||
? await ProxyUtils.process(
|
? await ProxyUtils.process(
|
||||||
{ $files: files, $content: filesContent },
|
{ $files: files, $content: filesContent, $file: file },
|
||||||
file.process,
|
file.process,
|
||||||
)
|
)
|
||||||
: { $content: filesContent, $files: files };
|
: { $content: filesContent, $files: files };
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ async function produceArtifact({
|
|||||||
proxy || sub.proxy,
|
proxy || sub.proxy,
|
||||||
undefined,
|
undefined,
|
||||||
awaitCustomCache,
|
awaitCustomCache,
|
||||||
noCache,
|
noCache || sub.noCache,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -122,7 +122,7 @@ async function produceArtifact({
|
|||||||
proxy || sub.proxy,
|
proxy || sub.proxy,
|
||||||
undefined,
|
undefined,
|
||||||
awaitCustomCache,
|
awaitCustomCache,
|
||||||
noCache,
|
noCache || sub.noCache,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -216,6 +216,14 @@ async function produceArtifact({
|
|||||||
await Promise.all(
|
await Promise.all(
|
||||||
subnames.map(async (name) => {
|
subnames.map(async (name) => {
|
||||||
const sub = findByName(allSubs, name);
|
const sub = findByName(allSubs, name);
|
||||||
|
const passThroughUA = sub.passThroughUA;
|
||||||
|
let reqUA = sub.ua;
|
||||||
|
if (passThroughUA) {
|
||||||
|
$.info(
|
||||||
|
`订阅开启了透传 User-Agent, 使用请求的 User-Agent: ${ua}`,
|
||||||
|
);
|
||||||
|
reqUA = ua;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
$.info(`正在处理子订阅:${sub.name}...`);
|
$.info(`正在处理子订阅:${sub.name}...`);
|
||||||
let raw;
|
let raw;
|
||||||
@@ -237,14 +245,14 @@ async function produceArtifact({
|
|||||||
try {
|
try {
|
||||||
return await download(
|
return await download(
|
||||||
url,
|
url,
|
||||||
sub.ua,
|
reqUA,
|
||||||
undefined,
|
undefined,
|
||||||
proxy ||
|
proxy ||
|
||||||
sub.proxy ||
|
sub.proxy ||
|
||||||
collection.proxy,
|
collection.proxy,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
noCache,
|
noCache || sub.noCache,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -512,7 +520,12 @@ async function produceArtifact({
|
|||||||
const processed =
|
const processed =
|
||||||
Array.isArray(file.process) && file.process.length > 0
|
Array.isArray(file.process) && file.process.length > 0
|
||||||
? await ProxyUtils.process(
|
? await ProxyUtils.process(
|
||||||
{ $files: files, $content: filesContent, $options },
|
{
|
||||||
|
$files: files,
|
||||||
|
$content: filesContent,
|
||||||
|
$options,
|
||||||
|
$file: file,
|
||||||
|
},
|
||||||
file.process,
|
file.process,
|
||||||
)
|
)
|
||||||
: { $content: filesContent, $files: files, $options };
|
: { $content: filesContent, $files: files, $options };
|
||||||
@@ -620,6 +633,15 @@ async function syncArtifacts() {
|
|||||||
const resp = await syncToGist(files);
|
const resp = await syncToGist(files);
|
||||||
const body = JSON.parse(resp.body);
|
const body = JSON.parse(resp.body);
|
||||||
|
|
||||||
|
delete body.history;
|
||||||
|
delete body.forks;
|
||||||
|
delete body.owner;
|
||||||
|
Object.values(body.files).forEach((file) => {
|
||||||
|
delete file.content;
|
||||||
|
});
|
||||||
|
$.info('上传配置响应:');
|
||||||
|
$.info(JSON.stringify(body, null, 2));
|
||||||
|
|
||||||
for (const artifact of allArtifacts) {
|
for (const artifact of allArtifacts) {
|
||||||
if (artifact.sync) {
|
if (artifact.sync) {
|
||||||
artifact.updated = new Date().getTime();
|
artifact.updated = new Date().getTime();
|
||||||
@@ -738,6 +760,16 @@ async function syncArtifact(req, res) {
|
|||||||
});
|
});
|
||||||
artifact.updated = new Date().getTime();
|
artifact.updated = new Date().getTime();
|
||||||
const body = JSON.parse(resp.body);
|
const body = JSON.parse(resp.body);
|
||||||
|
|
||||||
|
delete body.history;
|
||||||
|
delete body.forks;
|
||||||
|
delete body.owner;
|
||||||
|
Object.values(body.files).forEach((file) => {
|
||||||
|
delete file.content;
|
||||||
|
});
|
||||||
|
$.info('上传配置响应:');
|
||||||
|
$.info(JSON.stringify(body, null, 2));
|
||||||
|
|
||||||
let files = body.files;
|
let files = body.files;
|
||||||
let isGitLab;
|
let isGitLab;
|
||||||
if (Array.isArray(files)) {
|
if (Array.isArray(files)) {
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ export default async function download(
|
|||||||
if (preprocess) {
|
if (preprocess) {
|
||||||
try {
|
try {
|
||||||
if (clashPreprocessor.test(body)) {
|
if (clashPreprocessor.test(body)) {
|
||||||
body = clashPreprocessor.parse(body);
|
body = clashPreprocessor.parse(body, true);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
$.error(`Clash Pre-processor error: ${e}`);
|
$.error(`Clash Pre-processor error: ${e}`);
|
||||||
|
|||||||
@@ -114,15 +114,17 @@ export default class Gist {
|
|||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return this.http.get('/gists').then((response) => {
|
return this.http
|
||||||
const gists = JSON.parse(response.body);
|
.get('/gists?per_page=100&page=1')
|
||||||
for (let g of gists) {
|
.then((response) => {
|
||||||
if (g.description === this.key) {
|
const gists = JSON.parse(response.body);
|
||||||
return g;
|
for (let g of gists) {
|
||||||
|
if (g.description === this.key) {
|
||||||
|
return g;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return;
|
||||||
return;
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
import gte from 'semver/functions/gte';
|
||||||
|
import coerce from 'semver/functions/coerce';
|
||||||
|
import $ from '@/core/app';
|
||||||
|
|
||||||
export function getUserAgentFromHeaders(headers) {
|
export function getUserAgentFromHeaders(headers) {
|
||||||
const keys = Object.keys(headers);
|
const keys = Object.keys(headers);
|
||||||
let UA = '';
|
let UA = '';
|
||||||
@@ -56,3 +60,17 @@ export function getPlatformFromHeaders(headers) {
|
|||||||
const { UA, ua, accept } = getUserAgentFromHeaders(headers);
|
const { UA, ua, accept } = getUserAgentFromHeaders(headers);
|
||||||
return getPlatformFromUserAgent({ ua, UA, accept });
|
return getPlatformFromUserAgent({ ua, UA, accept });
|
||||||
}
|
}
|
||||||
|
export function shouldIncludeUnsupportedProxy(platform, ua) {
|
||||||
|
try {
|
||||||
|
const version = coerce(ua).version;
|
||||||
|
if (platform === 'Stash' && gte(version, '2.8.0')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (platform === 'Egern' && gte(version, '1.29.0')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$.error(`获取版本号失败: ${e}`);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
// 13. `test-url` 为测延迟链接, `test-timeout` 为测延迟超时
|
// 13. `test-url` 为测延迟链接, `test-timeout` 为测延迟超时
|
||||||
// 14. `ports` 为端口跳跃, `hop-interval` 变换端口号的时间间隔
|
// 14. `ports` 为端口跳跃, `hop-interval` 变换端口号的时间间隔
|
||||||
// 15. `ip-version` 设置节点使用 IP 版本,可选:dual,ipv4,ipv6,ipv4-prefer,ipv6-prefer. 会进行内部转换, 若无法匹配则使用原始值
|
// 15. `ip-version` 设置节点使用 IP 版本,可选:dual,ipv4,ipv6,ipv4-prefer,ipv6-prefer. 会进行内部转换, 若无法匹配则使用原始值
|
||||||
|
// 16. `sing-box` 支持使用 `_network` 来设置 `network`, 例如 `tcp`, `udp`
|
||||||
|
|
||||||
// require 为 Node.js 的 require, 在 Node.js 运行环境下 可以用来引入模块
|
// require 为 Node.js 的 require, 在 Node.js 运行环境下 可以用来引入模块
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user