Compare commits

...

24 Commits

Author SHA1 Message Date
xream
e97fb1e6d9 fix: 兼容更多 Trojan URI 格式 2024-02-08 23:06:39 +08:00
xream
e40b9a77c4 fix: 修复 Loon UDP 参数 2024-02-07 15:14:24 +08:00
xream
df0ac8a218 fix: 缓存不合法时即刻重置 2024-02-07 01:14:52 +08:00
xream
36377f3c20 fix: 修复 Surge 协议 test-url 字段 2024-02-06 22:59:54 +08:00
xream
47f26bdac8 feat: Loon 解析器支持参数 ua=clash.meta&timeout=3000, 支持从链接重新获取 2024-02-06 21:29:10 +08:00
xream
a8a89ee2a2 fix: 修复 PassWall VLESS URI 兼容性 2024-02-06 00:30:53 +08:00
xream
199c5fb337 fix: 修复域名解析 2024-02-05 13:19:22 +08:00
xream
df505616ec chore: Stash 覆写增加图标 2024-02-05 11:40:02 +08:00
xream
c5b11f8b36 fix: 修复过滤非法节点功能 2024-02-04 01:22:27 +08:00
xream
b19b49d2fa chore: 端口号允许为 0 2024-02-03 23:59:26 +08:00
xream
395c6e4e4a chore: 在 bundle 文件顶部添加版本号 2024-02-03 22:34:58 +08:00
xream
ae1c738f70 chore: 开发流程使用 esbuild 2024-02-03 21:30:27 +08:00
xream
02d54208b0 feat: 支持 Hysteria(v1) URI 2024-02-03 17:54:37 +08:00
xream
5d3fc499ce feat: 支持 TUIC v5 URI 2024-02-03 16:17:26 +08:00
xream
d23bc7663e feat: 支持 Loon fast-open 2024-02-02 22:55:00 +08:00
xream
15704ea1c9 chore: 增加 esbuild bundle(暂不启用 仅本地使用) 2024-02-02 19:53:14 +08:00
xream
68cb393a7e feat: 更新 Surge, Loon, QX 输入的 Shadowsocks cipher 2024-02-02 13:23:59 +08:00
xream
2e99f28aa5 feat: 输出时校验 Surge, Surfboard, Loon, QX Shadowsocks cipher 2024-02-02 13:04:32 +08:00
xream
1a18e65e47 chore: VLESS HTTP 传输层增加默认 path 2024-02-02 12:44:08 +08:00
xream
bbd5341d7a chore: 脚本说明 2024-02-02 11:58:01 +08:00
xream
623802d73f chore: 增加脚本说明 2024-02-01 21:59:24 +08:00
xream
19920dbfa3 chore: 增加脚本说明 2024-02-01 21:58:30 +08:00
xream
8764e01d7e chore: 增加脚本说明 2024-02-01 21:58:17 +08:00
xream
31b6dd0507 chore: YAML 解析兼容(保持类型) 2024-01-31 03:27:07 +08:00
29 changed files with 873 additions and 104 deletions

View File

@@ -84,15 +84,25 @@ Install `pnpm`
Go to `backend` directories, install node dependencies:
```
pnpm install
pnpm i
```
1. In `backend`, run the backend server on http://localhost:3000
babel(old school)
```
pnpm start
```
or
esbuild(experimental)
```
pnpm run --parallel "/^dev:.*/"
```
## LICENSE
This project is under the GPL V3 LICENSE.

77
backend/bundle-esbuild.js Normal file
View File

@@ -0,0 +1,77 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const { build } = require('esbuild');
!(async () => {
const version = JSON.parse(
fs.readFileSync(path.join(__dirname, 'package.json'), 'utf-8'),
).version.trim();
const artifacts = [
{ src: 'src/main.js', dest: 'sub-store.min.js' },
{
src: 'src/products/resource-parser.loon.js',
dest: 'dist/sub-store-parser.loon.min.js',
},
{
src: 'src/products/cron-sync-artifacts.js',
dest: 'dist/cron-sync-artifacts.min.js',
},
{ src: 'src/products/sub-store-0.js', dest: 'dist/sub-store-0.min.js' },
{ src: 'src/products/sub-store-1.js', dest: 'dist/sub-store-1.min.js' },
];
for await (const artifact of artifacts) {
await build({
entryPoints: [artifact.src],
bundle: true,
minify: true,
sourcemap: false,
platform: 'browser',
format: 'iife',
outfile: artifact.dest,
});
}
let content = fs.readFileSync(path.join(__dirname, 'sub-store.min.js'), {
encoding: 'utf8',
});
content = content.replace(
/eval\(('|")(require\(('|").*?('|")\))('|")\)/g,
'$2',
);
fs.writeFileSync(
path.join(__dirname, 'dist/sub-store.no-bundle.js'),
content,
{
encoding: 'utf8',
},
);
await build({
entryPoints: ['dist/sub-store.no-bundle.js'],
bundle: true,
minify: true,
sourcemap: false,
platform: 'node',
format: 'cjs',
outfile: 'dist/sub-store.bundle.js',
});
fs.writeFileSync(
path.join(__dirname, 'dist/sub-store.bundle.js'),
`// SUB_STORE_BACKEND_VERSION: ${version}
${fs.readFileSync(path.join(__dirname, 'dist/sub-store.bundle.js'), {
encoding: 'utf8',
})}`,
{
encoding: 'utf8',
},
);
})()
.catch((e) => {
console.log(e);
})
.finally(() => {
console.log('done');
});

View File

