Compare commits

...

15 Commits

Author SHA1 Message Date
xream
adc95bba60 feat: Surge 全协议支持 Shadow TLS, 部分协议增加 TLS Fingerprint 支持 2023-10-24 07:26:34 +08:00
xream
fab3644b86 feat: 支持 Shadowrocket Hysteria2 URI 格式输入 2023-10-18 23:48:45 +08:00
xream
c21ce0be16 fix: Surge Hysteria2 输出重复添加 tfo 的 bug 2023-10-18 05:09:10 +08:00
xream
fa65eb1850 feat: Base64 订阅关键词增加 VLESS 和 Hysteria2 2023-10-16 22:11:26 +08:00
xream
79c9b89c5f feat: Stash Hysteria2 2023-10-15 15:55:19 +08:00
xream
fca508ba8a feat: Surge Hysteria2 输入/输出增加 ecn 参数 2023-10-12 22:15:10 +08:00
xream
21b531a44d feat: Surge TUIC 输入/输出增加 ecn 参数 2023-10-12 22:09:58 +08:00
xream
4e5b46a43d feat: Surge Hysteria2 输出增加 download-bandwidth(若有值但解析失败则为 0) 2023-10-12 00:39:10 +08:00
xream
bf81ca4acf feat: 输入增加 Hysteria2 URI 支持; Surge Hysteria2 输出增加 fingerprint 2023-10-11 23:35:42 +08:00
xream
e7c0b23222 feat: Surge 输入输出增加 Hysteria2 2023-10-09 23:42:22 +08:00
xream
40fb0fd7f3 feat: 兼容更多 VMess URI 格式 2023-10-09 17:36:11 +08:00
xream
b061fca356 feat: Surge Snell 输入支持解析 reuse 字段 2023-10-08 16:42:35 +08:00
xream
d3c6c99b0a feat: proxy 增加 subName(订阅名), collectionName(组合订阅名); 脚本增加第三个参数 env(包含订阅/组合订阅/环境/版本等信息) 2023-10-08 13:21:22 +08:00
xream
3fbc280e28 [+] 重复节点通知中增加订阅名称和重复节点名称 2023-10-02 16:21:08 +08:00
xream
9e3e4c6e46 [+] Surge 输出支持 underlying-proxy; VMess/Vless URI 支持 gRPC mode(默认为 gun) 2023-10-01 22:05:51 +08:00
19 changed files with 566 additions and 72 deletions

View File

@@ -30,13 +30,14 @@ Core functionalities:
- [x] SSR URI
- [x] SSD URI
- [x] V2RayN URI
- [x] Hysteria2 URI
- [x] QX (SS, SSR, VMess, Trojan, HTTP)
- [x] Loon (SS, SSR, VMess, Trojan, HTTP, WireGuard, VLESS)
- [x] Surge (SS, VMess, Trojan, HTTP, TUIC, Snell, SSR(external, only for macOS), WireGuard(Surge to Surge))
- [x] ShadowRocket (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, WireGuard, Hysteria)
- [x] Clash.Meta (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, WireGuard, Hysteria)
- [x] Surge (SS, VMess, Trojan, HTTP, TUIC, Snell, Hysteria2, SSR(external, only for macOS), WireGuard(Surge to Surge))
- [x] ShadowRocket (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, Hysteria2)
- [x] Clash.Meta (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, WireGuard, Hysteria, Hysteria2)
- [x] Stash (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, WireGuard, Hysteria)
- [x] Clash (SS, SSR, VMess, Trojan, HTTP, Snell)
- [x] Clash (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, WireGuard)
### Supported Target Platforms

View File

