Compare commits

...

8 Commits

Author SHA1 Message Date
xream
23e8fbd1b7 feat: Proxy URI Scheme 支持省略端口号(http 默认为 80, tls 默认为 443) 2025-02-06 14:59:50 +08:00
xream
b94b3c366b feat: Egern 正式支持 Shadowsocks 2022
Some checks are pending
build / build (push) Waiting to run
2025-02-06 00:04:54 +08:00
xream
afb5f7b880 feat: 支持 VLESS spx 参数; 支持 Trojan 结合 REALITY/XHTTP 2025-02-05 20:01:41 +08:00
xream
74ec133a79 feat: Loon 正式支持 Shadow-TLS
Some checks failed
build / build (push) Has been cancelled
2025-02-03 13:47:17 +08:00
xream
2a76eb6462 feat: mihomo snell 版本小于 3 的节点, 强制去除 udp 字段, 防止内核报错
Some checks are pending
build / build (push) Waiting to run
2025-02-02 18:59:14 +08:00
xream
9ac5e136a6 feat: 去除订阅流量信息中空字段, 增强兼容性 2025-02-02 18:39:46 +08:00
xream
38f5a97a20 fix: 修复 Surge 输入的 tfo
Some checks failed
build / build (push) Has been cancelled
2025-01-31 15:14:19 +08:00
xream
14a3488ce2 fix: 修复 Egern 和 Stash 可根据 User-Agent 自动包含官方/商店版/未续费订阅不支持的协议
Some checks failed
build / build (push) Has been cancelled
2025-01-26 20:41:57 +08:00
16 changed files with 178 additions and 60 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "sub-store", "name": "sub-store",
"version": "2.16.20", "version": "2.16.28",
"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": {

View File

@@ -11,6 +11,7 @@ import getSurgeParser from './peggy/surge';
import getLoonParser from './peggy/loon'; import getLoonParser from './peggy/loon';
import getQXParser from './peggy/qx'; import getQXParser from './peggy/qx';
import getTrojanURIParser from './peggy/trojan-uri'; import getTrojanURIParser from './peggy/trojan-uri';
import $ from '@/core/app';
import { Base64 } from 'js-base64'; import { Base64 } from 'js-base64';
@@ -40,8 +41,21 @@ function URI_PROXY() {
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
let [__, type, tls, username, password, server, port, query, name] = let [__, type, tls, username, password, server, port, query, name] =
line.match( line.match(
/^(socks5|http|http)(\+tls|s)?:\/\/(?:(.*?):(.*?)@)?(.*?):(\d+?)(\?.*?)?(?:#(.*?))?$/, /^(socks5|http|http)(\+tls|s)?:\/\/(?:(.*?):(.*?)@)?(.*?)(?::(\d+?))?(\?.*?)?(?:#(.*?))?$/,
); );
if (port) {
port = parseInt(port, 10);
} else {
if (tls) {
port = 443;
} else if (type === 'http') {
port = 80;
} else {
$.error(`port is not present in line: ${line}`);
throw new Error(`port is not present in line: ${line}`);
}
$.info(`port is not present in line: ${line}, set to ${port}`);
}
const proxy = { const proxy = {
name: name:
@@ -529,6 +543,9 @@ function URI_VLESS() {
if (params.sid) { if (params.sid) {
opts['short-id'] = params.sid; opts['short-id'] = params.sid;
} }
if (params.spx) {
opts['_spider-x'] = params.spx;
}
if (Object.keys(opts).length > 0) { if (Object.keys(opts).length > 0) {
// proxy[`${params.security}-opts`] = opts; // proxy[`${params.security}-opts`] = opts;
proxy[`${params.security}-opts`] = opts; proxy[`${params.security}-opts`] = opts;
@@ -596,8 +613,12 @@ 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.mode) {
proxy._mode = params.mode;
}
if (params.extra) { if (params.extra) {
proxy.extra = params.extra; proxy._extra = params.extra;
} }
} }

View File

@@ -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/udp_port/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/tfo/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") {
@@ -52,7 +52,7 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
} }
handleShadowTLS(); handleShadowTLS();
} }
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* { vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls/sni/tls_fingerprint/tls_verification/fast_open/tfo/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "vmess"; proxy.type = "vmess";
proxy.cipher = proxy.cipher || "none"; proxy.cipher = proxy.cipher || "none";
if (proxy.aead) { if (proxy.aead) {
@@ -63,25 +63,25 @@ vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/
handleWebsocket(); handleWebsocket();
handleShadowTLS(); handleShadowTLS();
} }
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/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)* { trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "trojan"; proxy.type = "trojan";
handleWebsocket(); handleWebsocket();
handleShadowTLS(); handleShadowTLS();
} }
https = tag equals "https" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* { https = tag equals "https" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "http"; proxy.type = "http";
proxy.tls = true; proxy.tls = true;
handleShadowTLS(); handleShadowTLS();
} }
http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* { http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "http"; proxy.type = "http";
handleShadowTLS(); handleShadowTLS();
} }
ssh = tag equals "ssh" address (username password)? (usernamek passwordk)? (server_fingerprint/idle_timeout/private_key/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* { ssh = tag equals "ssh" address (username password)? (usernamek passwordk)? (server_fingerprint/idle_timeout/private_key/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "ssh"; proxy.type = "ssh";
handleShadowTLS(); handleShadowTLS();
} }
snell = tag equals "snell" address (snell_version/snell_psk/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/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* { snell = tag equals "snell" address (snell_version/snell_psk/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/tfo/udp_relay/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "snell"; proxy.type = "snell";
// handle obfs // handle obfs
if (obfs.type == "http" || obfs.type === "tls") { if (obfs.type == "http" || obfs.type === "tls") {
@@ -104,20 +104,20 @@ wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/under
proxy.type = "wireguard-surge"; proxy.type = "wireguard-surge";
handleShadowTLS(); handleShadowTLS();
} }
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/port_hopping_interval/others)* { hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/fast_open/tfo/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/port_hopping_interval/others)* {
proxy.type = "hysteria2"; proxy.type = "hysteria2";
handleShadowTLS(); handleShadowTLS();
} }
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* { socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "socks5"; proxy.type = "socks5";
handleShadowTLS(); handleShadowTLS();
} }
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_fingerprint/tls_verification/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* { socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_fingerprint/tls_verification/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "socks5"; proxy.type = "socks5";
proxy.tls = true; proxy.tls = true;
handleShadowTLS(); handleShadowTLS();
} }
direct = tag equals "direct" (udp_relay/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/block_quic/others)* { direct = tag equals "direct" (udp_relay/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/block_quic/others)* {
proxy.type = "direct"; proxy.type = "direct";
} }

