mirror of
https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-10 00:52:40 +00:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c78b87834 | ||
|
|
ea88cc1794 | ||
|
|
c8b197c0a1 | ||
|
|
69fab11344 | ||
|
|
955c74a77d | ||
|
|
6d51774d36 | ||
|
|
a91f9d7728 | ||
|
|
df366cf8eb | ||
|
|
c547f34f57 | ||
|
|
a4ff32331a | ||
|
|
14648d6401 | ||
|
|
6a66475154 | ||
|
|
adc95bba60 | ||
|
|
fab3644b86 | ||
|
|
c21ce0be16 | ||
|
|
fa65eb1850 | ||
|
|
79c9b89c5f | ||
|
|
fca508ba8a | ||
|
|
21b531a44d | ||
|
|
4e5b46a43d | ||
|
|
bf81ca4acf | ||
|
|
e7c0b23222 | ||
|
|
40fb0fd7f3 | ||
|
|
b061fca356 | ||
|
|
d3c6c99b0a | ||
|
|
3fbc280e28 | ||
|
|
9e3e4c6e46 | ||
|
|
bc0dd4b175 | ||
|
|
7603fac036 | ||
|
|
9acc161684 | ||
|
|
024582a99d | ||
|
|
1d31a80b9f | ||
|
|
b2d0276836 | ||
|
|
3211fbf357 | ||
|
|
33a17c2d66 | ||
|
|
2c89a0ddbd | ||
|
|
939022e5a3 | ||
|
|
59bca5670d | ||
|
|
07b38cf971 | ||
|
|
28186f596f | ||
|
|
ea31b1d0ec |
25
README.md
25
README.md
@@ -30,13 +30,14 @@ Core functionalities:
|
|||||||
- [x] SSR URI
|
- [x] SSR URI
|
||||||
- [x] SSD URI
|
- [x] SSD URI
|
||||||
- [x] V2RayN URI
|
- [x] V2RayN URI
|
||||||
|
- [x] Hysteria2 URI
|
||||||
- [x] QX (SS, SSR, VMess, Trojan, HTTP)
|
- [x] QX (SS, SSR, VMess, Trojan, HTTP)
|
||||||
- [x] Loon (SS, SSR, VMess, Trojan, HTTP, WireGuard, VLESS)
|
- [x] Loon (SS, SSR, VMess, Trojan, HTTP, WireGuard, VLESS, Hysteria2)
|
||||||
- [x] Surge (SS, VMess, Trojan, HTTP, TUIC, Snell, SSR(external, only for macOS), WireGuard(Surge to Surge))
|
- [x] Surge (SS, VMess, Trojan, HTTP, TUIC, Snell, Hysteria2, SSR(external, only for macOS), WireGuard(Surge to Surge))
|
||||||
- [x] ShadowRocket (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, WireGuard, Hysteria)
|
- [x] ShadowRocket (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, Hysteria2)
|
||||||
- [x] Clash.Meta (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, WireGuard, Hysteria)
|
- [x] Clash.Meta (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, WireGuard, Hysteria, Hysteria2)
|
||||||
- [x] Stash (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, WireGuard, Hysteria)
|
- [x] Stash (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, WireGuard, Hysteria)
|
||||||
- [x] Clash (SS, SSR, VMess, Trojan, HTTP, Snell)
|
- [x] Clash (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, WireGuard)
|
||||||
|
|
||||||
### Supported Target Platforms
|
### Supported Target Platforms
|
||||||
|
|
||||||
@@ -75,24 +76,20 @@ Core functionalities:
|
|||||||
|
|
||||||
### Development
|
### Development
|
||||||
|
|
||||||
Go to `backend` and `web` directories, install node dependencies:
|
Install `pnpm`
|
||||||
|
|
||||||
|
Go to `backend` directories, install node dependencies:
|
||||||
|
|
||||||
```
|
```
|
||||||
npm install
|
pnpm install
|
||||||
```
|
```
|
||||||
|
|
||||||
1. In `backend`, run the backend server on http://localhost:3000
|
1. In `backend`, run the backend server on http://localhost:3000
|
||||||
|
|
||||||
```
|
```
|
||||||
npm run serve
|
pnpm start
|
||||||
```
|
```
|
||||||
|
|
||||||
2. In`web`, start the vue-cli server
|
|
||||||
|
|
||||||
```
|
|
||||||
npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
## LICENSE
|
## LICENSE
|
||||||
|
|
||||||
This project is under the GPL V3 LICENSE.
|
This project is under the GPL V3 LICENSE.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sub-store",
|
"name": "sub-store",
|
||||||
"version": "2.14.48",
|
"version": "2.14.87",
|
||||||
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
|
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
|
||||||
"main": "src/main.js",
|
"main": "src/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ export const SCHEMA_VERSION_KEY = 'schemaVersion';
|
|||||||
export const SETTINGS_KEY = 'settings';
|
export const SETTINGS_KEY = 'settings';
|
||||||
export const SUBS_KEY = 'subs';
|
export const SUBS_KEY = 'subs';
|
||||||
export const COLLECTIONS_KEY = 'collections';
|
export const COLLECTIONS_KEY = 'collections';
|
||||||
|
export const FILES_KEY = 'files';
|
||||||
|
export const MODULES_KEY = 'modules';
|
||||||
export const ARTIFACTS_KEY = 'artifacts';
|
export const ARTIFACTS_KEY = 'artifacts';
|
||||||
export const RULES_KEY = 'rules';
|
export const RULES_KEY = 'rules';
|
||||||
export const GIST_BACKUP_KEY = 'Auto Generated Sub-Store Backup';
|
export const GIST_BACKUP_KEY = 'Auto Generated Sub-Store Backup';
|
||||||
|
|||||||
@@ -63,11 +63,11 @@ function parse(raw) {
|
|||||||
return proxies;
|
return proxies;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function process(proxies, operators = [], targetPlatform) {
|
async function process(proxies, operators = [], targetPlatform, source) {
|
||||||
for (const item of operators) {
|
for (const item of operators) {
|
||||||
// process script
|
// process script
|
||||||
let script;
|
let script;
|
||||||
const $arguments = {};
|
let $arguments = {};
|
||||||
if (item.type.indexOf('Script') !== -1) {
|
if (item.type.indexOf('Script') !== -1) {
|
||||||
const { mode, content } = item.args;
|
const { mode, content } = item.args;
|
||||||
if (mode === 'link') {
|
if (mode === 'link') {
|
||||||
@@ -75,10 +75,19 @@ async function process(proxies, operators = [], targetPlatform) {
|
|||||||
// extract link arguments
|
// extract link arguments
|
||||||
const rawArgs = url.split('#');
|
const rawArgs = url.split('#');
|
||||||
if (rawArgs.length > 1) {
|
if (rawArgs.length > 1) {
|
||||||
for (const pair of rawArgs[1].split('&')) {
|
try {
|
||||||
const key = pair.split('=')[0];
|
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
|
||||||
const value = pair.split('=')[1] || true;
|
$arguments = JSON.parse(decodeURIComponent(rawArgs[1]));
|
||||||
$arguments[key] = value;
|
} catch (e) {
|
||||||
|
for (const pair of rawArgs[1].split('&')) {
|
||||||
|
const key = pair.split('=')[0];
|
||||||
|
const value = pair.split('=')[1];
|
||||||
|
// 部分兼容之前的逻辑 const value = pair.split('=')[1] || true;
|
||||||
|
$arguments[key] =
|
||||||
|
value == null || value === ''
|
||||||
|
? true
|
||||||
|
: decodeURIComponent(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,6 +122,7 @@ async function process(proxies, operators = [], targetPlatform) {
|
|||||||
script,
|
script,
|
||||||
targetPlatform,
|
targetPlatform,
|
||||||
$arguments,
|
$arguments,
|
||||||
|
source,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
processor = PROXY_PROCESSORS[item.type](item.args || {});
|
processor = PROXY_PROCESSORS[item.type](item.args || {});
|
||||||
@@ -199,9 +209,17 @@ function lastParse(proxy) {
|
|||||||
delete proxy.network;
|
delete proxy.network;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (['trojan', 'tuic', 'hysteria'].includes(proxy.type)) {
|
if (['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(proxy.type)) {
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
}
|
}
|
||||||
|
if (proxy.network) {
|
||||||
|
let transportHost = proxy[`${proxy.network}-opts`]?.headers?.Host;
|
||||||
|
let transporthost = proxy[`${proxy.network}-opts`]?.headers?.host;
|
||||||
|
if (transporthost && !transportHost) {
|
||||||
|
proxy[`${proxy.network}-opts`].headers.Host = transporthost;
|
||||||
|
delete proxy[`${proxy.network}-opts`].headers.host;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (proxy.tls && !proxy.sni) {
|
if (proxy.tls && !proxy.sni) {
|
||||||
if (proxy.network) {
|
if (proxy.network) {
|
||||||
let transportHost = proxy[`${proxy.network}-opts`]?.headers?.Host;
|
let transportHost = proxy[`${proxy.network}-opts`]?.headers?.Host;
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ function URI_VMess() {
|
|||||||
params.add = server;
|
params.add = server;
|
||||||
}
|
}
|
||||||
const proxy = {
|
const proxy = {
|
||||||
name: params.ps ?? params.remark,
|
name: params.ps ?? params.remarks,
|
||||||
type: 'vmess',
|
type: 'vmess',
|
||||||
server: params.add,
|
server: params.add,
|
||||||
port: parseInt(getIfPresent(params.port), 10),
|
port: parseInt(getIfPresent(params.port), 10),
|
||||||
@@ -267,9 +267,19 @@ function URI_VMess() {
|
|||||||
params.obfs === 'http'
|
params.obfs === 'http'
|
||||||
) {
|
) {
|
||||||
proxy.network = 'http';
|
proxy.network = 'http';
|
||||||
|
} else if (['grpc'].includes(params.net)) {
|
||||||
|
proxy.network = 'grpc';
|
||||||
}
|
}
|
||||||
if (proxy.network) {
|
if (proxy.network) {
|
||||||
let transportHost = params.host ?? params.obfsParam;
|
let transportHost = params.host ?? params.obfsParam;
|
||||||
|
try {
|
||||||
|
const parsedObfs = JSON.parse(transportHost);
|
||||||
|
const parsedHost = parsedObfs?.Host;
|
||||||
|
if (parsedHost) {
|
||||||
|
transportHost = parsedHost;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-empty
|
||||||
|
} catch (e) {}
|
||||||
let transportPath = params.path;
|
let transportPath = params.path;
|
||||||
|
|
||||||
if (proxy.network === 'http') {
|
if (proxy.network === 'http') {
|
||||||
@@ -285,10 +295,17 @@ function URI_VMess() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (transportPath || transportHost) {
|
if (transportPath || transportHost) {
|
||||||
proxy[`${proxy.network}-opts`] = {
|
if (['grpc'].includes(proxy.network)) {
|
||||||
path: getIfNotBlank(transportPath),
|
proxy[`${proxy.network}-opts`] = {
|
||||||
headers: { Host: getIfNotBlank(transportHost) },
|
'grpc-service-name': getIfNotBlank(transportPath),
|
||||||
};
|
'_grpc-type': getIfNotBlank(params.type),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
proxy[`${proxy.network}-opts`] = {
|
||||||
|
path: getIfNotBlank(transportPath),
|
||||||
|
headers: { Host: getIfNotBlank(transportHost) },
|
||||||
|
};
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
delete proxy.network;
|
delete proxy.network;
|
||||||
}
|
}
|
||||||
@@ -365,6 +382,10 @@ function URI_VLESS() {
|
|||||||
if (params.serviceName) {
|
if (params.serviceName) {
|
||||||
opts[`${proxy.network}-service-name`] = params.serviceName;
|
opts[`${proxy.network}-service-name`] = params.serviceName;
|
||||||
}
|
}
|
||||||
|
// https://github.com/XTLS/Xray-core/issues/91
|
||||||
|
if (['grpc'].includes(proxy.network)) {
|
||||||
|
opts['_grpc-type'] = params.mode || 'gun';
|
||||||
|
}
|
||||||
if (Object.keys(opts).length > 0) {
|
if (Object.keys(opts).length > 0) {
|
||||||
proxy[`${proxy.network}-opts`] = opts;
|
proxy[`${proxy.network}-opts`] = opts;
|
||||||
}
|
}
|
||||||
@@ -383,6 +404,56 @@ function URI_VLESS() {
|
|||||||
};
|
};
|
||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
}
|
}
|
||||||
|
function URI_Hysteria2() {
|
||||||
|
const name = 'URI Hysteria2 Parser';
|
||||||
|
const test = (line) => {
|
||||||
|
return /^hysteria2:\/\//.test(line);
|
||||||
|
};
|
||||||
|
const parse = (line) => {
|
||||||
|
line = line.split('hysteria2://')[1];
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
let [__, password, server, ___, port, addons, name] =
|
||||||
|
/^(.*?)@(.*?)(:(\d+))?\/?\?(.*?)(?:#(.*?))$/.exec(line);
|
||||||
|
port = parseInt(`${port}`, 10);
|
||||||
|
if (isNaN(port)) {
|
||||||
|
port = 443;
|
||||||
|
}
|
||||||
|
password = decodeURIComponent(password);
|
||||||
|
name = decodeURIComponent(name) ?? `Hysteria2 ${server}:${port}`;
|
||||||
|
|
||||||
|
const proxy = {
|
||||||
|
type: 'hysteria2',
|
||||||
|
name,
|
||||||
|
server,
|
||||||
|
port,
|
||||||
|
password,
|
||||||
|
};
|
||||||
|
|
||||||
|
const params = {};
|
||||||
|
for (const addon of addons.split('&')) {
|
||||||
|
const [key, valueRaw] = addon.split('=');
|
||||||
|
let value = valueRaw;
|
||||||
|
value = decodeURIComponent(valueRaw);
|
||||||
|
params[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy.sni = params.sni;
|
||||||
|
if (!proxy.sni && params.peer) {
|
||||||
|
proxy.sni = params.peer;
|
||||||
|
}
|
||||||
|
if (params.obfs && params.obfs !== 'none') {
|
||||||
|
proxy.obfs = params.obfs;
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy['obfs-password'] = params['obfs-password'];
|
||||||
|
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(params.insecure);
|
||||||
|
proxy.tfo = /(TRUE)|1/i.test(params.fastopen);
|
||||||
|
proxy['tls-fingerprint'] = params.pinSHA256;
|
||||||
|
|
||||||
|
return proxy;
|
||||||
|
};
|
||||||
|
return { name, test, parse };
|
||||||
|
}
|
||||||
|
|
||||||
// Trojan URI format
|
// Trojan URI format
|
||||||
function URI_Trojan() {
|
function URI_Trojan() {
|
||||||
@@ -392,8 +463,16 @@ function URI_Trojan() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const parse = (line) => {
|
const parse = (line) => {
|
||||||
|
let [newLine, name] = line.split(/#(.+)/, 2);
|
||||||
const parser = getTrojanURIParser();
|
const parser = getTrojanURIParser();
|
||||||
const proxy = parser.parse(line);
|
const proxy = parser.parse(newLine);
|
||||||
|
if (isNotBlank(name)) {
|
||||||
|
try {
|
||||||
|
proxy.name = decodeURIComponent(name);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
return proxy;
|
return proxy;
|
||||||
};
|
};
|
||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
@@ -416,13 +495,14 @@ function Clash_All() {
|
|||||||
'ss',
|
'ss',
|
||||||
'ssr',
|
'ssr',
|
||||||
'vmess',
|
'vmess',
|
||||||
'socks',
|
'socks5',
|
||||||
'http',
|
'http',
|
||||||
'snell',
|
'snell',
|
||||||
'trojan',
|
'trojan',
|
||||||
'tuic',
|
'tuic',
|
||||||
'vless',
|
'vless',
|
||||||
'hysteria',
|
'hysteria',
|
||||||
|
'hysteria2',
|
||||||
'wireguard',
|
'wireguard',
|
||||||
].includes(proxy.type)
|
].includes(proxy.type)
|
||||||
) {
|
) {
|
||||||
@@ -447,6 +527,10 @@ function Clash_All() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (proxy['benchmark-url']) {
|
||||||
|
proxy['test-url'] = proxy['benchmark-url'];
|
||||||
|
}
|
||||||
|
|
||||||
return proxy;
|
return proxy;
|
||||||
};
|
};
|
||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
@@ -570,6 +654,15 @@ function Loon_Trojan() {
|
|||||||
const parse = (line) => getLoonParser().parse(line);
|
const parse = (line) => getLoonParser().parse(line);
|
||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
}
|
}
|
||||||
|
function Loon_Hysteria2() {
|
||||||
|
const name = 'Loon Hysteria2 Parser';
|
||||||
|
const test = (line) => {
|
||||||
|
return /^.*=\s*Hysteria2/i.test(line.split(',')[0]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const parse = (line) => getLoonParser().parse(line);
|
||||||
|
return { name, test, parse };
|
||||||
|
}
|
||||||
|
|
||||||
function Loon_Http() {
|
function Loon_Http() {
|
||||||
const name = 'Loon HTTP Parser';
|
const name = 'Loon HTTP Parser';
|
||||||
@@ -762,11 +855,21 @@ function Surge_WireGuard() {
|
|||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Surge_Hysteria2() {
|
||||||
|
const name = 'Surge Hysteria2 Parser';
|
||||||
|
const test = (line) => {
|
||||||
|
return /^.*=\s*hysteria2/.test(line.split(',')[0]);
|
||||||
|
};
|
||||||
|
const parse = (line) => getSurgeParser().parse(line);
|
||||||
|
return { name, test, parse };
|
||||||
|
}
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
URI_SS(),
|
URI_SS(),
|
||||||
URI_SSR(),
|
URI_SSR(),
|
||||||
URI_VMess(),
|
URI_VMess(),
|
||||||
URI_VLESS(),
|
URI_VLESS(),
|
||||||
|
URI_Hysteria2(),
|
||||||
URI_Trojan(),
|
URI_Trojan(),
|
||||||
Clash_All(),
|
Clash_All(),
|
||||||
Surge_SS(),
|
Surge_SS(),
|
||||||
@@ -776,11 +879,13 @@ export default [
|
|||||||
Surge_Snell(),
|
Surge_Snell(),
|
||||||
Surge_Tuic(),
|
Surge_Tuic(),
|
||||||
Surge_WireGuard(),
|
Surge_WireGuard(),
|
||||||
|
Surge_Hysteria2(),
|
||||||
Surge_Socks5(),
|
Surge_Socks5(),
|
||||||
Loon_SS(),
|
Loon_SS(),
|
||||||
Loon_SSR(),
|
Loon_SSR(),
|
||||||
Loon_VMess(),
|
Loon_VMess(),
|
||||||
Loon_Vless(),
|
Loon_Vless(),
|
||||||
|
Loon_Hysteria2(),
|
||||||
Loon_Trojan(),
|
Loon_Trojan(),
|
||||||
Loon_Http(),
|
Loon_Http(),
|
||||||
Loon_WireGuard(),
|
Loon_WireGuard(),
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const grammars = String.raw`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http) {
|
start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/hysteria2) {
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +68,9 @@ trojan = tag equals "trojan"i address password (transport/transport_host/transpo
|
|||||||
proxy.type = "trojan";
|
proxy.type = "trojan";
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
|
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/udp_relay/download_bandwidth/ecn/others)* {
|
||||||
|
proxy.type = "hysteria2";
|
||||||
|
}
|
||||||
https = tag equals "https"i address (username password)? (tls_host/tls_verification/fast_open/udp_relay/others)* {
|
https = tag equals "https"i address (username password)? (tls_host/tls_verification/fast_open/udp_relay/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
@@ -167,6 +170,9 @@ tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-
|
|||||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||||
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
||||||
|
|
||||||
|
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
||||||
|
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
||||||
|
|
||||||
tag = match:[^=,]* { proxy.name = match.join("").trim(); }
|
tag = match:[^=,]* { proxy.name = match.join("").trim(); }
|
||||||
comma = _ "," _
|
comma = _ "," _
|
||||||
equals = _ "=" _
|
equals = _ "=" _
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http) {
|
start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/hysteria2) {
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +66,9 @@ trojan = tag equals "trojan"i address password (transport/transport_host/transpo
|
|||||||
proxy.type = "trojan";
|
proxy.type = "trojan";
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
|
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/udp_relay/download_bandwidth/ecn/others)* {
|
||||||
|
proxy.type = "hysteria2";
|
||||||
|
}
|
||||||
https = tag equals "https"i address (username password)? (tls_host/tls_verification/fast_open/udp_relay/others)* {
|
https = tag equals "https"i address (username password)? (tls_host/tls_verification/fast_open/udp_relay/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
@@ -165,6 +168,9 @@ tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-
|
|||||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||||
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
||||||
|
|
||||||
|
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
||||||
|
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
||||||
|
|
||||||
tag = match:[^=,]* { proxy.name = match.join("").trim(); }
|
tag = match:[^=,]* { proxy.name = match.join("").trim(); }
|
||||||
comma = _ "," _
|
comma = _ "," _
|
||||||
equals = _ "=" _
|
equals = _ "=" _
|
||||||
|
|||||||
@@ -43,13 +43,13 @@ start = (trojan/shadowsocks/vmess/http/socks5) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
trojan = "trojan" equals address
|
trojan = "trojan" equals address
|
||||||
(password/over_tls/tls_host/tls_fingerprint/tls_verification/obfs/obfs_host/obfs_uri/tag/udp_relay/udp_over_tcp/fast_open/others)* {
|
(password/over_tls/tls_host/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/obfs/obfs_host/obfs_uri/tag/udp_relay/udp_over_tcp/fast_open/server_check_url/others)* {
|
||||||
proxy.type = "trojan";
|
proxy.type = "trojan";
|
||||||
handleObfs();
|
handleObfs();
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowsocks = "shadowsocks" equals address
|
shadowsocks = "shadowsocks" equals address
|
||||||
(password/method/obfs_ssr/obfs_ss/obfs_host/obfs_uri/ssr_protocol/ssr_protocol_param/tls_fingerprint/tls_verification/udp_relay/udp_over_tcp/fast_open/tag/others)* {
|
(password/method/obfs_ssr/obfs_ss/obfs_host/obfs_uri/ssr_protocol/ssr_protocol_param/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/udp_relay/udp_over_tcp/fast_open/tag/server_check_url/others)* {
|
||||||
if (proxy.protocol) {
|
if (proxy.protocol) {
|
||||||
proxy.type = "ssr";
|
proxy.type = "ssr";
|
||||||
// handle ssr obfs
|
// handle ssr obfs
|
||||||
@@ -80,7 +80,7 @@ shadowsocks = "shadowsocks" equals address
|
|||||||
}
|
}
|
||||||
|
|
||||||
vmess = "vmess" equals address
|
vmess = "vmess" equals address
|
||||||
(uuid/method/over_tls/tls_host/tls_fingerprint/tls_verification/tag/obfs/obfs_host/obfs_uri/udp_relay/udp_over_tcp/fast_open/aead/others)* {
|
(uuid/method/over_tls/tls_host/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/tag/obfs/obfs_host/obfs_uri/udp_relay/udp_over_tcp/fast_open/aead/server_check_url/others)* {
|
||||||
proxy.type = "vmess";
|
proxy.type = "vmess";
|
||||||
proxy.cipher = proxy.cipher || "none";
|
proxy.cipher = proxy.cipher || "none";
|
||||||
if (proxy.aead) {
|
if (proxy.aead) {
|
||||||
@@ -92,12 +92,12 @@ vmess = "vmess" equals address
|
|||||||
}
|
}
|
||||||
|
|
||||||
http = "http" equals address
|
http = "http" equals address
|
||||||
(username/password/over_tls/tls_host/tls_fingerprint/tls_verification/tag/fast_open/udp_relay/udp_over_tcp/others)*{
|
(username/password/over_tls/tls_host/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/tag/fast_open/udp_relay/udp_over_tcp/server_check_url/others)*{
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
}
|
}
|
||||||
|
|
||||||
socks5 = "socks5" equals address
|
socks5 = "socks5" equals address
|
||||||
(username/password/password/over_tls/tls_host/tls_fingerprint/tls_verification/tag/fast_open/udp_relay/udp_over_tcp/others)* {
|
(username/password/password/over_tls/tls_host/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/tag/fast_open/udp_relay/udp_over_tcp/server_check_url/others)* {
|
||||||
proxy.type = "socks5";
|
proxy.type = "socks5";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +155,14 @@ tls_verification = comma "tls-verification" equals flag:bool {
|
|||||||
proxy["skip-cert-verify"] = !flag;
|
proxy["skip-cert-verify"] = !flag;
|
||||||
}
|
}
|
||||||
tls_fingerprint = comma "tls-cert-sha256" equals tls_fingerprint:$[^,]+ { proxy["tls-fingerprint"] = tls_fingerprint.trim(); }
|
tls_fingerprint = comma "tls-cert-sha256" equals tls_fingerprint:$[^,]+ { proxy["tls-fingerprint"] = tls_fingerprint.trim(); }
|
||||||
|
tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals param:$[^=,]+ { proxy["tls-pubkey-sha256"] = param; }
|
||||||
|
tls_alpn = comma "tls-alpn" equals param:$[^=,]+ { proxy["tls-alpn"] = param; }
|
||||||
|
tls_no_session_ticket = comma "tls-no-session-ticket" equals flag:bool {
|
||||||
|
proxy["tls-no-session-ticket"] = flag;
|
||||||
|
}
|
||||||
|
tls_no_session_reuse = comma "tls-no-session-reuse" equals flag:bool {
|
||||||
|
proxy["tls-no-session-reuse"] = flag;
|
||||||
|
}
|
||||||
|
|
||||||
obfs_ss = comma "obfs" equals type:("http"/"tls"/"wss"/"ws"/"over-tls") { obfs.type = type; return type; }
|
obfs_ss = comma "obfs" equals type:("http"/"tls"/"wss"/"ws"/"over-tls") { obfs.type = type; return type; }
|
||||||
obfs_ssr = comma "obfs" equals type:("plain"/"http_simple"/"http_post"/"random_head"/"tls1.2_ticket_auth"/"tls1.2_ticket_fastauth") { obfs.type = type; return type; }
|
obfs_ssr = comma "obfs" equals type:("plain"/"http_simple"/"http_post"/"random_head"/"tls1.2_ticket_auth"/"tls1.2_ticket_fastauth") { obfs.type = type; return type; }
|
||||||
@@ -166,6 +174,8 @@ obfs_uri = comma "obfs-uri" equals uri:uri { obfs.path = uri; }
|
|||||||
ssr_protocol = comma "ssr-protocol" equals protocol:("origin"/"auth_sha1_v4"/"auth_aes128_md5"/"auth_aes128_sha1"/"auth_chain_a"/"auth_chain_b") { proxy.protocol = protocol; return protocol; }
|
ssr_protocol = comma "ssr-protocol" equals protocol:("origin"/"auth_sha1_v4"/"auth_aes128_md5"/"auth_aes128_sha1"/"auth_chain_a"/"auth_chain_b") { proxy.protocol = protocol; return protocol; }
|
||||||
ssr_protocol_param = comma "ssr-protocol-param" equals param:$[^=,]+ { proxy["protocol-param"] = param; }
|
ssr_protocol_param = comma "ssr-protocol-param" equals param:$[^=,]+ { proxy["protocol-param"] = param; }
|
||||||
|
|
||||||
|
server_check_url = comma "server_check_url" equals param:$[^=,]+ { proxy["test-url"] = param; }
|
||||||
|
|
||||||
uri = $[^,]+
|
uri = $[^,]+
|
||||||
|
|
||||||
tag = comma "tag" equals tag:[^=,]+ { proxy.name = tag.join(""); }
|
tag = comma "tag" equals tag:[^=,]+ { proxy.name = tag.join(""); }
|
||||||
|
|||||||
@@ -41,13 +41,13 @@ start = (trojan/shadowsocks/vmess/http/socks5) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
trojan = "trojan" equals address
|
trojan = "trojan" equals address
|
||||||
(password/over_tls/tls_host/tls_fingerprint/tls_verification/obfs/obfs_host/obfs_uri/tag/udp_relay/udp_over_tcp/fast_open/others)* {
|
(password/over_tls/tls_host/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/obfs/obfs_host/obfs_uri/tag/udp_relay/udp_over_tcp/fast_open/server_check_url/others)* {
|
||||||
proxy.type = "trojan";
|
proxy.type = "trojan";
|
||||||
handleObfs();
|
handleObfs();
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowsocks = "shadowsocks" equals address
|
shadowsocks = "shadowsocks" equals address
|
||||||
(password/method/obfs_ssr/obfs_ss/obfs_host/obfs_uri/ssr_protocol/ssr_protocol_param/tls_fingerprint/tls_verification/udp_relay/udp_over_tcp/fast_open/tag/others)* {
|
(password/method/obfs_ssr/obfs_ss/obfs_host/obfs_uri/ssr_protocol/ssr_protocol_param/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/udp_relay/udp_over_tcp/fast_open/tag/server_check_url/others)* {
|
||||||
if (proxy.protocol) {
|
if (proxy.protocol) {
|
||||||
proxy.type = "ssr";
|
proxy.type = "ssr";
|
||||||
// handle ssr obfs
|
// handle ssr obfs
|
||||||
@@ -78,7 +78,7 @@ shadowsocks = "shadowsocks" equals address
|
|||||||
}
|
}
|
||||||
|
|
||||||
vmess = "vmess" equals address
|
vmess = "vmess" equals address
|
||||||
(uuid/method/over_tls/tls_host/tls_fingerprint/tls_verification/tag/obfs/obfs_host/obfs_uri/udp_relay/udp_over_tcp/fast_open/aead/others)* {
|
(uuid/method/over_tls/tls_host/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/tag/obfs/obfs_host/obfs_uri/udp_relay/udp_over_tcp/fast_open/aead/server_check_url/others)* {
|
||||||
proxy.type = "vmess";
|
proxy.type = "vmess";
|
||||||
proxy.cipher = proxy.cipher || "none";
|
proxy.cipher = proxy.cipher || "none";
|
||||||
if (proxy.aead) {
|
if (proxy.aead) {
|
||||||
@@ -90,12 +90,12 @@ vmess = "vmess" equals address
|
|||||||
}
|
}
|
||||||
|
|
||||||
http = "http" equals address
|
http = "http" equals address
|
||||||
(username/password/over_tls/tls_host/tls_fingerprint/tls_verification/tag/fast_open/udp_relay/udp_over_tcp/others)*{
|
(username/password/over_tls/tls_host/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/tag/fast_open/udp_relay/udp_over_tcp/server_check_url/others)*{
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
}
|
}
|
||||||
|
|
||||||
socks5 = "socks5" equals address
|
socks5 = "socks5" equals address
|
||||||
(username/password/password/over_tls/tls_host/tls_fingerprint/tls_verification/tag/fast_open/udp_relay/udp_over_tcp/others)* {
|
(username/password/password/over_tls/tls_host/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/tag/fast_open/udp_relay/udp_over_tcp/server_check_url/others)* {
|
||||||
proxy.type = "socks5";
|
proxy.type = "socks5";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +153,14 @@ tls_verification = comma "tls-verification" equals flag:bool {
|
|||||||
proxy["skip-cert-verify"] = !flag;
|
proxy["skip-cert-verify"] = !flag;
|
||||||
}
|
}
|
||||||
tls_fingerprint = comma "tls-cert-sha256" equals tls_fingerprint:$[^,]+ { proxy["tls-fingerprint"] = tls_fingerprint.trim(); }
|
tls_fingerprint = comma "tls-cert-sha256" equals tls_fingerprint:$[^,]+ { proxy["tls-fingerprint"] = tls_fingerprint.trim(); }
|
||||||
|
tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals param:$[^=,]+ { proxy["tls-pubkey-sha256"] = param; }
|
||||||
|
tls_alpn = comma "tls-alpn" equals param:$[^=,]+ { proxy["tls-alpn"] = param; }
|
||||||
|
tls_no_session_ticket = comma "tls-no-session-ticket" equals flag:bool {
|
||||||
|
proxy["tls-no-session-ticket"] = flag;
|
||||||
|
}
|
||||||
|
tls_no_session_reuse = comma "tls-no-session-reuse" equals flag:bool {
|
||||||
|
proxy["tls-no-session-reuse"] = flag;
|
||||||
|
}
|
||||||
|
|
||||||
obfs_ss = comma "obfs" equals type:("http"/"tls"/"wss"/"ws"/"over-tls") { obfs.type = type; return type; }
|
obfs_ss = comma "obfs" equals type:("http"/"tls"/"wss"/"ws"/"over-tls") { obfs.type = type; return type; }
|
||||||
obfs_ssr = comma "obfs" equals type:("plain"/"http_simple"/"http_post"/"random_head"/"tls1.2_ticket_auth"/"tls1.2_ticket_fastauth") { obfs.type = type; return type; }
|
obfs_ssr = comma "obfs" equals type:("plain"/"http_simple"/"http_post"/"random_head"/"tls1.2_ticket_auth"/"tls1.2_ticket_fastauth") { obfs.type = type; return type; }
|
||||||
@@ -164,6 +172,8 @@ obfs_uri = comma "obfs-uri" equals uri:uri { obfs.path = uri; }
|
|||||||
ssr_protocol = comma "ssr-protocol" equals protocol:("origin"/"auth_sha1_v4"/"auth_aes128_md5"/"auth_aes128_sha1"/"auth_chain_a"/"auth_chain_b") { proxy.protocol = protocol; return protocol; }
|
ssr_protocol = comma "ssr-protocol" equals protocol:("origin"/"auth_sha1_v4"/"auth_aes128_md5"/"auth_aes128_sha1"/"auth_chain_a"/"auth_chain_b") { proxy.protocol = protocol; return protocol; }
|
||||||
ssr_protocol_param = comma "ssr-protocol-param" equals param:$[^=,]+ { proxy["protocol-param"] = param; }
|
ssr_protocol_param = comma "ssr-protocol-param" equals param:$[^=,]+ { proxy["protocol-param"] = param; }
|
||||||
|
|
||||||
|
server_check_url = comma "server_check_url" equals param:$[^=,]+ { proxy["test-url"] = param; }
|
||||||
|
|
||||||
uri = $[^,]+
|
uri = $[^,]+
|
||||||
|
|
||||||
tag = comma "tag" equals tag:[^=,]+ { proxy.name = tag.join(""); }
|
tag = comma "tag" equals tag:[^=,]+ { proxy.name = tag.join(""); }
|
||||||
|
|||||||
@@ -32,11 +32,11 @@ const grammars = String.raw`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v5/wireguard) {
|
start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v5/wireguard/hysteria2) {
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/fast_open/udp_relay/others)* {
|
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "ss";
|
proxy.type = "ss";
|
||||||
// handle obfs
|
// handle obfs
|
||||||
if (obfs.type == "http" || obfs.type === "tls") {
|
if (obfs.type == "http" || obfs.type === "tls") {
|
||||||
@@ -46,7 +46,7 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
|
|||||||
$set(proxy, "plugin-opts.path", obfs.path);
|
$set(proxy, "plugin-opts.path", obfs.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/others)* {
|
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "vmess";
|
proxy.type = "vmess";
|
||||||
proxy.cipher = proxy.cipher || "none";
|
proxy.cipher = proxy.cipher || "none";
|
||||||
if (proxy.aead) {
|
if (proxy.aead) {
|
||||||
@@ -56,18 +56,18 @@ vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/
|
|||||||
}
|
}
|
||||||
handleWebsocket();
|
handleWebsocket();
|
||||||
}
|
}
|
||||||
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/others)* {
|
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "trojan";
|
proxy.type = "trojan";
|
||||||
handleWebsocket();
|
handleWebsocket();
|
||||||
}
|
}
|
||||||
https = tag equals "https" address (username password)? (sni/tls_fingerprint/tls_verification/fast_open/others)* {
|
https = tag equals "https" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
}
|
}
|
||||||
http = tag equals "http" address (username password)? (fast_open/others)* {
|
http = tag equals "http" address (username password)? (usernamek passwordk)? (fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
}
|
}
|
||||||
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/fast_open/udp_relay/others)* {
|
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/fast_open/udp_relay/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "snell";
|
proxy.type = "snell";
|
||||||
// handle obfs
|
// handle obfs
|
||||||
if (obfs.type == "http" || obfs.type === "tls") {
|
if (obfs.type == "http" || obfs.type === "tls") {
|
||||||
@@ -76,20 +76,23 @@ snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_
|
|||||||
$set(proxy, "obfs-opts.path", obfs.path);
|
$set(proxy, "obfs-opts.path", obfs.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tuic = tag equals "tuic" address (alpn/token/ip_version/tls_verification/sni/fast_open/tfo/others)* {
|
tuic = tag equals "tuic" address (alpn/token/ip_version/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "tuic";
|
proxy.type = "tuic";
|
||||||
}
|
}
|
||||||
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/tls_verification/sni/fast_open/tfo/others)* {
|
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "tuic";
|
proxy.type = "tuic";
|
||||||
proxy.version = 5;
|
proxy.version = 5;
|
||||||
}
|
}
|
||||||
wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/underlying_proxy/test_url/others)* {
|
wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/underlying_proxy/test_url/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "wireguard-surge";
|
proxy.type = "wireguard-surge";
|
||||||
}
|
}
|
||||||
socks5 = tag equals "socks5" address (username password)? (fast_open/others)* {
|
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/test_url/sni/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
|
proxy.type = "hysteria2";
|
||||||
|
}
|
||||||
|
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "socks5";
|
proxy.type = "socks5";
|
||||||
}
|
}
|
||||||
socks5_tls = tag equals "socks5-tls" address (username password)? (sni/tls_fingerprint/tls_verification/fast_open/others)* {
|
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "socks5";
|
proxy.type = "socks5";
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
}
|
}
|
||||||
@@ -160,6 +163,7 @@ tls_fingerprint = comma "server-cert-fingerprint-sha256" equals tls_fingerprint:
|
|||||||
snell_psk = comma "psk" equals match:[^,]+ { proxy.psk = match.join(""); }
|
snell_psk = comma "psk" equals match:[^,]+ { proxy.psk = match.join(""); }
|
||||||
snell_version = comma "version" equals match:$[0-9]+ { proxy.version = parseInt(match.trim()); }
|
snell_version = comma "version" equals match:$[0-9]+ { proxy.version = parseInt(match.trim()); }
|
||||||
|
|
||||||
|
usernamek = comma "username" equals match:[^,]+ { proxy.username = match.join(""); }
|
||||||
passwordk = comma "password" equals match:[^,]+ { proxy.password = match.join(""); }
|
passwordk = comma "password" equals match:[^,]+ { proxy.password = match.join(""); }
|
||||||
vmess_uuid = comma "username" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
vmess_uuid = comma "username" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
||||||
vmess_aead = comma "vmess-aead" equals flag:bool { proxy.aead = flag; }
|
vmess_aead = comma "vmess-aead" equals flag:bool { proxy.aead = flag; }
|
||||||
@@ -188,12 +192,19 @@ uri = $[^,]+
|
|||||||
|
|
||||||
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
||||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||||
|
reuse = comma "reuse" equals flag:bool { proxy.reuse = flag; }
|
||||||
|
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
||||||
tfo = comma "tfo" equals flag:bool { proxy.tfo = flag; }
|
tfo = comma "tfo" equals flag:bool { proxy.tfo = flag; }
|
||||||
ip_version = comma "ip-version" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }
|
ip_version = comma "ip-version" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }
|
||||||
section_name = comma "section-name" equals match:[^,]+ { proxy["section-name"] = match.join(""); }
|
section_name = comma "section-name" equals match:[^,]+ { proxy["section-name"] = match.join(""); }
|
||||||
no_error_alert = comma "no-error-alert" equals match:[^,]+ { proxy["no-error-alert"] = match.join(""); }
|
no_error_alert = comma "no-error-alert" equals match:[^,]+ { proxy["no-error-alert"] = match.join(""); }
|
||||||
underlying_proxy = comma "underlying-proxy" equals match:[^,]+ { proxy["underlying-proxy"] = match.join(""); }
|
underlying_proxy = comma "underlying-proxy" equals match:[^,]+ { proxy["underlying-proxy"] = match.join(""); }
|
||||||
|
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
||||||
test_url = comma "test-url" equals match:[^,]+ { proxy["test-url"] = match.join(""); }
|
test_url = comma "test-url" equals match:[^,]+ { proxy["test-url"] = match.join(""); }
|
||||||
|
block_quic = comma "block-quic" equals match:[^,]+ { proxy["block-quic"] = match.join(""); }
|
||||||
|
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
|
||||||
|
shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-sni"] = match.join(""); }
|
||||||
|
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join(""); }
|
||||||
token = comma "token" equals match:[^,]+ { proxy.token = match.join(""); }
|
token = comma "token" equals match:[^,]+ { proxy.token = match.join(""); }
|
||||||
alpn = comma "alpn" equals match:[^,]+ { proxy.alpn = match.join(""); }
|
alpn = comma "alpn" equals match:[^,]+ { proxy.alpn = match.join(""); }
|
||||||
uuidk = comma "uuid" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
uuidk = comma "uuid" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
||||||
|
|||||||
@@ -30,11 +30,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v5/wireguard) {
|
start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v5/wireguard/hysteria2) {
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/fast_open/udp_relay/others)* {
|
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "ss";
|
proxy.type = "ss";
|
||||||
// handle obfs
|
// handle obfs
|
||||||
if (obfs.type == "http" || obfs.type === "tls") {
|
if (obfs.type == "http" || obfs.type === "tls") {
|
||||||
@@ -44,7 +44,7 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
|
|||||||
$set(proxy, "plugin-opts.path", obfs.path);
|
$set(proxy, "plugin-opts.path", obfs.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/others)* {
|
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "vmess";
|
proxy.type = "vmess";
|
||||||
proxy.cipher = proxy.cipher || "none";
|
proxy.cipher = proxy.cipher || "none";
|
||||||
if (proxy.aead) {
|
if (proxy.aead) {
|
||||||
@@ -54,18 +54,18 @@ vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/
|
|||||||
}
|
}
|
||||||
handleWebsocket();
|
handleWebsocket();
|
||||||
}
|
}
|
||||||
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/others)* {
|
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "trojan";
|
proxy.type = "trojan";
|
||||||
handleWebsocket();
|
handleWebsocket();
|
||||||
}
|
}
|
||||||
https = tag equals "https" address (username password)? (sni/tls_fingerprint/tls_verification/fast_open/others)* {
|
https = tag equals "https" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
}
|
}
|
||||||
http = tag equals "http" address (username password)? (fast_open/others)* {
|
http = tag equals "http" address (username password)? (usernamek passwordk)? (fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
}
|
}
|
||||||
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/fast_open/udp_relay/others)* {
|
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/fast_open/udp_relay/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "snell";
|
proxy.type = "snell";
|
||||||
// handle obfs
|
// handle obfs
|
||||||
if (obfs.type == "http" || obfs.type === "tls") {
|
if (obfs.type == "http" || obfs.type === "tls") {
|
||||||
@@ -74,20 +74,23 @@ snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_
|
|||||||
$set(proxy, "obfs-opts.path", obfs.path);
|
$set(proxy, "obfs-opts.path", obfs.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tuic = tag equals "tuic" address (alpn/token/ip_version/tls_verification/sni/fast_open/tfo/others)* {
|
tuic = tag equals "tuic" address (alpn/token/ip_version/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "tuic";
|
proxy.type = "tuic";
|
||||||
}
|
}
|
||||||
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/tls_verification/sni/fast_open/tfo/others)* {
|
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "tuic";
|
proxy.type = "tuic";
|
||||||
proxy.version = 5;
|
proxy.version = 5;
|
||||||
}
|
}
|
||||||
wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/underlying_proxy/test_url/others)* {
|
wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/underlying_proxy/test_url/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "wireguard-surge";
|
proxy.type = "wireguard-surge";
|
||||||
}
|
}
|
||||||
socks5 = tag equals "socks5" address (username password)? (fast_open/others)* {
|
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/test_url/sni/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
|
proxy.type = "hysteria2";
|
||||||
|
}
|
||||||
|
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "socks5";
|
proxy.type = "socks5";
|
||||||
}
|
}
|
||||||
socks5_tls = tag equals "socks5-tls" address (username password)? (sni/tls_fingerprint/tls_verification/fast_open/others)* {
|
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "socks5";
|
proxy.type = "socks5";
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
}
|
}
|
||||||
@@ -158,6 +161,7 @@ tls_fingerprint = comma "server-cert-fingerprint-sha256" equals tls_fingerprint:
|
|||||||
snell_psk = comma "psk" equals match:[^,]+ { proxy.psk = match.join(""); }
|
snell_psk = comma "psk" equals match:[^,]+ { proxy.psk = match.join(""); }
|
||||||
snell_version = comma "version" equals match:$[0-9]+ { proxy.version = parseInt(match.trim()); }
|
snell_version = comma "version" equals match:$[0-9]+ { proxy.version = parseInt(match.trim()); }
|
||||||
|
|
||||||
|
usernamek = comma "username" equals match:[^,]+ { proxy.username = match.join(""); }
|
||||||
passwordk = comma "password" equals match:[^,]+ { proxy.password = match.join(""); }
|
passwordk = comma "password" equals match:[^,]+ { proxy.password = match.join(""); }
|
||||||
vmess_uuid = comma "username" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
vmess_uuid = comma "username" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
||||||
vmess_aead = comma "vmess-aead" equals flag:bool { proxy.aead = flag; }
|
vmess_aead = comma "vmess-aead" equals flag:bool { proxy.aead = flag; }
|
||||||
@@ -186,12 +190,19 @@ uri = $[^,]+
|
|||||||
|
|
||||||
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
||||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||||
|
reuse = comma "reuse" equals flag:bool { proxy.reuse = flag; }
|
||||||
|
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
||||||
tfo = comma "tfo" equals flag:bool { proxy.tfo = flag; }
|
tfo = comma "tfo" equals flag:bool { proxy.tfo = flag; }
|
||||||
ip_version = comma "ip-version" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }
|
ip_version = comma "ip-version" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }
|
||||||
section_name = comma "section-name" equals match:[^,]+ { proxy["section-name"] = match.join(""); }
|
section_name = comma "section-name" equals match:[^,]+ { proxy["section-name"] = match.join(""); }
|
||||||
no_error_alert = comma "no-error-alert" equals match:[^,]+ { proxy["no-error-alert"] = match.join(""); }
|
no_error_alert = comma "no-error-alert" equals match:[^,]+ { proxy["no-error-alert"] = match.join(""); }
|
||||||
underlying_proxy = comma "underlying-proxy" equals match:[^,]+ { proxy["underlying-proxy"] = match.join(""); }
|
underlying_proxy = comma "underlying-proxy" equals match:[^,]+ { proxy["underlying-proxy"] = match.join(""); }
|
||||||
|
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
||||||
test_url = comma "test-url" equals match:[^,]+ { proxy["test-url"] = match.join(""); }
|
test_url = comma "test-url" equals match:[^,]+ { proxy["test-url"] = match.join(""); }
|
||||||
|
block_quic = comma "block-quic" equals match:[^,]+ { proxy["block-quic"] = match.join(""); }
|
||||||
|
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
|
||||||
|
shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-sni"] = match.join(""); }
|
||||||
|
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join(""); }
|
||||||
token = comma "token" equals match:[^,]+ { proxy.token = match.join(""); }
|
token = comma "token" equals match:[^,]+ { proxy.token = match.join(""); }
|
||||||
alpn = comma "alpn" equals match:[^,]+ { proxy.alpn = match.join(""); }
|
alpn = comma "alpn" equals match:[^,]+ { proxy.alpn = match.join(""); }
|
||||||
uuidk = comma "uuid" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
uuidk = comma "uuid" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
||||||
|
|||||||
@@ -13,13 +13,15 @@ function Base64Encoded() {
|
|||||||
const name = 'Base64 Pre-processor';
|
const name = 'Base64 Pre-processor';
|
||||||
|
|
||||||
const keys = [
|
const keys = [
|
||||||
'dm1lc3M',
|
'dm1lc3M', // vmess
|
||||||
'c3NyOi8v',
|
'c3NyOi8v', // ssr://
|
||||||
'dHJvamFu',
|
'dHJvamFu', // trojan
|
||||||
'c3M6Ly',
|
'c3M6Ly', // ss:/
|
||||||
'c3NkOi8v',
|
'c3NkOi8v', // ssd://
|
||||||
'c2hhZG93',
|
'c2hhZG93', // shadow
|
||||||
'aHR0c',
|
'aHR0c', // htt
|
||||||
|
'dmxlc3M=', // vless
|
||||||
|
'aHlzdGVyaWEy', // hysteria2
|
||||||
];
|
];
|
||||||
|
|
||||||
const test = function (raw) {
|
const test = function (raw) {
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import lodash from 'lodash';
|
|||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
import { hex_md5 } from '@/vendor/md5';
|
import { hex_md5 } from '@/vendor/md5';
|
||||||
import { ProxyUtils } from '@/core/proxy-utils';
|
import { ProxyUtils } from '@/core/proxy-utils';
|
||||||
|
import env from '@/utils/env';
|
||||||
|
import { getFlowHeaders, parseFlowHeaders, flowTransfer } from '@/utils/flow';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The rule "(name CONTAINS "🇨🇳") AND (port IN [80, 443])" can be expressed as follows:
|
The rule "(name CONTAINS "🇨🇳") AND (port IN [80, 443])" can be expressed as follows:
|
||||||
@@ -294,7 +296,7 @@ function RegexDeleteOperator(regex) {
|
|||||||
1. This function name should be `operator`!
|
1. This function name should be `operator`!
|
||||||
2. Always declare variables before using them!
|
2. Always declare variables before using them!
|
||||||
*/
|
*/
|
||||||
function ScriptOperator(script, targetPlatform, $arguments) {
|
function ScriptOperator(script, targetPlatform, $arguments, source) {
|
||||||
return {
|
return {
|
||||||
name: 'Script Operator',
|
name: 'Script Operator',
|
||||||
func: async (proxies) => {
|
func: async (proxies) => {
|
||||||
@@ -305,7 +307,7 @@ function ScriptOperator(script, targetPlatform, $arguments) {
|
|||||||
script,
|
script,
|
||||||
$arguments,
|
$arguments,
|
||||||
);
|
);
|
||||||
output = operator(proxies, targetPlatform);
|
output = operator(proxies, targetPlatform, { source, ...env });
|
||||||
})();
|
})();
|
||||||
return output;
|
return output;
|
||||||
},
|
},
|
||||||
@@ -562,7 +564,7 @@ function TypeFilter(types) {
|
|||||||
1. This function name should be `filter`!
|
1. This function name should be `filter`!
|
||||||
2. Always declare variables before using them!
|
2. Always declare variables before using them!
|
||||||
*/
|
*/
|
||||||
function ScriptFilter(script, targetPlatform, $arguments) {
|
function ScriptFilter(script, targetPlatform, $arguments, source) {
|
||||||
return {
|
return {
|
||||||
name: 'Script Filter',
|
name: 'Script Filter',
|
||||||
func: async (proxies) => {
|
func: async (proxies) => {
|
||||||
@@ -573,7 +575,7 @@ function ScriptFilter(script, targetPlatform, $arguments) {
|
|||||||
script,
|
script,
|
||||||
$arguments,
|
$arguments,
|
||||||
);
|
);
|
||||||
output = filter(proxies, targetPlatform);
|
output = filter(proxies, targetPlatform, { source, ...env });
|
||||||
})();
|
})();
|
||||||
return output;
|
return output;
|
||||||
},
|
},
|
||||||
@@ -667,6 +669,7 @@ function removeFlag(str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createDynamicFunction(name, script, $arguments) {
|
function createDynamicFunction(name, script, $arguments) {
|
||||||
|
const flowUtils = { getFlowHeaders, parseFlowHeaders, flowTransfer };
|
||||||
if ($.env.isLoon) {
|
if ($.env.isLoon) {
|
||||||
return new Function(
|
return new Function(
|
||||||
'$arguments',
|
'$arguments',
|
||||||
@@ -677,6 +680,7 @@ function createDynamicFunction(name, script, $arguments) {
|
|||||||
'$notification',
|
'$notification',
|
||||||
'ProxyUtils',
|
'ProxyUtils',
|
||||||
'scriptResourceCache',
|
'scriptResourceCache',
|
||||||
|
'flowUtils',
|
||||||
`${script}\n return ${name}`,
|
`${script}\n return ${name}`,
|
||||||
)(
|
)(
|
||||||
$arguments,
|
$arguments,
|
||||||
@@ -690,6 +694,7 @@ function createDynamicFunction(name, script, $arguments) {
|
|||||||
$notification,
|
$notification,
|
||||||
ProxyUtils,
|
ProxyUtils,
|
||||||
scriptResourceCache,
|
scriptResourceCache,
|
||||||
|
flowUtils,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return new Function(
|
return new Function(
|
||||||
@@ -698,7 +703,9 @@ function createDynamicFunction(name, script, $arguments) {
|
|||||||
'lodash',
|
'lodash',
|
||||||
'ProxyUtils',
|
'ProxyUtils',
|
||||||
'scriptResourceCache',
|
'scriptResourceCache',
|
||||||
|
'flowUtils',
|
||||||
|
|
||||||
`${script}\n return ${name}`,
|
`${script}\n return ${name}`,
|
||||||
)($arguments, $, lodash, ProxyUtils, scriptResourceCache);
|
)($arguments, $, lodash, ProxyUtils, scriptResourceCache, flowUtils);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import { isPresent } from '@/core/proxy-utils/producers/utils';
|
|||||||
export default function Clash_Producer() {
|
export default function Clash_Producer() {
|
||||||
const type = 'ALL';
|
const type = 'ALL';
|
||||||
const produce = (proxies) => {
|
const produce = (proxies) => {
|
||||||
|
// VLESS XTLS is not supported by Clash
|
||||||
|
// https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml#L532
|
||||||
|
// github.com/Dreamacro/clash/pull/2891/files
|
||||||
// filter unsupported proxies
|
// filter unsupported proxies
|
||||||
proxies = proxies.filter((proxy) => {
|
proxies = proxies.filter((proxy) => {
|
||||||
if (
|
if (
|
||||||
@@ -10,17 +13,17 @@ export default function Clash_Producer() {
|
|||||||
'ss',
|
'ss',
|
||||||
'ssr',
|
'ssr',
|
||||||
'vmess',
|
'vmess',
|
||||||
'socks',
|
'vless',
|
||||||
|
'socks5',
|
||||||
'http',
|
'http',
|
||||||
'snell',
|
'snell',
|
||||||
'trojan',
|
'trojan',
|
||||||
'wireguard',
|
'wireguard',
|
||||||
].includes(proxy.type)
|
].includes(proxy.type) ||
|
||||||
) {
|
(proxy.type === 'snell' && String(proxy.version) === '4') ||
|
||||||
return false;
|
(proxy.type === 'vless' &&
|
||||||
} else if (
|
(typeof proxy.flow !== 'undefined' ||
|
||||||
proxy.type === 'snell' &&
|
proxy['reality-opts']))
|
||||||
String(proxy.version) === '4'
|
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -87,10 +90,26 @@ export default function Clash_Producer() {
|
|||||||
proxy['http-opts'].headers.Host = [httpHost];
|
proxy['http-opts'].headers.Host = [httpHost];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (['trojan', 'tuic', 'hysteria'].includes(proxy.type)) {
|
if (
|
||||||
|
['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(
|
||||||
|
proxy.type,
|
||||||
|
)
|
||||||
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (proxy['tls-fingerprint']) {
|
||||||
|
proxy.fingerprint = proxy['tls-fingerprint'];
|
||||||
|
}
|
||||||
delete proxy['tls-fingerprint'];
|
delete proxy['tls-fingerprint'];
|
||||||
|
delete proxy.subName;
|
||||||
|
delete proxy.collectionName;
|
||||||
|
if (
|
||||||
|
['grpc'].includes(proxy.network) &&
|
||||||
|
proxy[`${proxy.network}-opts`]
|
||||||
|
) {
|
||||||
|
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
|
||||||
|
}
|
||||||
return ' - ' + JSON.stringify(proxy) + '\n';
|
return ' - ' + JSON.stringify(proxy) + '\n';
|
||||||
})
|
})
|
||||||
.join('')
|
.join('')
|
||||||
|
|||||||
@@ -63,6 +63,13 @@ export default function ClashMeta_Producer() {
|
|||||||
proxy.version = 5;
|
proxy.version = 5;
|
||||||
}
|
}
|
||||||
} else if (proxy.type === 'hysteria') {
|
} else if (proxy.type === 'hysteria') {
|
||||||
|
// auth_str 将会在未来某个时候删除 但是有的机场不规范
|
||||||
|
if (
|
||||||
|
isPresent(proxy, 'auth_str') &&
|
||||||
|
!isPresent(proxy, 'auth-str')
|
||||||
|
) {
|
||||||
|
proxy['auth-str'] = proxy['auth_str'];
|
||||||
|
}
|
||||||
if (isPresent(proxy, 'alpn')) {
|
if (isPresent(proxy, 'alpn')) {
|
||||||
proxy.alpn = Array.isArray(proxy.alpn)
|
proxy.alpn = Array.isArray(proxy.alpn)
|
||||||
? proxy.alpn
|
? proxy.alpn
|
||||||
@@ -81,6 +88,11 @@ export default function ClashMeta_Producer() {
|
|||||||
proxy['preshared-key'] =
|
proxy['preshared-key'] =
|
||||||
proxy['preshared-key'] ?? proxy['pre-shared-key'];
|
proxy['preshared-key'] ?? proxy['pre-shared-key'];
|
||||||
proxy['pre-shared-key'] = proxy['preshared-key'];
|
proxy['pre-shared-key'] = proxy['preshared-key'];
|
||||||
|
} else if (proxy.type === 'vless') {
|
||||||
|
if (isPresent(proxy, 'sni')) {
|
||||||
|
proxy.servername = proxy.sni;
|
||||||
|
delete proxy.sni;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -103,11 +115,26 @@ export default function ClashMeta_Producer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['trojan', 'tuic', 'hysteria'].includes(proxy.type)) {
|
if (
|
||||||
|
['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(
|
||||||
|
proxy.type,
|
||||||
|
)
|
||||||
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (proxy['tls-fingerprint']) {
|
||||||
|
proxy.fingerprint = proxy['tls-fingerprint'];
|
||||||
|
}
|
||||||
delete proxy['tls-fingerprint'];
|
delete proxy['tls-fingerprint'];
|
||||||
|
delete proxy.subName;
|
||||||
|
delete proxy.collectionName;
|
||||||
|
if (
|
||||||
|
['grpc'].includes(proxy.network) &&
|
||||||
|
proxy[`${proxy.network}-opts`]
|
||||||
|
) {
|
||||||
|
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
|
||||||
|
}
|
||||||
return ' - ' + JSON.stringify(proxy) + '\n';
|
return ' - ' + JSON.stringify(proxy) + '\n';
|
||||||
})
|
})
|
||||||
.join('')
|
.join('')
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ export default function Loon_Producer() {
|
|||||||
return http(proxy);
|
return http(proxy);
|
||||||
case 'wireguard':
|
case 'wireguard':
|
||||||
return wireguard(proxy);
|
return wireguard(proxy);
|
||||||
|
case 'hysteria2':
|
||||||
|
return hysteria2(proxy);
|
||||||
}
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`,
|
`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`,
|
||||||
@@ -334,3 +336,33 @@ function wireguard(proxy) {
|
|||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hysteria2(proxy) {
|
||||||
|
if (proxy.obfs || proxy['obfs-password']) {
|
||||||
|
throw new Error(`obfs is unsupported`);
|
||||||
|
}
|
||||||
|
const result = new Result(proxy);
|
||||||
|
result.append(`${proxy.name}=Hysteria2,${proxy.server},${proxy.port}`);
|
||||||
|
|
||||||
|
result.appendIfPresent(`,"${proxy.password}"`, 'password');
|
||||||
|
|
||||||
|
// sni
|
||||||
|
result.appendIfPresent(`,tls-name=${proxy.sni}`, 'sni');
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,skip-cert-verify=${proxy['skip-cert-verify']}`,
|
||||||
|
'skip-cert-verify',
|
||||||
|
);
|
||||||
|
|
||||||
|
// udp
|
||||||
|
result.appendIfPresent(`,udp=${proxy.udp}`, 'udp');
|
||||||
|
|
||||||
|
// download-bandwidth
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,download-bandwidth=${`${proxy['down']}`.match(/\d+/)?.[0] || 0}`,
|
||||||
|
'down',
|
||||||
|
);
|
||||||
|
|
||||||
|
result.appendIfPresent(`,ecn=${proxy.ecn}`, 'ecn');
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|||||||
@@ -63,6 +63,19 @@ function shadowsocks(proxy) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (needTls(proxy)) {
|
if (needTls(proxy)) {
|
||||||
|
appendIfPresent(
|
||||||
|
`,tls-pubkey-sha256=${proxy['tls-pubkey-sha256']}`,
|
||||||
|
'tls-pubkey-sha256',
|
||||||
|
);
|
||||||
|
appendIfPresent(`,tls-alpn=${proxy['tls-alpn']}`, 'tls-alpn');
|
||||||
|
appendIfPresent(
|
||||||
|
`,tls-no-session-ticket=${proxy['tls-no-session-ticket']}`,
|
||||||
|
'tls-no-session-ticket',
|
||||||
|
);
|
||||||
|
appendIfPresent(
|
||||||
|
`,tls-no-session-reuse=${proxy['tls-no-session-reuse']}`,
|
||||||
|
'tls-no-session-reuse',
|
||||||
|
);
|
||||||
// tls fingerprint
|
// tls fingerprint
|
||||||
appendIfPresent(
|
appendIfPresent(
|
||||||
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
|
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
|
||||||
@@ -83,6 +96,12 @@ function shadowsocks(proxy) {
|
|||||||
// udp
|
// udp
|
||||||
appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
||||||
|
|
||||||
|
// server_check_url
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,server_check_url=${proxy['test-url']}`,
|
||||||
|
'test-url',
|
||||||
|
);
|
||||||
|
|
||||||
// tag
|
// tag
|
||||||
append(`,tag=${proxy.name}`);
|
append(`,tag=${proxy.name}`);
|
||||||
|
|
||||||
@@ -115,6 +134,12 @@ function shadowsocksr(proxy) {
|
|||||||
// udp
|
// udp
|
||||||
appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
||||||
|
|
||||||
|
// server_check_url
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,server_check_url=${proxy['test-url']}`,
|
||||||
|
'test-url',
|
||||||
|
);
|
||||||
|
|
||||||
// tag
|
// tag
|
||||||
append(`,tag=${proxy.name}`);
|
append(`,tag=${proxy.name}`);
|
||||||
|
|
||||||
@@ -153,6 +178,19 @@ function trojan(proxy) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (needTls(proxy)) {
|
if (needTls(proxy)) {
|
||||||
|
appendIfPresent(
|
||||||
|
`,tls-pubkey-sha256=${proxy['tls-pubkey-sha256']}`,
|
||||||
|
'tls-pubkey-sha256',
|
||||||
|
);
|
||||||
|
appendIfPresent(`,tls-alpn=${proxy['tls-alpn']}`, 'tls-alpn');
|
||||||
|
appendIfPresent(
|
||||||
|
`,tls-no-session-ticket=${proxy['tls-no-session-ticket']}`,
|
||||||
|
'tls-no-session-ticket',
|
||||||
|
);
|
||||||
|
appendIfPresent(
|
||||||
|
`,tls-no-session-reuse=${proxy['tls-no-session-reuse']}`,
|
||||||
|
'tls-no-session-reuse',
|
||||||
|
);
|
||||||
// tls fingerprint
|
// tls fingerprint
|
||||||
appendIfPresent(
|
appendIfPresent(
|
||||||
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
|
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
|
||||||
@@ -173,6 +211,12 @@ function trojan(proxy) {
|
|||||||
// udp
|
// udp
|
||||||
appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
||||||
|
|
||||||
|
// server_check_url
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,server_check_url=${proxy['test-url']}`,
|
||||||
|
'test-url',
|
||||||
|
);
|
||||||
|
|
||||||
// tag
|
// tag
|
||||||
append(`,tag=${proxy.name}`);
|
append(`,tag=${proxy.name}`);
|
||||||
|
|
||||||
@@ -230,6 +274,19 @@ function vmess(proxy) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (needTls(proxy)) {
|
if (needTls(proxy)) {
|
||||||
|
appendIfPresent(
|
||||||
|
`,tls-pubkey-sha256=${proxy['tls-pubkey-sha256']}`,
|
||||||
|
'tls-pubkey-sha256',
|
||||||
|
);
|
||||||
|
appendIfPresent(`,tls-alpn=${proxy['tls-alpn']}`, 'tls-alpn');
|
||||||
|
appendIfPresent(
|
||||||
|
`,tls-no-session-ticket=${proxy['tls-no-session-ticket']}`,
|
||||||
|
'tls-no-session-ticket',
|
||||||
|
);
|
||||||
|
appendIfPresent(
|
||||||
|
`,tls-no-session-reuse=${proxy['tls-no-session-reuse']}`,
|
||||||
|
'tls-no-session-reuse',
|
||||||
|
);
|
||||||
// tls fingerprint
|
// tls fingerprint
|
||||||
appendIfPresent(
|
appendIfPresent(
|
||||||
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
|
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
|
||||||
@@ -257,6 +314,12 @@ function vmess(proxy) {
|
|||||||
// udp
|
// udp
|
||||||
appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
||||||
|
|
||||||
|
// server_check_url
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,server_check_url=${proxy['test-url']}`,
|
||||||
|
'test-url',
|
||||||
|
);
|
||||||
|
|
||||||
// tag
|
// tag
|
||||||
append(`,tag=${proxy.name}`);
|
append(`,tag=${proxy.name}`);
|
||||||
|
|
||||||
@@ -279,6 +342,19 @@ function http(proxy) {
|
|||||||
appendIfPresent(`,over-tls=${proxy.tls}`, 'tls');
|
appendIfPresent(`,over-tls=${proxy.tls}`, 'tls');
|
||||||
|
|
||||||
if (needTls(proxy)) {
|
if (needTls(proxy)) {
|
||||||
|
appendIfPresent(
|
||||||
|
`,tls-pubkey-sha256=${proxy['tls-pubkey-sha256']}`,
|
||||||
|
'tls-pubkey-sha256',
|
||||||
|
);
|
||||||
|
appendIfPresent(`,tls-alpn=${proxy['tls-alpn']}`, 'tls-alpn');
|
||||||
|
appendIfPresent(
|
||||||
|
`,tls-no-session-ticket=${proxy['tls-no-session-ticket']}`,
|
||||||
|
'tls-no-session-ticket',
|
||||||
|
);
|
||||||
|
appendIfPresent(
|
||||||
|
`,tls-no-session-reuse=${proxy['tls-no-session-reuse']}`,
|
||||||
|
'tls-no-session-reuse',
|
||||||
|
);
|
||||||
// tls fingerprint
|
// tls fingerprint
|
||||||
appendIfPresent(
|
appendIfPresent(
|
||||||
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
|
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
|
||||||
@@ -299,6 +375,12 @@ function http(proxy) {
|
|||||||
// udp
|
// udp
|
||||||
appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
||||||
|
|
||||||
|
// server_check_url
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,server_check_url=${proxy['test-url']}`,
|
||||||
|
'test-url',
|
||||||
|
);
|
||||||
|
|
||||||
// tag
|
// tag
|
||||||
append(`,tag=${proxy.name}`);
|
append(`,tag=${proxy.name}`);
|
||||||
|
|
||||||
@@ -321,6 +403,19 @@ function socks5(proxy) {
|
|||||||
appendIfPresent(`,over-tls=${proxy.tls}`, 'tls');
|
appendIfPresent(`,over-tls=${proxy.tls}`, 'tls');
|
||||||
|
|
||||||
if (needTls(proxy)) {
|
if (needTls(proxy)) {
|
||||||
|
appendIfPresent(
|
||||||
|
`,tls-pubkey-sha256=${proxy['tls-pubkey-sha256']}`,
|
||||||
|
'tls-pubkey-sha256',
|
||||||
|
);
|
||||||
|
appendIfPresent(`,tls-alpn=${proxy['tls-alpn']}`, 'tls-alpn');
|
||||||
|
appendIfPresent(
|
||||||
|
`,tls-no-session-ticket=${proxy['tls-no-session-ticket']}`,
|
||||||
|
'tls-no-session-ticket',
|
||||||
|
);
|
||||||
|
appendIfPresent(
|
||||||
|
`,tls-no-session-reuse=${proxy['tls-no-session-reuse']}`,
|
||||||
|
'tls-no-session-reuse',
|
||||||
|
);
|
||||||
// tls fingerprint
|
// tls fingerprint
|
||||||
appendIfPresent(
|
appendIfPresent(
|
||||||
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
|
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
|
||||||
@@ -341,6 +436,12 @@ function socks5(proxy) {
|
|||||||
// udp
|
// udp
|
||||||
appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
||||||
|
|
||||||
|
// server_check_url
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,server_check_url=${proxy['test-url']}`,
|
||||||
|
'test-url',
|
||||||
|
);
|
||||||
|
|
||||||
// tag
|
// tag
|
||||||
append(`,tag=${proxy.name}`);
|
append(`,tag=${proxy.name}`);
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,13 @@ export default function ShadowRocket_Producer() {
|
|||||||
proxy.version = 5;
|
proxy.version = 5;
|
||||||
}
|
}
|
||||||
} else if (proxy.type === 'hysteria') {
|
} else if (proxy.type === 'hysteria') {
|
||||||
|
// auth_str 将会在未来某个时候删除 但是有的机场不规范
|
||||||
|
if (
|
||||||
|
isPresent(proxy, 'auth_str') &&
|
||||||
|
!isPresent(proxy, 'auth-str')
|
||||||
|
) {
|
||||||
|
proxy['auth-str'] = proxy['auth_str'];
|
||||||
|
}
|
||||||
if (isPresent(proxy, 'alpn')) {
|
if (isPresent(proxy, 'alpn')) {
|
||||||
proxy.alpn = Array.isArray(proxy.alpn)
|
proxy.alpn = Array.isArray(proxy.alpn)
|
||||||
? proxy.alpn
|
? proxy.alpn
|
||||||
@@ -108,11 +115,26 @@ export default function ShadowRocket_Producer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['trojan', 'tuic', 'hysteria'].includes(proxy.type)) {
|
if (
|
||||||
|
['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(
|
||||||
|
proxy.type,
|
||||||
|
)
|
||||||
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (proxy['tls-fingerprint']) {
|
||||||
|
proxy.fingerprint = proxy['tls-fingerprint'];
|
||||||
|
}
|
||||||
delete proxy['tls-fingerprint'];
|
delete proxy['tls-fingerprint'];
|
||||||
|
delete proxy.subName;
|
||||||
|
delete proxy.collectionName;
|
||||||
|
if (
|
||||||
|
['grpc'].includes(proxy.network) &&
|
||||||
|
proxy[`${proxy.network}-opts`]
|
||||||
|
) {
|
||||||
|
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
|
||||||
|
}
|
||||||
return ' - ' + JSON.stringify(proxy) + '\n';
|
return ' - ' + JSON.stringify(proxy) + '\n';
|
||||||
})
|
})
|
||||||
.join('')
|
.join('')
|
||||||
|
|||||||
@@ -12,13 +12,15 @@ export default function Stash_Producer() {
|
|||||||
'ss',
|
'ss',
|
||||||
'ssr',
|
'ssr',
|
||||||
'vmess',
|
'vmess',
|
||||||
'socks',
|
'socks5',
|
||||||
'http',
|
'http',
|
||||||
'snell',
|
'snell',
|
||||||
'trojan',
|
'trojan',
|
||||||
'tuic',
|
'tuic',
|
||||||
'vless',
|
'vless',
|
||||||
'wireguard',
|
'wireguard',
|
||||||
|
'hysteria',
|
||||||
|
'hysteria2',
|
||||||
].includes(proxy.type) ||
|
].includes(proxy.type) ||
|
||||||
(proxy.type === 'snell' &&
|
(proxy.type === 'snell' &&
|
||||||
String(proxy.version) === '4') ||
|
String(proxy.version) === '4') ||
|
||||||
@@ -67,6 +69,7 @@ export default function Stash_Producer() {
|
|||||||
!isPresent(proxy, 'fast-open')
|
!isPresent(proxy, 'fast-open')
|
||||||
) {
|
) {
|
||||||
proxy['fast-open'] = proxy.tfo;
|
proxy['fast-open'] = proxy.tfo;
|
||||||
|
delete proxy.tfo;
|
||||||
}
|
}
|
||||||
// https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/adapter/outbound/tuic.go#L197
|
// https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/adapter/outbound/tuic.go#L197
|
||||||
if (
|
if (
|
||||||
@@ -76,6 +79,13 @@ export default function Stash_Producer() {
|
|||||||
proxy.version = 5;
|
proxy.version = 5;
|
||||||
}
|
}
|
||||||
} else if (proxy.type === 'hysteria') {
|
} else if (proxy.type === 'hysteria') {
|
||||||
|
// auth_str 将会在未来某个时候删除 但是有的机场不规范
|
||||||
|
if (
|
||||||
|
isPresent(proxy, 'auth_str') &&
|
||||||
|
!isPresent(proxy, 'auth-str')
|
||||||
|
) {
|
||||||
|
proxy['auth-str'] = proxy['auth_str'];
|
||||||
|
}
|
||||||
if (isPresent(proxy, 'alpn')) {
|
if (isPresent(proxy, 'alpn')) {
|
||||||
proxy.alpn = Array.isArray(proxy.alpn)
|
proxy.alpn = Array.isArray(proxy.alpn)
|
||||||
? proxy.alpn
|
? proxy.alpn
|
||||||
@@ -86,6 +96,50 @@ export default function Stash_Producer() {
|
|||||||
!isPresent(proxy, 'fast-open')
|
!isPresent(proxy, 'fast-open')
|
||||||
) {
|
) {
|
||||||
proxy['fast-open'] = proxy.tfo;
|
proxy['fast-open'] = proxy.tfo;
|
||||||
|
delete proxy.tfo;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isPresent(proxy, 'down') &&
|
||||||
|
!isPresent(proxy, 'down-speed')
|
||||||
|
) {
|
||||||
|
proxy['down-speed'] = proxy.down;
|
||||||
|
delete proxy.down;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isPresent(proxy, 'up') &&
|
||||||
|
!isPresent(proxy, 'up-speed')
|
||||||
|
) {
|
||||||
|
proxy['up-speed'] = proxy.up;
|
||||||
|
delete proxy.up;
|
||||||
|
}
|
||||||
|
} else if (proxy.type === 'hysteria2') {
|
||||||
|
if (
|
||||||
|
isPresent(proxy, 'password') &&
|
||||||
|
!isPresent(proxy, 'auth')
|
||||||
|
) {
|
||||||
|
proxy.auth = proxy.password;
|
||||||
|
delete proxy.password;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isPresent(proxy, 'tfo') &&
|
||||||
|
!isPresent(proxy, 'fast-open')
|
||||||
|
) {
|
||||||
|
proxy['fast-open'] = proxy.tfo;
|
||||||
|
delete proxy.tfo;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isPresent(proxy, 'down') &&
|
||||||
|
!isPresent(proxy, 'down-speed')
|
||||||
|
) {
|
||||||
|
proxy['down-speed'] = proxy.down;
|
||||||
|
delete proxy.down;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isPresent(proxy, 'up') &&
|
||||||
|
!isPresent(proxy, 'up-speed')
|
||||||
|
) {
|
||||||
|
proxy['up-speed'] = proxy.up;
|
||||||
|
delete proxy.up;
|
||||||
}
|
}
|
||||||
} else if (proxy.type === 'wireguard') {
|
} else if (proxy.type === 'wireguard') {
|
||||||
proxy.keepalive =
|
proxy.keepalive =
|
||||||
@@ -120,10 +174,31 @@ export default function Stash_Producer() {
|
|||||||
proxy['http-opts'].headers.Host = [httpHost];
|
proxy['http-opts'].headers.Host = [httpHost];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (['trojan', 'tuic', 'hysteria'].includes(proxy.type)) {
|
if (
|
||||||
|
['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(
|
||||||
|
proxy.type,
|
||||||
|
)
|
||||||
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
|
if (proxy['tls-fingerprint']) {
|
||||||
|
proxy.fingerprint = proxy['tls-fingerprint'];
|
||||||
|
}
|
||||||
delete proxy['tls-fingerprint'];
|
delete proxy['tls-fingerprint'];
|
||||||
|
|
||||||
|
if (proxy['test-url']) {
|
||||||
|
proxy['benchmark-url'] = proxy['test-url'];
|
||||||
|
delete proxy['test-url'];
|
||||||
|
}
|
||||||
|
|
||||||
|
delete proxy.subName;
|
||||||
|
delete proxy.collectionName;
|
||||||
|
if (
|
||||||
|
['grpc'].includes(proxy.network) &&
|
||||||
|
proxy[`${proxy.network}-opts`]
|
||||||
|
) {
|
||||||
|
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
|
||||||
|
}
|
||||||
return ' - ' + JSON.stringify(proxy) + '\n';
|
return ' - ' + JSON.stringify(proxy) + '\n';
|
||||||
})
|
})
|
||||||
.join('')
|
.join('')
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ export default function Surge_Producer() {
|
|||||||
return tuic(proxy);
|
return tuic(proxy);
|
||||||
case 'wireguard-surge':
|
case 'wireguard-surge':
|
||||||
return wireguard(proxy);
|
return wireguard(proxy);
|
||||||
|
case 'hysteria2':
|
||||||
|
return hysteria2(proxy);
|
||||||
}
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`,
|
`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`,
|
||||||
@@ -71,6 +73,29 @@ function shadowsocks(proxy) {
|
|||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||||
|
|
||||||
|
// shadow-tls
|
||||||
|
if (isPresent(proxy, 'shadow-tls-password')) {
|
||||||
|
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
|
||||||
|
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
|
||||||
|
'shadow-tls-version',
|
||||||
|
);
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
|
||||||
|
'shadow-tls-sni',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// block-quic
|
||||||
|
result.appendIfPresent(`,block-quic=${proxy['block-quic']}`, 'block-quic');
|
||||||
|
|
||||||
|
// underlying-proxy
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,underlying-proxy=${proxy['underlying-proxy']}`,
|
||||||
|
'underlying-proxy',
|
||||||
|
);
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,6 +132,29 @@ function trojan(proxy) {
|
|||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||||
|
|
||||||
|
// shadow-tls
|
||||||
|
if (isPresent(proxy, 'shadow-tls-password')) {
|
||||||
|
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
|
||||||
|
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
|
||||||
|
'shadow-tls-version',
|
||||||
|
);
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
|
||||||
|
'shadow-tls-sni',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// block-quic
|
||||||
|
result.appendIfPresent(`,block-quic=${proxy['block-quic']}`, 'block-quic');
|
||||||
|
|
||||||
|
// underlying-proxy
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,underlying-proxy=${proxy['underlying-proxy']}`,
|
||||||
|
'underlying-proxy',
|
||||||
|
);
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,6 +198,29 @@ function vmess(proxy) {
|
|||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||||
|
|
||||||
|
// shadow-tls
|
||||||
|
if (isPresent(proxy, 'shadow-tls-password')) {
|
||||||
|
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
|
||||||
|
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
|
||||||
|
'shadow-tls-version',
|
||||||
|
);
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
|
||||||
|
'shadow-tls-sni',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// block-quic
|
||||||
|
result.appendIfPresent(`,block-quic=${proxy['block-quic']}`, 'block-quic');
|
||||||
|
|
||||||
|
// underlying-proxy
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,underlying-proxy=${proxy['underlying-proxy']}`,
|
||||||
|
'underlying-proxy',
|
||||||
|
);
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,6 +253,29 @@ function http(proxy) {
|
|||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||||
|
|
||||||
|
// shadow-tls
|
||||||
|
if (isPresent(proxy, 'shadow-tls-password')) {
|
||||||
|
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
|
||||||
|
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
|
||||||
|
'shadow-tls-version',
|
||||||
|
);
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
|
||||||
|
'shadow-tls-sni',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// block-quic
|
||||||
|
result.appendIfPresent(`,block-quic=${proxy['block-quic']}`, 'block-quic');
|
||||||
|
|
||||||
|
// underlying-proxy
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,underlying-proxy=${proxy['underlying-proxy']}`,
|
||||||
|
'underlying-proxy',
|
||||||
|
);
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,6 +310,29 @@ function socks5(proxy) {
|
|||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||||
|
|
||||||
|
// shadow-tls
|
||||||
|
if (isPresent(proxy, 'shadow-tls-password')) {
|
||||||
|
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
|
||||||
|
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
|
||||||
|
'shadow-tls-version',
|
||||||
|
);
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
|
||||||
|
'shadow-tls-sni',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// block-quic
|
||||||
|
result.appendIfPresent(`,block-quic=${proxy['block-quic']}`, 'block-quic');
|
||||||
|
|
||||||
|
// underlying-proxy
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,underlying-proxy=${proxy['underlying-proxy']}`,
|
||||||
|
'underlying-proxy',
|
||||||
|
);
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,6 +362,29 @@ function snell(proxy) {
|
|||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||||
|
|
||||||
|
// shadow-tls
|
||||||
|
if (isPresent(proxy, 'shadow-tls-password')) {
|
||||||
|
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
|
||||||
|
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
|
||||||
|
'shadow-tls-version',
|
||||||
|
);
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
|
||||||
|
'shadow-tls-sni',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// block-quic
|
||||||
|
result.appendIfPresent(`,block-quic=${proxy['block-quic']}`, 'block-quic');
|
||||||
|
|
||||||
|
// underlying-proxy
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,underlying-proxy=${proxy['underlying-proxy']}`,
|
||||||
|
'underlying-proxy',
|
||||||
|
);
|
||||||
|
|
||||||
// reuse
|
// reuse
|
||||||
result.appendIfPresent(`,reuse=${proxy['reuse']}`, 'reuse');
|
result.appendIfPresent(`,reuse=${proxy['reuse']}`, 'reuse');
|
||||||
|
|
||||||
@@ -281,13 +421,47 @@ function tuic(proxy) {
|
|||||||
'skip-cert-verify',
|
'skip-cert-verify',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// tls fingerprint
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,server-cert-fingerprint-sha256=${proxy['tls-fingerprint']}`,
|
||||||
|
'tls-fingerprint',
|
||||||
|
);
|
||||||
|
|
||||||
// tfo
|
// tfo
|
||||||
result.appendIfPresent(`,tfo=${proxy['fast-open']}`, 'fast-open');
|
if (isPresent(proxy, 'tfo')) {
|
||||||
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
|
result.append(`,tfo=${proxy['tfo']}`);
|
||||||
|
} else if (isPresent(proxy, 'fast-open')) {
|
||||||
|
result.append(`,tfo=${proxy['fast-open']}`);
|
||||||
|
}
|
||||||
|
|
||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||||
|
|
||||||
|
// shadow-tls
|
||||||
|
if (isPresent(proxy, 'shadow-tls-password')) {
|
||||||
|
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
|
||||||
|
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
|
||||||
|
'shadow-tls-version',
|
||||||
|
);
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
|
||||||
|
'shadow-tls-sni',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// block-quic
|
||||||
|
result.appendIfPresent(`,block-quic=${proxy['block-quic']}`, 'block-quic');
|
||||||
|
|
||||||
|
// underlying-proxy
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,underlying-proxy=${proxy['underlying-proxy']}`,
|
||||||
|
'underlying-proxy',
|
||||||
|
);
|
||||||
|
|
||||||
|
result.appendIfPresent(`,ecn=${proxy.ecn}`, 'ecn');
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,10 +478,7 @@ function wireguard(proxy) {
|
|||||||
`,no-error-alert=${proxy['no-error-alert']}`,
|
`,no-error-alert=${proxy['no-error-alert']}`,
|
||||||
'no-error-alert',
|
'no-error-alert',
|
||||||
);
|
);
|
||||||
result.appendIfPresent(
|
|
||||||
`,underlying-proxy=${proxy['underlying-proxy']}`,
|
|
||||||
'underlying-proxy',
|
|
||||||
);
|
|
||||||
result.appendIfPresent(
|
result.appendIfPresent(
|
||||||
`,ip-version=${ipVersions[proxy['ip-version']] || proxy['ip-version']}`,
|
`,ip-version=${ipVersions[proxy['ip-version']] || proxy['ip-version']}`,
|
||||||
'ip-version',
|
'ip-version',
|
||||||
@@ -316,6 +487,103 @@ function wireguard(proxy) {
|
|||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||||
|
|
||||||
|
// shadow-tls
|
||||||
|
if (isPresent(proxy, 'shadow-tls-password')) {
|
||||||
|
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
|
||||||
|
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
|
||||||
|
'shadow-tls-version',
|
||||||
|
);
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
|
||||||
|
'shadow-tls-sni',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// block-quic
|
||||||
|
result.appendIfPresent(`,block-quic=${proxy['block-quic']}`, 'block-quic');
|
||||||
|
|
||||||
|
// underlying-proxy
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,underlying-proxy=${proxy['underlying-proxy']}`,
|
||||||
|
'underlying-proxy',
|
||||||
|
);
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hysteria2(proxy) {
|
||||||
|
if (proxy.obfs || proxy['obfs-password']) {
|
||||||
|
throw new Error(`obfs is unsupported`);
|
||||||
|
}
|
||||||
|
const result = new Result(proxy);
|
||||||
|
result.append(`${proxy.name}=hysteria2,${proxy.server},${proxy.port}`);
|
||||||
|
|
||||||
|
result.appendIfPresent(`,password=${proxy.password}`, 'password');
|
||||||
|
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,ip-version=${ipVersions[proxy['ip-version']] || proxy['ip-version']}`,
|
||||||
|
'ip-version',
|
||||||
|
);
|
||||||
|
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,no-error-alert=${proxy['no-error-alert']}`,
|
||||||
|
'no-error-alert',
|
||||||
|
);
|
||||||
|
|
||||||
|
// tls verification
|
||||||
|
result.appendIfPresent(`,sni=${proxy.sni}`, 'sni');
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,skip-cert-verify=${proxy['skip-cert-verify']}`,
|
||||||
|
'skip-cert-verify',
|
||||||
|
);
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,server-cert-fingerprint-sha256=${proxy['tls-fingerprint']}`,
|
||||||
|
'tls-fingerprint',
|
||||||
|
);
|
||||||
|
|
||||||
|
// tfo
|
||||||
|
if (isPresent(proxy, 'tfo')) {
|
||||||
|
result.append(`,tfo=${proxy['tfo']}`);
|
||||||
|
} else if (isPresent(proxy, 'fast-open')) {
|
||||||
|
result.append(`,tfo=${proxy['fast-open']}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// test-url
|
||||||
|
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||||
|
|
||||||
|
// shadow-tls
|
||||||
|
if (isPresent(proxy, 'shadow-tls-password')) {
|
||||||
|
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
|
||||||
|
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
|
||||||
|
'shadow-tls-version',
|
||||||
|
);
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
|
||||||
|
'shadow-tls-sni',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// block-quic
|
||||||
|
result.appendIfPresent(`,block-quic=${proxy['block-quic']}`, 'block-quic');
|
||||||
|
|
||||||
|
// underlying-proxy
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,underlying-proxy=${proxy['underlying-proxy']}`,
|
||||||
|
'underlying-proxy',
|
||||||
|
);
|
||||||
|
|
||||||
|
// download-bandwidth
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,download-bandwidth=${`${proxy['down']}`.match(/\d+/)?.[0] || 0}`,
|
||||||
|
'down',
|
||||||
|
);
|
||||||
|
|
||||||
|
result.appendIfPresent(`,ecn=${proxy.ecn}`, 'ecn');
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,6 +91,16 @@ export default function URI_Producer() {
|
|||||||
? vmessTransportHost[0]
|
? vmessTransportHost[0]
|
||||||
: vmessTransportHost;
|
: vmessTransportHost;
|
||||||
}
|
}
|
||||||
|
if (['grpc'].includes(proxy.network)) {
|
||||||
|
result.path =
|
||||||
|
proxy[`${proxy.network}-opts`]?.[
|
||||||
|
'grpc-service-name'
|
||||||
|
];
|
||||||
|
// https://github.com/XTLS/Xray-core/issues/91
|
||||||
|
result.type =
|
||||||
|
proxy[`${proxy.network}-opts`]?.['_grpc-type'] ||
|
||||||
|
'gun';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
result = 'vmess://' + Base64.encode(JSON.stringify(result));
|
result = 'vmess://' + Base64.encode(JSON.stringify(result));
|
||||||
break;
|
break;
|
||||||
@@ -141,6 +151,12 @@ export default function URI_Producer() {
|
|||||||
let vlessTransport = `&type=${encodeURIComponent(
|
let vlessTransport = `&type=${encodeURIComponent(
|
||||||
proxy.network,
|
proxy.network,
|
||||||
)}`;
|
)}`;
|
||||||
|
if (['grpc'].includes(proxy.network)) {
|
||||||
|
// https://github.com/XTLS/Xray-core/issues/91
|
||||||
|
vlessTransport += `&mode=${encodeURIComponent(
|
||||||
|
proxy[`${proxy.network}-opts`]?.['_grpc-type'] || 'gun',
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
|
||||||
let vlessTransportServiceName =
|
let vlessTransportServiceName =
|
||||||
proxy[`${proxy.network}-opts`]?.[
|
proxy[`${proxy.network}-opts`]?.[
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import registerArtifactRoutes from '@/restful/artifacts';
|
|||||||
import registerSettingRoutes from '@/restful/settings';
|
import registerSettingRoutes from '@/restful/settings';
|
||||||
import registerMiscRoutes from '@/restful/miscs';
|
import registerMiscRoutes from '@/restful/miscs';
|
||||||
import registerSortRoutes from '@/restful/sort';
|
import registerSortRoutes from '@/restful/sort';
|
||||||
|
import registerFileRoutes from '@/restful/file';
|
||||||
|
import registerModuleRoutes from '@/restful/module';
|
||||||
|
|
||||||
migrate();
|
migrate();
|
||||||
serve();
|
serve();
|
||||||
@@ -30,6 +32,8 @@ function serve() {
|
|||||||
// register routes
|
// register routes
|
||||||
registerCollectionRoutes($app);
|
registerCollectionRoutes($app);
|
||||||
registerSubscriptionRoutes($app);
|
registerSubscriptionRoutes($app);
|
||||||
|
registerFileRoutes($app);
|
||||||
|
registerModuleRoutes($app);
|
||||||
registerArtifactRoutes($app);
|
registerArtifactRoutes($app);
|
||||||
registerSettingRoutes($app);
|
registerSettingRoutes($app);
|
||||||
registerSortRoutes($app);
|
registerSortRoutes($app);
|
||||||
|
|||||||
@@ -22,6 +22,16 @@ export default function register($app) {
|
|||||||
function createCollection(req, res) {
|
function createCollection(req, res) {
|
||||||
const collection = req.body;
|
const collection = req.body;
|
||||||
$.info(`正在创建组合订阅:${collection.name}`);
|
$.info(`正在创建组合订阅:${collection.name}`);
|
||||||
|
if (/\//.test(collection.name)) {
|
||||||
|
failed(
|
||||||
|
res,
|
||||||
|
new RequestInvalidError(
|
||||||
|
'INVALID_NAME',
|
||||||
|
`Collection ${collection.name} is invalid`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const allCols = $.read(COLLECTIONS_KEY);
|
const allCols = $.read(COLLECTIONS_KEY);
|
||||||
if (findByName(allCols, collection.name)) {
|
if (findByName(allCols, collection.name)) {
|
||||||
failed(
|
failed(
|
||||||
@@ -31,6 +41,7 @@ function createCollection(req, res) {
|
|||||||
`Collection ${collection.name} already exists.`,
|
`Collection ${collection.name} already exists.`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
allCols.push(collection);
|
allCols.push(collection);
|
||||||
$.write(allCols, COLLECTIONS_KEY);
|
$.write(allCols, COLLECTIONS_KEY);
|
||||||
|
|||||||
109
backend/src/restful/file.js
Normal file
109
backend/src/restful/file.js
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
||||||
|
import { FILES_KEY } from '@/constants';
|
||||||
|
import { failed, success } from '@/restful/response';
|
||||||
|
import $ from '@/core/app';
|
||||||
|
import { RequestInvalidError, ResourceNotFoundError } from '@/restful/errors';
|
||||||
|
|
||||||
|
export default function register($app) {
|
||||||
|
if (!$.read(FILES_KEY)) $.write([], FILES_KEY);
|
||||||
|
|
||||||
|
$app.route('/api/file/:name')
|
||||||
|
.get(getFile)
|
||||||
|
.patch(updateFile)
|
||||||
|
.delete(deleteFile);
|
||||||
|
|
||||||
|
$app.route('/api/files').get(getAllFiles).post(createFile).put(replaceFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// file API
|
||||||
|
function createFile(req, res) {
|
||||||
|
const file = req.body;
|
||||||
|
file.name = `${file.name ?? Date.now()}`;
|
||||||
|
$.info(`正在创建文件:${file.name}`);
|
||||||
|
const allFiles = $.read(FILES_KEY);
|
||||||
|
if (findByName(allFiles, file.name)) {
|
||||||
|
return failed(
|
||||||
|
res,
|
||||||
|
new RequestInvalidError(
|
||||||
|
'DUPLICATE_KEY',
|
||||||
|
req.body.name
|
||||||
|
? `已存在 name 为 ${file.name} 的文件`
|
||||||
|
: `无法同时创建相同的文件 可稍后重试`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
allFiles.push(file);
|
||||||
|
$.write(allFiles, FILES_KEY);
|
||||||
|
success(res, file, 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFile(req, res) {
|
||||||
|
let { name } = req.params;
|
||||||
|
name = decodeURIComponent(name);
|
||||||
|
const allFiles = $.read(FILES_KEY);
|
||||||
|
const file = findByName(allFiles, name);
|
||||||
|
if (file) {
|
||||||
|
res.status(200).json(file.content);
|
||||||
|
} else {
|
||||||
|
failed(
|
||||||
|
res,
|
||||||
|
new ResourceNotFoundError(
|
||||||
|
`FILE_NOT_FOUND`,
|
||||||
|
`File ${name} does not exist`,
|
||||||
|
404,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFile(req, res) {
|
||||||
|
let { name } = req.params;
|
||||||
|
name = decodeURIComponent(name);
|
||||||
|
let file = req.body;
|
||||||
|
const allFiles = $.read(FILES_KEY);
|
||||||
|
const oldFile = findByName(allFiles, name);
|
||||||
|
if (oldFile) {
|
||||||
|
const newFile = {
|
||||||
|
...oldFile,
|
||||||
|
...file,
|
||||||
|
};
|
||||||
|
$.info(`正在更新文件:${name}...`);
|
||||||
|
|
||||||
|
updateByName(allFiles, name, newFile);
|
||||||
|
$.write(allFiles, FILES_KEY);
|
||||||
|
success(res, newFile);
|
||||||
|
} else {
|
||||||
|
failed(
|
||||||
|
res,
|
||||||
|
new ResourceNotFoundError(
|
||||||
|
'RESOURCE_NOT_FOUND',
|
||||||
|
`File ${name} does not exist!`,
|
||||||
|
),
|
||||||
|
404,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteFile(req, res) {
|
||||||
|
let { name } = req.params;
|
||||||
|
name = decodeURIComponent(name);
|
||||||
|
$.info(`正在删除文件:${name}`);
|
||||||
|
let allFiles = $.read(FILES_KEY);
|
||||||
|
deleteByName(allFiles, name);
|
||||||
|
$.write(allFiles, FILES_KEY);
|
||||||
|
success(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllFiles(req, res) {
|
||||||
|
const allFiles = $.read(FILES_KEY);
|
||||||
|
success(
|
||||||
|
res, // eslint-disable-next-line no-unused-vars
|
||||||
|
allFiles.map(({ content, ...rest }) => rest),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceFile(req, res) {
|
||||||
|
const allFiles = req.body;
|
||||||
|
$.write(allFiles, FILES_KEY);
|
||||||
|
success(res);
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@ import $ from '@/core/app';
|
|||||||
import registerSubscriptionRoutes from './subscriptions';
|
import registerSubscriptionRoutes from './subscriptions';
|
||||||
import registerCollectionRoutes from './collections';
|
import registerCollectionRoutes from './collections';
|
||||||
import registerArtifactRoutes from './artifacts';
|
import registerArtifactRoutes from './artifacts';
|
||||||
|
import registerFileRoutes from './file';
|
||||||
|
import registerModuleRoutes from './module';
|
||||||
import registerSyncRoutes from './sync';
|
import registerSyncRoutes from './sync';
|
||||||
import registerDownloadRoutes from './download';
|
import registerDownloadRoutes from './download';
|
||||||
import registerSettingRoutes from './settings';
|
import registerSettingRoutes from './settings';
|
||||||
@@ -23,6 +25,8 @@ export default function serve() {
|
|||||||
registerSortingRoutes($app);
|
registerSortingRoutes($app);
|
||||||
registerSettingRoutes($app);
|
registerSettingRoutes($app);
|
||||||
registerArtifactRoutes($app);
|
registerArtifactRoutes($app);
|
||||||
|
registerFileRoutes($app);
|
||||||
|
registerModuleRoutes($app);
|
||||||
registerSyncRoutes($app);
|
registerSyncRoutes($app);
|
||||||
registerNodeInfoRoutes($app);
|
registerNodeInfoRoutes($app);
|
||||||
registerMiscRoutes($app);
|
registerMiscRoutes($app);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
import { ENV } from '@/vendor/open-api';
|
import { ENV } from '@/vendor/open-api';
|
||||||
import { failed, success } from '@/restful/response';
|
import { failed, success } from '@/restful/response';
|
||||||
import { version as substoreVersion } from '../../package.json';
|
|
||||||
import { updateArtifactStore, updateGitHubAvatar } from '@/restful/settings';
|
import { updateArtifactStore, updateGitHubAvatar } from '@/restful/settings';
|
||||||
import resourceCache from '@/utils/resource-cache';
|
import resourceCache from '@/utils/resource-cache';
|
||||||
import {
|
import {
|
||||||
@@ -12,6 +11,7 @@ import {
|
|||||||
import { InternalServerError, RequestInvalidError } from '@/restful/errors';
|
import { InternalServerError, RequestInvalidError } from '@/restful/errors';
|
||||||
import Gist from '@/utils/gist';
|
import Gist from '@/utils/gist';
|
||||||
import migrate from '@/utils/migration';
|
import migrate from '@/utils/migration';
|
||||||
|
import env from '@/utils/env';
|
||||||
|
|
||||||
export default function register($app) {
|
export default function register($app) {
|
||||||
// utils
|
// utils
|
||||||
@@ -49,19 +49,7 @@ export default function register($app) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getEnv(req, res) {
|
function getEnv(req, res) {
|
||||||
const { isNode, isQX, isLoon, isSurge, isStash, isShadowRocket } = ENV();
|
success(res, env);
|
||||||
let backend = 'Node';
|
|
||||||
if (isNode) backend = 'Node';
|
|
||||||
if (isQX) backend = 'QX';
|
|
||||||
if (isLoon) backend = 'Loon';
|
|
||||||
if (isSurge) backend = 'Surge';
|
|
||||||
if (isStash) backend = 'Stash';
|
|
||||||
if (isShadowRocket) backend = 'ShadowRocket';
|
|
||||||
|
|
||||||
success(res, {
|
|
||||||
backend,
|
|
||||||
version: substoreVersion,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refresh(_, res) {
|
async function refresh(_, res) {
|
||||||
|
|||||||
114
backend/src/restful/module.js
Normal file
114
backend/src/restful/module.js
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
||||||
|
import { MODULES_KEY } from '@/constants';
|
||||||
|
import { failed, success } from '@/restful/response';
|
||||||
|
import $ from '@/core/app';
|
||||||
|
import { RequestInvalidError, ResourceNotFoundError } from '@/restful/errors';
|
||||||
|
import { hex_md5 } from '@/vendor/md5';
|
||||||
|
|
||||||
|
export default function register($app) {
|
||||||
|
if (!$.read(MODULES_KEY)) $.write([], MODULES_KEY);
|
||||||
|
|
||||||
|
$app.route('/api/module/:name')
|
||||||
|
.get(getModule)
|
||||||
|
.patch(updateModule)
|
||||||
|
.delete(deleteModule);
|
||||||
|
|
||||||
|
$app.route('/api/modules')
|
||||||
|
.get(getAllModules)
|
||||||
|
.post(createModule)
|
||||||
|
.put(replaceModule);
|
||||||
|
}
|
||||||
|
|
||||||
|
// module API
|
||||||
|
function createModule(req, res) {
|
||||||
|
const module = req.body;
|
||||||
|
module.name = `${module.name ?? hex_md5(JSON.stringify(module))}`;
|
||||||
|
$.info(`正在创建模块:${module.name}`);
|
||||||
|
const allModules = $.read(MODULES_KEY);
|
||||||
|
if (findByName(allModules, module.name)) {
|
||||||
|
return failed(
|
||||||
|
res,
|
||||||
|
new RequestInvalidError(
|
||||||
|
'DUPLICATE_KEY',
|
||||||
|
req.body.name
|
||||||
|
? `已存在 name 为 ${module.name} 的模块`
|
||||||
|
: `已存在相同的模块 请勿重复添加`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
allModules.push(module);
|
||||||
|
$.write(allModules, MODULES_KEY);
|
||||||
|
success(res, module, 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModule(req, res) {
|
||||||
|
let { name } = req.params;
|
||||||
|
name = decodeURIComponent(name);
|
||||||
|
const allModules = $.read(MODULES_KEY);
|
||||||
|
const module = findByName(allModules, name);
|
||||||
|
if (module) {
|
||||||
|
res.status(200).json(module.content);
|
||||||
|
} else {
|
||||||
|
failed(
|
||||||
|
res,
|
||||||
|
new ResourceNotFoundError(
|
||||||
|
`MODULE_NOT_FOUND`,
|
||||||
|
`Module ${name} does not exist`,
|
||||||
|
404,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateModule(req, res) {
|
||||||
|
let { name } = req.params;
|
||||||
|
name = decodeURIComponent(name);
|
||||||
|
let module = req.body;
|
||||||
|
const allModules = $.read(MODULES_KEY);
|
||||||
|
const oldModule = findByName(allModules, name);
|
||||||
|
if (oldModule) {
|
||||||
|
const newModule = {
|
||||||
|
...oldModule,
|
||||||
|
...module,
|
||||||
|
};
|
||||||
|
$.info(`正在更新模块:${name}...`);
|
||||||
|
|
||||||
|
updateByName(allModules, name, newModule);
|
||||||
|
$.write(allModules, MODULES_KEY);
|
||||||
|
success(res, newModule);
|
||||||
|
} else {
|
||||||
|
failed(
|
||||||
|
res,
|
||||||
|
new ResourceNotFoundError(
|
||||||
|
'RESOURCE_NOT_FOUND',
|
||||||
|
`Module ${name} does not exist!`,
|
||||||
|
),
|
||||||
|
404,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteModule(req, res) {
|
||||||
|
let { name } = req.params;
|
||||||
|
name = decodeURIComponent(name);
|
||||||
|
$.info(`正在删除模块:${name}`);
|
||||||
|
let allModules = $.read(MODULES_KEY);
|
||||||
|
deleteByName(allModules, name);
|
||||||
|
$.write(allModules, MODULES_KEY);
|
||||||
|
success(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllModules(req, res) {
|
||||||
|
const allModules = $.read(MODULES_KEY);
|
||||||
|
success(
|
||||||
|
res,
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
allModules.map(({ content, ...rest }) => rest),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceModule(req, res) {
|
||||||
|
const allModules = req.body;
|
||||||
|
$.write(allModules, MODULES_KEY);
|
||||||
|
success(res);
|
||||||
|
}
|
||||||
@@ -12,98 +12,133 @@ export default function register($app) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function compareSub(req, res) {
|
async function compareSub(req, res) {
|
||||||
const sub = req.body;
|
try {
|
||||||
const target = req.query.target || 'JSON';
|
const sub = req.body;
|
||||||
let content;
|
const target = req.query.target || 'JSON';
|
||||||
if (sub.source === 'local') {
|
let content;
|
||||||
content = sub.content;
|
if (sub.source === 'local') {
|
||||||
} else {
|
content = sub.content;
|
||||||
try {
|
} else {
|
||||||
content = await download(sub.url, sub.ua);
|
|
||||||
} catch (err) {
|
|
||||||
failed(
|
|
||||||
res,
|
|
||||||
new NetworkError(
|
|
||||||
'FAILED_TO_DOWNLOAD_RESOURCE',
|
|
||||||
'无法下载远程资源',
|
|
||||||
`Reason: ${err}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// parse proxies
|
|
||||||
const original = ProxyUtils.parse(content);
|
|
||||||
|
|
||||||
// add id
|
|
||||||
original.forEach((proxy, i) => {
|
|
||||||
proxy.id = i;
|
|
||||||
});
|
|
||||||
|
|
||||||
// apply processors
|
|
||||||
const processed = await ProxyUtils.process(
|
|
||||||
original,
|
|
||||||
sub.process || [],
|
|
||||||
target,
|
|
||||||
);
|
|
||||||
|
|
||||||
// produce
|
|
||||||
success(res, { original, processed });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function compareCollection(req, res) {
|
|
||||||
const allSubs = $.read(SUBS_KEY);
|
|
||||||
const collection = req.body;
|
|
||||||
const subnames = collection.subscriptions;
|
|
||||||
const results = {};
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
subnames.map(async (name) => {
|
|
||||||
const sub = findByName(allSubs, name);
|
|
||||||
try {
|
try {
|
||||||
let raw;
|
content = await download(sub.url, sub.ua);
|
||||||
if (sub.source === 'local') {
|
|
||||||
raw = sub.content;
|
|
||||||
} else {
|
|
||||||
raw = await download(sub.url, sub.ua);
|
|
||||||
}
|
|
||||||
// parse proxies
|
|
||||||
let currentProxies = ProxyUtils.parse(raw);
|
|
||||||
// apply processors
|
|
||||||
currentProxies = await ProxyUtils.process(
|
|
||||||
currentProxies,
|
|
||||||
sub.process || [],
|
|
||||||
'JSON',
|
|
||||||
);
|
|
||||||
results[name] = currentProxies;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
failed(
|
failed(
|
||||||
res,
|
res,
|
||||||
new InternalServerError(
|
new NetworkError(
|
||||||
'PROCESS_FAILED',
|
'FAILED_TO_DOWNLOAD_RESOURCE',
|
||||||
`处理子订阅 ${name} 失败`,
|
'无法下载远程资源',
|
||||||
`Reason: ${err}`,
|
`Reason: ${err}`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}),
|
}
|
||||||
);
|
// parse proxies
|
||||||
|
const original = ProxyUtils.parse(content);
|
||||||
|
|
||||||
// merge proxies with the original order
|
// add id
|
||||||
const original = Array.prototype.concat.apply(
|
original.forEach((proxy, i) => {
|
||||||
[],
|
proxy.id = i;
|
||||||
subnames.map((name) => results[name] || []),
|
proxy.subName = sub.name;
|
||||||
);
|
});
|
||||||
|
|
||||||
original.forEach((proxy, i) => {
|
// apply processors
|
||||||
proxy.id = i;
|
const processed = await ProxyUtils.process(
|
||||||
});
|
original,
|
||||||
|
sub.process || [],
|
||||||
|
target,
|
||||||
|
{ [sub.name]: sub },
|
||||||
|
);
|
||||||
|
|
||||||
const processed = await ProxyUtils.process(
|
// produce
|
||||||
original,
|
success(res, { original, processed });
|
||||||
collection.process || [],
|
} catch (err) {
|
||||||
'JSON',
|
$.error(err.message ?? err);
|
||||||
);
|
failed(
|
||||||
|
res,
|
||||||
success(res, { original, processed });
|
new InternalServerError(
|
||||||
|
`INTERNAL_SERVER_ERROR`,
|
||||||
|
`Failed to preview subscription`,
|
||||||
|
`Reason: ${err.message ?? err}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function compareCollection(req, res) {
|
||||||
|
try {
|
||||||
|
const allSubs = $.read(SUBS_KEY);
|
||||||
|
const collection = req.body;
|
||||||
|
const subnames = collection.subscriptions;
|
||||||
|
const results = {};
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
subnames.map(async (name) => {
|
||||||
|
const sub = findByName(allSubs, name);
|
||||||
|
try {
|
||||||
|
let raw;
|
||||||
|
if (sub.source === 'local') {
|
||||||
|
raw = sub.content;
|
||||||
|
} else {
|
||||||
|
raw = await download(sub.url, sub.ua);
|
||||||
|
}
|
||||||
|
// parse proxies
|
||||||
|
let currentProxies = ProxyUtils.parse(raw);
|
||||||
|
|
||||||
|
currentProxies.forEach((proxy) => {
|
||||||
|
proxy.subName = sub.name;
|
||||||
|
proxy.collectionName = collection.name;
|
||||||
|
});
|
||||||
|
|
||||||
|
// apply processors
|
||||||
|
currentProxies = await ProxyUtils.process(
|
||||||
|
currentProxies,
|
||||||
|
sub.process || [],
|
||||||
|
'JSON',
|
||||||
|
{ [sub.name]: sub, _collection: collection },
|
||||||
|
);
|
||||||
|
results[name] = currentProxies;
|
||||||
|
} catch (err) {
|
||||||
|
failed(
|
||||||
|
res,
|
||||||
|
new InternalServerError(
|
||||||
|
'PROCESS_FAILED',
|
||||||
|
`处理子订阅 ${name} 失败`,
|
||||||
|
`Reason: ${err}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// merge proxies with the original order
|
||||||
|
const original = Array.prototype.concat.apply(
|
||||||
|
[],
|
||||||
|
subnames.map((name) => results[name] || []),
|
||||||
|
);
|
||||||
|
|
||||||
|
original.forEach((proxy, i) => {
|
||||||
|
proxy.id = i;
|
||||||
|
proxy.collectionName = collection.name;
|
||||||
|
});
|
||||||
|
|
||||||
|
const processed = await ProxyUtils.process(
|
||||||
|
original,
|
||||||
|
collection.process || [],
|
||||||
|
'JSON',
|
||||||
|
{ _collection: collection },
|
||||||
|
);
|
||||||
|
|
||||||
|
success(res, { original, processed });
|
||||||
|
} catch (err) {
|
||||||
|
$.error(err.message ?? err);
|
||||||
|
failed(
|
||||||
|
res,
|
||||||
|
new InternalServerError(
|
||||||
|
`INTERNAL_SERVER_ERROR`,
|
||||||
|
`Failed to preview collection`,
|
||||||
|
`Reason: ${err.message ?? err}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export default function register($app) {
|
|||||||
function sortSubs(req, res) {
|
function sortSubs(req, res) {
|
||||||
const orders = req.body;
|
const orders = req.body;
|
||||||
const allSubs = $.read(SUBS_KEY);
|
const allSubs = $.read(SUBS_KEY);
|
||||||
allSubs.sort((a, b) => orders.indexOf(a) - orders.indexOf(b));
|
allSubs.sort((a, b) => orders.indexOf(a.name) - orders.indexOf(b.name));
|
||||||
$.write(allSubs, SUBS_KEY);
|
$.write(allSubs, SUBS_KEY);
|
||||||
success(res, allSubs);
|
success(res, allSubs);
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,7 @@ function sortSubs(req, res) {
|
|||||||
function sortCollections(req, res) {
|
function sortCollections(req, res) {
|
||||||
const orders = req.body;
|
const orders = req.body;
|
||||||
const allCols = $.read(COLLECTIONS_KEY);
|
const allCols = $.read(COLLECTIONS_KEY);
|
||||||
allCols.sort((a, b) => orders.indexOf(a) - orders.indexOf(b));
|
allCols.sort((a, b) => orders.indexOf(a.name) - orders.indexOf(b.name));
|
||||||
$.write(allCols, COLLECTIONS_KEY);
|
$.write(allCols, COLLECTIONS_KEY);
|
||||||
success(res, allCols);
|
success(res, allCols);
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,9 @@ function sortCollections(req, res) {
|
|||||||
function sortArtifacts(req, res) {
|
function sortArtifacts(req, res) {
|
||||||
const orders = req.body;
|
const orders = req.body;
|
||||||
const allArtifacts = $.read(ARTIFACTS_KEY);
|
const allArtifacts = $.read(ARTIFACTS_KEY);
|
||||||
allArtifacts.sort((a, b) => orders.indexOf(a) - orders.indexOf(b));
|
allArtifacts.sort(
|
||||||
|
(a, b) => orders.indexOf(a.name) - orders.indexOf(b.name),
|
||||||
|
);
|
||||||
$.write(allArtifacts, ARTIFACTS_KEY);
|
$.write(allArtifacts, ARTIFACTS_KEY);
|
||||||
success(res, allArtifacts);
|
success(res, allArtifacts);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
} from './errors';
|
} from './errors';
|
||||||
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
||||||
import { SUBS_KEY, COLLECTIONS_KEY, ARTIFACTS_KEY } from '@/constants';
|
import { SUBS_KEY, COLLECTIONS_KEY, ARTIFACTS_KEY } from '@/constants';
|
||||||
import { getFlowHeaders } from '@/utils/flow';
|
import { getFlowHeaders, parseFlowHeaders } from '@/utils/flow';
|
||||||
import { success, failed } from './response';
|
import { success, failed } from './response';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
|
|
||||||
@@ -68,20 +68,7 @@ async function getFlowInfo(req, res) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// unit is KB
|
success(res, parseFlowHeaders(flowHeaders));
|
||||||
const uploadMatch = flowHeaders.match(/upload=(-?)(\d+)/);
|
|
||||||
const upload = Number(uploadMatch[1] + uploadMatch[2]);
|
|
||||||
|
|
||||||
const downloadMatch = flowHeaders.match(/download=(-?)(\d+)/);
|
|
||||||
const download = Number(downloadMatch[1] + downloadMatch[2]);
|
|
||||||
|
|
||||||
const total = Number(flowHeaders.match(/total=(\d+)/)[1]);
|
|
||||||
|
|
||||||
// optional expire timestamp
|
|
||||||
const match = flowHeaders.match(/expire=(\d+)/);
|
|
||||||
const expires = match ? Number(match[1]) : undefined;
|
|
||||||
|
|
||||||
success(res, { expires, total, usage: { upload, download } });
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
failed(
|
failed(
|
||||||
res,
|
res,
|
||||||
@@ -96,6 +83,16 @@ async function getFlowInfo(req, res) {
|
|||||||
function createSubscription(req, res) {
|
function createSubscription(req, res) {
|
||||||
const sub = req.body;
|
const sub = req.body;
|
||||||
$.info(`正在创建订阅: ${sub.name}`);
|
$.info(`正在创建订阅: ${sub.name}`);
|
||||||
|
if (/\//.test(sub.name)) {
|
||||||
|
failed(
|
||||||
|
res,
|
||||||
|
new RequestInvalidError(
|
||||||
|
'INVALID_NAME',
|
||||||
|
`Subscription ${sub.name} is invalid`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const allSubs = $.read(SUBS_KEY);
|
const allSubs = $.read(SUBS_KEY);
|
||||||
if (findByName(allSubs, sub.name)) {
|
if (findByName(allSubs, sub.name)) {
|
||||||
failed(
|
failed(
|
||||||
@@ -105,6 +102,7 @@ function createSubscription(req, res) {
|
|||||||
`Subscription ${sub.name} already exists.`,
|
`Subscription ${sub.name} already exists.`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
allSubs.push(sub);
|
allSubs.push(sub);
|
||||||
$.write(allSubs, SUBS_KEY);
|
$.write(allSubs, SUBS_KEY);
|
||||||
|
|||||||
@@ -36,11 +36,15 @@ async function produceArtifact({ type, name, platform }) {
|
|||||||
}
|
}
|
||||||
// parse proxies
|
// parse proxies
|
||||||
let proxies = ProxyUtils.parse(raw);
|
let proxies = ProxyUtils.parse(raw);
|
||||||
|
proxies.forEach((proxy) => {
|
||||||
|
proxy.subName = sub.name;
|
||||||
|
});
|
||||||
// apply processors
|
// apply processors
|
||||||
proxies = await ProxyUtils.process(
|
proxies = await ProxyUtils.process(
|
||||||
proxies,
|
proxies,
|
||||||
sub.process || [],
|
sub.process || [],
|
||||||
platform,
|
platform,
|
||||||
|
{ [sub.name]: sub },
|
||||||
);
|
);
|
||||||
if (proxies.length === 0) {
|
if (proxies.length === 0) {
|
||||||
throw new Error(`订阅 ${name} 中不含有效节点`);
|
throw new Error(`订阅 ${name} 中不含有效节点`);
|
||||||
@@ -51,7 +55,7 @@ async function produceArtifact({ type, name, platform }) {
|
|||||||
if (exist[proxy.name]) {
|
if (exist[proxy.name]) {
|
||||||
$.notify(
|
$.notify(
|
||||||
'🌍 Sub-Store',
|
'🌍 Sub-Store',
|
||||||
'⚠️ 订阅包含重复节点!',
|
`⚠️ 订阅 ${name} 包含重复节点 ${proxy.name}!`,
|
||||||
'请仔细检测配置!',
|
'请仔细检测配置!',
|
||||||
{
|
{
|
||||||
'media-url':
|
'media-url':
|
||||||
@@ -86,11 +90,18 @@ async function produceArtifact({ type, name, platform }) {
|
|||||||
}
|
}
|
||||||
// parse proxies
|
// parse proxies
|
||||||
let currentProxies = ProxyUtils.parse(raw);
|
let currentProxies = ProxyUtils.parse(raw);
|
||||||
|
|
||||||
|
currentProxies.forEach((proxy) => {
|
||||||
|
proxy.subName = sub.name;
|
||||||
|
proxy.collectionName = collection.name;
|
||||||
|
});
|
||||||
|
|
||||||
// apply processors
|
// apply processors
|
||||||
currentProxies = await ProxyUtils.process(
|
currentProxies = await ProxyUtils.process(
|
||||||
currentProxies,
|
currentProxies,
|
||||||
sub.process || [],
|
sub.process || [],
|
||||||
platform,
|
platform,
|
||||||
|
{ [sub.name]: sub, _collection: collection },
|
||||||
);
|
);
|
||||||
results[name] = currentProxies;
|
results[name] = currentProxies;
|
||||||
processed++;
|
processed++;
|
||||||
@@ -127,11 +138,16 @@ async function produceArtifact({ type, name, platform }) {
|
|||||||
subnames.map((name) => results[name] || []),
|
subnames.map((name) => results[name] || []),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
proxies.forEach((proxy) => {
|
||||||
|
proxy.collectionName = collection.name;
|
||||||
|
});
|
||||||
|
|
||||||
// apply own processors
|
// apply own processors
|
||||||
proxies = await ProxyUtils.process(
|
proxies = await ProxyUtils.process(
|
||||||
proxies,
|
proxies,
|
||||||
collection.process || [],
|
collection.process || [],
|
||||||
platform,
|
platform,
|
||||||
|
{ _collection: collection },
|
||||||
);
|
);
|
||||||
if (proxies.length === 0) {
|
if (proxies.length === 0) {
|
||||||
throw new Error(`组合订阅 ${name} 中不含有效节点`);
|
throw new Error(`组合订阅 ${name} 中不含有效节点`);
|
||||||
@@ -142,7 +158,7 @@ async function produceArtifact({ type, name, platform }) {
|
|||||||
if (exist[proxy.name]) {
|
if (exist[proxy.name]) {
|
||||||
$.notify(
|
$.notify(
|
||||||
'🌍 Sub-Store',
|
'🌍 Sub-Store',
|
||||||
'⚠️ 订阅包含重复节点!',
|
`⚠️ 组合订阅 ${name} 包含重复节点 ${proxy.name}!`,
|
||||||
'请仔细检测配置!',
|
'请仔细检测配置!',
|
||||||
{
|
{
|
||||||
'media-url':
|
'media-url':
|
||||||
|
|||||||
@@ -1,10 +1,49 @@
|
|||||||
|
import { FILES_KEY, MODULES_KEY } from '@/constants';
|
||||||
|
import { findByName } from '@/utils/database';
|
||||||
import { HTTP, ENV } from '@/vendor/open-api';
|
import { HTTP, ENV } from '@/vendor/open-api';
|
||||||
import { hex_md5 } from '@/vendor/md5';
|
import { hex_md5 } from '@/vendor/md5';
|
||||||
import resourceCache from '@/utils/resource-cache';
|
import resourceCache from '@/utils/resource-cache';
|
||||||
|
import $ from '@/core/app';
|
||||||
|
|
||||||
const tasks = new Map();
|
const tasks = new Map();
|
||||||
|
|
||||||
export default async function download(url, ua) {
|
export default async function download(url, ua) {
|
||||||
|
let $arguments = {};
|
||||||
|
const rawArgs = url.split('#');
|
||||||
|
if (rawArgs.length > 1) {
|
||||||
|
try {
|
||||||
|
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
|
||||||
|
$arguments = JSON.parse(decodeURIComponent(rawArgs[1]));
|
||||||
|
} catch (e) {
|
||||||
|
for (const pair of rawArgs[1].split('&')) {
|
||||||
|
const key = pair.split('=')[0];
|
||||||
|
const value = pair.split('=')[1];
|
||||||
|
// 部分兼容之前的逻辑 const value = pair.split('=')[1] || true;
|
||||||
|
$arguments[key] =
|
||||||
|
value == null || value === ''
|
||||||
|
? true
|
||||||
|
: decodeURIComponent(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadUrlMatch = url.match(/^\/api\/(file|module)\/(.+)/);
|
||||||
|
if (downloadUrlMatch) {
|
||||||
|
let type = downloadUrlMatch?.[1];
|
||||||
|
let name = downloadUrlMatch?.[2];
|
||||||
|
if (name == null) {
|
||||||
|
throw new Error(`本地 ${type} URL 无效: ${url}`);
|
||||||
|
}
|
||||||
|
name = decodeURIComponent(name);
|
||||||
|
const key = type === 'module' ? MODULES_KEY : FILES_KEY;
|
||||||
|
const item = findByName($.read(key), name);
|
||||||
|
if (!item) {
|
||||||
|
throw new Error(`找不到本地 ${type}: ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return item.content;
|
||||||
|
}
|
||||||
|
|
||||||
const { isNode } = ENV();
|
const { isNode } = ENV();
|
||||||
ua = ua || 'Quantumult%20X/1.0.29 (iPhone14,5; iOS 15.4.1)';
|
ua = ua || 'Quantumult%20X/1.0.29 (iPhone14,5; iOS 15.4.1)';
|
||||||
const id = hex_md5(ua + url);
|
const id = hex_md5(ua + url);
|
||||||
@@ -21,7 +60,7 @@ export default async function download(url, ua) {
|
|||||||
const result = new Promise((resolve, reject) => {
|
const result = new Promise((resolve, reject) => {
|
||||||
// try to find in app cache
|
// try to find in app cache
|
||||||
const cached = resourceCache.get(id);
|
const cached = resourceCache.get(id);
|
||||||
if (cached) {
|
if (!$arguments?.noCache && cached) {
|
||||||
resolve(cached);
|
resolve(cached);
|
||||||
} else {
|
} else {
|
||||||
http.get(url)
|
http.get(url)
|
||||||
|
|||||||
16
backend/src/utils/env.js
Normal file
16
backend/src/utils/env.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { version as substoreVersion } from '../../package.json';
|
||||||
|
import { ENV } from '@/vendor/open-api';
|
||||||
|
|
||||||
|
const { isNode, isQX, isLoon, isSurge, isStash, isShadowRocket } = ENV();
|
||||||
|
let backend = 'Node';
|
||||||
|
if (isNode) backend = 'Node';
|
||||||
|
if (isQX) backend = 'QX';
|
||||||
|
if (isLoon) backend = 'Loon';
|
||||||
|
if (isSurge) backend = 'Surge';
|
||||||
|
if (isStash) backend = 'Stash';
|
||||||
|
if (isShadowRocket) backend = 'ShadowRocket';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
backend,
|
||||||
|
version: substoreVersion,
|
||||||
|
};
|
||||||
@@ -13,3 +13,28 @@ export async function getFlowHeaders(url) {
|
|||||||
)[0];
|
)[0];
|
||||||
return headers[subkey];
|
return headers[subkey];
|
||||||
}
|
}
|
||||||
|
export function parseFlowHeaders(flowHeaders) {
|
||||||
|
if (!flowHeaders) return;
|
||||||
|
// unit is KB
|
||||||
|
const uploadMatch = flowHeaders.match(/upload=(-?)(\d+)/);
|
||||||
|
const upload = Number(uploadMatch[1] + uploadMatch[2]);
|
||||||
|
|
||||||
|
const downloadMatch = flowHeaders.match(/download=(-?)(\d+)/);
|
||||||
|
const download = Number(downloadMatch[1] + downloadMatch[2]);
|
||||||
|
|
||||||
|
const total = Number(flowHeaders.match(/total=(\d+)/)[1]);
|
||||||
|
|
||||||
|
// optional expire timestamp
|
||||||
|
const match = flowHeaders.match(/expire=(\d+)/);
|
||||||
|
const expires = match ? Number(match[1]) : undefined;
|
||||||
|
|
||||||
|
return { expires, total, usage: { upload, download } };
|
||||||
|
}
|
||||||
|
export function flowTransfer(flow, unit = 'B') {
|
||||||
|
const unitList = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'];
|
||||||
|
let unitIndex = unitList.indexOf(unit);
|
||||||
|
|
||||||
|
return flow < 1024
|
||||||
|
? { value: flow.toFixed(1), unit: unit }
|
||||||
|
: flowTransfer(flow / 1024, unitList[++unitIndex]);
|
||||||
|
}
|
||||||
|
|||||||
32
backend/src/vendor/open-api.js
vendored
32
backend/src/vendor/open-api.js
vendored
@@ -49,27 +49,32 @@ export class OpenAPI {
|
|||||||
|
|
||||||
if (isNode) {
|
if (isNode) {
|
||||||
// create a json for root cache
|
// create a json for root cache
|
||||||
let fpath = 'root.json';
|
const basePath =
|
||||||
if (!this.node.fs.existsSync(fpath)) {
|
eval('process.env.SUB_STORE_DATA_BASE_PATH') || '.';
|
||||||
this.node.fs.writeFileSync(fpath, JSON.stringify({}), {
|
let rootPath = `${basePath}/root.json`;
|
||||||
|
|
||||||
|
this.log(`Root path: ${rootPath}`);
|
||||||
|
if (!this.node.fs.existsSync(rootPath)) {
|
||||||
|
this.node.fs.writeFileSync(rootPath, JSON.stringify({}), {
|
||||||
flag: 'wx',
|
flag: 'wx',
|
||||||
});
|
});
|
||||||
this.root = {};
|
this.root = {};
|
||||||
} else {
|
} else {
|
||||||
this.root = JSON.parse(this.node.fs.readFileSync(`${fpath}`));
|
this.root = JSON.parse(
|
||||||
|
this.node.fs.readFileSync(`${rootPath}`),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a json file with the given name if not exists
|
// create a json file with the given name if not exists
|
||||||
fpath = `${this.name}.json`;
|
let fpath = `${basePath}/${this.name}.json`;
|
||||||
|
this.log(`Data path: ${fpath}`);
|
||||||
if (!this.node.fs.existsSync(fpath)) {
|
if (!this.node.fs.existsSync(fpath)) {
|
||||||
this.node.fs.writeFileSync(fpath, JSON.stringify({}), {
|
this.node.fs.writeFileSync(fpath, JSON.stringify({}), {
|
||||||
flag: 'wx',
|
flag: 'wx',
|
||||||
});
|
});
|
||||||
this.cache = {};
|
this.cache = {};
|
||||||
} else {
|
} else {
|
||||||
this.cache = JSON.parse(
|
this.cache = JSON.parse(this.node.fs.readFileSync(`${fpath}`));
|
||||||
this.node.fs.readFileSync(`${this.name}.json`),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,14 +85,17 @@ export class OpenAPI {
|
|||||||
if (isQX) $prefs.setValueForKey(data, this.name);
|
if (isQX) $prefs.setValueForKey(data, this.name);
|
||||||
if (isLoon || isSurge) $persistentStore.write(data, this.name);
|
if (isLoon || isSurge) $persistentStore.write(data, this.name);
|
||||||
if (isNode) {
|
if (isNode) {
|
||||||
|
const basePath =
|
||||||
|
eval('process.env.SUB_STORE_DATA_BASE_PATH') || '.';
|
||||||
|
|
||||||
this.node.fs.writeFileSync(
|
this.node.fs.writeFileSync(
|
||||||
`${this.name}.json`,
|
`${basePath}/${this.name}.json`,
|
||||||
data,
|
data,
|
||||||
{ flag: 'w' },
|
{ flag: 'w' },
|
||||||
(err) => console.log(err),
|
(err) => console.log(err),
|
||||||
);
|
);
|
||||||
this.node.fs.writeFileSync(
|
this.node.fs.writeFileSync(
|
||||||
'root.json',
|
`${basePath}/root.json`,
|
||||||
JSON.stringify(this.root, null, 2),
|
JSON.stringify(this.root, null, 2),
|
||||||
{ flag: 'w' },
|
{ flag: 'w' },
|
||||||
(err) => console.log(err),
|
(err) => console.log(err),
|
||||||
@@ -308,7 +316,9 @@ export function HTTP(defaultOptions = { baseURL: '' }) {
|
|||||||
return (
|
return (
|
||||||
timer
|
timer
|
||||||
? Promise.race([timer, worker]).then((res) => {
|
? Promise.race([timer, worker]).then((res) => {
|
||||||
clearTimeout(timeoutid);
|
if (typeof clearTimeout !== 'undefined') {
|
||||||
|
clearTimeout(timeoutid);
|
||||||
|
}
|
||||||
return res;
|
return res;
|
||||||
})
|
})
|
||||||
: worker
|
: worker
|
||||||
|
|||||||
Reference in New Issue
Block a user