Compare commits

...

16 Commits

Author SHA1 Message Date
xream
4a2a2297f6 feat: Shadowsocks URI 支持 Shadow TLS plugin 2025-02-10 06:32:17 +08:00
xream
07d5a913f0 feat: 同步配置逻辑优化
Some checks are pending
build / build (push) Waiting to run
2025-02-09 20:58:27 +08:00
xream
421df8f0d4 doc: README 2025-02-07 19:43:06 +08:00
xream
e14944dd19 feat: 调整 Egern VMess security 逻辑
Some checks failed
build / build (push) Has been cancelled
2025-02-06 18:18:15 +08:00
xream
bf18c51f6a feat: mihomo 和 Shadowrocket VMess cipher 支持 zero 2025-02-06 18:08:46 +08:00
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
xream
6afec4f668 feat: Egern 增加 TUIC
Some checks failed
build / build (push) Has been cancelled
2025-01-23 08:22:48 +08:00
xream
b1874e510d feat: 支持 VLESS XHTTP extra
Some checks are pending
build / build (push) Waiting to run
2025-01-22 09:43:43 +08:00
xream
48aaaf5c99 doc: README 2025-01-21 12:02:49 +08:00
20 changed files with 302 additions and 93 deletions

View File

@@ -26,9 +26,9 @@ Core functionalities:
### Supported Input Formats ### Supported Input Formats
> ⚠️ Do not use `Shadowrocket` to export URI and then import it as input. It is not a standard URI. > ⚠️ Do not use `Shadowrocket` or `NekoBox` to export URI and then import it as input. The URIs exported in this way may not be standard URIs.
- [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`

View File

@@ -5,7 +5,7 @@
* ╚════██║██║ ██║██╔══██╗╚════╝╚════██║ ██║ ██║ ██║██╔══██╗██╔══╝ * ╚════██║██║ ██║██╔══██╗╚════╝╚════██║ ██║ ██║ ██║██╔══██╗██╔══╝
* ███████║╚██████╔╝██████╔╝ ███████║ ██║ ╚██████╔╝██║ ██║███████╗ * ███████║╚██████╔╝██████╔╝ ███████║ ██║ ╚██████╔╝██║ ██║███████╗
* ╚══════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ * ╚══════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝
* Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket! * Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket!
* @updated: <%= updated %> * @updated: <%= updated %>
* @version: <%= pkg.version %> * @version: <%= pkg.version %>
* @author: Peng-YM * @author: Peng-YM

View File

@@ -1,7 +1,7 @@
{ {
"name": "sub-store", "name": "sub-store",
"version": "2.16.18", "version": "2.16.32",
"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": {
"preinstall": "npx only-allow pnpm", "preinstall": "npx only-allow pnpm",

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:
@@ -132,11 +146,10 @@ function URI_SS() {
// } // }
// handle obfs // handle obfs
const idx = content.indexOf('?plugin='); const pluginMatch = content.match(/[?&]plugin=([^&]+)/);
if (idx !== -1) { if (pluginMatch) {
const pluginInfo = ( const pluginInfo = (
'plugin=' + 'plugin=' + decodeURIComponent(pluginMatch[1])
decodeURIComponent(content.split('?plugin=')[1].split('&')[0])
).split(';'); ).split(';');
const params = {}; const params = {};
for (const item of pluginInfo) { for (const item of pluginInfo) {
@@ -161,6 +174,16 @@ function URI_SS() {
tls: getIfPresent(params.tls), tls: getIfPresent(params.tls),
}; };
break; break;
case 'shadow-tls': {
proxy.plugin = 'shadow-tls';
const version = getIfNotBlank(params['version']);
proxy['plugin-opts'] = {
host: getIfNotBlank(params['host']),
password: getIfNotBlank(params['password']),
version: version ? parseInt(version, 10) : undefined,
};
break;
}
default: default:
throw new Error( throw new Error(
`Unsupported plugin option: ${params.plugin}`, `Unsupported plugin option: ${params.plugin}`,
@@ -529,6 +552,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,6 +622,13 @@ 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) {
proxy._extra = params.extra;
}
} }
return proxy; return proxy;

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

@@ -32,9 +32,10 @@ export default function ClashMeta_Producer() {
isPresent(proxy, 'cipher') && isPresent(proxy, 'cipher') &&
![ ![
'auto', 'auto',
'none',
'zero',
'aes-128-gcm', 'aes-128-gcm',
'chacha20-poly1305', 'chacha20-poly1305',
'none',
].includes(proxy.cipher) ].includes(proxy.cipher)
) { ) {
proxy.cipher = 'auto'; proxy.cipher = 'auto';
@@ -86,6 +87,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

@@ -14,6 +14,7 @@ export default function Egern_Producer() {
'hysteria2', 'hysteria2',
'vless', 'vless',
'vmess', 'vmess',
'tuic',
].includes(proxy.type) || ].includes(proxy.type) ||
(proxy.type === 'ss' && (proxy.type === 'ss' &&
((proxy.plugin === 'obfs' && ((proxy.plugin === 'obfs' &&
@@ -47,23 +48,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',
'2022-blake3-aes-128-gcm',
'2022-blake3-aes-256-gcm',
]
: []),
].includes(proxy.cipher))) || ].includes(proxy.cipher))) ||
(proxy.type === 'vmess' && (proxy.type === 'vmess' &&
(![ !['http', 'ws', 'tcp'].includes(proxy.network) &&
'auto', proxy.network) ||
'aes-128-gcm',
'chacha20-poly1305',
'none',
'zero',
].includes(proxy.cipher) ||
(!['http', 'ws', 'tcp'].includes(proxy.network) &&
proxy.network))) ||
(proxy.type === 'trojan' && (proxy.type === 'trojan' &&
!['http', 'ws', 'tcp'].includes(proxy.network) && !['http', 'ws', 'tcp'].includes(proxy.network) &&
proxy.network) || proxy.network) ||
@@ -71,7 +61,10 @@ export default function Egern_Producer() {
(typeof proxy.flow !== 'undefined' || (typeof proxy.flow !== 'undefined' ||
proxy['reality-opts'] || proxy['reality-opts'] ||
(!['http', 'ws', 'tcp'].includes(proxy.network) && (!['http', 'ws', 'tcp'].includes(proxy.network) &&
proxy.network))) proxy.network))) ||
(proxy.type === 'tuic' &&
proxy.token &&
proxy.token.length !== 0)
) { ) {
return false; return false;
} }
@@ -152,6 +145,23 @@ export default function Egern_Producer() {
proxy.obfs = 'salamander'; proxy.obfs = 'salamander';
proxy.obfs_password = proxy['obfs-password']; proxy.obfs_password = proxy['obfs-password'];
} }
} else if (proxy.type === 'tuic') {
proxy = {
type: 'tuic',
name: proxy.name,
server: proxy.server,
port: proxy.port,
uuid: proxy.uuid,
password: proxy.password,
next_hop: proxy.next_hop,
sni: proxy.sni,
alpn: Array.isArray(proxy.alpn)
? proxy.alpn
: [proxy.alpn || 'h3'],
skip_tls_verify: proxy['skip-cert-verify'],
port_hopping: proxy.ports,
port_hopping_interval: proxy['hop-interval'],
};
} else if (proxy.type === 'trojan') { } else if (proxy.type === 'trojan') {
if (proxy.network === 'ws') { if (proxy.network === 'ws') {
proxy.websocket = { proxy.websocket = {
@@ -174,6 +184,19 @@ export default function Egern_Producer() {
websocket: proxy.websocket, websocket: proxy.websocket,
}; };
} else if (proxy.type === 'vmess') { } else if (proxy.type === 'vmess') {
let security = proxy.cipher;
if (
security &&
![
'auto',
'none',
'zero',
'aes-128-gcm',
'chacha20-poly1305',
].includes(security)
) {
security = 'auto';
}
if (proxy.network === 'ws') { if (proxy.network === 'ws') {
proxy.transport = { proxy.transport = {
[proxy.tls ? 'wss' : 'ws']: { [proxy.tls ? 'wss' : 'ws']: {
@@ -218,7 +241,7 @@ export default function Egern_Producer() {
server: proxy.server, server: proxy.server,
port: proxy.port, port: proxy.port,
user_id: proxy.uuid, user_id: proxy.uuid,
security: proxy.cipher, security,
tfo: proxy.tfo || proxy['fast-open'], tfo: proxy.tfo || proxy['fast-open'],
legacy: proxy.legacy, legacy: proxy.legacy,
udp_relay: udp_relay:

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

@@ -1,6 +1,6 @@
import { isPresent } from '@/core/proxy-utils/producers/utils'; import { isPresent } from '@/core/proxy-utils/producers/utils';
export default function ShadowRocket_Producer() { export default function Shadowrocket_Producer() {
const type = 'ALL'; const type = 'ALL';
const produce = (proxies, type, opts = {}) => { const produce = (proxies, type, opts = {}) => {
const list = proxies const list = proxies
@@ -32,9 +32,10 @@ export default function ShadowRocket_Producer() {
isPresent(proxy, 'cipher') && isPresent(proxy, 'cipher') &&
![ ![
'auto', 'auto',
'none',
'zero',
'aes-128-gcm', 'aes-128-gcm',
'chacha20-poly1305', 'chacha20-poly1305',
'none',
].includes(proxy.cipher) ].includes(proxy.cipher)
) { ) {
proxy.cipher = 'auto'; proxy.cipher = 'auto';
@@ -102,6 +103,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

@@ -54,6 +54,11 @@ export default function URI_Producer() {
}${opts.tls ? ';tls' : ''}`, }${opts.tls ? ';tls' : ''}`,
); );
break; break;
case 'shadow-tls':
result += encodeURIComponent(
`shadow-tls;host=${opts.host};password=${opts.password};version=${opts.version}`,
);
break;
default: default:
throw new Error( throw new Error(
`Unsupported plugin option: ${proxy.plugin}`, `Unsupported plugin option: ${proxy.plugin}`,
@@ -149,6 +154,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 +165,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';
} }
@@ -188,6 +198,14 @@ 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 mode = '';
if (proxy._mode) {
mode = `&mode=${encodeURIComponent(proxy._mode)}`;
}
let vlessType = proxy.network; let vlessType = proxy.network;
if ( if (
proxy.network === 'ws' && proxy.network === 'ws' &&
@@ -254,7 +272,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}${spx}${pbk}${mode}${extra}#${encodeURIComponent(
proxy.name, proxy.name,
)}`; )}`;
break; break;
@@ -324,11 +342,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

@@ -84,6 +84,7 @@ async function doSync() {
const files = {}; const files = {};
try { try {
const valid = [];
const invalid = []; const invalid = [];
const allSubs = $.read(SUBS_KEY); const allSubs = $.read(SUBS_KEY);
const allCols = $.read(COLLECTIONS_KEY); const allCols = $.read(COLLECTIONS_KEY);
@@ -156,19 +157,26 @@ async function doSync() {
files[encodeURIComponent(artifact.name)] = { files[encodeURIComponent(artifact.name)] = {
content: output, content: output,
}; };
valid.push(artifact.name);
} }
} catch (e) { } catch (e) {
$.error( $.error(
`同步配置 ${artifact.name} 发生错误: ${e.message ?? e}`, `生成同步配置 ${artifact.name} 发生错误: ${
e.message ?? e
}`,
); );
invalid.push(artifact.name); invalid.push(artifact.name);
} }
}), }),
); );
if (invalid.length > 0) { $.info(`${valid.length} 个同步配置生成成功: ${valid.join(', ')}`);
$.info(`${invalid.length} 个同步配置生成失败: ${invalid.join(', ')}`);
if (valid.length === 0) {
throw new Error( throw new Error(
`同步配置 ${invalid.join(', ')} 发生错误 详情请查看日志`, `同步配置 ${invalid.join(', ')} 生成失败 详情请查看日志`,
); );
} }
@@ -184,7 +192,11 @@ async function doSync() {
$.info(JSON.stringify(body, null, 2)); $.info(JSON.stringify(body, null, 2));
for (const artifact of allArtifacts) { for (const artifact of allArtifacts) {
if (artifact.sync) { if (
artifact.sync &&
artifact.source &&
valid.includes(artifact.name)
) {
artifact.updated = new Date().getTime(); artifact.updated = new Date().getTime();
// extract real url from gist // extract real url from gist
let files = body.files; let files = body.files;
@@ -212,9 +224,18 @@ async function doSync() {
} }
$.write(allArtifacts, ARTIFACTS_KEY); $.write(allArtifacts, ARTIFACTS_KEY);
$.notify('🌍 Sub-Store', '全部订阅同步成功'); $.info('上传配置成功');
if (invalid.length > 0) {
$.notify(
'🌍 Sub-Store',
`同步配置成功 ${valid.length} 个, 失败 ${invalid.length} 个, 详情请查看日志`,
);
} else {
$.notify('🌍 Sub-Store', '同步配置完成');
}
} catch (e) { } catch (e) {
$.notify('🌍 Sub-Store', '同步订阅失败', `原因:${e.message ?? e}`); $.notify('🌍 Sub-Store', '同步配置失败', `原因:${e.message ?? e}`);
$.error(`无法同步订阅配置到 Gist原因${e}`); $.error(`无法同步配置到 Gist原因${e}`);
} }
} }

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

@@ -540,6 +540,7 @@ async function syncArtifacts() {
const files = {}; const files = {};
try { try {
const valid = [];
const invalid = []; const invalid = [];
const allSubs = $.read(SUBS_KEY); const allSubs = $.read(SUBS_KEY);
const allCols = $.read(COLLECTIONS_KEY); const allCols = $.read(COLLECTIONS_KEY);
@@ -614,19 +615,26 @@ async function syncArtifacts() {
files[encodeURIComponent(artifact.name)] = { files[encodeURIComponent(artifact.name)] = {
content: output, content: output,
}; };
valid.push(artifact.name);
} }
} catch (e) { } catch (e) {
$.error( $.error(
`同步配置 ${artifact.name} 发生错误: ${e.message ?? e}`, `生成同步配置 ${artifact.name} 发生错误: ${
e.message ?? e
}`,
); );
invalid.push(artifact.name); invalid.push(artifact.name);
} }
}), }),
); );
if (invalid.length > 0) { $.info(`${valid.length} 个同步配置生成成功: ${valid.join(', ')}`);
$.info(`${invalid.length} 个同步配置生成失败: ${invalid.join(', ')}`);
if (valid.length === 0) {
throw new Error( throw new Error(
`同步配置 ${invalid.join(', ')} 发生错误 详情请查看日志`, `同步配置 ${invalid.join(', ')} 生成失败 详情请查看日志`,
); );
} }
@@ -643,7 +651,11 @@ async function syncArtifacts() {
$.info(JSON.stringify(body, null, 2)); $.info(JSON.stringify(body, null, 2));
for (const artifact of allArtifacts) { for (const artifact of allArtifacts) {
if (artifact.sync) { if (
artifact.sync &&
artifact.source &&
valid.includes(artifact.name)
) {
artifact.updated = new Date().getTime(); artifact.updated = new Date().getTime();
// extract real url from gist // extract real url from gist
let files = body.files; let files = body.files;
@@ -671,9 +683,17 @@ async function syncArtifacts() {
} }
$.write(allArtifacts, ARTIFACTS_KEY); $.write(allArtifacts, ARTIFACTS_KEY);
$.info('全部订阅同步成功'); $.info('上传配置成功');
if (invalid.length > 0) {
throw new Error(
`同步配置成功 ${valid.length} 个, 失败 ${invalid.length} 个, 详情请查看日志`,
);
} else {
$.info(`同步配置成功 ${valid.length}`);
}
} catch (e) { } catch (e) {
$.error(`同步订阅失败,原因:${e.message ?? e}`); $.error(`同步配置失败,原因:${e.message ?? e}`);
throw e; throw e;
} }
} }
@@ -683,7 +703,7 @@ async function syncAllArtifacts(_, res) {
await syncArtifacts(); await syncArtifacts();
success(res); success(res);
} catch (e) { } catch (e) {
$.error(`同步订阅失败,原因:${e.message ?? e}`); $.error(`同步配置失败,原因:${e.message ?? e}`);
failed( failed(
res, res,
new InternalServerError( new InternalServerError(

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) {