View File

@@ -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/udp_port/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/tfo/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") {
@@ -50,7 +50,7 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
} }
handleShadowTLS(); handleShadowTLS();
} }
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* { vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls/sni/tls_fingerprint/tls_verification/fast_open/tfo/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "vmess"; proxy.type = "vmess";
proxy.cipher = proxy.cipher || "none"; proxy.cipher = proxy.cipher || "none";
if (proxy.aead) { if (proxy.aead) {
@@ -61,25 +61,25 @@ vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/
handleWebsocket(); handleWebsocket();
handleShadowTLS(); handleShadowTLS();
} }
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/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)* { trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "trojan"; proxy.type = "trojan";
handleWebsocket(); handleWebsocket();
handleShadowTLS(); handleShadowTLS();
} }
https = tag equals "https" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* { https = tag equals "https" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "http"; proxy.type = "http";
proxy.tls = true; proxy.tls = true;
handleShadowTLS(); handleShadowTLS();
} }
http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* { http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "http"; proxy.type = "http";
handleShadowTLS(); handleShadowTLS();
} }
ssh = tag equals "ssh" address (username password)? (usernamek passwordk)? (server_fingerprint/idle_timeout/private_key/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* { ssh = tag equals "ssh" address (username password)? (usernamek passwordk)? (server_fingerprint/idle_timeout/private_key/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "ssh"; proxy.type = "ssh";
handleShadowTLS(); handleShadowTLS();
} }
snell = tag equals "snell" address (snell_version/snell_psk/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/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* { snell = tag equals "snell" address (snell_version/snell_psk/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/tfo/udp_relay/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "snell"; proxy.type = "snell";
// handle obfs // handle obfs
if (obfs.type == "http" || obfs.type === "tls") { if (obfs.type == "http" || obfs.type === "tls") {
@@ -106,16 +106,16 @@ hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying
proxy.type = "hysteria2"; proxy.type = "hysteria2";
handleShadowTLS(); handleShadowTLS();
} }
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* { socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "socks5"; proxy.type = "socks5";
handleShadowTLS(); handleShadowTLS();
} }
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_fingerprint/tls_verification/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* { socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_fingerprint/tls_verification/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "socks5"; proxy.type = "socks5";
proxy.tls = true; proxy.tls = true;
handleShadowTLS(); handleShadowTLS();
} }
direct = tag equals "direct" (udp_relay/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/block_quic/others)* { direct = tag equals "direct" (udp_relay/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/block_quic/others)* {
proxy.type = "direct"; proxy.type = "direct";
} }
address = comma server:server comma port:port { address = comma server:server comma port:port {

View File

@@ -80,6 +80,9 @@ port = digits:[0-9]+ {
} }
params = "?" head:param tail:("&"@param)* { params = "?" head:param tail:("&"@param)* {
for (const [key, value] of Object.entries(params)) {
params[key] = decodeURIComponent(value);
}
proxy["skip-cert-verify"] = toBool(params["allowInsecure"]); proxy["skip-cert-verify"] = toBool(params["allowInsecure"]);
proxy.sni = params["sni"] || params["peer"]; proxy.sni = params["sni"] || params["peer"];
proxy['client-fingerprint'] = params.fp; proxy['client-fingerprint'] = params.fp;
@@ -115,6 +118,27 @@ params = "?" head:param tail:("&"@param)* {
$set(proxy, proxy.network+"-opts.v2ray-http-upgrade-fast-open", true); $set(proxy, proxy.network+"-opts.v2ray-http-upgrade-fast-open", true);
} }
} }
if (['reality'].includes(params.security)) {
const opts = {};
if (params.pbk) {
opts['public-key'] = params.pbk;
}
if (params.sid) {
opts['short-id'] = params.sid;
}
if (params.spx) {
opts['_spider-x'] = params.spx;
}
if (params.mode) {
proxy._mode = params.mode;
}
if (params.extra) {
proxy._extra = params.extra;
}
if (Object.keys(opts).length > 0) {
$set(proxy, params.security+"-opts", opts);
}
}
} }
proxy.udp = toBool(params["udp"]); proxy.udp = toBool(params["udp"]);