@@ -3,23 +3,49 @@ const fs = require('fs');
const path = require('path');
const { build } = require('esbuild');
let content = fs.readFileSync(path.join(__dirname, 'sub-store.min.js'), {
encoding: 'utf8',
});
content = content.replace(
/eval\(('|")(require\(('|").*?('|")\))('|")\)/g,
'$2',
);
fs.writeFileSync(path.join(__dirname, 'dist/sub-store.no-bundle.js'), content, {
encoding: 'utf8',
});
!(async () => {
const version = JSON.parse(
fs.readFileSync(path.join(__dirname, 'package.json'), 'utf-8'),
).version.trim();
build({
entryPoints: ['dist/sub-store.no-bundle.js'],
bundle: true,
minify: true,
sourcemap: true,
platform: 'node',
format: 'cjs',
outfile: 'dist/sub-store.bundle.js',
});
let content = fs.readFileSync(path.join(__dirname, 'sub-store.min.js'), {
encoding: 'utf8',
});
content = content.replace(
/eval\(('|")(require\(('|").*?('|")\))('|")\)/g,
'$2',
);
fs.writeFileSync(
path.join(__dirname, 'dist/sub-store.no-bundle.js'),
content,
{
encoding: 'utf8',
},
);
await build({
entryPoints: ['dist/sub-store.no-bundle.js'],
bundle: true,
minify: true,
sourcemap: true,
platform: 'node',
format: 'cjs',
outfile: 'dist/sub-store.bundle.js',
});
fs.writeFileSync(
path.join(__dirname, 'dist/sub-store.bundle.js'),
`// SUB_STORE_BACKEND_VERSION: ${version}
${fs.readFileSync(path.join(__dirname, 'dist/sub-store.bundle.js'), {
encoding: 'utf8',
})}`,
{
encoding: 'utf8',
},
);
})()
.catch((e) => {
console.log(e);
})
.finally(() => {
console.log('done');
});

24
backend/dev-esbuild.js Normal file
View File

@@ -0,0 +1,24 @@
#!/usr/bin/env node
const { build } = require('esbuild');
!(async () => {
const artifacts = [{ src: 'src/main.js', dest: 'sub-store.min.js' }];
for await (const artifact of artifacts) {
await build({
entryPoints: [artifact.src],
bundle: true,
minify: false,
sourcemap: false,
platform: 'node',
format: 'cjs',
outfile: artifact.dest,
});
}
})()
.catch((e) => {
console.log(e);
})
.finally(() => {
console.log('done');
});

View File

@@ -1,6 +1,6 @@
{
"name": "sub-store",
"version": "2.14.197",
"version": "2.14.214",
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
"main": "src/main.js",
"scripts": {
@@ -8,6 +8,8 @@
"test": "gulp peggy && npx cross-env BABEL_ENV=test mocha src/test/**/*.spec.js --require @babel/register --recursive",
"serve": "node sub-store.min.js",
"start": "nodemon -w src -w package.json --exec babel-node src/main.js",
"dev:esbuild": "nodemon -w src -w package.json dev-esbuild.js",
"dev:run": "nodemon -w sub-store.json -w sub-store.min.js sub-store.min.js",
"build": "gulp",
"bundle": "node bundle.js"
},

View File

@@ -63,7 +63,7 @@ function parse(raw) {
return proxies;
}
async function process(proxies, operators = [], targetPlatform, source) {
async function processFn(proxies, operators = [], targetPlatform, source) {
for (const item of operators) {
// process script
let script;
@@ -188,7 +188,7 @@ function produce(proxies, targetPlatform, type, opts = {}) {
export const ProxyUtils = {
parse,
process,
process: processFn,
produce,
isIPv4,
isIPv6,
@@ -317,6 +317,17 @@ function lastParse(proxy) {
if (['hysteria', 'hysteria2'].includes(proxy.type) && !proxy.ports) {
delete proxy.ports;
}
if (['vless'].includes(proxy.type)) {
if (['http'].includes(proxy.network)) {
let transportPath = proxy[`${proxy.network}-opts`]?.path;
if (!transportPath) {
if (!proxy[`${proxy.network}-opts`]) {
proxy[`${proxy.network}-opts`] = {};
}
proxy[`${proxy.network}-opts`].path = ['/'];
}
}
}
return proxy;
}

View File

@@ -545,6 +545,123 @@ function URI_Hysteria2() {
};
return { name, test, parse };
}
function URI_Hysteria() {
const name = 'URI Hysteria Parser';
const test = (line) => {
return /^(hysteria|hy):\/\//.test(line);
};
const parse = (line) => {
line = line.split(/(hysteria|hy):\/\//)[2];
// eslint-disable-next-line no-unused-vars
let [__, server, ___, port, ____, addons = '', name] =
/^(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line);
port = parseInt(`${port}`, 10);
if (isNaN(port)) {
port = 443;
}
if (name != null) {
name = decodeURIComponent(name);
}
name = name ?? `Hysteria ${server}:${port}`;
const proxy = {
type: 'hysteria',
name,
server,
port,
};
const params = {};
for (const addon of addons.split('&')) {
let [key, value] = addon.split('=');
key = key.replace(/_/, '-');
value = decodeURIComponent(value);
if (['alpn'].includes(key)) {
proxy[key] = value ? value.split(',') : undefined;
} else if (['insecure'].includes(key)) {
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(value);
} else if (['auth'].includes(key)) {
proxy['auth-str'] = value;
} else if (['mport'].includes(key)) {
proxy['ports'] = value;
} else if (['obfsParam'].includes(key)) {
proxy['obfs'] = value;
} else if (['upmbps'].includes(key)) {
proxy['up'] = value;
} else if (['downmbps'].includes(key)) {
proxy['down'] = value;
} else if (['obfs'].includes(key)) {
// obfs: Obfuscation mode (optional, empty or "xplus")
proxy['_obfs'] = value || '';
} else if (['fast-open', 'peer'].includes(key)) {
params[key] = value;
} else {
proxy[key] = value;
}
}
if (!proxy.sni && params.peer) {
proxy.sni = params.peer;
}
if (!proxy['fast-open'] && params.fastopen) {
proxy['fast-open'] = true;
}
if (!proxy.protocol) {
// protocol: protocol to use ("udp", "wechat-video", "faketcp") (optional, default: "udp")
proxy.protocol = 'udp';
}
return proxy;
};
return { name, test, parse };
}
function URI_TUIC() {
const name = 'URI TUIC Parser';
const test = (line) => {
return /^tuic:\/\//.test(line);
};
const parse = (line) => {
line = line.split(/tuic:\/\//)[1];
// eslint-disable-next-line no-unused-vars
let [__, uuid, password, server, ___, port, ____, addons = '', name] =
/^(.*?):(.*?)@(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line);
port = parseInt(`${port}`, 10);
if (isNaN(port)) {
port = 443;
}
password = decodeURIComponent(password);
if (name != null) {
name = decodeURIComponent(name);
}
name = name ?? `TUIC ${server}:${port}`;
const proxy = {
type: 'tuic',
name,
server,
port,
password,
uuid,
};
for (const addon of addons.split('&')) {
let [key, value] = addon.split('=');
key = key.replace(/_/, '-');
value = decodeURIComponent(value);
if (['alpn'].includes(key)) {
proxy[key] = value ? value.split(',') : undefined;
} else if (['allow-insecure'].includes(key)) {
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(value);
} else if (['disable-sni', 'reduce-rtt'].includes(key)) {
proxy[key] = /(TRUE)|1/i.test(value);
} else {
proxy[key] = value;
}
}
return proxy;
};
return { name, test, parse };
}
// Trojan URI format
function URI_Trojan() {
@@ -1051,6 +1168,8 @@ export default [
URI_SSR(),
URI_VMess(),
URI_VLESS(),
URI_TUIC(),
URI_Hysteria(),
URI_Hysteria2(),
URI_Trojan(),
Clash_All(),

View File

@@ -68,7 +68,7 @@ trojan = tag equals "trojan"i address password (transport/transport_host/transpo
proxy.type = "trojan";
handleTransport();
}
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/udp_relay/download_bandwidth/ecn/others)* {
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/udp_relay/fast_open/download_bandwidth/ecn/others)* {
proxy.type = "hysteria2";
}
https = tag equals "https"i address (username password)? (tls_host/tls_verification/fast_open/udp_relay/others)* {
@@ -117,7 +117,7 @@ port = digits:[0-9]+ {
method = comma cipher:cipher {
proxy.cipher = cipher;
}
cipher = ("aes-128-gcm"/"aes-192-gcm"/"aes-256-gcm"/"aes-128-cfb"/"aes-192-cfb"/"aes-256-cfb"/"aes-128-ctr"/"aes-192-ctr"/"aes-256-ctr"/"rc4-md5"/"xchacha20-ietf-poly1305"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none"/"auto");
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"/"auto"/"bf-cfb"/"camellia-128-cfb"/"camellia-192-cfb"/"camellia-256-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none"/"rc4-md5"/"rc4"/"salsa20"/"xchacha20-ietf-poly1305");
username = & {
let j = peg$currPos;

View File

@@ -66,7 +66,7 @@ trojan = tag equals "trojan"i address password (transport/transport_host/transpo
proxy.type = "trojan";
handleTransport();
}
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/udp_relay/download_bandwidth/ecn/others)* {
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/udp_relay/fast_open/download_bandwidth/ecn/others)* {
proxy.type = "hysteria2";
}
https = tag equals "https"i address (username password)? (tls_host/tls_verification/fast_open/udp_relay/others)* {
@@ -115,7 +115,7 @@ port = digits:[0-9]+ {
method = comma cipher:cipher {
proxy.cipher = cipher;
}
cipher = ("aes-128-gcm"/"aes-192-gcm"/"aes-256-gcm"/"aes-128-cfb"/"aes-192-cfb"/"aes-256-cfb"/"aes-128-ctr"/"aes-192-ctr"/"aes-256-ctr"/"rc4-md5"/"xchacha20-ietf-poly1305"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none"/"auto");
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"/"auto"/"bf-cfb"/"camellia-128-cfb"/"camellia-192-cfb"/"camellia-256-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none"/"rc4-md5"/"rc4"/"salsa20"/"xchacha20-ietf-poly1305");
username = & {
let j = peg$currPos;

View File

@@ -149,7 +149,7 @@ uuid = comma "password" equals uuid:[^=,]+ { proxy.uuid = uuid.join("").trim();
method = comma "method" equals cipher:cipher {
proxy.cipher = cipher;
};
cipher = ("aes-128-gcm"/"aes-192-gcm"/"aes-256-gcm"/"aes-128-cfb"/"aes-192-cfb"/"aes-256-cfb"/"aes-128-ctr"/"aes-192-ctr"/"aes-256-ctr"/"rc4-md5"/"xchacha20-ietf-poly1305"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none");
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"none"/"rc2-cfb"/"rc4-md5-6"/"rc4-md5"/"salsa20"/"xchacha20-ietf-poly1305");
aead = comma "aead" equals flag:bool { proxy.aead = flag; }
udp_relay = comma "udp-relay" equals flag:bool { proxy.udp = flag; }

View File

@@ -147,7 +147,7 @@ uuid = comma "password" equals uuid:[^=,]+ { proxy.uuid = uuid.join("").trim();
method = comma "method" equals cipher:cipher {
proxy.cipher = cipher;
};
cipher = ("aes-128-gcm"/"aes-192-gcm"/"aes-256-gcm"/"aes-128-cfb"/"aes-192-cfb"/"aes-256-cfb"/"aes-128-ctr"/"aes-192-ctr"/"aes-256-ctr"/"rc4-md5"/"xchacha20-ietf-poly1305"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none");
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"none"/"rc2-cfb"/"rc4-md5-6"/"rc4-md5"/"salsa20"/"xchacha20-ietf-poly1305");
aead = comma "aead" equals flag:bool { proxy.aead = flag; }
udp_relay = comma "udp-relay" equals flag:bool { proxy.udp = flag; }

View File

@@ -41,7 +41,7 @@ start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v
return proxy;
}
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/test_url/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "ss";
// handle obfs
if (obfs.type == "http" || obfs.type === "tls") {
@@ -52,7 +52,7 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
}
handleShadowTLS();
}
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/underlying_proxy/no_error_alert/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/underlying_proxy/test_url/no_error_alert/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "vmess";
proxy.cipher = proxy.cipher || "none";
if (proxy.aead) {
@@ -63,21 +63,21 @@ vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/
handleWebsocket();
handleShadowTLS();
}
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/test_url/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "trojan";
handleWebsocket();
handleShadowTLS();
}
https = tag equals "https" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
https = tag equals "https" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/test_url/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "http";
proxy.tls = true;
handleShadowTLS();
}
http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/underlying_proxy/test_url/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "http";
handleShadowTLS();
}
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/no_error_alert/fast_open/udp_relay/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/test_url/no_error_alert/fast_open/udp_relay/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "snell";
// handle obfs
if (obfs.type == "http" || obfs.type === "tls") {
@@ -87,11 +87,11 @@ snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_
}
handleShadowTLS();
}
tuic = tag equals "tuic" address (alpn/token/ip_version/underlying_proxy/no_error_alert/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
tuic = tag equals "tuic" address (alpn/token/ip_version/underlying_proxy/test_url/no_error_alert/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "tuic";
handleShadowTLS();
}
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/underlying_proxy/no_error_alert/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/underlying_proxy/test_url/no_error_alert/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "tuic";
proxy.version = 5;
handleShadowTLS();
@@ -104,11 +104,11 @@ hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying
proxy.type = "hysteria2";
handleShadowTLS();
}
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (no_error_alert/ip_version/underlying_proxy/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (no_error_alert/ip_version/underlying_proxy/test_url/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "socks5";
handleShadowTLS();
}
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (no_error_alert/ip_version/underlying_proxy/sni/tls_fingerprint/tls_verification/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (no_error_alert/ip_version/underlying_proxy/test_url/sni/tls_fingerprint/tls_verification/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "socks5";
proxy.tls = true;
handleShadowTLS();
@@ -188,7 +188,7 @@ vmess_aead = comma "vmess-aead" equals flag:bool { proxy.aead = flag; }
method = comma "encrypt-method" equals cipher:cipher {
proxy.cipher = cipher;
}
cipher = ("aes-128-gcm"/"aes-192-gcm"/"aes-256-gcm"/"aes-128-cfb"/"aes-192-cfb"/"aes-256-cfb"/"aes-128-ctr"/"aes-192-ctr"/"aes-256-ctr"/"rc4-md5"/"xchacha20-ietf-poly1305"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none");
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"/"camellia-128-cfb"/"camellia-192-cfb"/"camellia-256-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"idea-cfb"/"none"/"rc2-cfb"/"rc4-md5"/"rc4"/"salsa20"/"seed-cfb"/"xchacha20-ietf-poly1305");
ws = comma "ws" equals flag:bool { obfs.type = "ws"; }
ws_headers = comma "ws-headers" equals headers:$[^,]+ {

View File

@@ -39,7 +39,7 @@ start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v
return proxy;
}
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/test_url/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "ss";
// handle obfs
if (obfs.type == "http" || obfs.type === "tls") {
@@ -50,7 +50,7 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
}
handleShadowTLS();
}
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/underlying_proxy/no_error_alert/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/underlying_proxy/test_url/no_error_alert/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "vmess";
proxy.cipher = proxy.cipher || "none";
if (proxy.aead) {
@@ -61,21 +61,21 @@ vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/
handleWebsocket();
handleShadowTLS();
}
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/test_url/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "trojan";
handleWebsocket();
handleShadowTLS();
}
https = tag equals "https" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
https = tag equals "https" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/test_url/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "http";
proxy.tls = true;
handleShadowTLS();
}
http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/underlying_proxy/test_url/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "http";
handleShadowTLS();
}
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/no_error_alert/fast_open/udp_relay/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/test_url/no_error_alert/fast_open/udp_relay/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "snell";
// handle obfs
if (obfs.type == "http" || obfs.type === "tls") {
@@ -85,11 +85,11 @@ snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_
}
handleShadowTLS();
}
tuic = tag equals "tuic" address (alpn/token/ip_version/underlying_proxy/no_error_alert/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
tuic = tag equals "tuic" address (alpn/token/ip_version/underlying_proxy/test_url/no_error_alert/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "tuic";
handleShadowTLS();
}
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/underlying_proxy/no_error_alert/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/underlying_proxy/test_url/no_error_alert/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "tuic";
proxy.version = 5;
handleShadowTLS();
@@ -102,11 +102,11 @@ hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying
proxy.type = "hysteria2";
handleShadowTLS();
}
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (no_error_alert/ip_version/underlying_proxy/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (no_error_alert/ip_version/underlying_proxy/test_url/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "socks5";
handleShadowTLS();
}
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (no_error_alert/ip_version/underlying_proxy/sni/tls_fingerprint/tls_verification/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (no_error_alert/ip_version/underlying_proxy/test_url/sni/tls_fingerprint/tls_verification/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "socks5";
proxy.tls = true;
handleShadowTLS();
@@ -186,7 +186,7 @@ vmess_aead = comma "vmess-aead" equals flag:bool { proxy.aead = flag; }
method = comma "encrypt-method" equals cipher:cipher {
proxy.cipher = cipher;
}
cipher = ("aes-128-gcm"/"aes-192-gcm"/"aes-256-gcm"/"aes-128-cfb"/"aes-192-cfb"/"aes-256-cfb"/"aes-128-ctr"/"aes-192-ctr"/"aes-256-ctr"/"rc4-md5"/"xchacha20-ietf-poly1305"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none");
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"/"camellia-128-cfb"/"camellia-192-cfb"/"camellia-256-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"idea-cfb"/"none"/"rc2-cfb"/"rc4-md5"/"rc4"/"salsa20"/"seed-cfb"/"xchacha20-ietf-poly1305");
ws = comma "ws" equals flag:bool { obfs.type = "ws"; }
ws_headers = comma "ws-headers" equals headers:$[^,]+ {

View File

@@ -30,7 +30,7 @@ start = (trojan) {
return proxy
}
trojan = "trojan://" password:password "@" server:server ":" port:port params? name:name?{
trojan = "trojan://" password:password "@" server:server ":" port:port "/"? params? name:name?{
proxy.type = "trojan";
proxy.password = password;
proxy.server = server;
@@ -79,7 +79,7 @@ port = digits:[0-9]+ {
}
}
params = "/"? "?" head:param tail:("&"@param)* {
params = "?" head:param tail:("&"@param)* {
proxy["skip-cert-verify"] = toBool(params["allowInsecure"]);
proxy.sni = params["sni"] || params["peer"];

View File

@@ -28,7 +28,7 @@ start = (trojan) {
return proxy
}
trojan = "trojan://" password:password "@" server:server ":" port:port params? name:name?{
trojan = "trojan://" password:password "@" server:server ":" port:port "/"? params? name:name?{
proxy.type = "trojan";
proxy.password = password;
proxy.server = server;
@@ -77,7 +77,7 @@ port = digits:[0-9]+ {
}
}
params = "/"? "?" head:param tail:("&"@param)* {
params = "?" head:param tail:("&"@param)* {
proxy["skip-cert-verify"] = toBool(params["allowInsecure"]);
proxy.sni = params["sni"] || params["peer"];

View File

@@ -87,12 +87,15 @@ function QuickSettingOperator(args) {
if (get(args.useless)) {
const filter = UselessFilter();
const selected = filter.func(proxies);
proxies.filter((_, i) => selected[i]);
proxies = proxies.filter(
(p, i) => selected[i] && p.port > 0 && p.port <= 65535,
);
}
return proxies.map((proxy) => {
proxy.udp = get(args.udp, proxy.udp);
proxy.tfo = get(args.tfo, proxy.tfo);
proxy['fast-open'] = get(args.tfo, proxy['fast-open']);
proxy['skip-cert-verify'] = get(
args.scert,
proxy['skip-cert-verify'],
@@ -510,7 +513,7 @@ function ResolveDomainOperator({ provider, type, filter }) {
return proxies.filter((p) => {
if (filter === 'removeFailed') {
return p['no-resolve'] || p.resolved;
return isIP(p.server) || p['no-resolve'] || p.resolved;
} else if (filter === 'IPOnly') {
return isIP(p.server);
} else if (filter === 'IPv4Only') {

View File

@@ -32,6 +32,32 @@ export default function Loon_Producer() {
function shadowsocks(proxy) {
const result = new Result(proxy);
if (
![
'rc4',
'rc4-md5',
'aes-128-cfb',
'aes-192-cfb',
'aes-256-cfb',
'aes-128-ctr',
'aes-192-ctr',
'aes-256-ctr',
'bf-cfb',
'camellia-128-cfb',
'camellia-192-cfb',
'camellia-256-cfb',
'salsa20',
'chacha20',
'chacha20-ietf',
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305',
'xchacha20-ietf-poly1305',
].includes(proxy.cipher)
) {
throw new Error(`cipher ${proxy.cipher} is not supported`);
}
result.append(
`${proxy.name}=shadowsocks,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"`,
);
@@ -57,7 +83,9 @@ function shadowsocks(proxy) {
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
// udp
result.appendIfPresent(`,udp=${proxy.udp}`, 'udp');
if (proxy.udp) {
result.append(`,udp=true`);
}
return result.toString();
}
@@ -83,7 +111,9 @@ function shadowsocksr(proxy) {
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
// udp
result.appendIfPresent(`,udp=${proxy.udp}`, 'udp');
if (proxy.udp) {
result.append(`,udp=true`);
}
return result.toString();
}
@@ -126,7 +156,9 @@ function trojan(proxy) {
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
// udp
result.appendIfPresent(`,udp=${proxy.udp}`, 'udp');
if (proxy.udp) {
result.append(`,udp=true`);
}
return result.toString();
}
@@ -193,7 +225,9 @@ function vmess(proxy) {
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
// udp
result.appendIfPresent(`,udp=${proxy.udp}`, 'udp');
if (proxy.udp) {
result.append(`,udp=true`);
}
return result.toString();
}
@@ -255,7 +289,9 @@ function vless(proxy) {
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
// udp
result.appendIfPresent(`,udp=${proxy.udp}`, 'udp');
if (proxy.udp) {
result.append(`,udp=true`);
}
return result.toString();
}
@@ -278,8 +314,6 @@ function http(proxy) {
// tfo
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
// udp
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
return result.toString();
}
@@ -360,8 +394,13 @@ function hysteria2(proxy) {
'skip-cert-verify',
);
// tfo
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
// udp
result.appendIfPresent(`,udp=${proxy.udp}`, 'udp');
if (proxy.udp) {
result.append(`,udp=true`);
}
// download-bandwidth
result.appendIfPresent(

View File

@@ -37,7 +37,36 @@ function shadowsocks(proxy) {
const result = new Result(proxy);
const append = result.append.bind(result);
const appendIfPresent = result.appendIfPresent.bind(result);
if (!proxy.cipher) {
proxy.cipher = 'none';
}
if (
![
'none',
'rc4-md5',
'rc4-md5-6',
'aes-128-cfb',
'aes-192-cfb',
'aes-256-cfb',
'aes-128-ctr',
'aes-192-ctr',
'aes-256-ctr',
'bf-cfb',
'cast5-cfb',
'des-cfb',
'rc2-cfb',
'salsa20',
'chacha20',
'chacha20-ietf',
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305',
'xchacha20-ietf-poly1305',
].includes(proxy.cipher)
) {
throw new Error(`cipher ${proxy.cipher} is not supported`);
}
append(`shadowsocks=${proxy.server}:${proxy.port}`);
append(`,method=${proxy.cipher}`);
append(`,password=${proxy.password}`);

View File

@@ -225,7 +225,7 @@ const httpParser = (proxy = {}) => {
server_port: parseInt(`${proxy.port}`, 10),
tls: { enabled: false, server_name: proxy.server, insecure: false },
};
if (parsedProxy.server_port < 1 || parsedProxy.server_port > 65535)
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
throw 'invalid port';
if (proxy.username) parsedProxy.username = proxy.username;
if (proxy.password) parsedProxy.password = proxy.password;
@@ -252,7 +252,7 @@ const socks5Parser = (proxy = {}) => {
password: proxy.password,
version: '5',
};
if (parsedProxy.server_port < 1 || parsedProxy.server_port > 65535)
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
throw 'invalid port';
if (proxy.username) parsedProxy.username = proxy.username;
if (proxy.password) parsedProxy.password = proxy.password;
@@ -287,7 +287,7 @@ const shadowTLSParser = (proxy = {}) => {
},
},
};
if (stPart.server_port < 1 || stPart.server_port > 65535)
if (stPart.server_port < 0 || stPart.server_port > 65535)
throw '端口值非法';
if (proxy['fast-open'] === true) stPart.udp_fragment = true;
tfoParser(proxy, stPart);
@@ -303,7 +303,7 @@ const ssParser = (proxy = {}) => {
method: proxy.cipher,
password: proxy.password,
};
if (parsedProxy.server_port < 1 || parsedProxy.server_port > 65535)
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;
@@ -379,7 +379,7 @@ const ssrParser = (proxy = {}) => {
obfs: proxy.obfs,
protocol: proxy.protocol,
};
if (parsedProxy.server_port < 1 || parsedProxy.server_port > 65535)
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
throw 'invalid port';
if (proxy['obfs-param']) parsedProxy.obfs_param = proxy['obfs-param'];
if (proxy['protocol-param'] && proxy['protocol-param'] !== '')
@@ -412,7 +412,7 @@ const vmessParser = (proxy = {}) => {
].indexOf(parsedProxy.security) === -1
)
parsedProxy.security = 'auto';
if (parsedProxy.server_port < 1 || parsedProxy.server_port > 65535)
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
throw 'invalid port';
if (proxy.xudp) parsedProxy.packet_encoding = 'xudp';
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
@@ -436,7 +436,7 @@ const vlessParser = (proxy = {}) => {
uuid: proxy.uuid,
tls: { enabled: false, server_name: proxy.server, insecure: false },
};
if (parsedProxy.server_port < 1 || parsedProxy.server_port > 65535)
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
throw 'invalid port';
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
if (proxy.flow === 'xtls-rprx-vision') parsedProxy.flow = proxy.flow;
@@ -457,7 +457,7 @@ const trojanParser = (proxy = {}) => {
password: proxy.password,
tls: { enabled: true, server_name: proxy.server, insecure: false },
};
if (parsedProxy.server_port < 1 || parsedProxy.server_port > 65535)
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
throw 'invalid port';
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
@@ -477,7 +477,7 @@ const hysteriaParser = (proxy = {}) => {
disable_mtu_discovery: false,
tls: { enabled: true, server_name: proxy.server, insecure: false },
};
if (parsedProxy.server_port < 1 || parsedProxy.server_port > 65535)
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
throw 'invalid port';
if (proxy.auth_str) parsedProxy.auth_str = `${proxy.auth_str}`;
if (proxy['auth-str']) parsedProxy.auth_str = `${proxy['auth-str']}`;
@@ -524,7 +524,7 @@ const hysteria2Parser = (proxy = {}) => {
obfs: {},
tls: { enabled: true, server_name: proxy.server, insecure: false },
};
if (parsedProxy.server_port < 1 || parsedProxy.server_port > 65535)
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
throw 'invalid port';
if (proxy.up) parsedProxy.up_mbps = parseInt(`${proxy.up}`, 10);
if (proxy.down) parsedProxy.down_mbps = parseInt(`${proxy.down}`, 10);
@@ -547,7 +547,7 @@ const tuic5Parser = (proxy = {}) => {
password: proxy.password,
tls: { enabled: true, server_name: proxy.server, insecure: false },
};
if (parsedProxy.server_port < 1 || parsedProxy.server_port > 65535)
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
throw 'invalid port';
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
if (
@@ -583,7 +583,7 @@ const wireguardParser = (proxy = {}) => {
pre_shared_key: proxy['pre-shared-key'],
reserved: [],
};
if (parsedProxy.server_port < 1 || parsedProxy.server_port > 65535)
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
throw 'invalid port';
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
if (typeof proxy.reserved === 'string') {
@@ -637,6 +637,35 @@ export default function singbox_Producer() {
}
break;
case 'ss':
// if (!proxy.cipher) {
// proxy.cipher = 'none';
// }
// if (
// ![
// '2022-blake3-aes-128-gcm',
// '2022-blake3-aes-256-gcm',
// '2022-blake3-chacha20-poly1305',
// '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',
// 'chacha20-ietf',
// 'chacha20-ietf-poly1305',
// 'none',
// 'rc4-md5',
// 'xchacha20',
// 'xchacha20-ietf-poly1305',
// ].includes(proxy.cipher)
// ) {
// throw new Error(
// `cipher ${proxy.cipher} is not supported`,
// );
// }
if (proxy.plugin === 'shadow-tls') {
const { ssPart, stPart } =
shadowTLSParser(proxy);

View File

@@ -31,6 +31,32 @@ export default function Surfboard_Producer() {
function shadowsocks(proxy) {
const result = new Result(proxy);
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
if (
![
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305',
'xchacha20-ietf-poly1305',
'rc4',
'rc4-md5',
'aes-128-cfb',
'aes-192-cfb',
'aes-256-cfb',
'aes-128-ctr',
'aes-192-ctr',
'aes-256-ctr',
'bf-cfb',
'camellia-128-cfb',
'camellia-192-cfb',
'camellia-256-cfb',
'salsa20',
'chacha20',
'chacha20-ietf',
].includes(proxy.cipher)
) {
throw new Error(`cipher ${proxy.cipher} is not supported`);
}
result.append(`,encrypt-method=${proxy.cipher}`);
result.appendIfPresent(`,password=${proxy.password}`, 'password');

View File

@@ -44,6 +44,41 @@ export default function Surge_Producer() {
function shadowsocks(proxy) {
const result = new Result(proxy);
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
if (!proxy.cipher) {
proxy.cipher = 'none';
}
if (
![
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305',
'xchacha20-ietf-poly1305',
'rc4',
'rc4-md5',
'aes-128-cfb',
'aes-192-cfb',
'aes-256-cfb',
'aes-128-ctr',
'aes-192-ctr',
'aes-256-ctr',
'bf-cfb',
'camellia-128-cfb',
'camellia-192-cfb',
'camellia-256-cfb',
'cast5-cfb',
'des-cfb',
'idea-cfb',
'rc2-cfb',
'seed-cfb',
'salsa20',
'chacha20',
'chacha20-ietf',
'none',
].includes(proxy.cipher)
) {
throw new Error(`cipher ${proxy.cipher} is not supported`);
}
result.append(`,encrypt-method=${proxy.cipher}`);
result.appendIfPresent(`,password=${proxy.password}`, 'password');

View File

@@ -6,6 +6,11 @@ export default function URI_Producer() {
const type = 'SINGLE';
const produce = (proxy) => {
let result = '';
delete proxy.subName;
delete proxy.collectionName;
if (['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(proxy.type)) {
delete proxy.tls;
}
if (proxy.server && isIPv6(proxy.server)) {
proxy.server = `[${proxy.server}]`;
}
@@ -197,9 +202,9 @@ export default function URI_Producer() {
result = `vless://${proxy.uuid}@${proxy.server}:${
proxy.port
}?${vlessTransport}&security=${encodeURIComponent(
}?security=${encodeURIComponent(
security,
)}${alpn}${allowInsecure}${sni}${fp}${flow}${sid}${pbk}#${encodeURIComponent(
)}${vlessTransport}${alpn}${allowInsecure}${sni}${fp}${flow}${sid}${pbk}#${encodeURIComponent(
proxy.name,
)}`;
break;
@@ -285,6 +290,119 @@ export default function URI_Producer() {
'&',
)}#${encodeURIComponent(proxy.name)}`;
break;
case 'hysteria':
let hysteriaParams = [];
Object.keys(proxy).forEach((key) => {
if (!['name', 'type', 'server', 'port'].includes(key)) {
const i = key.replace(/-/, '_');
if (['alpn'].includes(key)) {
if (proxy[key]) {
hysteriaParams.push(
`${i}=${encodeURIComponent(
Array.isArray(proxy[key])
? proxy[key][0]
: proxy[key],
)}`,
);
}
} else if (['skip-cert-verify'].includes(key)) {
if (proxy[key]) {
hysteriaParams.push(`insecure=1`);
}
} else if (['tfo', 'fast-open'].includes(key)) {
if (
proxy[key] &&
!hysteriaParams.includes('fastopen=1')
) {
hysteriaParams.push(`fastopen=1`);
}
} else if (['ports'].includes(key)) {
hysteriaParams.push(`mport=${proxy[key]}`);
} else if (['auth-str'].includes(key)) {
hysteriaParams.push(`auth=${proxy[key]}`);
} else if (['up'].includes(key)) {
hysteriaParams.push(`upmbps=${proxy[key]}`);
} else if (['down'].includes(key)) {
hysteriaParams.push(`downmbps=${proxy[key]}`);
} else if (['_obfs'].includes(key)) {
hysteriaParams.push(`obfs=${proxy[key]}`);
} else if (['obfs'].includes(key)) {
hysteriaParams.push(`obfsParam=${proxy[key]}`);
} else if (['sni'].includes(key)) {
hysteriaParams.push(`peer=${proxy[key]}`);
} else if (proxy[key]) {
hysteriaParams.push(
`${i}=${encodeURIComponent(proxy[key])}`,
);
}
}
});
result = `hysteria://${proxy.server}:${
proxy.port
}?${hysteriaParams.join('&')}#${encodeURIComponent(
proxy.name,
)}`;
break;
case 'tuic':
if (!proxy.token || proxy.token.length === 0) {
let tuicParams = [];
Object.keys(proxy).forEach((key) => {
if (
![
'name',
'type',
'uuid',
'password',
'server',
'port',
].includes(key)
) {
const i = key.replace(/-/, '_');
if (['alpn'].includes(key)) {
if (proxy[key]) {
tuicParams.push(
`${i}=${encodeURIComponent(
Array.isArray(proxy[key])
? proxy[key][0]
: proxy[key],
)}`,
);
}
} else if (['skip-cert-verify'].includes(key)) {
if (proxy[key]) {
tuicParams.push(`allow_insecure=1`);
}
} else if (['tfo', 'fast-open'].includes(key)) {
if (
proxy[key] &&
!tuicParams.includes('fast_open=1')
) {
tuicParams.push(`fast_open=1`);
}
} else if (
['disable-sni', 'reduce-rtt'].includes(key) &&
proxy[key]
) {
tuicParams.push(`${i}=1`);
} else if (proxy[key]) {
tuicParams.push(
`${i}=${encodeURIComponent(proxy[key])}`,
);
}
}
});
result = `tuic://${encodeURIComponent(
proxy.uuid,
)}:${encodeURIComponent(proxy.password)}@${proxy.server}:${
proxy.port
}?${tuicParams.join('&')}#${encodeURIComponent(
proxy.name,
)}`;
break;
}
}
return result;
};

View File

@@ -2,28 +2,79 @@
import { ProxyUtils } from '@/core/proxy-utils';
import { RuleUtils } from '@/core/rule-utils';
import { version } from '../../package.json';
import download from '@/utils/download';
console.log(
`
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
Sub-Store -- v${version}
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
`,
);
let result = '';
let resource = typeof $resource !== 'undefined' ? $resource : '';
let resourceType = typeof $resourceType !== 'undefined' ? $resourceType : '';
let resourceUrl = typeof $resourceUrl !== 'undefined' ? $resourceUrl : '';
const RESOURCE_TYPE = {
PROXY: 1,
RULE: 2,
};
!(async () => {
console.log(
`
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
Sub-Store -- v${version}
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
`,
);
let result = $resource;
let arg;
if (typeof $argument != 'undefined') {
arg = Object.fromEntries(
$argument.split('&').map((item) => item.split('=')),
);
} else {
arg = {};
}
if ($resourceType === RESOURCE_TYPE.PROXY) {
const proxies = ProxyUtils.parse($resource);
result = ProxyUtils.produce(proxies, 'Loon');
} else if ($resourceType === RESOURCE_TYPE.RULE) {
const rules = RuleUtils.parse($resource);
result = RuleUtils.produce(rules, 'Loon');
}
const RESOURCE_TYPE = {
PROXY: 1,
RULE: 2,
};
$done(result);
result = resource;
if (resourceType === RESOURCE_TYPE.PROXY) {
try {
let proxies = ProxyUtils.parse(resource);
result = ProxyUtils.produce(proxies, 'Loon');
} catch (e) {
console.log('解析器: 使用 resource 出现错误');
console.log(e.message ?? e);
}
if ((!result || /^\s*$/.test(result)) && resourceUrl) {
console.log(`解析器: 尝试从 ${resourceUrl} 获取订阅`);
try {
let raw = await download(resourceUrl, arg?.ua, arg?.timeout);
let proxies = ProxyUtils.parse(raw);
result = ProxyUtils.produce(proxies, 'Loon');
} 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 ((!result || /^\s*$/.test(result)) && resourceUrl) {
console.log(`解析器: 尝试从 ${resourceUrl} 获取规则`);
try {
let raw = await download(resourceUrl, arg?.ua, arg?.timeout);
let rules = RuleUtils.parse(raw);
result = RuleUtils.produce(rules, 'Loon');
} catch (e) {
console.log(e.message ?? e);
}
}
}
})()
.catch(async (e) => {
console.log('解析器: 出现错误');
console.log(e.message ?? e);
})
.finally(() => {
$done(result || '');
});

View File

@@ -10,7 +10,17 @@ class ResourceCache {
if (!$.read(HEADERS_RESOURCE_CACHE_KEY)) {
$.write('{}', HEADERS_RESOURCE_CACHE_KEY);
}
this.resourceCache = JSON.parse($.read(HEADERS_RESOURCE_CACHE_KEY));
try {
this.resourceCache = JSON.parse($.read(HEADERS_RESOURCE_CACHE_KEY));
} catch (e) {
$.error(
`解析持久化缓存中的 ${HEADERS_RESOURCE_CACHE_KEY} 失败, 重置为 {}, 错误: ${
e?.message ?? e
}`,
);
this.resourceCache = {};
$.write('{}', HEADERS_RESOURCE_CACHE_KEY);
}
this._cleanup();
}

View File

@@ -7,7 +7,17 @@ class ResourceCache {
if (!$.read(RESOURCE_CACHE_KEY)) {
$.write('{}', RESOURCE_CACHE_KEY);
}
this.resourceCache = JSON.parse($.read(RESOURCE_CACHE_KEY));
try {
this.resourceCache = JSON.parse($.read(RESOURCE_CACHE_KEY));
} catch (e) {
$.error(
`解析持久化缓存中的 ${RESOURCE_CACHE_KEY} 失败, 重置为 {}, 错误: ${
e?.message ?? e
}`,
);
this.resourceCache = {};
$.write('{}', RESOURCE_CACHE_KEY);
}
this._cleanup();
}

View File

@@ -10,7 +10,17 @@ class ResourceCache {
if (!$.read(SCRIPT_RESOURCE_CACHE_KEY)) {
$.write('{}', SCRIPT_RESOURCE_CACHE_KEY);
}
this.resourceCache = JSON.parse($.read(SCRIPT_RESOURCE_CACHE_KEY));
try {
this.resourceCache = JSON.parse($.read(SCRIPT_RESOURCE_CACHE_KEY));
} catch (e) {
$.error(
`解析持久化缓存中的 ${SCRIPT_RESOURCE_CACHE_KEY} 失败, 重置为 {}, 错误: ${
e?.message ?? e
}`,
);
this.resourceCache = {};
$.write('{}', SCRIPT_RESOURCE_CACHE_KEY);
}
this._cleanup();
}

View File

@@ -4,7 +4,15 @@ function retry(fn, content, ...args) {
try {
return fn(content, ...args);
} catch (e) {
return fn(content.replace(/!<str>/g, ''), ...args);
return fn(
dump(
fn(
content.replace(/!<str>\s*/g, '__SubStoreJSYAMLString__'),
...args,
),
).replace(/__SubStoreJSYAMLString__/g, ''),
...args,
);
}
}

View File

@@ -1,5 +1,6 @@
name: Sub-Store
desc: 高级订阅管理工具 @Peng-YM. 定时任务默认为每天 0 点
icon: https://raw.githubusercontent.com/cc63/ICON/main/Sub-Store.png
http:
mitm:

131
scripts/demo.js Normal file
View File

@@ -0,0 +1,131 @@
function operator(proxies = [], targetPlatform, context) {
// 支持快捷操作 不一定要写一个 function
// 可参考 https://t.me/zhetengsha/970
// https://t.me/zhetengsha/1009
// proxies 为传入的内部节点数组
// 结构大致参考了 Clash.Meta(mihomo) 有私货
// 可在预览界面点击节点查看 JSON 结构 或查看 `target=JSON` 的通用订阅
// 1. `no-resolve` 为不解析域名
// 2. 域名解析后 会多一个 `resolved` 字段
// 3. 节点字段 `exec` 为 `ssr-local` 路径, 默认 `/usr/local/bin/ssr-local`; 端口从 10000 开始递增(暂不支持配置)
// $arguments 为传入的脚本参数
// targetPlatform 为输出的目标平台
// lodash
// $substore 为 OpenAPI
// 参考 https://github.com/Peng-YM/QuanX/blob/master/Tools/OpenAPI/README.md
// scriptResourceCache 缓存
// 可参考 https://t.me/zhetengsha/1003
// ProxyUtils 为节点处理工具
// 可参考 https://t.me/zhetengsha/1066
// const ProxyUtils = {
// parse, // 订阅解析
// process, // 节点操作/文件操作
// produce, // 输出订阅
// isIPv4,
// isIPv6,
// isIP,
// yaml, // yaml 解析和生成
// }
// flowUtils 为机场订阅流量信息处理工具
// 可参考 https://t.me/zhetengsha/948
// https://github.com/sub-store-org/Sub-Store/blob/31b6dd0507a9286d6ab834ec94ad3050f6bdc86b/backend/src/utils/download.js#L104
// context 为传入的上下文
// 有三种情况, 按需判断
// 若存在 `source._collection` 且 `source._collection.subscriptions` 中的 key 在 `source` 上也存在, 说明输出结果为组合订阅, 但是脚本设置在单条订阅上
// 若存在 `source._collection` 但 `source._collection.subscriptions` 中的 key 在 `source` 上不存在, 说明输出结果为组合订阅, 脚本设置在组合订阅上
// 若不存在 `source._collection`, 说明输出结果为单条订阅, 脚本设置在此单条订阅上
// 1. 输出单条订阅 sub-1 时, 该单条订阅中的脚本上下文为:
// {
// "source": {
// "sub-1": {
// "name": "sub-1",
// "displayName": "",
// "mergeSources": "",
// "ignoreFailedRemoteSub": true,
// "process": [],
// "icon": "",
// "source": "local",
// "url": "",
// "content": "",
// "ua": "",
// "display-name": "",
// "useCacheForFailedRemoteSub": false
// }
// },
// "backend": "Node",
// "version": "2.14.198"
// }
// 2. 输出组合订阅 collection-1 时, 该组合订阅中的脚本上下文为:
// {
// "source": {
// "_collection": {
// "name": "collection-1",
// "displayName": "",
// "mergeSources": "",
// "ignoreFailedRemoteSub": false,
// "icon": "",
// "process": [],
// "subscriptions": [
// "sub-1"
// ],
// "display-name": ""
// }
// },
// "backend": "Node",
// "version": "2.14.198"
// }
// 3. 输出组合订阅 collection-1 时, 该组合订阅中的单条订阅 sub-1 中的某个脚本上下文为:
// {
// "source": {
// "sub-1": {
// "name": "sub-1",
// "displayName": "",
// "mergeSources": "",
// "ignoreFailedRemoteSub": true,
// "icon": "",
// "process": [],
// "source": "local",
// "url": "",
// "content": "",
// "ua": "",
// "display-name": "",
// "useCacheForFailedRemoteSub": false
// },
// "_collection": {
// "name": "collection-1",
// "displayName": "",
// "mergeSources": "",
// "ignoreFailedRemoteSub": false,
// "icon": "",
// "process": [],
// "subscriptions": [
// "sub-1"
// ],
// "display-name": ""
// }
// },
// "backend": "Node",
// "version": "2.14.198"
// }
// 参数说明
// 可参考 https://github.com/sub-store-org/Sub-Store/wiki/%E9%93%BE%E6%8E%A5%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E
console.log(JSON.stringify(context, null, 2))
return proxies
}