@@ -1,6 +1,6 @@
{
"name": "sub-store",
"version": "2.14.60",
"version": "2.14.76",
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
"main": "src/main.js",
"scripts": {

View File

@@ -63,7 +63,7 @@ function parse(raw) {
return proxies;
}
async function process(proxies, operators = [], targetPlatform) {
async function process(proxies, operators = [], targetPlatform, source) {
for (const item of operators) {
// process script
let script;
@@ -122,6 +122,7 @@ async function process(proxies, operators = [], targetPlatform) {
script,
targetPlatform,
$arguments,
source,
);
} else {
processor = PROXY_PROCESSORS[item.type](item.args || {});

View File

@@ -240,7 +240,7 @@ function URI_VMess() {
params.add = server;
}
const proxy = {
name: params.ps ?? params.remark,
name: params.ps ?? params.remarks,
type: 'vmess',
server: params.add,
port: parseInt(getIfPresent(params.port), 10),
@@ -267,9 +267,19 @@ function URI_VMess() {
params.obfs === 'http'
) {
proxy.network = 'http';
} else if (['grpc'].includes(params.net)) {
proxy.network = 'grpc';
}
if (proxy.network) {
let transportHost = params.host ?? params.obfsParam;
try {
const parsedObfs = JSON.parse(transportHost);
const parsedHost = parsedObfs?.Host;
if (parsedHost) {
transportHost = parsedHost;
}
// eslint-disable-next-line no-empty
} catch (e) {}
let transportPath = params.path;
if (proxy.network === 'http') {
@@ -285,10 +295,17 @@ function URI_VMess() {
}
}
if (transportPath || transportHost) {
proxy[`${proxy.network}-opts`] = {
path: getIfNotBlank(transportPath),
headers: { Host: getIfNotBlank(transportHost) },
};
if (['grpc'].includes(proxy.network)) {
proxy[`${proxy.network}-opts`] = {
'grpc-service-name': getIfNotBlank(transportPath),
'_grpc-type': getIfNotBlank(params.type),
};
} else {
proxy[`${proxy.network}-opts`] = {
path: getIfNotBlank(transportPath),
headers: { Host: getIfNotBlank(transportHost) },
};
}
} else {
delete proxy.network;
}
@@ -365,6 +382,10 @@ function URI_VLESS() {
if (params.serviceName) {
opts[`${proxy.network}-service-name`] = params.serviceName;
}
// https://github.com/XTLS/Xray-core/issues/91
if (['grpc'].includes(proxy.network)) {
opts['_grpc-type'] = params.mode || 'gun';
}
if (Object.keys(opts).length > 0) {
proxy[`${proxy.network}-opts`] = opts;
}
@@ -383,6 +404,56 @@ function URI_VLESS() {
};
return { name, test, parse };
}
function URI_Hysteria2() {
const name = 'URI Hysteria2 Parser';
const test = (line) => {
return /^hysteria2:\/\//.test(line);
};
const parse = (line) => {
line = line.split('hysteria2://')[1];
// eslint-disable-next-line no-unused-vars
let [__, password, server, ___, port, addons, name] =
/^(.*?)@(.*?)(:(\d+))?\/?\?(.*?)(?:#(.*?))$/.exec(line);
port = parseInt(`${port}`, 10);
if (isNaN(port)) {
port = 443;
}
password = decodeURIComponent(password);
name = decodeURIComponent(name) ?? `Hysteria2 ${server}:${port}`;
const proxy = {
type: 'hysteria2',
name,
server,
port,
password,
};
const params = {};
for (const addon of addons.split('&')) {
const [key, valueRaw] = addon.split('=');
let value = valueRaw;
value = decodeURIComponent(valueRaw);
params[key] = value;
}
proxy.sni = params.sni;
if (!proxy.sni && params.peer) {
proxy.sni = params.peer;
}
if (params.obfs && params.obfs !== 'none') {
proxy.obfs = params.obfs;
}
proxy['obfs-password'] = params['obfs-password'];
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(params.insecure);
proxy.tfo = /(TRUE)|1/i.test(params.fastopen);
proxy['tls-fingerprint'] = params.pinSHA256;
return proxy;
};
return { name, test, parse };
}
// Trojan URI format
function URI_Trojan() {
@@ -763,11 +834,21 @@ function Surge_WireGuard() {
return { name, test, parse };
}
function Surge_Hysteria2() {
const name = 'Surge Hysteria2 Parser';
const test = (line) => {
return /^.*=\s*hysteria2/.test(line.split(',')[0]);
};
const parse = (line) => getSurgeParser().parse(line);
return { name, test, parse };
}
export default [
URI_SS(),
URI_SSR(),
URI_VMess(),
URI_VLESS(),
URI_Hysteria2(),
URI_Trojan(),
Clash_All(),
Surge_SS(),
@@ -777,6 +858,7 @@ export default [
Surge_Snell(),
Surge_Tuic(),
Surge_WireGuard(),
Surge_Hysteria2(),
Surge_Socks5(),
Loon_SS(),
Loon_SSR(),

View File

@@ -32,11 +32,11 @@ const grammars = String.raw`
}
}
start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v5/wireguard) {
start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v5/wireguard/hysteria2) {
return proxy;
}
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/fast_open/udp_relay/others)* {
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "ss";
// handle obfs
if (obfs.type == "http" || obfs.type === "tls") {
@@ -46,7 +46,7 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
$set(proxy, "plugin-opts.path", obfs.path);
}
}
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/others)* {
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "vmess";
proxy.cipher = proxy.cipher || "none";
if (proxy.aead) {
@@ -56,18 +56,18 @@ vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/
}
handleWebsocket();
}
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/others)* {
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "trojan";
handleWebsocket();
}
https = tag equals "https" address (username password)? (sni/tls_fingerprint/tls_verification/fast_open/others)* {
https = tag equals "https" address (username password)? (sni/tls_fingerprint/tls_verification/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "http";
proxy.tls = true;
}
http = tag equals "http" address (username password)? (fast_open/others)* {
http = tag equals "http" address (username password)? (fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "http";
}
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/fast_open/udp_relay/others)* {
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/fast_open/udp_relay/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "snell";
// handle obfs
if (obfs.type == "http" || obfs.type === "tls") {
@@ -76,20 +76,23 @@ snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_
$set(proxy, "obfs-opts.path", obfs.path);
}
}
tuic = tag equals "tuic" address (alpn/token/ip_version/tls_verification/sni/fast_open/tfo/others)* {
tuic = tag equals "tuic" address (alpn/token/ip_version/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "tuic";
}
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/tls_verification/sni/fast_open/tfo/others)* {
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "tuic";
proxy.version = 5;
}
wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/underlying_proxy/test_url/others)* {
wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/underlying_proxy/test_url/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "wireguard-surge";
}
socks5 = tag equals "socks5" address (username password)? (fast_open/others)* {
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/test_url/sni/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "hysteria2";
}
socks5 = tag equals "socks5" address (username password)? (fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "socks5";
}
socks5_tls = tag equals "socks5-tls" address (username password)? (sni/tls_fingerprint/tls_verification/fast_open/others)* {
socks5_tls = tag equals "socks5-tls" address (username password)? (sni/tls_fingerprint/tls_verification/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "socks5";
proxy.tls = true;
}
@@ -188,12 +191,18 @@ uri = $[^,]+
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
reuse = comma "reuse" equals flag:bool { proxy.reuse = flag; }
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
tfo = comma "tfo" equals flag:bool { proxy.tfo = flag; }
ip_version = comma "ip-version" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }
section_name = comma "section-name" equals match:[^,]+ { proxy["section-name"] = match.join(""); }
no_error_alert = comma "no-error-alert" equals match:[^,]+ { proxy["no-error-alert"] = match.join(""); }
underlying_proxy = comma "underlying-proxy" equals match:[^,]+ { proxy["underlying-proxy"] = match.join(""); }
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
test_url = comma "test-url" equals match:[^,]+ { proxy["test-url"] = match.join(""); }
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-sni"] = match.join(""); }
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join(""); }
token = comma "token" equals match:[^,]+ { proxy.token = match.join(""); }
alpn = comma "alpn" equals match:[^,]+ { proxy.alpn = match.join(""); }
uuidk = comma "uuid" equals match:[^,]+ { proxy.uuid = match.join(""); }

View File

@@ -30,11 +30,11 @@
}
}
start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v5/wireguard) {
start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v5/wireguard/hysteria2) {
return proxy;
}
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/fast_open/udp_relay/others)* {
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "ss";
// handle obfs
if (obfs.type == "http" || obfs.type === "tls") {
@@ -44,7 +44,7 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
$set(proxy, "plugin-opts.path", obfs.path);
}
}
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/others)* {
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "vmess";
proxy.cipher = proxy.cipher || "none";
if (proxy.aead) {
@@ -54,18 +54,18 @@ vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/
}
handleWebsocket();
}
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/others)* {
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "trojan";
handleWebsocket();
}
https = tag equals "https" address (username password)? (sni/tls_fingerprint/tls_verification/fast_open/others)* {
https = tag equals "https" address (username password)? (sni/tls_fingerprint/tls_verification/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "http";
proxy.tls = true;
}
http = tag equals "http" address (username password)? (fast_open/others)* {
http = tag equals "http" address (username password)? (fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "http";
}
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/fast_open/udp_relay/others)* {
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/fast_open/udp_relay/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "snell";
// handle obfs
if (obfs.type == "http" || obfs.type === "tls") {
@@ -74,20 +74,23 @@ snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_
$set(proxy, "obfs-opts.path", obfs.path);
}
}
tuic = tag equals "tuic" address (alpn/token/ip_version/tls_verification/sni/fast_open/tfo/others)* {
tuic = tag equals "tuic" address (alpn/token/ip_version/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "tuic";
}
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/tls_verification/sni/fast_open/tfo/others)* {
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "tuic";
proxy.version = 5;
}
wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/underlying_proxy/test_url/others)* {
wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/underlying_proxy/test_url/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "wireguard-surge";
}
socks5 = tag equals "socks5" address (username password)? (fast_open/others)* {
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/test_url/sni/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "hysteria2";
}
socks5 = tag equals "socks5" address (username password)? (fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "socks5";
}
socks5_tls = tag equals "socks5-tls" address (username password)? (sni/tls_fingerprint/tls_verification/fast_open/others)* {
socks5_tls = tag equals "socks5-tls" address (username password)? (sni/tls_fingerprint/tls_verification/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "socks5";
proxy.tls = true;
}
@@ -186,12 +189,18 @@ uri = $[^,]+
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
reuse = comma "reuse" equals flag:bool { proxy.reuse = flag; }
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
tfo = comma "tfo" equals flag:bool { proxy.tfo = flag; }
ip_version = comma "ip-version" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }
section_name = comma "section-name" equals match:[^,]+ { proxy["section-name"] = match.join(""); }
no_error_alert = comma "no-error-alert" equals match:[^,]+ { proxy["no-error-alert"] = match.join(""); }
underlying_proxy = comma "underlying-proxy" equals match:[^,]+ { proxy["underlying-proxy"] = match.join(""); }
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
test_url = comma "test-url" equals match:[^,]+ { proxy["test-url"] = match.join(""); }
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-sni"] = match.join(""); }
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join(""); }
token = comma "token" equals match:[^,]+ { proxy.token = match.join(""); }
alpn = comma "alpn" equals match:[^,]+ { proxy.alpn = match.join(""); }
uuidk = comma "uuid" equals match:[^,]+ { proxy.uuid = match.join(""); }

View File

@@ -13,13 +13,15 @@ function Base64Encoded() {
const name = 'Base64 Pre-processor';
const keys = [
'dm1lc3M',
'c3NyOi8v',
'dHJvamFu',
'c3M6Ly',
'c3NkOi8v',
'c2hhZG93',
'aHR0c',
'dm1lc3M', // vmess
'c3NyOi8v', // ssr://
'dHJvamFu', // trojan
'c3M6Ly', // ss:/
'c3NkOi8v', // ssd://
'c2hhZG93', // shadow
'aHR0c', // htt
'dmxlc3M=', // vless
'aHlzdGVyaWEy', // hysteria2
];
const test = function (raw) {

View File

@@ -7,6 +7,7 @@ import lodash from 'lodash';
import $ from '@/core/app';
import { hex_md5 } from '@/vendor/md5';
import { ProxyUtils } from '@/core/proxy-utils';
import env from '@/utils/env';
/**
The rule "(name CONTAINS "🇨🇳") AND (port IN [80, 443])" can be expressed as follows:
@@ -294,7 +295,7 @@ function RegexDeleteOperator(regex) {
1. This function name should be `operator`!
2. Always declare variables before using them!
*/
function ScriptOperator(script, targetPlatform, $arguments) {
function ScriptOperator(script, targetPlatform, $arguments, source) {
return {
name: 'Script Operator',
func: async (proxies) => {
@@ -305,7 +306,7 @@ function ScriptOperator(script, targetPlatform, $arguments) {
script,
$arguments,
);
output = operator(proxies, targetPlatform);
output = operator(proxies, targetPlatform, { source, ...env });
})();
return output;
},
@@ -562,7 +563,7 @@ function TypeFilter(types) {
1. This function name should be `filter`!
2. Always declare variables before using them!
*/
function ScriptFilter(script, targetPlatform, $arguments) {
function ScriptFilter(script, targetPlatform, $arguments, source) {
return {
name: 'Script Filter',
func: async (proxies) => {
@@ -573,7 +574,7 @@ function ScriptFilter(script, targetPlatform, $arguments) {
script,
$arguments,
);
output = filter(proxies, targetPlatform);
output = filter(proxies, targetPlatform, { source, ...env });
})();
return output;
},

View File

@@ -97,7 +97,19 @@ export default function Clash_Producer() {
) {
delete proxy.tls;
}
if (proxy['tls-fingerprint']) {
proxy.fingerprint = proxy['tls-fingerprint'];
}
delete proxy['tls-fingerprint'];
delete proxy.subName;
delete proxy.collectionName;
if (
['grpc'].includes(proxy.network) &&
proxy[`${proxy.network}-opts`]
) {
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
}
return ' - ' + JSON.stringify(proxy) + '\n';
})
.join('')

View File

@@ -116,7 +116,18 @@ export default function ClashMeta_Producer() {
delete proxy.tls;
}
if (proxy['tls-fingerprint']) {
proxy.fingerprint = proxy['tls-fingerprint'];
}
delete proxy['tls-fingerprint'];
delete proxy.subName;
delete proxy.collectionName;
if (
['grpc'].includes(proxy.network) &&
proxy[`${proxy.network}-opts`]
) {
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
}
return ' - ' + JSON.stringify(proxy) + '\n';
})
.join('')

View File

@@ -116,7 +116,18 @@ export default function ShadowRocket_Producer() {
delete proxy.tls;
}
if (proxy['tls-fingerprint']) {
proxy.fingerprint = proxy['tls-fingerprint'];
}
delete proxy['tls-fingerprint'];
delete proxy.subName;
delete proxy.collectionName;
if (
['grpc'].includes(proxy.network) &&
proxy[`${proxy.network}-opts`]
) {
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
}
return ' - ' + JSON.stringify(proxy) + '\n';
})
.join('')

View File

@@ -19,6 +19,8 @@ export default function Stash_Producer() {
'tuic',
'vless',
'wireguard',
'hysteria',
'hysteria2',
].includes(proxy.type) ||
(proxy.type === 'snell' &&
String(proxy.version) === '4') ||
@@ -67,6 +69,7 @@ export default function Stash_Producer() {
!isPresent(proxy, 'fast-open')
) {
proxy['fast-open'] = proxy.tfo;
delete proxy.tfo;
}
// https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/adapter/outbound/tuic.go#L197
if (
@@ -86,6 +89,50 @@ export default function Stash_Producer() {
!isPresent(proxy, 'fast-open')
) {
proxy['fast-open'] = proxy.tfo;
delete proxy.tfo;
}
if (
isPresent(proxy, 'down') &&
!isPresent(proxy, 'down-speed')
) {
proxy['down-speed'] = proxy.down;
delete proxy.down;
}
if (
isPresent(proxy, 'up') &&
!isPresent(proxy, 'up-speed')
) {
proxy['up-speed'] = proxy.up;
delete proxy.up;
}
} else if (proxy.type === 'hysteria2') {
if (
isPresent(proxy, 'password') &&
!isPresent(proxy, 'auth')
) {
proxy.auth = proxy.password;
delete proxy.password;
}
if (
isPresent(proxy, 'tfo') &&
!isPresent(proxy, 'fast-open')
) {
proxy['fast-open'] = proxy.tfo;
delete proxy.tfo;
}
if (
isPresent(proxy, 'down') &&
!isPresent(proxy, 'down-speed')
) {
proxy['down-speed'] = proxy.down;
delete proxy.down;
}
if (
isPresent(proxy, 'up') &&
!isPresent(proxy, 'up-speed')
) {
proxy['up-speed'] = proxy.up;
delete proxy.up;
}
} else if (proxy.type === 'wireguard') {
proxy.keepalive =
@@ -127,7 +174,18 @@ export default function Stash_Producer() {
) {
delete proxy.tls;
}
if (proxy['tls-fingerprint']) {
proxy.fingerprint = proxy['tls-fingerprint'];
}
delete proxy['tls-fingerprint'];
delete proxy.subName;
delete proxy.collectionName;
if (
['grpc'].includes(proxy.network) &&
proxy[`${proxy.network}-opts`]
) {
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
}
return ' - ' + JSON.stringify(proxy) + '\n';
})
.join('')

View File

@@ -31,6 +31,8 @@ export default function Surge_Producer() {
return tuic(proxy);
case 'wireguard-surge':
return wireguard(proxy);
case 'hysteria2':
return hysteria2(proxy);
}
throw new Error(
`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`,
@@ -71,6 +73,27 @@ function shadowsocks(proxy) {
// test-url
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
// shadow-tls
if (isPresent(proxy, 'shadow-tls-password')) {
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
result.appendIfPresent(
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
'shadow-tls-version',
);
result.appendIfPresent(
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
'shadow-tls-sni',
);
}
// underlying-proxy
result.appendIfPresent(
`,underlying-proxy=${proxy['underlying-proxy']}`,
'underlying-proxy',
);
return result.toString();
}
@@ -107,6 +130,27 @@ function trojan(proxy) {
// test-url
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
// shadow-tls
if (isPresent(proxy, 'shadow-tls-password')) {
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
result.appendIfPresent(
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
'shadow-tls-version',
);
result.appendIfPresent(
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
'shadow-tls-sni',
);
}
// underlying-proxy
result.appendIfPresent(
`,underlying-proxy=${proxy['underlying-proxy']}`,
'underlying-proxy',
);
return result.toString();
}
@@ -150,6 +194,27 @@ function vmess(proxy) {
// test-url
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
// shadow-tls
if (isPresent(proxy, 'shadow-tls-password')) {
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
result.appendIfPresent(
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
'shadow-tls-version',
);
result.appendIfPresent(
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
'shadow-tls-sni',
);
}
// underlying-proxy
result.appendIfPresent(
`,underlying-proxy=${proxy['underlying-proxy']}`,
'underlying-proxy',
);
return result.toString();
}
@@ -182,6 +247,27 @@ function http(proxy) {
// test-url
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
// shadow-tls
if (isPresent(proxy, 'shadow-tls-password')) {
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
result.appendIfPresent(
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
'shadow-tls-version',
);
result.appendIfPresent(
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
'shadow-tls-sni',
);
}
// underlying-proxy
result.appendIfPresent(
`,underlying-proxy=${proxy['underlying-proxy']}`,
'underlying-proxy',
);
return result.toString();
}
@@ -216,6 +302,27 @@ function socks5(proxy) {
// test-url
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
// shadow-tls
if (isPresent(proxy, 'shadow-tls-password')) {
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
result.appendIfPresent(
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
'shadow-tls-version',
);
result.appendIfPresent(
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
'shadow-tls-sni',
);
}
// underlying-proxy
result.appendIfPresent(
`,underlying-proxy=${proxy['underlying-proxy']}`,
'underlying-proxy',
);
return result.toString();
}
@@ -245,6 +352,27 @@ function snell(proxy) {
// test-url
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
// shadow-tls
if (isPresent(proxy, 'shadow-tls-password')) {
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
result.appendIfPresent(
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
'shadow-tls-version',
);
result.appendIfPresent(
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
'shadow-tls-sni',
);
}
// underlying-proxy
result.appendIfPresent(
`,underlying-proxy=${proxy['underlying-proxy']}`,
'underlying-proxy',
);
// reuse
result.appendIfPresent(`,reuse=${proxy['reuse']}`, 'reuse');
@@ -281,13 +409,45 @@ function tuic(proxy) {
'skip-cert-verify',
);
// tls fingerprint
result.appendIfPresent(
`,server-cert-fingerprint-sha256=${proxy['tls-fingerprint']}`,
'tls-fingerprint',
);
// tfo
result.appendIfPresent(`,tfo=${proxy['fast-open']}`, 'fast-open');
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
if (isPresent(proxy, 'tfo')) {
result.append(`,tfo=${proxy['tfo']}`);
} else if (isPresent(proxy, 'fast-open')) {
result.append(`,tfo=${proxy['fast-open']}`);
}
// test-url
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
// shadow-tls
if (isPresent(proxy, 'shadow-tls-password')) {
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
result.appendIfPresent(
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
'shadow-tls-version',
);
result.appendIfPresent(
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
'shadow-tls-sni',
);
}
// underlying-proxy
result.appendIfPresent(
`,underlying-proxy=${proxy['underlying-proxy']}`,
'underlying-proxy',
);
result.appendIfPresent(`,ecn=${proxy.ecn}`, 'ecn');
return result.toString();
}
@@ -304,10 +464,7 @@ function wireguard(proxy) {
`,no-error-alert=${proxy['no-error-alert']}`,
'no-error-alert',
);
result.appendIfPresent(
`,underlying-proxy=${proxy['underlying-proxy']}`,
'underlying-proxy',
);
result.appendIfPresent(
`,ip-version=${ipVersions[proxy['ip-version']] || proxy['ip-version']}`,
'ip-version',
@@ -316,6 +473,99 @@ function wireguard(proxy) {
// test-url
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
// shadow-tls
if (isPresent(proxy, 'shadow-tls-password')) {
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
result.appendIfPresent(
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
'shadow-tls-version',
);
result.appendIfPresent(
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
'shadow-tls-sni',
);
}
// underlying-proxy
result.appendIfPresent(
`,underlying-proxy=${proxy['underlying-proxy']}`,
'underlying-proxy',
);
return result.toString();
}
function hysteria2(proxy) {
if (proxy.obfs || proxy['obfs-password']) {
throw new Error(`obfs is unsupported`);
}
const result = new Result(proxy);
result.append(`${proxy.name}=hysteria2,${proxy.server},${proxy.port}`);
result.appendIfPresent(`,password=${proxy.password}`, 'password');
result.appendIfPresent(
`,ip-version=${ipVersions[proxy['ip-version']] || proxy['ip-version']}`,
'ip-version',
);
result.appendIfPresent(
`,no-error-alert=${proxy['no-error-alert']}`,
'no-error-alert',
);
// tls verification
result.appendIfPresent(`,sni=${proxy.sni}`, 'sni');
result.appendIfPresent(
`,skip-cert-verify=${proxy['skip-cert-verify']}`,
'skip-cert-verify',
);
result.appendIfPresent(
`,server-cert-fingerprint-sha256=${proxy['tls-fingerprint']}`,
'tls-fingerprint',
);
// tfo
if (isPresent(proxy, 'tfo')) {
result.append(`,tfo=${proxy['tfo']}`);
} else if (isPresent(proxy, 'fast-open')) {
result.append(`,tfo=${proxy['fast-open']}`);
}
// test-url
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
// shadow-tls
if (isPresent(proxy, 'shadow-tls-password')) {
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
result.appendIfPresent(
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
'shadow-tls-version',
);
result.appendIfPresent(
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
'shadow-tls-sni',
);
}
// underlying-proxy
result.appendIfPresent(
`,underlying-proxy=${proxy['underlying-proxy']}`,
'underlying-proxy',
);
// download-bandwidth
result.appendIfPresent(
`,download-bandwidth=${`${proxy['down']}`.match(/\d+/)?.[0] || 0}`,
'down',
);
result.appendIfPresent(`,ecn=${proxy.ecn}`, 'ecn');
return result.toString();
}

View File

@@ -91,6 +91,16 @@ export default function URI_Producer() {
? vmessTransportHost[0]
: vmessTransportHost;
}
if (['grpc'].includes(proxy.network)) {
result.path =
proxy[`${proxy.network}-opts`]?.[
'grpc-service-name'
];
// https://github.com/XTLS/Xray-core/issues/91
result.type =
proxy[`${proxy.network}-opts`]?.['_grpc-type'] ||
'gun';
}
}
result = 'vmess://' + Base64.encode(JSON.stringify(result));
break;
@@ -141,6 +151,12 @@ export default function URI_Producer() {
let vlessTransport = `&type=${encodeURIComponent(
proxy.network,
)}`;
if (['grpc'].includes(proxy.network)) {
// https://github.com/XTLS/Xray-core/issues/91
vlessTransport += `&mode=${encodeURIComponent(
proxy[`${proxy.network}-opts`]?.['_grpc-type'] || 'gun',
)}`;
}
let vlessTransportServiceName =
proxy[`${proxy.network}-opts`]?.[

View File

@@ -1,7 +1,6 @@
import $ from '@/core/app';
import { ENV } from '@/vendor/open-api';
import { failed, success } from '@/restful/response';
import { version as substoreVersion } from '../../package.json';
import { updateArtifactStore, updateGitHubAvatar } from '@/restful/settings';
import resourceCache from '@/utils/resource-cache';
import {
@@ -12,6 +11,7 @@ import {
import { InternalServerError, RequestInvalidError } from '@/restful/errors';
import Gist from '@/utils/gist';
import migrate from '@/utils/migration';
import env from '@/utils/env';
export default function register($app) {
// utils
@@ -49,19 +49,7 @@ export default function register($app) {
}
function getEnv(req, res) {
const { isNode, isQX, isLoon, isSurge, isStash, isShadowRocket } = ENV();
let backend = 'Node';
if (isNode) backend = 'Node';
if (isQX) backend = 'QX';
if (isLoon) backend = 'Loon';
if (isSurge) backend = 'Surge';
if (isStash) backend = 'Stash';
if (isShadowRocket) backend = 'ShadowRocket';
success(res, {
backend,
version: substoreVersion,
});
success(res, env);
}
async function refresh(_, res) {

View File

@@ -39,6 +39,7 @@ async function compareSub(req, res) {
// add id
original.forEach((proxy, i) => {
proxy.id = i;
proxy.subName = sub.name;
});
// apply processors
@@ -46,6 +47,7 @@ async function compareSub(req, res) {
original,
sub.process || [],
target,
{ [sub.name]: sub },
);
// produce
@@ -82,11 +84,18 @@ async function compareCollection(req, res) {
}
// parse proxies
let currentProxies = ProxyUtils.parse(raw);
currentProxies.forEach((proxy) => {
proxy.subName = sub.name;
proxy.collectionName = collection.name;
});
// apply processors
currentProxies = await ProxyUtils.process(
currentProxies,
sub.process || [],
'JSON',
{ [sub.name]: sub, _collection: collection },
);
results[name] = currentProxies;
} catch (err) {
@@ -110,12 +119,14 @@ async function compareCollection(req, res) {
original.forEach((proxy, i) => {
proxy.id = i;
proxy.collectionName = collection.name;
});
const processed = await ProxyUtils.process(
original,
collection.process || [],
'JSON',
{ _collection: collection },
);
success(res, { original, processed });

View File

@@ -36,11 +36,15 @@ async function produceArtifact({ type, name, platform }) {
}
// parse proxies
let proxies = ProxyUtils.parse(raw);
proxies.forEach((proxy) => {
proxy.subName = sub.name;
});
// apply processors
proxies = await ProxyUtils.process(
proxies,
sub.process || [],
platform,
{ [sub.name]: sub },
);
if (proxies.length === 0) {
throw new Error(`订阅 ${name} 中不含有效节点`);
@@ -51,7 +55,7 @@ async function produceArtifact({ type, name, platform }) {
if (exist[proxy.name]) {
$.notify(
'🌍 Sub-Store',
'⚠️ 订阅包含重复节点!',
`⚠️ 订阅 ${name} 包含重复节点 ${proxy.name}`,
'请仔细检测配置!',
{
'media-url':
@@ -86,11 +90,18 @@ async function produceArtifact({ type, name, platform }) {
}
// parse proxies
let currentProxies = ProxyUtils.parse(raw);
currentProxies.forEach((proxy) => {
proxy.subName = sub.name;
proxy.collectionName = collection.name;
});
// apply processors
currentProxies = await ProxyUtils.process(
currentProxies,
sub.process || [],
platform,
{ [sub.name]: sub, _collection: collection },
);
results[name] = currentProxies;
processed++;
@@ -127,11 +138,16 @@ async function produceArtifact({ type, name, platform }) {
subnames.map((name) => results[name] || []),
);
proxies.forEach((proxy) => {
proxy.collectionName = collection.name;
});
// apply own processors
proxies = await ProxyUtils.process(
proxies,
collection.process || [],
platform,
{ _collection: collection },
);
if (proxies.length === 0) {
throw new Error(`组合订阅 ${name} 中不含有效节点`);
@@ -142,7 +158,7 @@ async function produceArtifact({ type, name, platform }) {
if (exist[proxy.name]) {
$.notify(
'🌍 Sub-Store',
'⚠️ 订阅包含重复节点!',
`⚠️ 组合订阅 ${name} 包含重复节点 ${proxy.name}`,
'请仔细检测配置!',
{
'media-url':

16
backend/src/utils/env.js Normal file
View File

@@ -0,0 +1,16 @@
import { version as substoreVersion } from '../../package.json';
import { ENV } from '@/vendor/open-api';
const { isNode, isQX, isLoon, isSurge, isStash, isShadowRocket } = ENV();
let backend = 'Node';
if (isNode) backend = 'Node';
if (isQX) backend = 'QX';
if (isLoon) backend = 'Loon';
if (isSurge) backend = 'Surge';
if (isStash) backend = 'Stash';
if (isShadowRocket) backend = 'ShadowRocket';
export default {
backend,
version: substoreVersion,
};

View File

@@ -60,7 +60,9 @@ export class OpenAPI {
});
this.root = {};
} else {
this.root = JSON.parse(this.node.fs.readFileSync(`${rootPath}`));
this.root = JSON.parse(
this.node.fs.readFileSync(`${rootPath}`),
);
}
// create a json file with the given name if not exists
@@ -72,9 +74,7 @@ export class OpenAPI {
});
this.cache = {};
} else {
this.cache = JSON.parse(
this.node.fs.readFileSync(`${fpath}`),
);
this.cache = JSON.parse(this.node.fs.readFileSync(`${fpath}`));
}
}
}