View File

@@ -78,6 +78,9 @@ port = digits:[0-9]+ {
} }
params = "?" head:param tail:("&"@param)* { params = "?" head:param tail:("&"@param)* {
for (const [key, value] of Object.entries(params)) {
params[key] = decodeURIComponent(value);
}
proxy["skip-cert-verify"] = toBool(params["allowInsecure"]); proxy["skip-cert-verify"] = toBool(params["allowInsecure"]);
proxy.sni = params["sni"] || params["peer"]; proxy.sni = params["sni"] || params["peer"];
proxy['client-fingerprint'] = params.fp; proxy['client-fingerprint'] = params.fp;
@@ -113,6 +116,27 @@ params = "?" head:param tail:("&"@param)* {
$set(proxy, proxy.network+"-opts.v2ray-http-upgrade-fast-open", true); $set(proxy, proxy.network+"-opts.v2ray-http-upgrade-fast-open", true);
} }
} }
if (['reality'].includes(params.security)) {
const opts = {};
if (params.pbk) {
opts['public-key'] = params.pbk;
}
if (params.sid) {
opts['short-id'] = params.sid;
}
if (params.spx) {
opts['_spider-x'] = params.spx;
}
if (params.mode) {
proxy._mode = params.mode;
}
if (params.extra) {
proxy._extra = params.extra;
}
if (Object.keys(opts).length > 0) {
$set(proxy, params.security+"-opts", opts);
}
}
} }
proxy.udp = toBool(params["udp"]); proxy.udp = toBool(params["udp"]);

