Compare commits

..

1 Commits

Author SHA1 Message Date
xream
b0a544b879 feat: 正则排序支持顺序/倒序/原顺序(前端 > 2.15.10) 2025-03-28 12:24:48 +08:00
34 changed files with 225 additions and 709 deletions

View File

@@ -128,9 +128,3 @@ This project is under the GPL V3 LICENSE.
- Special thanks to @KOP-XIAO for his awesome resource-parser. Please give a [star](https://github.com/KOP-XIAO/QuantumultX) for his great work! - Special thanks to @KOP-XIAO for his awesome resource-parser. Please give a [star](https://github.com/KOP-XIAO/QuantumultX) for his great work!
- Special thanks to @Orz-3 and @58xinian for their awesome icons. - Special thanks to @Orz-3 and @58xinian for their awesome icons.
## Sponsors
[![image](./support.nodeseek.com_page_promotion_id=8.png)](https://yxvm.com)
[NodeSupport](https://github.com/NodeSeekDev/NodeSupport) sponsored this project.

View File

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

@@ -1,8 +1,7 @@
import { Base64 } from 'js-base64';
import { Buffer } from 'buffer'; import { Buffer } from 'buffer';
import rs from '@/utils/rs'; import rs from '@/utils/rs';
import YAML from '@/utils/yaml'; import YAML from '@/utils/yaml';
import download, { downloadFile } from '@/utils/download'; import download from '@/utils/download';
import { import {
isIPv4, isIPv4,
isIPv6, isIPv6,
@@ -331,11 +330,8 @@ export const ProxyUtils = {
MMDB, MMDB,
Gist, Gist,
download, download,
downloadFile,
isValidUUID, isValidUUID,
doh, doh,
Buffer,
Base64,
}; };
function tryParse(parser, line) { function tryParse(parser, line) {

View File

@@ -128,13 +128,13 @@ function URI_SS() {
// parse url // parse url
let content = line.split('ss://')[1]; let content = line.split('ss://')[1];
let name = line.split('#')[1];
const proxy = { const proxy = {
name: decodeURIComponent(line.split('#')[1]),
type: 'ss', type: 'ss',
}; };
content = content.split('#')[0]; // strip proxy name content = content.split('#')[0]; // strip proxy name
// handle IPV4 and IPV6 // handle IPV4 and IPV6
let serverAndPortArray = content.match(/@([^/?]*)(\/|\?|$)/); let serverAndPortArray = content.match(/@([^/]*)(\/|$)/);
let rawUserInfoStr = decodeURIComponent(content.split('@')[0]); // 其实应该分隔之后, 用户名和密码再 decodeURIComponent. 但是问题不大 let rawUserInfoStr = decodeURIComponent(content.split('@')[0]); // 其实应该分隔之后, 用户名和密码再 decodeURIComponent. 但是问题不大
let userInfoStr; let userInfoStr;
@@ -260,10 +260,6 @@ function URI_SS() {
if (/(&|\?)tfo=(1|true)/i.test(query)) { if (/(&|\?)tfo=(1|true)/i.test(query)) {
proxy.tfo = true; proxy.tfo = true;
} }
if (name != null) {
name = decodeURIComponent(name);
}
proxy.name = name ?? `SS ${proxy.server}:${proxy.port}`;
return proxy; return proxy;
}; };
return { name, test, parse }; return { name, test, parse };
@@ -456,12 +452,8 @@ function URI_VMess() {
); );
} }
// https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2) // https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2)
if (proxy.tls) { if (proxy.tls && params.sni && params.sni !== '') {
if (params.sni && params.sni !== '') { proxy.sni = params.sni;
proxy.sni = params.sni;
} else if (params.peer && params.peer !== '') {
proxy.sni = params.peer;
}
} }
let httpupgrade = false; let httpupgrade = false;
// handle obfs // handle obfs
@@ -500,11 +492,6 @@ function URI_VMess() {
} catch (e) {} } catch (e) {}
let transportPath = params.path; let transportPath = params.path;
// 补上默认 path
if (['ws'].includes(proxy.network)) {
transportPath = transportPath || '/';
}
if (proxy.network === 'http') { if (proxy.network === 'http') {
if (transportHost) { if (transportHost) {
// 1)http(tcp)->host中间逗号(,)隔开 // 1)http(tcp)->host中间逗号(,)隔开
@@ -752,8 +739,6 @@ function URI_AnyTLS() {
proxy[key] = value ? value.split(',') : undefined; proxy[key] = value ? value.split(',') : undefined;
} else if (['insecure'].includes(key)) { } else if (['insecure'].includes(key)) {
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(value); proxy['skip-cert-verify'] = /(TRUE)|1/i.test(value);
} else if (['udp'].includes(key)) {
proxy[key] = /(TRUE)|1/i.test(value);
} else { } else {
proxy[key] = value; proxy[key] = value;
} }
@@ -972,9 +957,6 @@ function URI_TUIC() {
proxy.tfo = true; proxy.tfo = true;
} else if (['disable-sni', 'reduce-rtt'].includes(key)) { } else if (['disable-sni', 'reduce-rtt'].includes(key)) {
proxy[key] = /(TRUE)|1/i.test(value); proxy[key] = /(TRUE)|1/i.test(value);
} else if (key === 'congestion-control') {
proxy['congestion-controller'] = value;
delete proxy[key];
} else { } else {
proxy[key] = value; proxy[key] = value;
} }

View File

@@ -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/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/block_quic/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/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/block_quic/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/ip_mode/public_key/short_id/block_quic/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/ip_mode/flow/public_key/short_id/block_quic/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/flow/public_key/short_id/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/ip_mode/block_quic/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/ip_mode/block_quic/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/ip_mode/block_quic/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/ip_mode/block_quic/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/ip_mode/block_quic/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";
} }
@@ -192,8 +192,6 @@ 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(""); }
salamander_password = comma "salamander-password" equals match:[^,]+ { proxy['obfs-password'] = match.join(""); proxy.obfs = 'salamander'; } salamander_password = comma "salamander-password" equals match:[^,]+ { proxy['obfs-password'] = match.join(""); proxy.obfs = 'salamander'; }
block_quic = comma "block-quic" equals flag:bool { if(flag) proxy["block-quic"] = "on"; else proxy["block-quic"] = "off"; }
tag = match:[^=,]* { proxy.name = match.join("").trim(); } tag = match:[^=,]* { proxy.name = match.join("").trim(); }
comma = _ "," _ comma = _ "," _
equals = _ "=" _ equals = _ "=" _

View File

@@ -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/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/block_quic/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/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/block_quic/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/ip_mode/public_key/short_id/block_quic/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/ip_mode/flow/public_key/short_id/block_quic/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/flow/public_key/short_id/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/ip_mode/block_quic/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/ip_mode/block_quic/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/ip_mode/block_quic/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/ip_mode/block_quic/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/ip_mode/block_quic/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";
} }
@@ -190,8 +190,6 @@ 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(""); }
salamander_password = comma "salamander-password" equals match:[^,]+ { proxy['obfs-password'] = match.join(""); proxy.obfs = 'salamander'; } salamander_password = comma "salamander-password" equals match:[^,]+ { proxy['obfs-password'] = match.join(""); proxy.obfs = 'salamander'; }
block_quic = comma "block-quic" equals flag:bool { if(flag) proxy["block-quic"] = "on"; else proxy["block-quic"] = "off"; }
tag = match:[^=,]* { proxy.name = match.join("").trim(); } tag = match:[^=,]* { proxy.name = match.join("").trim(); }
comma = _ "," _ comma = _ "," _
equals = _ "=" _ equals = _ "=" _

View File

