mirror of
https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-10 00:52:40 +00:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d12dc55bd | ||
|
|
af9a2c86c1 | ||
|
|
98892fa100 | ||
|
|
6e2411e2c2 | ||
|
|
b3f6876bbd | ||
|
|
d2c3956884 | ||
|
|
21c1e11976 | ||
|
|
e0f6b3e692 | ||
|
|
0d2920fadd | ||
|
|
da9b1d8795 | ||
|
|
4c4bda563a | ||
|
|
95f181351a | ||
|
|
3b85063f73 | ||
|
|
7f691c8511 | ||
|
|
55cc7dcd16 | ||
|
|
4f745b0232 | ||
|
|
28b233b62c | ||
|
|
44d72523ce | ||
|
|
b60995f7ac | ||
|
|
a262dfbbe8 | ||
|
|
166f3cb447 | ||
|
|
1f0463bfe2 | ||
|
|
302c92ed87 | ||
|
|
0d575e6e88 | ||
|
|
d41b54abde | ||
|
|
2c3e701149 | ||
|
|
b074f42fdc | ||
|
|
e054b71a62 | ||
|
|
7213cea16c | ||
|
|
260b1e5332 | ||
|
|
73e5d53f48 | ||
|
|
39829fa97a | ||
|
|
93d524331a | ||
|
|
e0c6cc4453 | ||
|
|
80955aa339 | ||
|
|
4d27e5bdac | ||
|
|
e2011de69e | ||
|
|
9568f4d6d9 |
14
README.md
14
README.md
@@ -41,6 +41,9 @@ Core functionalities:
|
||||
- [x] Surfboard (SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, WireGuard(Surfboard to Surfboard))
|
||||
- [x] Clash.Meta (Direct, SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC, SSH, mieru, AnyTLS)
|
||||
- [x] Stash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, TUIC, Juicity, SSH)
|
||||
|
||||
Deprecated:
|
||||
|
||||
- [x] Clash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard)
|
||||
|
||||
### Supported Target Platforms
|
||||
@@ -48,7 +51,6 @@ Core functionalities:
|
||||
- [x] Plain JSON
|
||||
- [x] Stash
|
||||
- [x] Clash.Meta(mihomo)
|
||||
- [x] Clash
|
||||
- [x] Surfboard
|
||||
- [x] Surge
|
||||
- [x] SurgeMac(Use mihomo to support protocols that are not supported by Surge itself)
|
||||
@@ -60,6 +62,10 @@ Core functionalities:
|
||||
- [x] V2Ray
|
||||
- [x] V2Ray URI
|
||||
|
||||
Deprecated:
|
||||
|
||||
- [x] Clash
|
||||
|
||||
## 2. Subscription Formatting
|
||||
|
||||
### Filtering
|
||||
@@ -122,3 +128,9 @@ 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 @Orz-3 and @58xinian for their awesome icons.
|
||||
|
||||
## Sponsors
|
||||
|
||||
[](https://yxvm.com)
|
||||
|
||||
[NodeSupport](https://github.com/NodeSeekDev/NodeSupport) sponsored this project.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sub-store",
|
||||
"version": "2.19.7",
|
||||
"version": "2.19.41",
|
||||
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket.",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Base64 } from 'js-base64';
|
||||
import { Buffer } from 'buffer';
|
||||
import rs from '@/utils/rs';
|
||||
import YAML from '@/utils/yaml';
|
||||
import download from '@/utils/download';
|
||||
import download, { downloadFile } from '@/utils/download';
|
||||
import {
|
||||
isIPv4,
|
||||
isIPv6,
|
||||
@@ -114,12 +115,7 @@ async function processFn(
|
||||
if (item.type.indexOf('Script') !== -1) {
|
||||
const { mode, content } = item.args;
|
||||
if (mode === 'link') {
|
||||
let noCache;
|
||||
let url = content || '';
|
||||
if (url.endsWith('#noCache')) {
|
||||
url = url.replace(/#noCache$/, '');
|
||||
noCache = true;
|
||||
}
|
||||
// extract link arguments
|
||||
const rawArgs = url.split('#');
|
||||
if (rawArgs.length > 1) {
|
||||
@@ -138,7 +134,14 @@ async function processFn(
|
||||
}
|
||||
}
|
||||
}
|
||||
url = `${url.split('#')[0]}${noCache ? '#noCache' : ''}`;
|
||||
url = `${url.split('#')[0]}${
|
||||
rawArgs[2]
|
||||
? `#${rawArgs[2]}`
|
||||
: $arguments?.noCache != null ||
|
||||
$arguments?.insecure != null
|
||||
? `#${rawArgs[1]}`
|
||||
: ''
|
||||
}`;
|
||||
const downloadUrlMatch = url.match(
|
||||
/^\/api\/(file|module)\/(.+)/,
|
||||
);
|
||||
@@ -328,8 +331,11 @@ export const ProxyUtils = {
|
||||
MMDB,
|
||||
Gist,
|
||||
download,
|
||||
downloadFile,
|
||||
isValidUUID,
|
||||
doh,
|
||||
Buffer,
|
||||
Base64,
|
||||
};
|
||||
|
||||
function tryParse(parser, line) {
|
||||
|
||||
@@ -128,13 +128,13 @@ function URI_SS() {
|
||||
// parse url
|
||||
let content = line.split('ss://')[1];
|
||||
|
||||
let name = line.split('#')[1];
|
||||
const proxy = {
|
||||
name: decodeURIComponent(line.split('#')[1]),
|
||||
type: 'ss',
|
||||
};
|
||||
content = content.split('#')[0]; // strip proxy name
|
||||
// handle IPV4 and IPV6
|
||||
let serverAndPortArray = content.match(/@([^/]*)(\/|$)/);
|
||||
let serverAndPortArray = content.match(/@([^/?]*)(\/|\?|$)/);
|
||||
|
||||
let rawUserInfoStr = decodeURIComponent(content.split('@')[0]); // 其实应该分隔之后, 用户名和密码再 decodeURIComponent. 但是问题不大
|
||||
let userInfoStr;
|
||||
@@ -260,6 +260,10 @@ function URI_SS() {
|
||||
if (/(&|\?)tfo=(1|true)/i.test(query)) {
|
||||
proxy.tfo = true;
|
||||
}
|
||||
if (name != null) {
|
||||
name = decodeURIComponent(name);
|
||||
}
|
||||
proxy.name = name ?? `SS ${proxy.server}:${proxy.port}`;
|
||||
return proxy;
|
||||
};
|
||||
return { name, test, parse };
|
||||
@@ -452,8 +456,12 @@ 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)
|
||||
if (proxy.tls && params.sni && params.sni !== '') {
|
||||
proxy.sni = params.sni;
|
||||
if (proxy.tls) {
|
||||
if (params.sni && params.sni !== '') {
|
||||
proxy.sni = params.sni;
|
||||
} else if (params.peer && params.peer !== '') {
|
||||
proxy.sni = params.peer;
|
||||
}
|
||||
}
|
||||
let httpupgrade = false;
|
||||
// handle obfs
|
||||
@@ -492,6 +500,11 @@ function URI_VMess() {
|
||||
} catch (e) {}
|
||||
let transportPath = params.path;
|
||||
|
||||
// 补上默认 path
|
||||
if (['ws'].includes(proxy.network)) {
|
||||
transportPath = transportPath || '/';
|
||||
}
|
||||
|
||||
if (proxy.network === 'http') {
|
||||
if (transportHost) {
|
||||
// 1)http(tcp)->host中间逗号(,)隔开
|
||||
@@ -739,6 +752,8 @@ function URI_AnyTLS() {
|
||||
proxy[key] = value ? value.split(',') : undefined;
|
||||
} else if (['insecure'].includes(key)) {
|
||||
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(value);
|
||||
} else if (['udp'].includes(key)) {
|
||||
proxy[key] = /(TRUE)|1/i.test(value);
|
||||
} else {
|
||||
proxy[key] = value;
|
||||
}
|
||||
@@ -957,6 +972,9 @@ function URI_TUIC() {
|
||||
proxy.tfo = true;
|
||||
} else if (['disable-sni', 'reduce-rtt'].includes(key)) {
|
||||
proxy[key] = /(TRUE)|1/i.test(value);
|
||||
} else if (key === 'congestion-control') {
|
||||
proxy['congestion-controller'] = value;
|
||||
delete proxy[key];
|
||||
} else {
|
||||
proxy[key] = value;
|
||||
}
|
||||
|
||||
@@ -39,12 +39,12 @@ start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/socks5/hysteria2
|
||||
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/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/block_quic/others)*{
|
||||
proxy.type = "ssr";
|
||||
// handle ssr obfs
|
||||
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/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/block_quic/others)* {
|
||||
proxy.type = "ss";
|
||||
// handle ss obfs
|
||||
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);
|
||||
}
|
||||
}
|
||||
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)* {
|
||||
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)* {
|
||||
proxy.type = "vmess";
|
||||
proxy.cipher = proxy.cipher || "none";
|
||||
proxy.alterId = proxy.alterId || 0;
|
||||
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/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/block_quic/others)* {
|
||||
proxy.type = "vless";
|
||||
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/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/block_quic/others)* {
|
||||
proxy.type = "trojan";
|
||||
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/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/block_quic/others)* {
|
||||
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/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/block_quic/others)* {
|
||||
proxy.type = "http";
|
||||
proxy.tls = true;
|
||||
}
|
||||
http = tag equals "http"i address (username password)? (fast_open/udp_relay/ip_mode/others)* {
|
||||
http = tag equals "http"i address (username password)? (fast_open/udp_relay/ip_mode/block_quic/others)* {
|
||||
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/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/block_quic/others)* {
|
||||
proxy.type = "socks5";
|
||||
}
|
||||
|
||||
@@ -192,6 +192,8 @@ ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
||||
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'; }
|
||||
|
||||
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(); }
|
||||
comma = _ "," _
|
||||
equals = _ "=" _
|
||||
|
||||
@@ -37,12 +37,12 @@ start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/socks5/hysteria2
|
||||
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/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/block_quic/others)*{
|
||||
proxy.type = "ssr";
|
||||
// handle ssr obfs
|
||||
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/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/block_quic/others)* {
|
||||
proxy.type = "ss";
|
||||
// handle ss obfs
|
||||
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);
|
||||
}
|
||||
}
|
||||
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)* {
|
||||
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)* {
|
||||
proxy.type = "vmess";
|
||||
proxy.cipher = proxy.cipher || "none";
|
||||
proxy.alterId = proxy.alterId || 0;
|
||||
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/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/block_quic/others)* {
|
||||
proxy.type = "vless";
|
||||
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/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/block_quic/others)* {
|
||||
proxy.type = "trojan";
|
||||
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/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/block_quic/others)* {
|
||||
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/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/block_quic/others)* {
|
||||
proxy.type = "http";
|
||||
proxy.tls = true;
|
||||
}
|
||||
http = tag equals "http"i address (username password)? (fast_open/udp_relay/ip_mode/others)* {
|
||||
http = tag equals "http"i address (username password)? (fast_open/udp_relay/ip_mode/block_quic/others)* {
|
||||
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/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/block_quic/others)* {
|
||||
proxy.type = "socks5";
|
||||
}
|
||||
|
||||
@@ -190,6 +190,8 @@ ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
||||
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'; }
|
||||
|
||||
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(); }
|
||||
comma = _ "," _
|
||||
equals = _ "=" _
|
||||
|
||||
@@ -49,7 +49,7 @@ trojan = "trojan" 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/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_new/fast_open/tag/server_check_url/others)* {
|
||||
if (proxy.protocol || proxy.type === "ssr") {
|
||||
proxy.type = "ssr";
|
||||
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)* {
|
||||
proxy.type = "vmess";
|
||||
proxy.cipher = proxy.cipher || "none";
|
||||
if (proxy.aead) {
|
||||
proxy.alterId = 0;
|
||||
if (proxy.aead === false) {
|
||||
proxy.alterId = 1;
|
||||
} else {
|
||||
proxy.alterId = proxy.alterId || 0;
|
||||
proxy.alterId = 0;
|
||||
}
|
||||
handleObfs();
|
||||
}
|
||||
@@ -145,18 +145,20 @@ port = digits:[0-9]+ {
|
||||
}
|
||||
}
|
||||
|
||||
username = comma "username" equals username:[^=,]+ { proxy.username = username.join("").trim(); }
|
||||
password = comma "password" equals password:[^=,]+ { proxy.password = password.join("").trim(); }
|
||||
uuid = comma "password" equals uuid:[^=,]+ { proxy.uuid = uuid.join("").trim(); }
|
||||
username = comma "username" equals username:[^,]+ { proxy.username = username.join("").trim(); }
|
||||
password = comma "password" equals password:[^,]+ { proxy.password = password.join("").trim(); }
|
||||
uuid = comma "password" equals uuid:[^,]+ { proxy.uuid = uuid.join("").trim(); }
|
||||
|
||||
method = comma "method" equals 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");
|
||||
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");
|
||||
aead = comma "aead" equals flag:bool { proxy.aead = 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_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; }
|
||||
|
||||
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
||||
|
||||
@@ -47,7 +47,7 @@ trojan = "trojan" 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/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_new/fast_open/tag/server_check_url/others)* {
|
||||
if (proxy.protocol || proxy.type === "ssr") {
|
||||
proxy.type = "ssr";
|
||||
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)* {
|
||||
proxy.type = "vmess";
|
||||
proxy.cipher = proxy.cipher || "none";
|
||||
if (proxy.aead) {
|
||||
proxy.alterId = 0;
|
||||
if (proxy.aead === false) {
|
||||
proxy.alterId = 1;
|
||||
} else {
|
||||
proxy.alterId = proxy.alterId || 0;
|
||||
proxy.alterId = 0;
|
||||
}
|
||||
handleObfs();
|
||||
}
|
||||
@@ -143,18 +143,20 @@ port = digits:[0-9]+ {
|
||||
}
|
||||
}
|
||||
|
||||
username = comma "username" equals username:[^=,]+ { proxy.username = username.join("").trim(); }
|
||||
password = comma "password" equals password:[^=,]+ { proxy.password = password.join("").trim(); }
|
||||
uuid = comma "password" equals uuid:[^=,]+ { proxy.uuid = uuid.join("").trim(); }
|
||||
username = comma "username" equals username:[^,]+ { proxy.username = username.join("").trim(); }
|
||||
password = comma "password" equals password:[^,]+ { proxy.password = password.join("").trim(); }
|
||||
uuid = comma "password" equals uuid:[^,]+ { proxy.uuid = uuid.join("").trim(); }
|
||||
|
||||
method = comma "method" equals 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");
|
||||
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");
|
||||
aead = comma "aead" equals flag:bool { proxy.aead = 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_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; }
|
||||
|
||||
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
||||
|
||||
@@ -55,10 +55,11 @@ 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)* {
|
||||
proxy.type = "vmess";
|
||||
proxy.cipher = proxy.cipher || "none";
|
||||
// Surfboard 与 Surge 默认不一致, 不管 Surfboard https://getsurfboard.com/docs/profile-format/proxy/external-proxy/vmess
|
||||
if (proxy.aead) {
|
||||
proxy.alterId = 0;
|
||||
} else {
|
||||
proxy.alterId = proxy.alterId || 0;
|
||||
proxy.alterId = 1;
|
||||
}
|
||||
handleWebsocket();
|
||||
handleShadowTLS();
|
||||
|
||||
@@ -53,10 +53,11 @@ 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)* {
|
||||
proxy.type = "vmess";
|
||||
proxy.cipher = proxy.cipher || "none";
|
||||
// Surfboard 与 Surge 默认不一致, 不管 Surfboard https://getsurfboard.com/docs/profile-format/proxy/external-proxy/vmess
|
||||
if (proxy.aead) {
|
||||
proxy.alterId = 0;
|
||||
} else {
|
||||
proxy.alterId = proxy.alterId || 0;
|
||||
proxy.alterId = 1;
|
||||
}
|
||||
handleWebsocket();
|
||||
handleShadowTLS();
|
||||
|
||||
@@ -284,7 +284,15 @@ function SortOperator(order = 'asc') {
|
||||
}
|
||||
|
||||
// sort by regex
|
||||
function RegexSortOperator(expressions) {
|
||||
function RegexSortOperator(input) {
|
||||
const order = input.order || 'asc';
|
||||
let expressions = input.expressions;
|
||||
if (Array.isArray(input)) {
|
||||
expressions = input;
|
||||
}
|
||||
if (!Array.isArray(expressions)) {
|
||||
expressions = [];
|
||||
}
|
||||
return {
|
||||
name: 'Regex Sort Operator',
|
||||
func: (proxies) => {
|
||||
@@ -295,8 +303,13 @@ function RegexSortOperator(expressions) {
|
||||
if (oA && !oB) return -1;
|
||||
if (oB && !oA) return 1;
|
||||
if (oA && oB) return oA < oB ? -1 : 1;
|
||||
if ((!oA && !oB) || (oA && oB && oA === oB))
|
||||
return a.name < b.name ? -1 : 1; // fallback to normal sort
|
||||
if (order === 'original') {
|
||||
return 0;
|
||||
} else if (order === 'desc') {
|
||||
return a.name < b.name ? 1 : -1;
|
||||
} else {
|
||||
return a.name < b.name ? -1 : 1;
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -1128,6 +1141,10 @@ function createDynamicFunction(name, script, $arguments, $options) {
|
||||
'$httpClient',
|
||||
'$notification',
|
||||
'ProxyUtils',
|
||||
'yaml',
|
||||
'Buffer',
|
||||
'b64d',
|
||||
'b64e',
|
||||
'scriptResourceCache',
|
||||
'flowUtils',
|
||||
'produceArtifact',
|
||||
@@ -1145,6 +1162,10 @@ function createDynamicFunction(name, script, $arguments, $options) {
|
||||
// eslint-disable-next-line no-undef
|
||||
$notification,
|
||||
ProxyUtils,
|
||||
ProxyUtils.yaml,
|
||||
ProxyUtils.Buffer,
|
||||
ProxyUtils.Base64.decode,
|
||||
ProxyUtils.Base64.encode,
|
||||
scriptResourceCache,
|
||||
flowUtils,
|
||||
produceArtifact,
|
||||
@@ -1157,6 +1178,10 @@ function createDynamicFunction(name, script, $arguments, $options) {
|
||||
'$substore',
|
||||
'lodash',
|
||||
'ProxyUtils',
|
||||
'yaml',
|
||||
'Buffer',
|
||||
'b64d',
|
||||
'b64e',
|
||||
'scriptResourceCache',
|
||||
'flowUtils',
|
||||
'produceArtifact',
|
||||
@@ -1168,6 +1193,10 @@ function createDynamicFunction(name, script, $arguments, $options) {
|
||||
$,
|
||||
lodash,
|
||||
ProxyUtils,
|
||||
ProxyUtils.yaml,
|
||||
ProxyUtils.Buffer,
|
||||
ProxyUtils.Base64.decode,
|
||||
ProxyUtils.Base64.encode,
|
||||
scriptResourceCache,
|
||||
flowUtils,
|
||||
produceArtifact,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { isPresent } from '@/core/proxy-utils/producers/utils';
|
||||
import $ from '@/core/app';
|
||||
|
||||
export default function Clash_Producer() {
|
||||
const type = 'ALL';
|
||||
@@ -46,6 +47,11 @@ export default function Clash_Producer() {
|
||||
proxy['reality-opts']))
|
||||
) {
|
||||
return false;
|
||||
} else if (proxy['underlying-proxy'] || proxy['dialer-proxy']) {
|
||||
$.error(
|
||||
`Clash 不支持前置代理字段. 已过滤节点 ${proxy.name}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
@@ -152,11 +158,6 @@ export default function Clash_Producer() {
|
||||
}
|
||||
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') {
|
||||
delete proxy.tls;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export default function Loon_Producer() {
|
||||
case 'trojan':
|
||||
return trojan(proxy);
|
||||
case 'vmess':
|
||||
return vmess(proxy);
|
||||
return vmess(proxy, opts['include-unsupported-proxy']);
|
||||
case 'vless':
|
||||
return vless(proxy, opts['include-unsupported-proxy']);
|
||||
case 'http':
|
||||
@@ -133,6 +133,13 @@ function shadowsocks(proxy) {
|
||||
// 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
|
||||
if (proxy.udp) {
|
||||
result.append(`,udp=true`);
|
||||
@@ -144,7 +151,7 @@ function shadowsocks(proxy) {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
function shadowsocksr(proxy, includeUnsupportedProxy) {
|
||||
function shadowsocksr(proxy) {
|
||||
const result = new Result(proxy);
|
||||
result.append(
|
||||
`${proxy.name}=shadowsocksr,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"`,
|
||||
@@ -203,6 +210,13 @@ function shadowsocksr(proxy, includeUnsupportedProxy) {
|
||||
// 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
|
||||
if (proxy.udp) {
|
||||
result.append(`,udp=true`);
|
||||
@@ -259,6 +273,13 @@ function trojan(proxy) {
|
||||
// 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
|
||||
if (proxy.udp) {
|
||||
result.append(`,udp=true`);
|
||||
@@ -270,6 +291,8 @@ function trojan(proxy) {
|
||||
}
|
||||
|
||||
function vmess(proxy) {
|
||||
const isReality = !!proxy['reality-opts'];
|
||||
|
||||
const result = new Result(proxy);
|
||||
result.append(
|
||||
`${proxy.name}=vmess,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.uuid}"`,
|
||||
@@ -317,20 +340,32 @@ function vmess(proxy) {
|
||||
'skip-cert-verify',
|
||||
);
|
||||
|
||||
// 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',
|
||||
);
|
||||
if (isReality) {
|
||||
result.appendIfPresent(`,sni=${proxy.sni}`, 'sni');
|
||||
result.appendIfPresent(
|
||||
`,public-key="${proxy['reality-opts']['public-key']}"`,
|
||||
'reality-opts.public-key',
|
||||
);
|
||||
result.appendIfPresent(
|
||||
`,short-id=${proxy['reality-opts']['short-id']}`,
|
||||
'reality-opts.short-id',
|
||||
);
|
||||
} 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
|
||||
if (isPresent(proxy, 'aead')) {
|
||||
result.append(`,alterId=0`);
|
||||
result.append(`,alterId=${proxy.aead ? 0 : 1}`);
|
||||
} else {
|
||||
result.append(`,alterId=${proxy.alterId}`);
|
||||
}
|
||||
@@ -338,6 +373,13 @@ function vmess(proxy) {
|
||||
// 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
|
||||
if (proxy.udp) {
|
||||
result.append(`,udp=true`);
|
||||
@@ -347,26 +389,18 @@ function vmess(proxy) {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
function vless(proxy, includeUnsupportedProxy) {
|
||||
if (
|
||||
!includeUnsupportedProxy &&
|
||||
(typeof proxy.flow !== 'undefined' || proxy['reality-opts'])
|
||||
) {
|
||||
throw new Error(`VLESS XTLS/REALITY is not supported`);
|
||||
}
|
||||
let isReality = false;
|
||||
if (includeUnsupportedProxy) {
|
||||
if (
|
||||
proxy['reality-opts'] &&
|
||||
['xtls-rprx-vision'].includes(proxy.flow)
|
||||
) {
|
||||
isReality = true;
|
||||
} else if (proxy['reality-opts'] || proxy.flow) {
|
||||
throw new Error(
|
||||
`VLESS XTLS/REALITY with flow(${proxy.flow}) is not supported`,
|
||||
);
|
||||
function vless(proxy) {
|
||||
let isXtls = false;
|
||||
const isReality = !!proxy['reality-opts'];
|
||||
|
||||
if (typeof proxy.flow !== 'undefined') {
|
||||
if (['xtls-rprx-vision'].includes(proxy.flow)) {
|
||||
isXtls = true;
|
||||
} else {
|
||||
throw new Error(`VLESS flow(${proxy.flow}) is not supported`);
|
||||
}
|
||||
}
|
||||
|
||||
const result = new Result(proxy);
|
||||
result.append(
|
||||
`${proxy.name}=vless,${proxy.server},${proxy.port},"${proxy.uuid}"`,
|
||||
@@ -414,7 +448,9 @@ function vless(proxy, includeUnsupportedProxy) {
|
||||
'skip-cert-verify',
|
||||
);
|
||||
|
||||
// sni
|
||||
if (isXtls) {
|
||||
result.appendIfPresent(`,flow=${proxy.flow}`, 'flow');
|
||||
}
|
||||
if (isReality) {
|
||||
result.appendIfPresent(`,sni=${proxy.sni}`, 'sni');
|
||||
result.appendIfPresent(
|
||||
@@ -426,21 +462,28 @@ function vless(proxy, includeUnsupportedProxy) {
|
||||
'reality-opts.short-id',
|
||||
);
|
||||
} 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',
|
||||
);
|
||||
}
|
||||
|
||||
result.appendIfPresent(
|
||||
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
|
||||
'tls-fingerprint',
|
||||
);
|
||||
result.appendIfPresent(
|
||||
`,tls-pubkey-sha256=${proxy['tls-pubkey-sha256']}`,
|
||||
'tls-pubkey-sha256',
|
||||
);
|
||||
|
||||
// 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
|
||||
if (proxy.udp) {
|
||||
result.append(`,udp=true`);
|
||||
@@ -468,6 +511,14 @@ function http(proxy) {
|
||||
|
||||
// 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'];
|
||||
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
||||
|
||||
@@ -494,6 +545,13 @@ function socks5(proxy) {
|
||||
// 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
|
||||
if (proxy.udp) {
|
||||
result.append(`,udp=true`);
|
||||
@@ -568,6 +626,13 @@ function wireguard(proxy) {
|
||||
const ip_version = ipVersions[proxy['ip-version']] || proxy['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();
|
||||
}
|
||||
|
||||
@@ -602,6 +667,13 @@ function hysteria2(proxy) {
|
||||
// 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
|
||||
if (proxy.udp) {
|
||||
result.append(`,udp=true`);
|
||||
|
||||
@@ -58,6 +58,8 @@ function shadowsocks(proxy) {
|
||||
'aes-256-gcm',
|
||||
'chacha20-ietf-poly1305',
|
||||
'xchacha20-ietf-poly1305',
|
||||
'2022-blake3-aes-128-gcm',
|
||||
'2022-blake3-aes-256-gcm',
|
||||
].includes(proxy.cipher)
|
||||
) {
|
||||
throw new Error(`cipher ${proxy.cipher} is not supported`);
|
||||
@@ -128,6 +130,20 @@ function shadowsocks(proxy) {
|
||||
// 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
|
||||
result.appendIfPresent(
|
||||
`,server_check_url=${proxy['test-url']}`,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { isPresent } from '@/core/proxy-utils/producers/utils';
|
||||
import $ from '@/core/app';
|
||||
|
||||
export default function Shadowrocket_Producer() {
|
||||
const type = 'ALL';
|
||||
@@ -10,6 +11,11 @@ export default function Shadowrocket_Producer() {
|
||||
return false;
|
||||
} else if (['mieru', 'anytls'].includes(proxy.type)) {
|
||||
return false;
|
||||
} else if (proxy['underlying-proxy'] || proxy['dialer-proxy']) {
|
||||
$.error(
|
||||
`Shadowrocket 不支持前置代理字段. 已过滤节点 ${proxy.name}. 请使用 App 内的 "代理通过" 功能`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
|
||||
@@ -31,6 +31,21 @@ const smuxParser = (smux, proxy) => {
|
||||
if (smux['min-streams'])
|
||||
proxy.multiplex.min_streams = parseInt(`${smux['min-streams']}`, 10);
|
||||
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) => {
|
||||
@@ -359,7 +374,16 @@ const ssParser = (proxy = {}) => {
|
||||
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
|
||||
throw 'invalid port';
|
||||
if (proxy.uot) parsedProxy.udp_over_tcp = true;
|
||||
if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true;
|
||||
if (proxy['udp-over-tcp']) {
|
||||
parsedProxy.udp_over_tcp = {
|
||||
enabled: true,
|
||||
version:
|
||||
!proxy['udp-over-tcp-version'] ||
|
||||
proxy['udp-over-tcp-version'] === 1
|
||||
? 1
|
||||
: 2,
|
||||
};
|
||||
}
|
||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||
networkParser(proxy, parsedProxy);
|
||||
tfoParser(proxy, parsedProxy);
|
||||
@@ -543,12 +567,13 @@ const hysteriaParser = (proxy = {}) => {
|
||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const reg = new RegExp('^[0-9]+[ \t]*[KMGT]*[Bb]ps$');
|
||||
if (reg.test(`${proxy.up}`)) {
|
||||
// sing-box 跟文档不一致, 但是懒得全转, 只处理最常见的 Mbps
|
||||
if (reg.test(`${proxy.up}`) && !`${proxy.up}`.endsWith('Mbps')) {
|
||||
parsedProxy.up = `${proxy.up}`;
|
||||
} else {
|
||||
parsedProxy.up_mbps = parseInt(`${proxy.up}`, 10);
|
||||
}
|
||||
if (reg.test(`${proxy.down}`)) {
|
||||
if (reg.test(`${proxy.down}`) && !`${proxy.down}`.endsWith('Mbps')) {
|
||||
parsedProxy.down = `${proxy.down}`;
|
||||
} else {
|
||||
parsedProxy.down_mbps = parseInt(`${proxy.down}`, 10);
|
||||
@@ -575,7 +600,7 @@ const hysteriaParser = (proxy = {}) => {
|
||||
smuxParser(proxy.smux, parsedProxy);
|
||||
return parsedProxy;
|
||||
};
|
||||
const hysteria2Parser = (proxy = {}, includeUnsupportedProxy) => {
|
||||
const hysteria2Parser = (proxy = {}) => {
|
||||
const parsedProxy = {
|
||||
tag: proxy.name,
|
||||
type: 'hysteria2',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { isPresent } from '@/core/proxy-utils/producers/utils';
|
||||
import $ from '@/core/app';
|
||||
|
||||
export default function Stash_Producer() {
|
||||
const type = 'ALL';
|
||||
@@ -39,12 +40,8 @@ export default function Stash_Producer() {
|
||||
'xchacha20',
|
||||
'chacha20-ietf-poly1305',
|
||||
'xchacha20-ietf-poly1305',
|
||||
...(opts['include-unsupported-proxy']
|
||||
? [
|
||||
'2022-blake3-aes-128-gcm',
|
||||
'2022-blake3-aes-256-gcm',
|
||||
]
|
||||
: []),
|
||||
'2022-blake3-aes-128-gcm',
|
||||
'2022-blake3-aes-256-gcm',
|
||||
].includes(proxy.cipher)) ||
|
||||
(proxy.type === 'snell' && String(proxy.version) === '4') ||
|
||||
(opts['include-unsupported-proxy']
|
||||
@@ -54,6 +51,11 @@ export default function Stash_Producer() {
|
||||
: proxy.type === 'vless' && proxy['reality-opts'])
|
||||
) {
|
||||
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;
|
||||
})
|
||||
@@ -261,11 +263,6 @@ export default function Stash_Producer() {
|
||||
}
|
||||
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') {
|
||||
delete proxy.tls;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export default function URI_Producer() {
|
||||
delete proxy.resolved;
|
||||
delete proxy['no-resolve'];
|
||||
for (const key in proxy) {
|
||||
if (proxy[key] == null || /^_/i.test(key)) {
|
||||
if (proxy[key] == null) {
|
||||
delete proxy[key];
|
||||
}
|
||||
}
|
||||
@@ -478,7 +478,7 @@ export default function URI_Producer() {
|
||||
hysteriaParams.push(`obfsParam=${proxy[key]}`);
|
||||
} else if (['sni'].includes(key)) {
|
||||
hysteriaParams.push(`peer=${proxy[key]}`);
|
||||
} else if (proxy[key]) {
|
||||
} else if (proxy[key] && !/^_/i.test(key)) {
|
||||
hysteriaParams.push(
|
||||
`${i}=${encodeURIComponent(proxy[key])}`,
|
||||
);
|
||||
@@ -535,7 +535,13 @@ export default function URI_Producer() {
|
||||
proxy[key]
|
||||
) {
|
||||
tuicParams.push(`${i.replace(/-/g, '_')}=1`);
|
||||
} else if (proxy[key]) {
|
||||
} else if (
|
||||
['congestion-controller'].includes(key)
|
||||
) {
|
||||
tuicParams.push(
|
||||
`congestion_control=${proxy[key]}`,
|
||||
);
|
||||
} else if (proxy[key] && !/^_/i.test(key)) {
|
||||
tuicParams.push(
|
||||
`${i.replace(
|
||||
/-/g,
|
||||
@@ -583,7 +589,11 @@ export default function URI_Producer() {
|
||||
if (proxy[key]) {
|
||||
anytlsParams.push(`insecure=1`);
|
||||
}
|
||||
} else if (proxy[key]) {
|
||||
} else if (['udp'].includes(key)) {
|
||||
if (proxy[key]) {
|
||||
anytlsParams.push(`udp=1`);
|
||||
}
|
||||
} else if (proxy[key] && !/^_/i.test(key)) {
|
||||
anytlsParams.push(
|
||||
`${i.replace(/-/g, '_')}=${encodeURIComponent(
|
||||
proxy[key],
|
||||
@@ -620,7 +630,7 @@ export default function URI_Producer() {
|
||||
if (proxy[key]) {
|
||||
wireguardParams.push(`${key}=1`);
|
||||
}
|
||||
} else if (proxy[key]) {
|
||||
} else if (proxy[key] && !/^_/i.test(key)) {
|
||||
wireguardParams.push(
|
||||
`${key}=${encodeURIComponent(proxy[key])}`,
|
||||
);
|
||||
|
||||
@@ -14,10 +14,12 @@ let resourceUrl = typeof $resourceUrl !== 'undefined' ? $resourceUrl : '';
|
||||
`
|
||||
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
|
||||
Sub-Store -- v${version}
|
||||
Loon -- ${$loon}
|
||||
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
|
||||
`,
|
||||
);
|
||||
|
||||
const build = $loon.match(/\((\d+)\)$/)?.[1];
|
||||
let arg;
|
||||
if (typeof $argument != 'undefined') {
|
||||
arg = Object.fromEntries(
|
||||
@@ -26,23 +28,28 @@ let resourceUrl = typeof $resourceUrl !== 'undefined' ? $resourceUrl : '';
|
||||
} else {
|
||||
arg = {};
|
||||
}
|
||||
console.log(`arg: ${JSON.stringify(arg)}`);
|
||||
|
||||
const RESOURCE_TYPE = {
|
||||
PROXY: 1,
|
||||
RULE: 2,
|
||||
};
|
||||
|
||||
result = resource;
|
||||
if (!arg.resourceUrlOnly) {
|
||||
result = resource;
|
||||
}
|
||||
|
||||
if (resourceType === RESOURCE_TYPE.PROXY) {
|
||||
try {
|
||||
let proxies = ProxyUtils.parse(resource);
|
||||
result = ProxyUtils.produce(proxies, 'Loon', undefined, {
|
||||
'include-unsupported-proxy': arg?.includeUnsupportedProxy,
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('解析器: 使用 resource 出现错误');
|
||||
console.log(e.message ?? e);
|
||||
if (!arg.resourceUrlOnly) {
|
||||
try {
|
||||
let proxies = ProxyUtils.parse(resource);
|
||||
result = ProxyUtils.produce(proxies, 'Loon', undefined, {
|
||||
'include-unsupported-proxy':
|
||||
arg?.includeUnsupportedProxy || build >= 842,
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('解析器: 使用 resource 出现错误');
|
||||
console.log(e.message ?? e);
|
||||
}
|
||||
}
|
||||
if ((!result || /^\s*$/.test(result)) && resourceUrl) {
|
||||
console.log(`解析器: 尝试从 ${resourceUrl} 获取订阅`);
|
||||
@@ -59,18 +66,21 @@ let resourceUrl = typeof $resourceUrl !== 'undefined' ? $resourceUrl : '';
|
||||
);
|
||||
let proxies = ProxyUtils.parse(raw);
|
||||
result = ProxyUtils.produce(proxies, 'Loon', undefined, {
|
||||
'include-unsupported-proxy': arg?.includeUnsupportedProxy,
|
||||
'include-unsupported-proxy':
|
||||
arg?.includeUnsupportedProxy || build >= 842,
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e.message ?? e);
|
||||
}
|
||||
}
|
||||
} else if (resourceType === RESOURCE_TYPE.RULE) {
|
||||
try {
|
||||
const rules = RuleUtils.parse(resource);
|
||||
result = RuleUtils.produce(rules, 'Loon');
|
||||
} catch (e) {
|
||||
console.log(e.message ?? e);
|
||||
if (!arg.resourceUrlOnly) {
|
||||
try {
|
||||
const rules = RuleUtils.parse(resource);
|
||||
result = RuleUtils.produce(rules, 'Loon');
|
||||
} catch (e) {
|
||||
console.log(e.message ?? e);
|
||||
}
|
||||
}
|
||||
if ((!result || /^\s*$/.test(result)) && resourceUrl) {
|
||||
console.log(`解析器: 尝试从 ${resourceUrl} 获取规则`);
|
||||
|
||||
@@ -111,7 +111,17 @@ async function downloadSubscription(req, res) {
|
||||
proxy,
|
||||
noCache,
|
||||
} = 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) {
|
||||
try {
|
||||
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
|
||||
@@ -376,7 +386,17 @@ async function downloadCollection(req, res) {
|
||||
noCache,
|
||||
} = 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) {
|
||||
try {
|
||||
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
|
||||
|
||||
@@ -52,8 +52,8 @@ function createFile(req, res) {
|
||||
async function getFile(req, res) {
|
||||
let { name } = req.params;
|
||||
name = decodeURIComponent(name);
|
||||
|
||||
$.info(`正在下载文件:${name}`);
|
||||
const reqUA = req.headers['user-agent'] || req.headers['User-Agent'];
|
||||
$.info(`正在下载文件:${name}\n请求 User-Agent: ${reqUA}`);
|
||||
let {
|
||||
url,
|
||||
subInfoUrl,
|
||||
@@ -64,8 +64,19 @@ async function getFile(req, res) {
|
||||
ignoreFailedRemoteFile,
|
||||
proxy,
|
||||
noCache,
|
||||
produceType,
|
||||
} = 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) {
|
||||
try {
|
||||
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
|
||||
@@ -118,6 +129,10 @@ async function getFile(req, res) {
|
||||
if (noCache) {
|
||||
$.info(`指定不使用缓存: ${noCache}`);
|
||||
}
|
||||
if (produceType) {
|
||||
produceType = decodeURIComponent(produceType);
|
||||
$.info(`指定生产类型: ${produceType}`);
|
||||
}
|
||||
|
||||
const allFiles = $.read(FILES_KEY);
|
||||
const file = findByName(allFiles, name);
|
||||
@@ -134,6 +149,7 @@ async function getFile(req, res) {
|
||||
$options,
|
||||
proxy,
|
||||
noCache,
|
||||
produceType,
|
||||
});
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import express from '@/vendor/express';
|
||||
import $ from '@/core/app';
|
||||
import migrate from '@/utils/migration';
|
||||
import download from '@/utils/download';
|
||||
import download, { downloadFile } from '@/utils/download';
|
||||
import { syncArtifacts, produceArtifact } from '@/restful/sync';
|
||||
import { gistBackupAction } from '@/restful/miscs';
|
||||
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_path = eval('process.env.SUB_STORE_FRONTEND_PATH');
|
||||
if (be_prefix || be_merge) {
|
||||
if(!fe_be_path.startsWith('/')){
|
||||
if (!fe_be_path.startsWith('/')) {
|
||||
throw new Error(
|
||||
'SUB_STORE_FRONTEND_BACKEND_PATH should start with /',
|
||||
);
|
||||
@@ -48,15 +48,20 @@ export default function serve() {
|
||||
$app.use((req, res, next) => {
|
||||
if (req.path.startsWith(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';
|
||||
}
|
||||
next();
|
||||
return;
|
||||
}
|
||||
const pathname = decodeURIComponent(req._parsedUrl.pathname) || '/';
|
||||
if(be_merge && req.path.startsWith('/share/') && req.query.token){
|
||||
if (req.method.toLowerCase() !== 'get'){
|
||||
const pathname =
|
||||
decodeURIComponent(req._parsedUrl.pathname) || '/';
|
||||
if (
|
||||
be_merge &&
|
||||
req.path.startsWith('/share/') &&
|
||||
req.query.token
|
||||
) {
|
||||
if (req.method.toLowerCase() !== 'get') {
|
||||
res.status(405).send('Method not allowed');
|
||||
return;
|
||||
}
|
||||
@@ -67,14 +72,14 @@ export default function serve() {
|
||||
`/share/${t.type}/${t.name}` === pathname &&
|
||||
(t.exp == null || t.exp > Date.now()),
|
||||
);
|
||||
if (token){
|
||||
if (token) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (be_merge && fe_path && req.path.indexOf('/',1) == -1) {
|
||||
if (req.path.indexOf('.') == -1){
|
||||
req.url = "/index.html"
|
||||
if (be_merge && fe_path && req.path.indexOf('/', 1) == -1) {
|
||||
if (req.path.indexOf('.') == -1) {
|
||||
req.url = '/index.html';
|
||||
}
|
||||
const express_ = eval(`require("express")`);
|
||||
const mime_ = eval(`require("mime-types")`);
|
||||
@@ -85,7 +90,7 @@ export default function serve() {
|
||||
if (type) {
|
||||
res.set('Content-Type', type);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
staticFileMiddleware(req, res, next);
|
||||
return;
|
||||
@@ -230,6 +235,60 @@ export default function serve() {
|
||||
// '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 fs = eval(`require("fs")`);
|
||||
const data_url = eval('process.env.SUB_STORE_DATA_URL');
|
||||
|
||||
@@ -47,15 +47,22 @@ async function previewFile(req, res) {
|
||||
}),
|
||||
);
|
||||
|
||||
if (
|
||||
!file.ignoreFailedRemoteFile &&
|
||||
Object.keys(errors).length > 0
|
||||
) {
|
||||
throw new Error(
|
||||
`文件 ${file.name} 的远程文件 ${Object.keys(
|
||||
errors,
|
||||
).join(', ')} 发生错误, 请查看日志`,
|
||||
);
|
||||
if (Object.keys(errors).length > 0) {
|
||||
if (!file.ignoreFailedRemoteFile) {
|
||||
throw new Error(
|
||||
`文件 ${file.name} 的远程文件 ${Object.keys(
|
||||
errors,
|
||||
).join(', ')} 发生错误, 请查看日志`,
|
||||
);
|
||||
} else if (file.ignoreFailedRemoteFile === 'enabled') {
|
||||
$.notify(
|
||||
`🌍 Sub-Store 预览文件失败`,
|
||||
`❌ ${file.name}`,
|
||||
`远程文件 ${Object.keys(errors).join(
|
||||
', ',
|
||||
)} 发生错误, 请查看日志`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (file.mergeSources === 'localFirst') {
|
||||
content.unshift(file.content);
|
||||
@@ -136,12 +143,22 @@ async function compareSub(req, res) {
|
||||
}),
|
||||
);
|
||||
|
||||
if (!sub.ignoreFailedRemoteSub && Object.keys(errors).length > 0) {
|
||||
throw new Error(
|
||||
`订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join(
|
||||
', ',
|
||||
)} 发生错误, 请查看日志`,
|
||||
);
|
||||
if (Object.keys(errors).length > 0) {
|
||||
if (!sub.ignoreFailedRemoteSub) {
|
||||
throw new Error(
|
||||
`订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join(
|
||||
', ',
|
||||
)} 发生错误, 请查看日志`,
|
||||
);
|
||||
} else if (sub.ignoreFailedRemoteSub === 'enabled') {
|
||||
$.notify(
|
||||
`🌍 Sub-Store 预览订阅失败`,
|
||||
`❌ ${sub.name}`,
|
||||
`远程订阅 ${Object.keys(errors).join(
|
||||
', ',
|
||||
)} 发生错误, 请查看日志`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (sub.mergeSources === 'localFirst') {
|
||||
content.unshift(sub.content);
|
||||
@@ -244,15 +261,25 @@ async function compareCollection(req, res) {
|
||||
}
|
||||
}),
|
||||
);
|
||||
if (
|
||||
!sub.ignoreFailedRemoteSub &&
|
||||
Object.keys(errors).length > 0
|
||||
) {
|
||||
throw new Error(
|
||||
`订阅 ${sub.name} 的远程订阅 ${Object.keys(
|
||||
errors,
|
||||
).join(', ')} 发生错误, 请查看日志`,
|
||||
);
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
if (!sub.ignoreFailedRemoteSub) {
|
||||
throw new Error(
|
||||
`订阅 ${sub.name} 的远程订阅 ${Object.keys(
|
||||
errors,
|
||||
).join(', ')} 发生错误, 请查看日志`,
|
||||
);
|
||||
} else if (
|
||||
sub.ignoreFailedRemoteSub === 'enabled'
|
||||
) {
|
||||
$.notify(
|
||||
`🌍 Sub-Store 预览订阅失败`,
|
||||
`❌ ${sub.name}`,
|
||||
`远程订阅 ${Object.keys(errors).join(
|
||||
', ',
|
||||
)} 发生错误, 请查看日志`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (sub.mergeSources === 'localFirst') {
|
||||
raw.unshift(sub.content);
|
||||
@@ -284,20 +311,28 @@ async function compareCollection(req, res) {
|
||||
errors[name] = err;
|
||||
|
||||
$.error(
|
||||
`❌ 处理组合订阅 ${collection.name} 中的子订阅: ${sub.name}时出现错误:${err}!`,
|
||||
`❌ 处理组合订阅 ${collection.name} 中的子订阅: ${sub.name} 时出现错误:${err}!`,
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
if (
|
||||
!collection.ignoreFailedRemoteSub &&
|
||||
Object.keys(errors).length > 0
|
||||
) {
|
||||
throw new Error(
|
||||
`组合订阅 ${collection.name} 中的子订阅 ${Object.keys(
|
||||
errors,
|
||||
).join(', ')} 发生错误, 请查看日志`,
|
||||
);
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
if (!collection.ignoreFailedRemoteSub) {
|
||||
throw new Error(
|
||||
`组合订阅 ${collection.name} 的子订阅 ${Object.keys(
|
||||
errors,
|
||||
).join(', ')} 发生错误, 请查看日志`,
|
||||
);
|
||||
} else if (collection.ignoreFailedRemoteSub === 'enabled') {
|
||||
$.notify(
|
||||
`🌍 Sub-Store 预览组合订阅失败`,
|
||||
`❌ ${collection.name}`,
|
||||
`子订阅 ${Object.keys(errors).join(
|
||||
', ',
|
||||
)} 发生错误, 请查看日志`,
|
||||
);
|
||||
}
|
||||
}
|
||||
// merge proxies with the original order
|
||||
const original = Array.prototype.concat.apply(
|
||||
|
||||
@@ -89,12 +89,23 @@ async function produceArtifact({
|
||||
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
|
||||
subIgnoreFailedRemoteSub = ignoreFailedRemoteSub;
|
||||
}
|
||||
if (!subIgnoreFailedRemoteSub && Object.keys(errors).length > 0) {
|
||||
throw new Error(
|
||||
`订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join(
|
||||
', ',
|
||||
)} 发生错误, 请查看日志`,
|
||||
);
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
if (!subIgnoreFailedRemoteSub) {
|
||||
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') {
|
||||
raw.unshift(content);
|
||||
@@ -138,12 +149,23 @@ async function produceArtifact({
|
||||
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
|
||||
subIgnoreFailedRemoteSub = ignoreFailedRemoteSub;
|
||||
}
|
||||
if (!subIgnoreFailedRemoteSub && Object.keys(errors).length > 0) {
|
||||
throw new Error(
|
||||
`订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join(
|
||||
', ',
|
||||
)} 发生错误, 请查看日志`,
|
||||
);
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
if (!subIgnoreFailedRemoteSub) {
|
||||
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') {
|
||||
raw.unshift(sub.content);
|
||||
@@ -151,6 +173,9 @@ async function produceArtifact({
|
||||
raw.push(sub.content);
|
||||
}
|
||||
}
|
||||
if (produceType === 'raw') {
|
||||
return (Array.isArray(raw) ? raw : [raw]).flat();
|
||||
}
|
||||
// parse proxies
|
||||
let proxies = (Array.isArray(raw) ? raw : [raw])
|
||||
.map((i) => ProxyUtils.parse(i))
|
||||
@@ -264,15 +289,25 @@ async function produceArtifact({
|
||||
}
|
||||
}),
|
||||
);
|
||||
if (
|
||||
!sub.ignoreFailedRemoteSub &&
|
||||
Object.keys(errors).length > 0
|
||||
) {
|
||||
throw new Error(
|
||||
`订阅 ${sub.name} 的远程订阅 ${Object.keys(
|
||||
errors,
|
||||
).join(', ')} 发生错误, 请查看日志`,
|
||||
);
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
if (!sub.ignoreFailedRemoteSub) {
|
||||
throw new Error(
|
||||
`订阅 ${sub.name} 的远程订阅 ${Object.keys(
|
||||
errors,
|
||||
).join(', ')} 发生错误, 请查看日志`,
|
||||
);
|
||||
} else if (
|
||||
sub.ignoreFailedRemoteSub === 'enabled'
|
||||
) {
|
||||
$.notify(
|
||||
`🌍 Sub-Store 处理订阅失败`,
|
||||
`❌ ${sub.name}`,
|
||||
`远程订阅 ${Object.keys(errors).join(
|
||||
', ',
|
||||
)} 发生错误, 请查看日志`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (sub.mergeSources === 'localFirst') {
|
||||
raw.unshift(sub.content);
|
||||
@@ -327,15 +362,23 @@ async function produceArtifact({
|
||||
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
|
||||
collectionIgnoreFailedRemoteSub = ignoreFailedRemoteSub;
|
||||
}
|
||||
if (
|
||||
!collectionIgnoreFailedRemoteSub &&
|
||||
Object.keys(errors).length > 0
|
||||
) {
|
||||
throw new Error(
|
||||
`组合订阅 ${name} 中的子订阅 ${Object.keys(errors).join(
|
||||
', ',
|
||||
)} 发生错误, 请查看日志`,
|
||||
);
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
if (!collectionIgnoreFailedRemoteSub) {
|
||||
throw new Error(
|
||||
`组合订阅 ${collection.name} 的子订阅 ${Object.keys(
|
||||
errors,
|
||||
).join(', ')} 发生错误, 请查看日志`,
|
||||
);
|
||||
} else if (collectionIgnoreFailedRemoteSub === 'enabled') {
|
||||
$.notify(
|
||||
`🌍 Sub-Store 处理组合订阅失败`,
|
||||
`❌ ${collection.name}`,
|
||||
`子订阅 ${Object.keys(errors).join(
|
||||
', ',
|
||||
)} 发生错误, 请查看日志`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// merge proxies with the original order
|
||||
@@ -505,15 +548,23 @@ async function produceArtifact({
|
||||
) {
|
||||
fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile;
|
||||
}
|
||||
if (
|
||||
!fileIgnoreFailedRemoteFile &&
|
||||
Object.keys(errors).length > 0
|
||||
) {
|
||||
throw new Error(
|
||||
`文件 ${file.name} 的远程文件 ${Object.keys(
|
||||
errors,
|
||||
).join(', ')} 发生错误, 请查看日志`,
|
||||
);
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
if (!fileIgnoreFailedRemoteFile) {
|
||||
throw new Error(
|
||||
`文件 ${file.name} 的远程文件 ${Object.keys(
|
||||
errors,
|
||||
).join(', ')} 发生错误, 请查看日志`,
|
||||
);
|
||||
} else if (fileIgnoreFailedRemoteFile === 'enabled') {
|
||||
$.notify(
|
||||
`🌍 Sub-Store 处理文件失败`,
|
||||
`❌ ${file.name}`,
|
||||
`远程文件 ${Object.keys(errors).join(
|
||||
', ',
|
||||
)} 发生错误, 请查看日志`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (file.mergeSources === 'localFirst') {
|
||||
raw.unshift(file.content);
|
||||
@@ -522,6 +573,9 @@ async function produceArtifact({
|
||||
}
|
||||
}
|
||||
}
|
||||
if (produceType === 'raw') {
|
||||
return (Array.isArray(raw) ? raw : [raw]).flat();
|
||||
}
|
||||
const files = (Array.isArray(raw) ? raw : [raw]).flat();
|
||||
let filesContent = files
|
||||
.filter((i) => i != null && i !== '')
|
||||
|
||||
@@ -273,3 +273,25 @@ export default async function download(
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ export function shouldIncludeUnsupportedProxy(platform, ua) {
|
||||
if (
|
||||
platform === 'Stash' &&
|
||||
target === 'Stash' &&
|
||||
gte(version, '2.8.0')
|
||||
gte(version, '3.1.0')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -90,7 +90,7 @@ export function shouldIncludeUnsupportedProxy(platform, ua) {
|
||||
if (
|
||||
platform === 'Loon' &&
|
||||
target === 'Loon' &&
|
||||
gte(version, '838.0.0')
|
||||
gte(version, '842.0.0')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -34,4 +34,6 @@ export default {
|
||||
load,
|
||||
safeDump,
|
||||
dump,
|
||||
parse: safeLoad,
|
||||
stringify: safeDump,
|
||||
};
|
||||
|
||||
@@ -13,7 +13,11 @@ function operator(proxies = [], targetPlatform, context) {
|
||||
// 5. `_subName` 为单条订阅名, `_subDisplayName` 为单条订阅显示名
|
||||
// 6. `_collectionName` 为组合订阅名, `_collectionDisplayName` 为组合订阅显示名
|
||||
// 7. `tls-fingerprint` 为 tls 指纹
|
||||
// 8. `underlying-proxy` 为前置代理
|
||||
// 8. `underlying-proxy` 为前置代理, 不同平台会自动转换
|
||||
// 只给 mihomo 输出的话, `dialer-proxy` 也行
|
||||
// 只给 sing-box 输出的话, `detour` 也行
|
||||
// 只给 egern 输出的话, `prev_hop` 也行
|
||||
// 输出到 Clash/Stash/Shadowrocket 时, 会过滤掉配置了前置代理的节点, 并提示使用对应的功能.
|
||||
// 9. `trojan`, `tuic`, `hysteria`, `hysteria2`, `juicity` 会在解析时设置 `tls`: true (会使用 tls 类协议的通用逻辑), 输出时删除
|
||||
// 10. `sni` 在某些协议里会自动与 `servername` 转换
|
||||
// 11. 读取节点的 ca-str 和 _ca (后端文件路径) 字段, 自动计算 fingerprint (参考 https://t.me/zhetengsha/1512)
|
||||
@@ -22,6 +26,7 @@ function operator(proxies = [], targetPlatform, context) {
|
||||
// 14. `ports` 为端口跳跃, `hop-interval` 变换端口号的时间间隔
|
||||
// 15. `ip-version` 设置节点使用 IP 版本,可选:dual,ipv4,ipv6,ipv4-prefer,ipv6-prefer. 会进行内部转换, 若无法匹配则使用原始值
|
||||
// 16. `sing-box` 支持使用 `_network` 来设置 `network`, 例如 `tcp`, `udp`
|
||||
// 17. `block-quic` 支持 `auto`, `on`, `off`. 不同的平台不一定都支持, 会自动转换
|
||||
|
||||
// require 为 Node.js 的 require, 在 Node.js 运行环境下 可以用来引入模块
|
||||
// 例如在 Node.js 环境下, 将文件内容写入 /tmp/1.txt 文件
|
||||
@@ -40,6 +45,16 @@ function operator(proxies = [], targetPlatform, context) {
|
||||
// 先这样处理 encodeURIComponent('arg1=a&arg2=b')
|
||||
// /api/file/foo?$options=arg1%3Da%26arg2%3Db
|
||||
|
||||
// 默认会带上 _req 字段, 结构为
|
||||
// {
|
||||
// method,
|
||||
// url,
|
||||
// path,
|
||||
// query,
|
||||
// params,
|
||||
// headers,
|
||||
// body,
|
||||
// }
|
||||
// console.log($options)
|
||||
|
||||
// targetPlatform 为输出的目标平台
|
||||
@@ -79,6 +94,12 @@ function operator(proxies = [], targetPlatform, context) {
|
||||
|
||||
// 其他平台同理, 持久化缓存数据在 JSON 里
|
||||
|
||||
// 当配合脚本使用时, 可以在脚本的前面添加一个脚本操作, 实现保留 1 小时的缓存. 这样比较灵活
|
||||
|
||||
// async function operator() {
|
||||
// scriptResourceCache._cleanup(undefined, 1 * 3600 * 1000);
|
||||
// }
|
||||
|
||||
// ProxyUtils 为节点处理工具
|
||||
// 可参考 https://t.me/zhetengsha/1066
|
||||
// const ProxyUtils = {
|
||||
@@ -96,9 +117,13 @@ function operator(proxies = [], targetPlatform, context) {
|
||||
// getISO, // 获取 ISO 3166-1 alpha-2 代码
|
||||
// Gist, // Gist 类
|
||||
// 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
|
||||
// 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
|
||||
// ⚠️ 注意: 函数式(即本文件这样的 function operator() {}) 和快捷操作(下面使用 $server) 只能二选一
|
||||
|
||||
BIN
support.nodeseek.com_page_promotion_id=8.png
Normal file
BIN
support.nodeseek.com_page_promotion_id=8.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 129 KiB |
Reference in New Issue
Block a user