View File

@@ -81,6 +81,8 @@ export default function Clash_Producer() {
proxy['preshared-key'] = proxy['preshared-key'] =
proxy['preshared-key'] ?? proxy['pre-shared-key']; proxy['preshared-key'] ?? proxy['pre-shared-key'];
proxy['pre-shared-key'] = proxy['preshared-key']; proxy['pre-shared-key'] = proxy['preshared-key'];
} else if (proxy.type === 'snell' && proxy.version < 3) {
delete proxy.udp;
} else if (proxy.type === 'vless') { } else if (proxy.type === 'vless') {
if (isPresent(proxy, 'sni')) { if (isPresent(proxy, 'sni')) {
proxy.servername = proxy.sni; proxy.servername = proxy.sni;

View File

@@ -86,6 +86,8 @@ export default function ClashMeta_Producer() {
proxy['preshared-key'] = proxy['preshared-key'] =
proxy['preshared-key'] ?? proxy['pre-shared-key']; proxy['preshared-key'] ?? proxy['pre-shared-key'];
proxy['pre-shared-key'] = proxy['preshared-key']; proxy['pre-shared-key'] = proxy['preshared-key'];
} else if (proxy.type === 'snell' && proxy.version < 3) {
delete proxy.udp;
} else if (proxy.type === 'vless') { } else if (proxy.type === 'vless') {
if (isPresent(proxy, 'sni')) { if (isPresent(proxy, 'sni')) {
proxy.servername = proxy.sni; proxy.servername = proxy.sni;

View File

@@ -48,12 +48,8 @@ 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',
'2022-blake3-aes-128-gcm',
'2022-blake3-aes-256-gcm',
]
: []),
].includes(proxy.cipher))) || ].includes(proxy.cipher))) ||
(proxy.type === 'vmess' && (proxy.type === 'vmess' &&
(![ (![

View File

@@ -15,9 +15,9 @@ 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);
case 'ssr': case 'ssr':
return shadowsocksr(proxy, opts['include-unsupported-proxy']); return shadowsocksr(proxy);
case 'trojan': case 'trojan':
return trojan(proxy); return trojan(proxy);
case 'vmess': case 'vmess':
@@ -40,7 +40,7 @@ export default function Loon_Producer() {
return { produce }; return { produce };
} }
function shadowsocks(proxy, includeUnsupportedProxy) { function shadowsocks(proxy) {
const result = new Result(proxy); const result = new Result(proxy);
if ( if (
![ ![
@@ -74,8 +74,6 @@ 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') {
@@ -107,7 +105,6 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
); );
// udp-port // udp-port
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port'); result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
isShadowTLS = true;
} 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;
@@ -130,7 +127,6 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
`,udp-port=${proxy['udp-port']}`, `,udp-port=${proxy['udp-port']}`,
'udp-port', 'udp-port',
); );
isShadowTLS = true;
} }
} }
@@ -142,11 +138,6 @@ 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']; const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version'); result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
@@ -170,8 +161,6 @@ function shadowsocksr(proxy, includeUnsupportedProxy) {
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 // shadow-tls
if (isPresent(proxy, 'shadow-tls-password')) { if (isPresent(proxy, 'shadow-tls-password')) {
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`); result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
@@ -186,7 +175,6 @@ function shadowsocksr(proxy, includeUnsupportedProxy) {
); );
// udp-port // udp-port
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port'); result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
isShadowTLS = true;
} 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;
@@ -209,7 +197,6 @@ function shadowsocksr(proxy, includeUnsupportedProxy) {
`,udp-port=${proxy['udp-port']}`, `,udp-port=${proxy['udp-port']}`,
'udp-port', 'udp-port',
); );
isShadowTLS = true;
} }
} }
@@ -221,11 +208,6 @@ function shadowsocksr(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']; const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version'); result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');

View File

@@ -102,6 +102,8 @@ export default function ShadowRocket_Producer() {
proxy['preshared-key'] = proxy['preshared-key'] =
proxy['preshared-key'] ?? proxy['pre-shared-key']; proxy['preshared-key'] ?? proxy['pre-shared-key'];
proxy['pre-shared-key'] = proxy['preshared-key']; proxy['pre-shared-key'] = proxy['preshared-key'];
} else if (proxy.type === 'snell' && proxy.version < 3) {
delete proxy.udp;
} else if (proxy.type === 'vless') { } else if (proxy.type === 'vless') {
if (isPresent(proxy, 'sni')) { if (isPresent(proxy, 'sni')) {
proxy.servername = proxy.sni; proxy.servername = proxy.sni;

View File

@@ -187,6 +187,8 @@ export default function Stash_Producer() {
proxy['preshared-key'] = proxy['preshared-key'] =
proxy['preshared-key'] ?? proxy['pre-shared-key']; proxy['preshared-key'] ?? proxy['pre-shared-key'];
proxy['pre-shared-key'] = proxy['preshared-key']; proxy['pre-shared-key'] = proxy['preshared-key'];
} else if (proxy.type === 'snell' && proxy.version < 3) {
delete proxy.udp;
} else if (proxy.type === 'vless') { } else if (proxy.type === 'vless') {
if (isPresent(proxy, 'sni')) { if (isPresent(proxy, 'sni')) {
proxy.servername = proxy.sni; proxy.servername = proxy.sni;

View File

@@ -149,6 +149,7 @@ export default function URI_Producer() {
const isReality = proxy['reality-opts']; const isReality = proxy['reality-opts'];
let sid = ''; let sid = '';
let pbk = ''; let pbk = '';
let spx = '';
if (isReality) { if (isReality) {
security = 'reality'; security = 'reality';
const publicKey = proxy['reality-opts']?.['public-key']; const publicKey = proxy['reality-opts']?.['public-key'];
@@ -159,6 +160,10 @@ export default function URI_Producer() {
if (shortId) { if (shortId) {
sid = `&sid=${encodeURIComponent(shortId)}`; sid = `&sid=${encodeURIComponent(shortId)}`;
} }
const spiderX = proxy['reality-opts']?.['_spider-x'];
if (spiderX) {
spx = `&spx=${encodeURIComponent(spiderX)}`;
}
} else if (proxy.tls) { } else if (proxy.tls) {
security = 'tls'; security = 'tls';
} }
@@ -189,8 +194,12 @@ export default function URI_Producer() {
flow = `&flow=${encodeURIComponent(proxy.flow)}`; flow = `&flow=${encodeURIComponent(proxy.flow)}`;
} }
let extra = ''; let extra = '';
if (proxy.extra) { if (proxy._extra) {
extra = `&extra=${encodeURIComponent(proxy.extra)}`; extra = `&extra=${encodeURIComponent(proxy._extra)}`;
}
let mode = '';
if (proxy._mode) {
mode = `&mode=${encodeURIComponent(proxy._mode)}`;
} }
let vlessType = proxy.network; let vlessType = proxy.network;
if ( if (
@@ -258,7 +267,7 @@ export default function URI_Producer() {
proxy.port proxy.port
}?security=${encodeURIComponent( }?security=${encodeURIComponent(
security, security,
)}${vlessTransport}${alpn}${allowInsecure}${sni}${fp}${flow}${sid}${pbk}${extra}#${encodeURIComponent( )}${vlessTransport}${alpn}${allowInsecure}${sni}${fp}${flow}${sid}${spx}${pbk}${mode}${extra}#${encodeURIComponent(
proxy.name, proxy.name,
)}`; )}`;
break; break;
@@ -328,11 +337,41 @@ export default function URI_Producer() {
: proxy.alpn.join(','), : proxy.alpn.join(','),
)}`; )}`;
} }
const trojanIsReality = proxy['reality-opts'];
let trojanSid = '';
let trojanPbk = '';
let trojanSpx = '';
let trojanSecurity = '';
let trojanMode = '';
let trojanExtra = '';
if (trojanIsReality) {
trojanSecurity = `&security=reality`;
const publicKey = proxy['reality-opts']?.['public-key'];
if (publicKey) {
trojanPbk = `&pbk=${encodeURIComponent(publicKey)}`;
}
const shortId = proxy['reality-opts']?.['short-id'];
if (shortId) {
trojanSid = `&sid=${encodeURIComponent(shortId)}`;
}
const spiderX = proxy['reality-opts']?.['_spider-x'];
if (spiderX) {
trojanSpx = `&spx=${encodeURIComponent(spiderX)}`;
}
if (proxy._extra) {
trojanExtra = `&extra=${encodeURIComponent(
proxy._extra,
)}`;
}
if (proxy._mode) {
trojanMode = `&mode=${encodeURIComponent(proxy._mode)}`;
}
}
result = `trojan://${proxy.password}@${proxy.server}:${ result = `trojan://${proxy.password}@${proxy.server}:${
proxy.port proxy.port
}?sni=${encodeURIComponent(proxy.sni || proxy.server)}${ }?sni=${encodeURIComponent(proxy.sni || proxy.server)}${
proxy['skip-cert-verify'] ? '&allowInsecure=1' : '' proxy['skip-cert-verify'] ? '&allowInsecure=1' : ''
}${trojanTransport}${trojanAlpn}${trojanFp}#${encodeURIComponent( }${trojanTransport}${trojanAlpn}${trojanFp}${trojanSecurity}${trojanSid}${trojanPbk}${trojanSpx}${trojanMode}${trojanExtra}#${encodeURIComponent(
proxy.name, proxy.name,
)}`; )}`;
break; break;