@@ -49,7 +49,7 @@ trojan = "trojan" equals address
} }
shadowsocks = "shadowsocks" equals address shadowsocks = "shadowsocks" equals address
(password/method/obfs_ssr/obfs_ss/obfs_host/obfs_uri/ssr_protocol/ssr_protocol_param/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/udp_relay/udp_over_tcp_new/fast_open/tag/server_check_url/others)* { (password/method/obfs_ssr/obfs_ss/obfs_host/obfs_uri/ssr_protocol/ssr_protocol_param/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/udp_relay/udp_over_tcp/fast_open/tag/server_check_url/others)* {
if (proxy.protocol || proxy.type === "ssr") { if (proxy.protocol || proxy.type === "ssr") {
proxy.type = "ssr"; proxy.type = "ssr";
if (!proxy.protocol) { if (!proxy.protocol) {
@@ -86,10 +86,10 @@ vmess = "vmess" equals address
(uuid/method/over_tls/tls_host/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/tag/obfs/obfs_host/obfs_uri/udp_relay/udp_over_tcp/fast_open/aead/server_check_url/others)* { (uuid/method/over_tls/tls_host/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/tag/obfs/obfs_host/obfs_uri/udp_relay/udp_over_tcp/fast_open/aead/server_check_url/others)* {
proxy.type = "vmess"; proxy.type = "vmess";
proxy.cipher = proxy.cipher || "none"; proxy.cipher = proxy.cipher || "none";
if (proxy.aead === false) { if (proxy.aead) {
proxy.alterId = 1;
} else {
proxy.alterId = 0; proxy.alterId = 0;
} else {
proxy.alterId = proxy.alterId || 0;
} }
handleObfs(); handleObfs();
} }
@@ -145,20 +145,18 @@ port = digits:[0-9]+ {
} }
} }
username = comma "username" equals username:[^,]+ { proxy.username = username.join("").trim(); } username = comma "username" equals username:[^=,]+ { proxy.username = username.join("").trim(); }
password = comma "password" equals password:[^,]+ { proxy.password = password.join("").trim(); } password = comma "password" equals password:[^=,]+ { proxy.password = password.join("").trim(); }
uuid = comma "password" equals uuid:[^,]+ { proxy.uuid = uuid.join("").trim(); } uuid = comma "password" equals uuid:[^=,]+ { proxy.uuid = uuid.join("").trim(); }
method = comma "method" equals cipher:cipher { method = comma "method" equals cipher:cipher {
proxy.cipher = cipher; proxy.cipher = cipher;
}; };
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"none"/"rc2-cfb"/"rc4-md5-6"/"rc4-md5"/"salsa20"/"xchacha20-ietf-poly1305"/"2022-blake3-aes-128-gcm"/"2022-blake3-aes-256-gcm"); cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"none"/"rc2-cfb"/"rc4-md5-6"/"rc4-md5"/"salsa20"/"xchacha20-ietf-poly1305");
aead = comma "aead" equals flag:bool { proxy.aead = flag; } aead = comma "aead" equals flag:bool { proxy.aead = flag; }
udp_relay = comma "udp-relay" equals flag:bool { proxy.udp = flag; } udp_relay = comma "udp-relay" equals flag:bool { proxy.udp = flag; }
udp_over_tcp = comma "udp-over-tcp" equals flag:bool { throw new Error("UDP over TCP is not supported"); } udp_over_tcp = comma "udp-over-tcp" equals flag:bool { throw new Error("UDP over TCP is not supported"); }
udp_over_tcp_new = comma "udp-over-tcp" equals param:$[^=,]+ { if (param === "sp.v1") { proxy["udp-over-tcp"] = true; proxy["udp-over-tcp-version"] = 1; } else if (param === "sp.v2") { proxy["udp-over-tcp"] = true; proxy["udp-over-tcp-version"] = 2; } else if (param === "true") { proxy["_ssr_python_uot"] = true; } else { throw new Error("Invalid value for udp-over-tcp"); } }
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; } fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; } over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }

View File

@@ -47,7 +47,7 @@ trojan = "trojan" equals address
} }
shadowsocks = "shadowsocks" equals address shadowsocks = "shadowsocks" equals address
(password/method/obfs_ssr/obfs_ss/obfs_host/obfs_uri/ssr_protocol/ssr_protocol_param/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/udp_relay/udp_over_tcp_new/fast_open/tag/server_check_url/others)* { (password/method/obfs_ssr/obfs_ss/obfs_host/obfs_uri/ssr_protocol/ssr_protocol_param/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/udp_relay/udp_over_tcp/fast_open/tag/server_check_url/others)* {
if (proxy.protocol || proxy.type === "ssr") { if (proxy.protocol || proxy.type === "ssr") {
proxy.type = "ssr"; proxy.type = "ssr";
if (!proxy.protocol) { if (!proxy.protocol) {
@@ -84,10 +84,10 @@ vmess = "vmess" equals address
(uuid/method/over_tls/tls_host/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/tag/obfs/obfs_host/obfs_uri/udp_relay/udp_over_tcp/fast_open/aead/server_check_url/others)* { (uuid/method/over_tls/tls_host/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/tag/obfs/obfs_host/obfs_uri/udp_relay/udp_over_tcp/fast_open/aead/server_check_url/others)* {
proxy.type = "vmess"; proxy.type = "vmess";
proxy.cipher = proxy.cipher || "none"; proxy.cipher = proxy.cipher || "none";
if (proxy.aead === false) { if (proxy.aead) {
proxy.alterId = 1;
} else {
proxy.alterId = 0; proxy.alterId = 0;
} else {
proxy.alterId = proxy.alterId || 0;
} }
handleObfs(); handleObfs();
} }
@@ -143,20 +143,18 @@ port = digits:[0-9]+ {
} }
} }
username = comma "username" equals username:[^,]+ { proxy.username = username.join("").trim(); } username = comma "username" equals username:[^=,]+ { proxy.username = username.join("").trim(); }
password = comma "password" equals password:[^,]+ { proxy.password = password.join("").trim(); } password = comma "password" equals password:[^=,]+ { proxy.password = password.join("").trim(); }
uuid = comma "password" equals uuid:[^,]+ { proxy.uuid = uuid.join("").trim(); } uuid = comma "password" equals uuid:[^=,]+ { proxy.uuid = uuid.join("").trim(); }
method = comma "method" equals cipher:cipher { method = comma "method" equals cipher:cipher {
proxy.cipher = cipher; proxy.cipher = cipher;
}; };
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"none"/"rc2-cfb"/"rc4-md5-6"/"rc4-md5"/"salsa20"/"xchacha20-ietf-poly1305"/"2022-blake3-aes-128-gcm"/"2022-blake3-aes-256-gcm"); cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"none"/"rc2-cfb"/"rc4-md5-6"/"rc4-md5"/"salsa20"/"xchacha20-ietf-poly1305");
aead = comma "aead" equals flag:bool { proxy.aead = flag; } aead = comma "aead" equals flag:bool { proxy.aead = flag; }
udp_relay = comma "udp-relay" equals flag:bool { proxy.udp = flag; } udp_relay = comma "udp-relay" equals flag:bool { proxy.udp = flag; }
udp_over_tcp = comma "udp-over-tcp" equals flag:bool { throw new Error("UDP over TCP is not supported"); } udp_over_tcp = comma "udp-over-tcp" equals flag:bool { throw new Error("UDP over TCP is not supported"); }
udp_over_tcp_new = comma "udp-over-tcp" equals param:$[^=,]+ { if (param === "sp.v1") { proxy["udp-over-tcp"] = true; proxy["udp-over-tcp-version"] = 1; } else if (param === "sp.v2") { proxy["udp-over-tcp"] = true; proxy["udp-over-tcp-version"] = 2; } else if (param === "true") { proxy["_ssr_python_uot"] = true; } else { throw new Error("Invalid value for udp-over-tcp"); } }
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; } fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; } over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }

View File