View File

@@ -293,7 +293,10 @@ async function downloadSubscription(req, res) {
} }
res.set( res.set(
'subscription-userinfo', 'subscription-userinfo',
[subUserInfo, flowInfo].filter((i) => i).join('; '), [subUserInfo, flowInfo]
.filter((i) => i)
.join('; ')
.replace(/\s*;\s*;\s*/g, ';'),
); );
} }
@@ -551,7 +554,10 @@ async function downloadCollection(req, res) {
.filter((i) => i) .filter((i) => i)
.join('; '); .join('; ');
if (subUserInfo) { if (subUserInfo) {
res.set('subscription-userinfo', subUserInfo); res.set(
'subscription-userinfo',
subUserInfo.replace(/\s*;\s*;\s*/g, ';'),
);
} }
if (platform === 'JSON') { if (platform === 'JSON') {
if (resultFormat === 'nezha') { if (resultFormat === 'nezha') {

View File

@@ -146,7 +146,10 @@ async function getFile(req, res) {
proxy || file.proxy, proxy || file.proxy,
); );
if (flowInfo) { if (flowInfo) {
res.set('subscription-userinfo', flowInfo); res.set(
'subscription-userinfo',
flowInfo.replace(/\s*;\s*;\s*/g, ';'),
);
} }
} }
} catch (err) { } catch (err) {

View File

@@ -62,11 +62,26 @@ export function getPlatformFromHeaders(headers) {
} }
export function shouldIncludeUnsupportedProxy(platform, ua) { export function shouldIncludeUnsupportedProxy(platform, ua) {
try { try {
const target = getPlatformFromUserAgent({
UA: ua,
ua: ua.toLowerCase(),
});
if (!['Stash', 'Egern'].includes(target)) {
return false;
}
const version = coerce(ua).version; const version = coerce(ua).version;
if (platform === 'Stash' && gte(version, '2.8.0')) { if (
platform === 'Stash' &&
target === 'Stash' &&
gte(version, '2.8.0')
) {
return true; return true;
} }
if (platform === 'Egern' && gte(version, '1.29.0')) { if (
platform === 'Egern' &&
target === 'Egern' &&
gte(version, '1.29.0')
) {
return true; return true;
} }
} catch (e) { } catch (e) {