@@ -55,11 +55,10 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
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)* { 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";
// Surfboard 与 Surge 默认不一致, 不管 Surfboard https://getsurfboard.com/docs/profile-format/proxy/external-proxy/vmess
if (proxy.aead) { if (proxy.aead) {
proxy.alterId = 0; proxy.alterId = 0;
} else { } else {
proxy.alterId = 1; proxy.alterId = proxy.alterId || 0;
} }
handleWebsocket(); handleWebsocket();
handleShadowTLS(); handleShadowTLS();

View File

@@ -53,11 +53,10 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
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)* { 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";
// Surfboard 与 Surge 默认不一致, 不管 Surfboard https://getsurfboard.com/docs/profile-format/proxy/external-proxy/vmess
if (proxy.aead) { if (proxy.aead) {
proxy.alterId = 0; proxy.alterId = 0;
} else { } else {
proxy.alterId = 1; proxy.alterId = proxy.alterId || 0;
} }
handleWebsocket(); handleWebsocket();
handleShadowTLS(); handleShadowTLS();

View File

@@ -50,26 +50,6 @@ function Base64Encoded() {
return { name, test, parse }; return { name, test, parse };
} }
function fallbackBase64Encoded() {
const name = 'Fallback Base64 Pre-processor';
const test = function (raw) {
return true;
};
const parse = function (raw) {
const decoded = Base64.decode(raw);
if (!/^\w+(:\/\/|\s*?=\s*?)\w+/m.test(decoded)) {
$.error(
`Fallback Base64 Pre-processor error: decoded line does not start with protocol`,
);
return raw;
}
return decoded;
};
return { name, test, parse };
}
function Clash() { function Clash() {
const name = 'Clash Pre-processor'; const name = 'Clash Pre-processor';
const test = function (raw) { const test = function (raw) {
@@ -183,11 +163,4 @@ function FullConfig() {
return { name, test, parse }; return { name, test, parse };
} }
export default [ export default [HTML(), Clash(), Base64Encoded(), SSD(), FullConfig()];
HTML(),
Clash(),
Base64Encoded(),
SSD(),
FullConfig(),
fallbackBase64Encoded(),
];

View File

@@ -284,15 +284,7 @@ function SortOperator(order = 'asc') {
} }
// sort by regex // sort by regex
function RegexSortOperator(input) { function RegexSortOperator(expressions, order = 'asc') {
const order = input.order || 'asc';
let expressions = input.expressions;
if (Array.isArray(input)) {
expressions = input;
}
if (!Array.isArray(expressions)) {
expressions = [];
}
return { return {
name: 'Regex Sort Operator', name: 'Regex Sort Operator',
func: (proxies) => { func: (proxies) => {
@@ -1141,10 +1133,6 @@ function createDynamicFunction(name, script, $arguments, $options) {
'$httpClient', '$httpClient',
'$notification', '$notification',
'ProxyUtils', 'ProxyUtils',
'yaml',
'Buffer',
'b64d',
'b64e',
'scriptResourceCache', 'scriptResourceCache',
'flowUtils', 'flowUtils',
'produceArtifact', 'produceArtifact',
@@ -1162,10 +1150,6 @@ function createDynamicFunction(name, script, $arguments, $options) {
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
$notification, $notification,
ProxyUtils, ProxyUtils,
ProxyUtils.yaml,
ProxyUtils.Buffer,
ProxyUtils.Base64.decode,
ProxyUtils.Base64.encode,
scriptResourceCache, scriptResourceCache,
flowUtils, flowUtils,
produceArtifact, produceArtifact,
@@ -1178,10 +1162,6 @@ function createDynamicFunction(name, script, $arguments, $options) {
'$substore', '$substore',
'lodash', 'lodash',
'ProxyUtils', 'ProxyUtils',
'yaml',
'Buffer',
'b64d',
'b64e',
'scriptResourceCache', 'scriptResourceCache',
'flowUtils', 'flowUtils',
'produceArtifact', 'produceArtifact',
@@ -1193,10 +1173,6 @@ function createDynamicFunction(name, script, $arguments, $options) {
$, $,
lodash, lodash,
ProxyUtils, ProxyUtils,
ProxyUtils.yaml,
ProxyUtils.Buffer,
ProxyUtils.Base64.decode,
ProxyUtils.Base64.encode,
scriptResourceCache, scriptResourceCache,
flowUtils, flowUtils,
produceArtifact, produceArtifact,

View File

@@ -1,5 +1,4 @@
import { isPresent } from '@/core/proxy-utils/producers/utils'; import { isPresent } from '@/core/proxy-utils/producers/utils';
import $ from '@/core/app';
export default function Clash_Producer() { export default function Clash_Producer() {
const type = 'ALL'; const type = 'ALL';
@@ -47,11 +46,6 @@ export default function Clash_Producer() {
proxy['reality-opts'])) proxy['reality-opts']))
) { ) {
return false; return false;
} else if (proxy['underlying-proxy'] || proxy['dialer-proxy']) {
$.error(
`Clash 不支持前置代理字段. 已过滤节点 ${proxy.name}`,
);
return false;
} }
return true; return true;
}) })
@@ -158,6 +152,11 @@ export default function Clash_Producer() {
} }
delete proxy['tls-fingerprint']; delete proxy['tls-fingerprint'];
if (proxy['underlying-proxy']) {
proxy['dialer-proxy'] = proxy['underlying-proxy'];
}
delete proxy['underlying-proxy'];
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') { if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
delete proxy.tls; delete proxy.tls;
} }

View File

@@ -21,7 +21,7 @@ export default function Loon_Producer() {
case 'trojan': case 'trojan':
return trojan(proxy); return trojan(proxy);
case 'vmess': case 'vmess':
return vmess(proxy, opts['include-unsupported-proxy']); return vmess(proxy);
case 'vless': case 'vless':
return vless(proxy, opts['include-unsupported-proxy']); return vless(proxy, opts['include-unsupported-proxy']);
case 'http': case 'http':
@@ -133,13 +133,6 @@ function shadowsocks(proxy) {
// tfo // tfo
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo'); result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
// block-quic
if (proxy['block-quic'] === 'on') {
result.append(',block-quic=true');
} else if (proxy['block-quic'] === 'off') {
result.append(',block-quic=false');
}
// udp // udp
if (proxy.udp) { if (proxy.udp) {
result.append(`,udp=true`); result.append(`,udp=true`);
@@ -151,7 +144,7 @@ function shadowsocks(proxy) {
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}"`,
@@ -210,13 +203,6 @@ function shadowsocksr(proxy) {
// tfo // tfo
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo'); result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
// block-quic
if (proxy['block-quic'] === 'on') {
result.append(',block-quic=true');
} else if (proxy['block-quic'] === 'off') {
result.append(',block-quic=false');
}
// udp // udp
if (proxy.udp) { if (proxy.udp) {
result.append(`,udp=true`); result.append(`,udp=true`);
@@ -273,13 +259,6 @@ function trojan(proxy) {
// tfo // tfo
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo'); result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
// block-quic
if (proxy['block-quic'] === 'on') {
result.append(',block-quic=true');
} else if (proxy['block-quic'] === 'off') {
result.append(',block-quic=false');
}
// udp // udp
if (proxy.udp) { if (proxy.udp) {
result.append(`,udp=true`); result.append(`,udp=true`);
@@ -291,8 +270,6 @@ function trojan(proxy) {
} }
function vmess(proxy) { function vmess(proxy) {
const isReality = !!proxy['reality-opts'];
const result = new Result(proxy); const result = new Result(proxy);
result.append( result.append(
`${proxy.name}=vmess,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.uuid}"`, `${proxy.name}=vmess,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.uuid}"`,
@@ -340,32 +317,20 @@ function vmess(proxy) {
'skip-cert-verify', 'skip-cert-verify',
); );
if (isReality) { // sni
result.appendIfPresent(`,sni=${proxy.sni}`, 'sni'); result.appendIfPresent(`,tls-name=${proxy.sni}`, 'sni');
result.appendIfPresent( result.appendIfPresent(
`,public-key="${proxy['reality-opts']['public-key']}"`, `,tls-cert-sha256=${proxy['tls-fingerprint']}`,
'reality-opts.public-key', 'tls-fingerprint',
); );
result.appendIfPresent( result.appendIfPresent(
`,short-id=${proxy['reality-opts']['short-id']}`, `,tls-pubkey-sha256=${proxy['tls-pubkey-sha256']}`,
'reality-opts.short-id', 'tls-pubkey-sha256',
); );
} else {
// sni
result.appendIfPresent(`,tls-name=${proxy.sni}`, 'sni');
result.appendIfPresent(
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
'tls-fingerprint',
);
result.appendIfPresent(
`,tls-pubkey-sha256=${proxy['tls-pubkey-sha256']}`,
'tls-pubkey-sha256',
);
}
// AEAD // AEAD
if (isPresent(proxy, 'aead')) { if (isPresent(proxy, 'aead')) {
result.append(`,alterId=${proxy.aead ? 0 : 1}`); result.append(`,alterId=0`);
} else { } else {
result.append(`,alterId=${proxy.alterId}`); result.append(`,alterId=${proxy.alterId}`);
} }
@@ -373,13 +338,6 @@ function vmess(proxy) {
// tfo // tfo
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo'); result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
// block-quic
if (proxy['block-quic'] === 'on') {
result.append(',block-quic=true');
} else if (proxy['block-quic'] === 'off') {
result.append(',block-quic=false');
}
// udp // udp
if (proxy.udp) { if (proxy.udp) {
result.append(`,udp=true`); result.append(`,udp=true`);
@@ -389,18 +347,28 @@ function vmess(proxy) {
return result.toString(); return result.toString();
} }
function vless(proxy) { function vless(proxy, includeUnsupportedProxy) {
let isXtls = false; if (
const isReality = !!proxy['reality-opts']; !includeUnsupportedProxy &&
(typeof proxy.flow !== 'undefined' || proxy['reality-opts'])
if (typeof proxy.flow !== 'undefined') { ) {
if (['xtls-rprx-vision'].includes(proxy.flow)) { throw new Error(`VLESS XTLS/REALITY is not supported`);
isXtls = true; }
} else { let isReality = false;
throw new Error(`VLESS flow(${proxy.flow}) is not supported`); if (includeUnsupportedProxy) {
if (
proxy['reality-opts'] &&
['xtls-rprx-vision'].includes(proxy.flow)
) {
isReality = true;
} else if (proxy['reality-opts']) {
throw new Error(
`VLESS REALITY with flow(${proxy.flow}) is not supported`,
);
} else if (proxy.flow) {
throw new Error(`VLESS XTLS is not supported`);
} }
} }
const result = new Result(proxy); const result = new Result(proxy);
result.append( result.append(
`${proxy.name}=vless,${proxy.server},${proxy.port},"${proxy.uuid}"`, `${proxy.name}=vless,${proxy.server},${proxy.port},"${proxy.uuid}"`,
@@ -448,10 +416,9 @@ function vless(proxy) {
'skip-cert-verify', 'skip-cert-verify',
); );
if (isXtls) { // sni
result.appendIfPresent(`,flow=${proxy.flow}`, 'flow');
}
if (isReality) { if (isReality) {
result.appendIfPresent(`,flow=${proxy.flow}`, 'flow');
result.appendIfPresent(`,sni=${proxy.sni}`, 'sni'); result.appendIfPresent(`,sni=${proxy.sni}`, 'sni');
result.appendIfPresent( result.appendIfPresent(
`,public-key="${proxy['reality-opts']['public-key']}"`, `,public-key="${proxy['reality-opts']['public-key']}"`,
@@ -462,28 +429,21 @@ function vless(proxy) {
'reality-opts.short-id', 'reality-opts.short-id',
); );
} else { } else {
// sni
result.appendIfPresent(`,tls-name=${proxy.sni}`, 'sni'); result.appendIfPresent(`,tls-name=${proxy.sni}`, 'sni');
result.appendIfPresent(
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
'tls-fingerprint',
);
result.appendIfPresent(
`,tls-pubkey-sha256=${proxy['tls-pubkey-sha256']}`,
'tls-pubkey-sha256',
);
} }
result.appendIfPresent(
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
'tls-fingerprint',
);
result.appendIfPresent(
`,tls-pubkey-sha256=${proxy['tls-pubkey-sha256']}`,
'tls-pubkey-sha256',
);
// tfo // tfo
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo'); result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
// block-quic
if (proxy['block-quic'] === 'on') {
result.append(',block-quic=true');
} else if (proxy['block-quic'] === 'off') {
result.append(',block-quic=false');
}
// udp // udp
if (proxy.udp) { if (proxy.udp) {
result.append(`,udp=true`); result.append(`,udp=true`);
@@ -511,14 +471,6 @@ function http(proxy) {
// tfo // tfo
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo'); result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
// block-quic
if (proxy['block-quic'] === 'on') {
result.append(',block-quic=true');
} else if (proxy['block-quic'] === 'off') {
result.append(',block-quic=false');
}
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');
@@ -545,13 +497,6 @@ function socks5(proxy) {
// tfo // tfo
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo'); result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
// block-quic
if (proxy['block-quic'] === 'on') {
result.append(',block-quic=true');
} else if (proxy['block-quic'] === 'off') {
result.append(',block-quic=false');
}
// udp // udp
if (proxy.udp) { if (proxy.udp) {
result.append(`,udp=true`); result.append(`,udp=true`);
@@ -626,13 +571,6 @@ function wireguard(proxy) {
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');
// block-quic
if (proxy['block-quic'] === 'on') {
result.append(',block-quic=true');
} else if (proxy['block-quic'] === 'off') {
result.append(',block-quic=false');
}
return result.toString(); return result.toString();
} }
@@ -667,13 +605,6 @@ function hysteria2(proxy) {
// tfo // tfo
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo'); result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
// block-quic
if (proxy['block-quic'] === 'on') {
result.append(',block-quic=true');
} else if (proxy['block-quic'] === 'off') {
result.append(',block-quic=false');
}
// udp // udp
if (proxy.udp) { if (proxy.udp) {
result.append(`,udp=true`); result.append(`,udp=true`);

View File

@@ -58,8 +58,6 @@ function shadowsocks(proxy) {
'aes-256-gcm', 'aes-256-gcm',
'chacha20-ietf-poly1305', 'chacha20-ietf-poly1305',
'xchacha20-ietf-poly1305', 'xchacha20-ietf-poly1305',
'2022-blake3-aes-128-gcm',
'2022-blake3-aes-256-gcm',
].includes(proxy.cipher) ].includes(proxy.cipher)
) { ) {
throw new Error(`cipher ${proxy.cipher} is not supported`); throw new Error(`cipher ${proxy.cipher} is not supported`);
@@ -130,20 +128,6 @@ function shadowsocks(proxy) {
// udp // udp
appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp'); appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
// udp over tcp
if (proxy['_ssr_python_uot']) {
append(`,udp-over-tcp=true`);
} else if (proxy['udp-over-tcp']) {
if (
!proxy['udp-over-tcp-version'] ||
proxy['udp-over-tcp-version'] === 1
) {
append(`,udp-over-tcp=sp.v1`);
} else if (proxy['udp-over-tcp-version'] === 2) {
append(`,udp-over-tcp=sp.v2`);
}
}
// server_check_url // server_check_url
result.appendIfPresent( result.appendIfPresent(
`,server_check_url=${proxy['test-url']}`, `,server_check_url=${proxy['test-url']}`,
@@ -405,8 +389,6 @@ function vless(proxy) {
else append(`,obfs=ws`); else append(`,obfs=ws`);
} else if (proxy.network === 'http') { } else if (proxy.network === 'http') {
append(`,obfs=http`); append(`,obfs=http`);
} else if (['tcp'].includes(proxy.network)) {
if (proxy.tls) append(`,obfs=over-tls`);
} else if (!['tcp'].includes(proxy.network)) { } else if (!['tcp'].includes(proxy.network)) {
throw new Error(`network ${proxy.network} is unsupported`); throw new Error(`network ${proxy.network} is unsupported`);
} }

View File

@@ -1,5 +1,4 @@
import { isPresent } from '@/core/proxy-utils/producers/utils'; import { isPresent } from '@/core/proxy-utils/producers/utils';
import $ from '@/core/app';
export default function Shadowrocket_Producer() { export default function Shadowrocket_Producer() {
const type = 'ALL'; const type = 'ALL';

View File

@@ -31,21 +31,6 @@ const smuxParser = (smux, proxy) => {
if (smux['min-streams']) if (smux['min-streams'])
proxy.multiplex.min_streams = parseInt(`${smux['min-streams']}`, 10); proxy.multiplex.min_streams = parseInt(`${smux['min-streams']}`, 10);
if (smux.padding) proxy.multiplex.padding = true; if (smux.padding) proxy.multiplex.padding = true;
if (smux['brutal-opts']?.up || smux['brutal-opts']?.down) {
proxy.multiplex.brutal = {
enabled: true,
};
if (smux['brutal-opts']?.up)
proxy.multiplex.brutal.up_mbps = parseInt(
`${smux['brutal-opts']?.up}`,
10,
);
if (smux['brutal-opts']?.down)
proxy.multiplex.brutal.down_mbps = parseInt(
`${smux['brutal-opts']?.down}`,
10,
);
}
}; };
const wsParser = (proxy, parsedProxy) => { const wsParser = (proxy, parsedProxy) => {
@@ -374,16 +359,7 @@ const ssParser = (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 (proxy.uot) parsedProxy.udp_over_tcp = true; if (proxy.uot) parsedProxy.udp_over_tcp = true;
if (proxy['udp-over-tcp']) { if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true;
parsedProxy.udp_over_tcp = {
enabled: true,
version:
!proxy['udp-over-tcp-version'] ||
proxy['udp-over-tcp-version'] === 1
? 1
: 2,
};
}
if (proxy['fast-open']) parsedProxy.udp_fragment = true; if (proxy['fast-open']) parsedProxy.udp_fragment = true;
networkParser(proxy, parsedProxy); networkParser(proxy, parsedProxy);
tfoParser(proxy, parsedProxy); tfoParser(proxy, parsedProxy);
@@ -567,13 +543,12 @@ const hysteriaParser = (proxy = {}) => {
if (proxy['fast-open']) parsedProxy.udp_fragment = true; if (proxy['fast-open']) parsedProxy.udp_fragment = true;
// eslint-disable-next-line no-control-regex // eslint-disable-next-line no-control-regex
const reg = new RegExp('^[0-9]+[ \t]*[KMGT]*[Bb]ps$'); const reg = new RegExp('^[0-9]+[ \t]*[KMGT]*[Bb]ps$');
// sing-box 跟文档不一致, 但是懒得全转, 只处理最常见的 Mbps if (reg.test(`${proxy.up}`)) {
if (reg.test(`${proxy.up}`) && !`${proxy.up}`.endsWith('Mbps')) {
parsedProxy.up = `${proxy.up}`; parsedProxy.up = `${proxy.up}`;
} else { } else {
parsedProxy.up_mbps = parseInt(`${proxy.up}`, 10); parsedProxy.up_mbps = parseInt(`${proxy.up}`, 10);
} }
if (reg.test(`${proxy.down}`) && !`${proxy.down}`.endsWith('Mbps')) { if (reg.test(`${proxy.down}`)) {
parsedProxy.down = `${proxy.down}`; parsedProxy.down = `${proxy.down}`;
} else { } else {
parsedProxy.down_mbps = parseInt(`${proxy.down}`, 10); parsedProxy.down_mbps = parseInt(`${proxy.down}`, 10);
@@ -600,7 +575,7 @@ const hysteriaParser = (proxy = {}) => {
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',

View File

@@ -1,5 +1,4 @@
import { isPresent } from '@/core/proxy-utils/producers/utils'; import { isPresent } from '@/core/proxy-utils/producers/utils';
import $ from '@/core/app';
export default function Stash_Producer() { export default function Stash_Producer() {
const type = 'ALL'; const type = 'ALL';
@@ -40,8 +39,12 @@ export default function Stash_Producer() {
'xchacha20', 'xchacha20',
'chacha20-ietf-poly1305', 'chacha20-ietf-poly1305',
'xchacha20-ietf-poly1305', 'xchacha20-ietf-poly1305',
'2022-blake3-aes-128-gcm', ...(opts['include-unsupported-proxy']
'2022-blake3-aes-256-gcm', ? [
'2022-blake3-aes-128-gcm',
'2022-blake3-aes-256-gcm',
]
: []),
].includes(proxy.cipher)) || ].includes(proxy.cipher)) ||
(proxy.type === 'snell' && String(proxy.version) === '4') || (proxy.type === 'snell' && String(proxy.version) === '4') ||
(opts['include-unsupported-proxy'] (opts['include-unsupported-proxy']
@@ -51,11 +54,6 @@ export default function Stash_Producer() {
: proxy.type === 'vless' && proxy['reality-opts']) : proxy.type === 'vless' && proxy['reality-opts'])
) { ) {
return false; return false;
} else if (proxy['underlying-proxy'] || proxy['dialer-proxy']) {
$.error(
`Stash 暂不支持前置代理字段. 已过滤节点 ${proxy.name}. 请使用 代理的转发链 https://stash.wiki/proxy-protocols/proxy-groups#relay`,
);
return false;
} }
return true; return true;
}) })
@@ -263,6 +261,11 @@ export default function Stash_Producer() {
} }
delete proxy['tls-fingerprint']; delete proxy['tls-fingerprint'];
if (proxy['underlying-proxy']) {
proxy['dialer-proxy'] = proxy['underlying-proxy'];
}
delete proxy['underlying-proxy'];
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') { if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
delete proxy.tls; delete proxy.tls;
} }

View File

@@ -12,7 +12,7 @@ export default function URI_Producer() {
delete proxy.resolved; delete proxy.resolved;
delete proxy['no-resolve']; delete proxy['no-resolve'];
for (const key in proxy) { for (const key in proxy) {
if (proxy[key] == null) { if (proxy[key] == null || /^_/i.test(key)) {
delete proxy[key]; delete proxy[key];
} }
} }
@@ -33,9 +33,7 @@ export default function URI_Producer() {
switch (proxy.type) { switch (proxy.type) {
case 'socks5': case 'socks5':
result = `socks://${encodeURIComponent( result = `socks://${encodeURIComponent(
Base64.encode( Base64.encode(`${proxy.username}:${proxy.password}`),
`${proxy.username ?? ''}:${proxy.password ?? ''}`,
),
)}@${proxy.server}:${proxy.port}#${proxy.name}`; )}@${proxy.server}:${proxy.port}#${proxy.name}`;
break; break;
case 'ss': case 'ss':
@@ -480,7 +478,7 @@ export default function URI_Producer() {
hysteriaParams.push(`obfsParam=${proxy[key]}`); hysteriaParams.push(`obfsParam=${proxy[key]}`);
} else if (['sni'].includes(key)) { } else if (['sni'].includes(key)) {
hysteriaParams.push(`peer=${proxy[key]}`); hysteriaParams.push(`peer=${proxy[key]}`);
} else if (proxy[key] && !/^_/i.test(key)) { } else if (proxy[key]) {
hysteriaParams.push( hysteriaParams.push(
`${i}=${encodeURIComponent(proxy[key])}`, `${i}=${encodeURIComponent(proxy[key])}`,
); );
@@ -537,13 +535,7 @@ export default function URI_Producer() {
proxy[key] proxy[key]
) { ) {
tuicParams.push(`${i.replace(/-/g, '_')}=1`); tuicParams.push(`${i.replace(/-/g, '_')}=1`);
} else if ( } else if (proxy[key]) {
['congestion-controller'].includes(key)
) {
tuicParams.push(
`congestion_control=${proxy[key]}`,
);
} else if (proxy[key] && !/^_/i.test(key)) {
tuicParams.push( tuicParams.push(
`${i.replace( `${i.replace(
/-/g, /-/g,
@@ -591,11 +583,7 @@ export default function URI_Producer() {
if (proxy[key]) { if (proxy[key]) {
anytlsParams.push(`insecure=1`); anytlsParams.push(`insecure=1`);
} }
} else if (['udp'].includes(key)) { } else if (proxy[key]) {
if (proxy[key]) {
anytlsParams.push(`udp=1`);
}
} else if (proxy[key] && !/^_/i.test(key)) {
anytlsParams.push( anytlsParams.push(
`${i.replace(/-/g, '_')}=${encodeURIComponent( `${i.replace(/-/g, '_')}=${encodeURIComponent(
proxy[key], proxy[key],
@@ -632,7 +620,7 @@ export default function URI_Producer() {
if (proxy[key]) { if (proxy[key]) {
wireguardParams.push(`${key}=1`); wireguardParams.push(`${key}=1`);
} }
} else if (proxy[key] && !/^_/i.test(key)) { } else if (proxy[key]) {
wireguardParams.push( wireguardParams.push(
`${key}=${encodeURIComponent(proxy[key])}`, `${key}=${encodeURIComponent(proxy[key])}`,
); );

View File

@@ -44,7 +44,7 @@ let resourceUrl = typeof $resourceUrl !== 'undefined' ? $resourceUrl : '';
let proxies = ProxyUtils.parse(resource); let proxies = ProxyUtils.parse(resource);
result = ProxyUtils.produce(proxies, 'Loon', undefined, { result = ProxyUtils.produce(proxies, 'Loon', undefined, {
'include-unsupported-proxy': 'include-unsupported-proxy':
arg?.includeUnsupportedProxy || build >= 842, arg?.includeUnsupportedProxy || build >= 838,
}); });
} catch (e) { } catch (e) {
console.log('解析器: 使用 resource 出现错误'); console.log('解析器: 使用 resource 出现错误');
@@ -67,7 +67,7 @@ let resourceUrl = typeof $resourceUrl !== 'undefined' ? $resourceUrl : '';
let proxies = ProxyUtils.parse(raw); let proxies = ProxyUtils.parse(raw);
result = ProxyUtils.produce(proxies, 'Loon', undefined, { result = ProxyUtils.produce(proxies, 'Loon', undefined, {
'include-unsupported-proxy': 'include-unsupported-proxy':
arg?.includeUnsupportedProxy || build >= 842, arg?.includeUnsupportedProxy || build >= 838,
}); });
} catch (e) { } catch (e) {
console.log(e.message ?? e); console.log(e.message ?? e);

View File

@@ -111,17 +111,7 @@ async function downloadSubscription(req, res) {
proxy, proxy,
noCache, noCache,
} = req.query; } = req.query;
let $options = { let $options = {};
_req: {
method: req.method,
url: req.url,
path: req.path,
query: req.query,
params: req.params,
headers: req.headers,
body: req.body,
},
};
if (req.query.$options) { if (req.query.$options) {
try { try {
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}` // 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
@@ -386,17 +376,7 @@ async function downloadCollection(req, res) {
noCache, noCache,
} = req.query; } = req.query;
let $options = { let $options = {};
_req: {
method: req.method,
url: req.url,
path: req.path,
query: req.query,
params: req.params,
headers: req.headers,
body: req.body,
},
};
if (req.query.$options) { if (req.query.$options) {
try { try {
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}` // 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`

View File

@@ -52,8 +52,8 @@ function createFile(req, res) {
async function getFile(req, res) { async function getFile(req, res) {
let { name } = req.params; let { name } = req.params;
name = decodeURIComponent(name); name = decodeURIComponent(name);
const reqUA = req.headers['user-agent'] || req.headers['User-Agent'];
$.info(`正在下载文件:${name}\n请求 User-Agent: ${reqUA}`); $.info(`正在下载文件:${name}`);
let { let {
url, url,
subInfoUrl, subInfoUrl,
@@ -64,19 +64,8 @@ async function getFile(req, res) {
ignoreFailedRemoteFile, ignoreFailedRemoteFile,
proxy, proxy,
noCache, noCache,
produceType,
} = req.query; } = req.query;
let $options = { let $options = {};
_req: {
method: req.method,
url: req.url,
path: req.path,
query: req.query,
params: req.params,
headers: req.headers,
body: req.body,
},
};
if (req.query.$options) { if (req.query.$options) {
try { try {
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}` // 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
@@ -129,10 +118,6 @@ async function getFile(req, res) {
if (noCache) { if (noCache) {
$.info(`指定不使用缓存: ${noCache}`); $.info(`指定不使用缓存: ${noCache}`);
} }
if (produceType) {
produceType = decodeURIComponent(produceType);
$.info(`指定生产类型: ${produceType}`);
}
const allFiles = $.read(FILES_KEY); const allFiles = $.read(FILES_KEY);
const file = findByName(allFiles, name); const file = findByName(allFiles, name);
@@ -149,7 +134,6 @@ async function getFile(req, res) {
$options, $options,
proxy, proxy,
noCache, noCache,
produceType,
}); });
try { try {

View File

@@ -1,7 +1,7 @@
import express from '@/vendor/express'; import express from '@/vendor/express';
import $ from '@/core/app'; import $ from '@/core/app';
import migrate from '@/utils/migration'; import migrate from '@/utils/migration';
import download, { downloadFile } from '@/utils/download'; import download from '@/utils/download';
import { syncArtifacts, produceArtifact } from '@/restful/sync'; import { syncArtifacts, produceArtifact } from '@/restful/sync';
import { gistBackupAction } from '@/restful/miscs'; import { gistBackupAction } from '@/restful/miscs';
import { TOKENS_KEY } from '@/constants'; import { TOKENS_KEY } from '@/constants';
@@ -35,7 +35,7 @@ export default function serve() {
const fe_be_path = eval('process.env.SUB_STORE_FRONTEND_BACKEND_PATH'); const fe_be_path = eval('process.env.SUB_STORE_FRONTEND_BACKEND_PATH');
const fe_path = eval('process.env.SUB_STORE_FRONTEND_PATH'); const fe_path = eval('process.env.SUB_STORE_FRONTEND_PATH');
if (be_prefix || be_merge) { if (be_prefix || be_merge) {
if (!fe_be_path.startsWith('/')) { if(!fe_be_path.startsWith('/')){
throw new Error( throw new Error(
'SUB_STORE_FRONTEND_BACKEND_PATH should start with /', 'SUB_STORE_FRONTEND_BACKEND_PATH should start with /',
); );
@@ -48,20 +48,15 @@ export default function serve() {
$app.use((req, res, next) => { $app.use((req, res, next) => {
if (req.path.startsWith(fe_be_path)) { if (req.path.startsWith(fe_be_path)) {
req.url = req.url.replace(fe_be_path, '') || '/'; req.url = req.url.replace(fe_be_path, '') || '/';
if (be_merge && req.url.startsWith('/api/')) { if(be_merge && req.url.startsWith('/api/')){
req.query['share'] = 'true'; req.query['share'] = 'true';
} }
next(); next();
return; return;
} }
const pathname = const pathname = decodeURIComponent(req._parsedUrl.pathname) || '/';
decodeURIComponent(req._parsedUrl.pathname) || '/'; if(be_merge && req.path.startsWith('/share/') && req.query.token){
if ( if (req.method.toLowerCase() !== 'get'){
be_merge &&
req.path.startsWith('/share/') &&
req.query.token
) {
if (req.method.toLowerCase() !== 'get') {
res.status(405).send('Method not allowed'); res.status(405).send('Method not allowed');
return; return;
} }
@@ -72,14 +67,14 @@ export default function serve() {
`/share/${t.type}/${t.name}` === pathname && `/share/${t.type}/${t.name}` === pathname &&
(t.exp == null || t.exp > Date.now()), (t.exp == null || t.exp > Date.now()),
); );
if (token) { if (token){
next(); next();
return; return;
} }
} }
if (be_merge && fe_path && req.path.indexOf('/', 1) == -1) { if (be_merge && fe_path && req.path.indexOf('/',1) == -1) {
if (req.path.indexOf('.') == -1) { if (req.path.indexOf('.') == -1){
req.url = '/index.html'; req.url = "/index.html"
} }
const express_ = eval(`require("express")`); const express_ = eval(`require("express")`);
const mime_ = eval(`require("mime-types")`); const mime_ = eval(`require("mime-types")`);
@@ -90,7 +85,7 @@ export default function serve() {
if (type) { if (type) {
res.set('Content-Type', type); res.set('Content-Type', type);
} }
}, }
}); });
staticFileMiddleware(req, res, next); staticFileMiddleware(req, res, next);
return; return;
@@ -235,60 +230,6 @@ export default function serve() {
// 'Asia/Shanghai' // timeZone // 'Asia/Shanghai' // timeZone
); );
} }
const mmdb_cron = eval('process.env.SUB_STORE_MMDB_CRON');
const countryFile = eval('process.env.SUB_STORE_MMDB_COUNTRY_PATH');
const countryUrl = eval('process.env.SUB_STORE_MMDB_COUNTRY_URL');
const asnFile = eval('process.env.SUB_STORE_MMDB_ASN_PATH');
const asnUrl = eval('process.env.SUB_STORE_MMDB_ASN_URL');
if (mmdb_cron && ((countryFile && countryUrl) || (asnFile && asnUrl))) {
$.info(`[MMDB CRON] ${mmdb_cron} enabled`);
const { CronJob } = eval(`require("cron")`);
new CronJob(
mmdb_cron,
async function () {
try {
$.info(`[MMDB CRON] ${mmdb_cron} started`);
if (countryFile && countryUrl) {
try {
$.info(
`[MMDB CRON] downloading ${countryUrl} to ${countryFile}`,
);
await downloadFile(countryUrl, countryFile);
} catch (e) {
$.error(
`[MMDB CRON] ${countryUrl} download failed: ${
e.message ?? e
}`,
);
}
}
if (asnFile && asnUrl) {
try {
$.info(
`[MMDB CRON] downloading ${asnUrl} to ${asnFile}`,
);
await downloadFile(asnUrl, asnFile);
} catch (e) {
$.error(
`[MMDB CRON] ${asnUrl} download failed: ${
e.message ?? e
}`,
);
}
}
$.info(`[MMDB CRON] ${mmdb_cron} finished`);
} catch (e) {
$.error(
`[MMDB CRON] ${mmdb_cron} error: ${e.message ?? e}`,
);
}
}, // onTick
null, // onComplete
true, // start
// 'Asia/Shanghai' // timeZone
);
}
const path = eval(`require("path")`); const path = eval(`require("path")`);
const fs = eval(`require("fs")`); const fs = eval(`require("fs")`);
const data_url = eval('process.env.SUB_STORE_DATA_URL'); const data_url = eval('process.env.SUB_STORE_DATA_URL');

View File

@@ -49,17 +49,11 @@ export default function register($app) {
success(res); success(res);
}); });
if (ENV().isNode) { // Redirect sub.store to vercel webpage
$app.get('/', getEnv); $app.get('/', async (req, res) => {
} else { // 302 redirect
// Redirect sub.store to vercel webpage res.set('location', 'https://sub-store.vercel.app/').status(302).end();
$app.get('/', async (req, res) => { });
// 302 redirect
res.set('location', 'https://sub-store.vercel.app/')
.status(302)
.end();
});
}
// handle preflight request for QX // handle preflight request for QX
if (ENV().isQX) { if (ENV().isQX) {

View File

@@ -47,22 +47,15 @@ async function previewFile(req, res) {
}), }),
); );
if (Object.keys(errors).length > 0) { if (
if (!file.ignoreFailedRemoteFile) { !file.ignoreFailedRemoteFile &&
throw new Error( Object.keys(errors).length > 0
`文件 ${file.name} 的远程文件 ${Object.keys( ) {
errors, throw new Error(
).join(', ')} 发生错误, 请查看日志`, `文件 ${file.name} 的远程文件 ${Object.keys(
); errors,
} else if (file.ignoreFailedRemoteFile === 'enabled') { ).join(', ')} 发生错误, 请查看日志`,
$.notify( );
`🌍 Sub-Store 预览文件失败`,
`${file.name}`,
`远程文件 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
}
} }
if (file.mergeSources === 'localFirst') { if (file.mergeSources === 'localFirst') {
content.unshift(file.content); content.unshift(file.content);
@@ -143,22 +136,12 @@ async function compareSub(req, res) {
}), }),
); );
if (Object.keys(errors).length > 0) { if (!sub.ignoreFailedRemoteSub && Object.keys(errors).length > 0) {
if (!sub.ignoreFailedRemoteSub) { throw new Error(
throw new Error( `订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join(
`订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join( ', ',
', ', )} 发生错误, 请查看日志`,
)} 发生错误, 请查看日志`, );
);
} else if (sub.ignoreFailedRemoteSub === 'enabled') {
$.notify(
`🌍 Sub-Store 预览订阅失败`,
`${sub.name}`,
`远程订阅 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
}
} }
if (sub.mergeSources === 'localFirst') { if (sub.mergeSources === 'localFirst') {
content.unshift(sub.content); content.unshift(sub.content);
@@ -261,25 +244,15 @@ async function compareCollection(req, res) {
} }
}), }),
); );
if (
if (Object.keys(errors).length > 0) { !sub.ignoreFailedRemoteSub &&
if (!sub.ignoreFailedRemoteSub) { Object.keys(errors).length > 0
throw new Error( ) {
`订阅 ${sub.name} 的远程订阅 ${Object.keys( throw new Error(
errors, `订阅 ${sub.name} 的远程订阅 ${Object.keys(
).join(', ')} 发生错误, 请查看日志`, errors,
); ).join(', ')} 发生错误, 请查看日志`,
} else if ( );
sub.ignoreFailedRemoteSub === 'enabled'
) {
$.notify(
`🌍 Sub-Store 预览订阅失败`,
`${sub.name}`,
`远程订阅 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
}
} }
if (sub.mergeSources === 'localFirst') { if (sub.mergeSources === 'localFirst') {
raw.unshift(sub.content); raw.unshift(sub.content);
@@ -311,28 +284,20 @@ async function compareCollection(req, res) {
errors[name] = err; errors[name] = err;
$.error( $.error(
`❌ 处理组合订阅 ${collection.name} 中的子订阅: ${sub.name} 时出现错误:${err}`, `❌ 处理组合订阅 ${collection.name} 中的子订阅: ${sub.name}时出现错误:${err}`,
); );
} }
}), }),
); );
if (
if (Object.keys(errors).length > 0) { !collection.ignoreFailedRemoteSub &&
if (!collection.ignoreFailedRemoteSub) { Object.keys(errors).length > 0
throw new Error( ) {
`组合订阅 ${collection.name} 的子订阅 ${Object.keys( throw new Error(
errors, `组合订阅 ${collection.name} 中的子订阅 ${Object.keys(
).join(', ')} 发生错误, 请查看日志`, errors,
); ).join(', ')} 发生错误, 请查看日志`,
} else if (collection.ignoreFailedRemoteSub === 'enabled') { );
$.notify(
`🌍 Sub-Store 预览组合订阅失败`,
`${collection.name}`,
`子订阅 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
}
} }
// merge proxies with the original order // merge proxies with the original order
const original = Array.prototype.concat.apply( const original = Array.prototype.concat.apply(

View File

@@ -89,23 +89,12 @@ async function produceArtifact({
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') { if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
subIgnoreFailedRemoteSub = ignoreFailedRemoteSub; subIgnoreFailedRemoteSub = ignoreFailedRemoteSub;
} }
if (!subIgnoreFailedRemoteSub && Object.keys(errors).length > 0) {
if (Object.keys(errors).length > 0) { throw new Error(
if (!subIgnoreFailedRemoteSub) { `订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join(
throw new Error( ', ',
`订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join( )} 发生错误, 请查看日志`,
', ', );
)} 发生错误, 请查看日志`,
);
} else if (subIgnoreFailedRemoteSub === 'enabled') {
$.notify(
`🌍 Sub-Store 处理订阅失败`,
`${sub.name}`,
`远程订阅 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
}
} }
if (mergeSources === 'localFirst') { if (mergeSources === 'localFirst') {
raw.unshift(content); raw.unshift(content);
@@ -149,23 +138,12 @@ async function produceArtifact({
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') { if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
subIgnoreFailedRemoteSub = ignoreFailedRemoteSub; subIgnoreFailedRemoteSub = ignoreFailedRemoteSub;
} }
if (!subIgnoreFailedRemoteSub && Object.keys(errors).length > 0) {
if (Object.keys(errors).length > 0) { throw new Error(
if (!subIgnoreFailedRemoteSub) { `订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join(
throw new Error( ', ',
`订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join( )} 发生错误, 请查看日志`,
', ', );
)} 发生错误, 请查看日志`,
);
} else if (subIgnoreFailedRemoteSub === 'enabled') {
$.notify(
`🌍 Sub-Store 处理订阅失败`,
`${sub.name}`,
`远程订阅 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
}
} }
if (sub.mergeSources === 'localFirst') { if (sub.mergeSources === 'localFirst') {
raw.unshift(sub.content); raw.unshift(sub.content);
@@ -173,9 +151,6 @@ async function produceArtifact({
raw.push(sub.content); raw.push(sub.content);
} }
} }
if (produceType === 'raw') {
return JSON.stringify((Array.isArray(raw) ? raw : [raw]).flat());
}
// parse proxies // parse proxies
let proxies = (Array.isArray(raw) ? raw : [raw]) let proxies = (Array.isArray(raw) ? raw : [raw])
.map((i) => ProxyUtils.parse(i)) .map((i) => ProxyUtils.parse(i))
@@ -289,25 +264,15 @@ async function produceArtifact({
} }
}), }),
); );
if (
if (Object.keys(errors).length > 0) { !sub.ignoreFailedRemoteSub &&
if (!sub.ignoreFailedRemoteSub) { Object.keys(errors).length > 0
throw new Error( ) {
`订阅 ${sub.name} 的远程订阅 ${Object.keys( throw new Error(
errors, `订阅 ${sub.name} 的远程订阅 ${Object.keys(
).join(', ')} 发生错误, 请查看日志`, errors,
); ).join(', ')} 发生错误, 请查看日志`,
} else if ( );
sub.ignoreFailedRemoteSub === 'enabled'
) {
$.notify(
`🌍 Sub-Store 处理订阅失败`,
`${sub.name}`,
`远程订阅 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
}
} }
if (sub.mergeSources === 'localFirst') { if (sub.mergeSources === 'localFirst') {
raw.unshift(sub.content); raw.unshift(sub.content);
@@ -362,23 +327,15 @@ async function produceArtifact({
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') { if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
collectionIgnoreFailedRemoteSub = ignoreFailedRemoteSub; collectionIgnoreFailedRemoteSub = ignoreFailedRemoteSub;
} }
if (
if (Object.keys(errors).length > 0) { !collectionIgnoreFailedRemoteSub &&
if (!collectionIgnoreFailedRemoteSub) { Object.keys(errors).length > 0
throw new Error( ) {
`组合订阅 ${collection.name} 的子订阅 ${Object.keys( throw new Error(
errors, `组合订阅 ${name} 中的子订阅 ${Object.keys(errors).join(
).join(', ')} 发生错误, 请查看日志`, ', ',
); )} 发生错误, 请查看日志`,
} else if (collectionIgnoreFailedRemoteSub === 'enabled') { );
$.notify(
`🌍 Sub-Store 处理组合订阅失败`,
`${collection.name}`,
`子订阅 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
}
} }
// merge proxies with the original order // merge proxies with the original order
@@ -548,23 +505,15 @@ async function produceArtifact({
) { ) {
fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile; fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile;
} }
if (
if (Object.keys(errors).length > 0) { !fileIgnoreFailedRemoteFile &&
if (!fileIgnoreFailedRemoteFile) { Object.keys(errors).length > 0
throw new Error( ) {
`文件 ${file.name} 的远程文件 ${Object.keys( throw new Error(
errors, `文件 ${file.name} 的远程文件 ${Object.keys(
).join(', ')} 发生错误, 请查看日志`, errors,
); ).join(', ')} 发生错误, 请查看日志`,
} else if (fileIgnoreFailedRemoteFile === 'enabled') { );
$.notify(
`🌍 Sub-Store 处理文件失败`,
`${file.name}`,
`远程文件 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
}
} }
if (file.mergeSources === 'localFirst') { if (file.mergeSources === 'localFirst') {
raw.unshift(file.content); raw.unshift(file.content);
@@ -573,9 +522,6 @@ async function produceArtifact({
} }
} }
} }
if (produceType === 'raw') {
return JSON.stringify((Array.isArray(raw) ? raw : [raw]).flat());
}
const files = (Array.isArray(raw) ? raw : [raw]).flat(); const files = (Array.isArray(raw) ? raw : [raw]).flat();
let filesContent = files let filesContent = files
.filter((i) => i != null && i !== '') .filter((i) => i != null && i !== '')

View File

@@ -273,25 +273,3 @@ export default async function download(
} }
return result; return result;
} }
export async function downloadFile(url, file) {
const undici = eval("require('undici')");
const fs = eval("require('fs')");
const { pipeline } = eval("require('stream/promises')");
const { Agent, interceptors, request } = undici;
$.info(`Downloading file...\nURL: ${url}\nFile: ${file}`);
const { body, statusCode } = await request(url, {
dispatcher: new Agent().compose(
interceptors.redirect({
maxRedirections: 3,
throwOnRedirect: true,
}),
),
});
if (statusCode !== 200)
throw new Error(`Failed to download file from ${url}`);
const fileStream = fs.createWriteStream(file);
await pipeline(body, fileStream);
$.info(`File downloaded from ${url} to ${file}`);
return file;
}

View File

@@ -75,7 +75,7 @@ export function shouldIncludeUnsupportedProxy(platform, ua) {
if ( if (
platform === 'Stash' && platform === 'Stash' &&
target === 'Stash' && target === 'Stash' &&
gte(version, '3.1.0') gte(version, '2.8.0')
) { ) {
return true; return true;
} }
@@ -90,7 +90,7 @@ export function shouldIncludeUnsupportedProxy(platform, ua) {
if ( if (
platform === 'Loon' && platform === 'Loon' &&
target === 'Loon' && target === 'Loon' &&
gte(version, '842.0.0') gte(version, '838.0.0')
) { ) {
return true; return true;
} }

View File

@@ -34,6 +34,4 @@ export default {
load, load,
safeDump, safeDump,
dump, dump,
parse: safeLoad,
stringify: safeDump,
}; };

View File

@@ -17,12 +17,10 @@ export default function express({ substore: $, port, host }) {
const express_ = eval(`require("express")`); const express_ = eval(`require("express")`);
const bodyParser = eval(`require("body-parser")`); const bodyParser = eval(`require("body-parser")`);
const app = express_(); const app = express_();
const limit = eval('process.env.SUB_STORE_BODY_JSON_LIMIT') || '1mb';
$.info(`[BACKEND] body JSON limit: ${limit}`);
app.use( app.use(
bodyParser.json({ bodyParser.json({
verify: rawBodySaver, verify: rawBodySaver,
limit, limit: eval('process.env.SUB_STORE_BODY_JSON_LIMIT') || '1mb',
}), }),
); );
app.use( app.use(
@@ -38,7 +36,7 @@ export default function express({ substore: $, port, host }) {
app.start = () => { app.start = () => {
const listener = app.listen(port, host, () => { const listener = app.listen(port, host, () => {
const { address, port } = listener.address(); const { address, port } = listener.address();
$.info(`[BACKEND] listening on ${address}:${port}`); $.info(`[BACKEND] ${address}:${port}`);
}); });
}; };
return app; return app;

View File

@@ -10,14 +10,6 @@ const isEgern = 'object' == typeof egern;
const isLanceX = 'undefined' != typeof $native; const isLanceX = 'undefined' != typeof $native;
const isGUIforCores = typeof $Plugins !== 'undefined'; const isGUIforCores = typeof $Plugins !== 'undefined';
function isPlainObject(obj) {
return (
obj !== null &&
typeof obj === 'object' &&
[null, Object.prototype].includes(Object.getPrototypeOf(obj))
);
}
export class OpenAPI { export class OpenAPI {
constructor(name = 'untitled', debug = false) { constructor(name = 'untitled', debug = false) {
this.name = name; this.name = name;
@@ -70,50 +62,29 @@ export class OpenAPI {
const basePath = const basePath =
eval('process.env.SUB_STORE_DATA_BASE_PATH') || '.'; eval('process.env.SUB_STORE_DATA_BASE_PATH') || '.';
let rootPath = `${basePath}/root.json`; let rootPath = `${basePath}/root.json`;
const backupRootPath = `${basePath}/root_${Date.now()}.json`;
this.log(`Root path: ${rootPath}`); this.log(`Root path: ${rootPath}`);
if (this.node.fs.existsSync(rootPath)) { if (!this.node.fs.existsSync(rootPath)) {
try {
this.root = JSON.parse(
this.node.fs.readFileSync(`${rootPath}`),
);
} catch (e) {
this.node.fs.copyFileSync(rootPath, backupRootPath);
this.error(
`Failed to parse ${rootPath}: ${e.message}. Backup created at ${backupRootPath}`,
);
}
}
if (!isPlainObject(this.root)) {
this.node.fs.writeFileSync(rootPath, JSON.stringify({}), { this.node.fs.writeFileSync(rootPath, JSON.stringify({}), {
flag: 'w', flag: 'wx',
}); });
this.root = {}; this.root = {};
} else {
this.root = JSON.parse(
this.node.fs.readFileSync(`${rootPath}`),
);
} }
// create a json file with the given name if not exists // create a json file with the given name if not exists
let fpath = `${basePath}/${this.name}.json`; let fpath = `${basePath}/${this.name}.json`;
const backupPath = `${basePath}/${this.name}_${Date.now()}.json`;
this.log(`Data path: ${fpath}`); this.log(`Data path: ${fpath}`);
if (this.node.fs.existsSync(fpath)) { if (!this.node.fs.existsSync(fpath)) {
try {
this.cache = JSON.parse(
this.node.fs.readFileSync(`${fpath}`),
);
} catch (e) {
this.node.fs.copyFileSync(fpath, backupPath);
this.error(
`Failed to parse ${fpath}: ${e.message}. Backup created at ${backupPath}`,
);
}
}
if (!isPlainObject(this.cache)) {
this.node.fs.writeFileSync(fpath, JSON.stringify({}), { this.node.fs.writeFileSync(fpath, JSON.stringify({}), {
flag: 'w', flag: 'wx',
}); });
this.cache = {}; this.cache = {};
} else {
this.cache = JSON.parse(this.node.fs.readFileSync(`${fpath}`));
} }
} }
} }

View File

@@ -13,13 +13,7 @@ function operator(proxies = [], targetPlatform, context) {
// 5. `_subName` 为单条订阅名, `_subDisplayName` 为单条订阅显示名 // 5. `_subName` 为单条订阅名, `_subDisplayName` 为单条订阅显示名
// 6. `_collectionName` 为组合订阅名, `_collectionDisplayName` 为组合订阅显示名 // 6. `_collectionName` 为组合订阅名, `_collectionDisplayName` 为组合订阅显示名
// 7. `tls-fingerprint` 为 tls 指纹 // 7. `tls-fingerprint` 为 tls 指纹
// 8. `underlying-proxy` 为前置代理, 不同平台会自动转换 // 8. `underlying-proxy` 为前置代理
// 例如 $server['underlying-proxy'] = '名称'
// 只给 mihomo 输出的话, `dialer-proxy` 也行
// 只给 sing-box 输出的话, `detour` 也行
// 只给 Egern 输出的话, `prev_hop` 也行
// 只给 Shadowrocket 输出的话, `chain` 也行
// 输出到 Clash/Stash 时, 会过滤掉配置了前置代理的节点, 并提示使用对应的功能.
// 9. `trojan`, `tuic`, `hysteria`, `hysteria2`, `juicity` 会在解析时设置 `tls`: true (会使用 tls 类协议的通用逻辑), 输出时删除 // 9. `trojan`, `tuic`, `hysteria`, `hysteria2`, `juicity` 会在解析时设置 `tls`: true (会使用 tls 类协议的通用逻辑), 输出时删除
// 10. `sni` 在某些协议里会自动与 `servername` 转换 // 10. `sni` 在某些协议里会自动与 `servername` 转换
// 11. 读取节点的 ca-str 和 _ca (后端文件路径) 字段, 自动计算 fingerprint (参考 https://t.me/zhetengsha/1512) // 11. 读取节点的 ca-str 和 _ca (后端文件路径) 字段, 自动计算 fingerprint (参考 https://t.me/zhetengsha/1512)
@@ -28,7 +22,6 @@ function operator(proxies = [], targetPlatform, context) {
// 14. `ports` 为端口跳跃, `hop-interval` 变换端口号的时间间隔 // 14. `ports` 为端口跳跃, `hop-interval` 变换端口号的时间间隔
// 15. `ip-version` 设置节点使用 IP 版本可选dualipv4ipv6ipv4-preferipv6-prefer. 会进行内部转换, 若无法匹配则使用原始值 // 15. `ip-version` 设置节点使用 IP 版本可选dualipv4ipv6ipv4-preferipv6-prefer. 会进行内部转换, 若无法匹配则使用原始值
// 16. `sing-box` 支持使用 `_network` 来设置 `network`, 例如 `tcp`, `udp` // 16. `sing-box` 支持使用 `_network` 来设置 `network`, 例如 `tcp`, `udp`
// 17. `block-quic` 支持 `auto`, `on`, `off`. 不同的平台不一定都支持, 会自动转换
// require 为 Node.js 的 require, 在 Node.js 运行环境下 可以用来引入模块 // require 为 Node.js 的 require, 在 Node.js 运行环境下 可以用来引入模块
// 例如在 Node.js 环境下, 将文件内容写入 /tmp/1.txt 文件 // 例如在 Node.js 环境下, 将文件内容写入 /tmp/1.txt 文件
@@ -47,16 +40,6 @@ function operator(proxies = [], targetPlatform, context) {
// 先这样处理 encodeURIComponent('arg1=a&arg2=b') // 先这样处理 encodeURIComponent('arg1=a&arg2=b')
// /api/file/foo?$options=arg1%3Da%26arg2%3Db // /api/file/foo?$options=arg1%3Da%26arg2%3Db
// 默认会带上 _req 字段, 结构为
// {
// method,
// url,
// path,
// query,
// params,
// headers,
// body,
// }
// console.log($options) // console.log($options)
// targetPlatform 为输出的目标平台 // targetPlatform 为输出的目标平台
@@ -96,12 +79,6 @@ function operator(proxies = [], targetPlatform, context) {
// 其他平台同理, 持久化缓存数据在 JSON 里 // 其他平台同理, 持久化缓存数据在 JSON 里
// 当配合脚本使用时, 可以在脚本的前面添加一个脚本操作, 实现保留 1 小时的缓存. 这样比较灵活
// async function operator() {
// scriptResourceCache._cleanup(undefined, 1 * 3600 * 1000);
// }
// ProxyUtils 为节点处理工具 // ProxyUtils 为节点处理工具
// 可参考 https://t.me/zhetengsha/1066 // 可参考 https://t.me/zhetengsha/1066
// const ProxyUtils = { // const ProxyUtils = {
@@ -119,13 +96,9 @@ function operator(proxies = [], targetPlatform, context) {
// getISO, // 获取 ISO 3166-1 alpha-2 代码 // getISO, // 获取 ISO 3166-1 alpha-2 代码
// Gist, // Gist 类 // Gist, // Gist 类
// download, // 内部的下载方法, 见 backend/src/utils/download.js // download, // 内部的下载方法, 见 backend/src/utils/download.js
// downloadFile, // 下载二进制文件, 见 backend/src/utils/download.js
// MMDB, // Node.js 环境 可用于模拟 Surge/Loon 的 $utils.ipasn, $utils.ipaso, $utils.geoip. 具体见 https://t.me/zhetengsha/1269 // MMDB, // Node.js 环境 可用于模拟 Surge/Loon 的 $utils.ipasn, $utils.ipaso, $utils.geoip. 具体见 https://t.me/zhetengsha/1269
// isValidUUID, // 辅助判断是否为有效的 UUID // isValidUUID, // 辅助判断是否为有效的 UUID
// Buffer, // https://github.com/feross/buffer
// Base64, // https://github.com/dankogai/js-base64
// } // }
// 为兼容 https://github.com/xishang0128/sparkle 的 JavaScript 覆写, 也可以直接使用 `b64d`(Base64 解码), `b64e`(Base64 编码), `Buffer`, `yaml`(简单兼容了下 `yaml.parse` 和 `yaml.stringify`)
// 如果只是为了快速修改或者筛选 可以参考 脚本操作支持节点快捷脚本 https://t.me/zhetengsha/970 和 脚本筛选支持节点快捷脚本 https://t.me/zhetengsha/1009 // 如果只是为了快速修改或者筛选 可以参考 脚本操作支持节点快捷脚本 https://t.me/zhetengsha/970 和 脚本筛选支持节点快捷脚本 https://t.me/zhetengsha/1009
// ⚠️ 注意: 函数式(即本文件这样的 function operator() {}) 和快捷操作(下面使用 $server) 只能二选一 // ⚠️ 注意: 函数式(即本文件这样的 function operator() {}) 和快捷操作(下面使用 $server) 只能二选一

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB