mirror of
https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-10 00:52:40 +00:00
Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b4c046b75 | ||
|
|
41034ceb46 | ||
|
|
6efb19c856 | ||
|
|
2cd30dfe68 | ||
|
|
d53947d820 | ||
|
|
7e75031e92 | ||
|
|
4a07c02dc1 | ||
|
|
95d6688539 | ||
|
|
a23e2ffcd6 | ||
|
|
fda1252d0e | ||
|
|
62c5c2e15b | ||
|
|
ffabcc9391 | ||
|
|
0825f15d04 | ||
|
|
fbf6b5ce6e | ||
|
|
3eb0816c88 | ||
|
|
8fc755ff02 | ||
|
|
6d3d6fa1b3 | ||
|
|
4ef4431c2c | ||
|
|
5058662651 | ||
|
|
f9d120bac3 | ||
|
|
72a445ae33 | ||
|
|
5e2a87e250 | ||
|
|
71fc9affbf | ||
|
|
6f82294c49 | ||
|
|
7c398ba51c | ||
|
|
7002eee88d | ||
|
|
bd21d58fe7 | ||
|
|
2ea46dcbf1 | ||
|
|
4a2a2297f6 | ||
|
|
07d5a913f0 | ||
|
|
421df8f0d4 | ||
|
|
e14944dd19 | ||
|
|
bf18c51f6a | ||
|
|
23e8fbd1b7 | ||
|
|
b94b3c366b | ||
|
|
afb5f7b880 | ||
|
|
74ec133a79 | ||
|
|
2a76eb6462 | ||
|
|
9ac5e136a6 | ||
|
|
38f5a97a20 | ||
|
|
14a3488ce2 | ||
|
|
6afec4f668 | ||
|
|
b1874e510d | ||
|
|
48aaaf5c99 | ||
|
|
7385e17a4c | ||
|
|
c3daea55ab | ||
|
|
fc9ff48b1f | ||
|
|
fb21890b68 | ||
|
|
2155cc9639 | ||
|
|
03e320cbd0 | ||
|
|
e325b9a39a | ||
|
|
87597f6fc2 | ||
|
|
3462d36c35 | ||
|
|
02946ec81c | ||
|
|
c963c872ff | ||
|
|
c4a1bb4ea1 | ||
|
|
f96d9dea74 | ||
|
|
01eb69d8ae | ||
|
|
797ba6f601 | ||
|
|
128353a7f3 | ||
|
|
e6f6d51608 | ||
|
|
589a6bfadb | ||
|
|
75012503f8 | ||
|
|
85a3e2ee54 | ||
|
|
95b7557635 | ||
|
|
14ca62db4a | ||
|
|
a2a754adb7 | ||
|
|
6b23f82953 | ||
|
|
e071a7f253 | ||
|
|
b9bba895e1 | ||
|
|
8090d678ee | ||
|
|
ff4be7ac38 | ||
|
|
7e2109dc68 | ||
|
|
278beae99a | ||
|
|
3aedd5943d |
19
.github/workflows/main.yml
vendored
19
.github/workflows/main.yml
vendored
@@ -1,5 +1,6 @@
|
|||||||
name: build
|
name: build
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
@@ -26,18 +27,18 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
npm install -g pnpm
|
npm install -g pnpm
|
||||||
cd backend && pnpm i --no-frozen-lockfile
|
cd backend && pnpm i --no-frozen-lockfile
|
||||||
- name: Test
|
# - name: Test
|
||||||
run: |
|
# run: |
|
||||||
cd backend
|
# cd backend
|
||||||
pnpm test
|
# pnpm test
|
||||||
- name: Build
|
# - name: Build
|
||||||
run: |
|
# run: |
|
||||||
cd backend
|
# cd backend
|
||||||
pnpm run build
|
# pnpm run build
|
||||||
- name: Bundle
|
- name: Bundle
|
||||||
run: |
|
run: |
|
||||||
cd backend
|
cd backend
|
||||||
pnpm run bundle
|
pnpm bundle:esbuild
|
||||||
- id: tag
|
- id: tag
|
||||||
name: Generate release tag
|
name: Generate release tag
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -26,13 +26,13 @@ Core functionalities:
|
|||||||
|
|
||||||
### Supported Input Formats
|
### Supported Input Formats
|
||||||
|
|
||||||
> ⚠️ Do not use `Shadowrocket` to export URI and then import it as input. It is not a standard URI.
|
> ⚠️ Do not use `Shadowrocket` or `NekoBox` to export URI and then import it as input. The URIs exported in this way may not be standard URIs.
|
||||||
|
|
||||||
- [x] Normal Proxy(`socks5`, `socks5+tls`, `http`, `https`(it's ok))
|
- [x] Proxy URI Scheme(`socks5`, `socks5+tls`, `http`, `https`(it's ok))
|
||||||
|
|
||||||
example: `socks5+tls://user:pass@ip:port#name`
|
example: `socks5+tls://user:pass@ip:port#name`
|
||||||
|
|
||||||
- [x] URI(SS, SSR, VMess, VLESS, Trojan, Hysteria, Hysteria 2, TUIC v5, WireGuard)
|
- [x] URI(SOCKS, SS, SSR, VMess, VLESS, Trojan, Hysteria, Hysteria 2, TUIC v5, WireGuard)
|
||||||
- [x] Clash Proxies YAML
|
- [x] Clash Proxies YAML
|
||||||
- [x] Clash Proxy JSON(single line)
|
- [x] Clash Proxy JSON(single line)
|
||||||
- [x] QX (SS, SSR, VMess, Trojan, HTTP, SOCKS5, VLESS)
|
- [x] QX (SS, SSR, VMess, Trojan, HTTP, SOCKS5, VLESS)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
* ╚════██║██║ ██║██╔══██╗╚════╝╚════██║ ██║ ██║ ██║██╔══██╗██╔══╝
|
* ╚════██║██║ ██║██╔══██╗╚════╝╚════██║ ██║ ██║ ██║██╔══██╗██╔══╝
|
||||||
* ███████║╚██████╔╝██████╔╝ ███████║ ██║ ╚██████╔╝██║ ██║███████╗
|
* ███████║╚██████╔╝██████╔╝ ███████║ ██║ ╚██████╔╝██║ ██║███████╗
|
||||||
* ╚══════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝
|
* ╚══════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝
|
||||||
* Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket!
|
* Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket!
|
||||||
* @updated: <%= updated %>
|
* @updated: <%= updated %>
|
||||||
* @version: <%= pkg.version %>
|
* @version: <%= pkg.version %>
|
||||||
* @author: Peng-YM
|
* @author: Peng-YM
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "sub-store",
|
"name": "sub-store",
|
||||||
"version": "2.15.3",
|
"version": "2.16.59",
|
||||||
"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": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
"dev:run": "nodemon -w sub-store.min.js sub-store.min.js",
|
"dev:run": "nodemon -w sub-store.min.js sub-store.min.js",
|
||||||
"build": "gulp",
|
"build": "gulp",
|
||||||
"bundle": "node bundle.js",
|
"bundle": "node bundle.js",
|
||||||
|
"bundle:esbuild": "node bundle-esbuild.js",
|
||||||
"changelog": "conventional-changelog -p cli -i CHANGELOG.md -s"
|
"changelog": "conventional-changelog -p cli -i CHANGELOG.md -s"
|
||||||
},
|
},
|
||||||
"author": "Peng-YM",
|
"author": "Peng-YM",
|
||||||
@@ -33,6 +34,7 @@
|
|||||||
"ms": "^2.1.3",
|
"ms": "^2.1.3",
|
||||||
"nanoid": "^3.3.3",
|
"nanoid": "^3.3.3",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
|
"semver": "^7.6.3",
|
||||||
"static-js-yaml": "^1.0.0"
|
"static-js-yaml": "^1.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
13502
backend/pnpm-lock.yaml
generated
13502
backend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@ import {
|
|||||||
isIPv4,
|
isIPv4,
|
||||||
isIPv6,
|
isIPv6,
|
||||||
isValidPortNumber,
|
isValidPortNumber,
|
||||||
|
isValidUUID,
|
||||||
isNotBlank,
|
isNotBlank,
|
||||||
ipAddress,
|
ipAddress,
|
||||||
getRandomPort,
|
getRandomPort,
|
||||||
@@ -21,6 +22,8 @@ import { findByName } from '@/utils/database';
|
|||||||
import { produceArtifact } from '@/restful/sync';
|
import { produceArtifact } from '@/restful/sync';
|
||||||
import { getFlag, removeFlag, getISO, MMDB } from '@/utils/geo';
|
import { getFlag, removeFlag, getISO, MMDB } from '@/utils/geo';
|
||||||
import Gist from '@/utils/gist';
|
import Gist from '@/utils/gist';
|
||||||
|
import { isPresent } from './producers/utils';
|
||||||
|
import { doh } from '@/utils/dns';
|
||||||
|
|
||||||
function preprocess(raw) {
|
function preprocess(raw) {
|
||||||
for (const processor of PROXY_PREPROCESSORS) {
|
for (const processor of PROXY_PREPROCESSORS) {
|
||||||
@@ -75,7 +78,16 @@ function parse(raw) {
|
|||||||
$.error(`Failed to parse line: ${line}`);
|
$.error(`Failed to parse line: ${line}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return proxies;
|
return proxies.filter((proxy) => {
|
||||||
|
if (['vless', 'vmess'].includes(proxy.type)) {
|
||||||
|
const isProxyUUIDValid = isValidUUID(proxy.uuid);
|
||||||
|
if (!isProxyUUIDValid) {
|
||||||
|
$.error(`UUID may be invalid: ${proxy.name} ${proxy.uuid}`);
|
||||||
|
}
|
||||||
|
// return isProxyUUIDValid;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processFn(
|
async function processFn(
|
||||||
@@ -214,10 +226,22 @@ function produce(proxies, targetPlatform, type, opts = {}) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// filter unsupported proxies
|
// filter unsupported proxies
|
||||||
proxies = proxies.filter(
|
proxies = proxies.filter((proxy) => {
|
||||||
(proxy) =>
|
// 检查代理是否支持目标平台
|
||||||
!(proxy.supported && proxy.supported[targetPlatform] === false),
|
if (proxy.supported && proxy.supported[targetPlatform] === false) {
|
||||||
);
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对于 vless 和 vmess 代理,需要额外验证 UUID
|
||||||
|
if (['vless', 'vmess'].includes(proxy.type)) {
|
||||||
|
const isProxyUUIDValid = isValidUUID(proxy.uuid);
|
||||||
|
if (!isProxyUUIDValid)
|
||||||
|
$.error(`UUID may be invalid: ${proxy.name} ${proxy.uuid}`);
|
||||||
|
// return isProxyUUIDValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
proxies = proxies.map((proxy) => {
|
proxies = proxies.map((proxy) => {
|
||||||
proxy._resolved = proxy.resolved;
|
proxy._resolved = proxy.resolved;
|
||||||
@@ -303,6 +327,8 @@ export const ProxyUtils = {
|
|||||||
MMDB,
|
MMDB,
|
||||||
Gist,
|
Gist,
|
||||||
download,
|
download,
|
||||||
|
isValidUUID,
|
||||||
|
doh,
|
||||||
};
|
};
|
||||||
|
|
||||||
function tryParse(parser, line) {
|
function tryParse(parser, line) {
|
||||||
@@ -343,6 +369,14 @@ function lastParse(proxy) {
|
|||||||
if (typeof proxy.password === 'number') {
|
if (typeof proxy.password === 'number') {
|
||||||
proxy.password = numberToString(proxy.password);
|
proxy.password = numberToString(proxy.password);
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
['ss'].includes(proxy.type) &&
|
||||||
|
proxy.cipher === 'none' &&
|
||||||
|
!proxy.password
|
||||||
|
) {
|
||||||
|
// https://github.com/MetaCubeX/mihomo/issues/1677
|
||||||
|
proxy.password = '';
|
||||||
|
}
|
||||||
if (proxy.interface) {
|
if (proxy.interface) {
|
||||||
proxy['interface-name'] = proxy.interface;
|
proxy['interface-name'] = proxy.interface;
|
||||||
delete proxy.interface;
|
delete proxy.interface;
|
||||||
@@ -392,9 +426,14 @@ function lastParse(proxy) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
['trojan', 'tuic', 'hysteria', 'hysteria2', 'juicity'].includes(
|
[
|
||||||
proxy.type,
|
'trojan',
|
||||||
)
|
'tuic',
|
||||||
|
'hysteria',
|
||||||
|
'hysteria2',
|
||||||
|
'juicity',
|
||||||
|
'anytls',
|
||||||
|
].includes(proxy.type)
|
||||||
) {
|
) {
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
}
|
}
|
||||||
@@ -564,6 +603,20 @@ function lastParse(proxy) {
|
|||||||
if (!proxy['tls-fingerprint'] && caStr) {
|
if (!proxy['tls-fingerprint'] && caStr) {
|
||||||
proxy['tls-fingerprint'] = rs.generateFingerprint(caStr);
|
proxy['tls-fingerprint'] = rs.generateFingerprint(caStr);
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
['shadowsocks'].includes(proxy.type) &&
|
||||||
|
isPresent(proxy, 'shadow-tls-password')
|
||||||
|
) {
|
||||||
|
proxy.plugin = 'shadow-tls';
|
||||||
|
proxy['plugin-opts'] = {
|
||||||
|
host: proxy['shadow-tls-sni'],
|
||||||
|
password: proxy['shadow-tls-password'],
|
||||||
|
version: proxy['shadow-tls-version'],
|
||||||
|
};
|
||||||
|
delete proxy['shadow-tls-sni'];
|
||||||
|
delete proxy['shadow-tls-password'];
|
||||||
|
delete proxy['shadow-tls-version'];
|
||||||
|
}
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import getSurgeParser from './peggy/surge';
|
|||||||
import getLoonParser from './peggy/loon';
|
import getLoonParser from './peggy/loon';
|
||||||
import getQXParser from './peggy/qx';
|
import getQXParser from './peggy/qx';
|
||||||
import getTrojanURIParser from './peggy/trojan-uri';
|
import getTrojanURIParser from './peggy/trojan-uri';
|
||||||
|
import $ from '@/core/app';
|
||||||
|
|
||||||
import { Base64 } from 'js-base64';
|
import { Base64 } from 'js-base64';
|
||||||
|
|
||||||
@@ -40,8 +41,21 @@ function URI_PROXY() {
|
|||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
let [__, type, tls, username, password, server, port, query, name] =
|
let [__, type, tls, username, password, server, port, query, name] =
|
||||||
line.match(
|
line.match(
|
||||||
/^(socks5|http|http)(\+tls|s)?:\/\/(?:(.*?):(.*?)@)?(.*?):(\d+?)(\?.*?)?(?:#(.*?))?$/,
|
/^(socks5|http|http)(\+tls|s)?:\/\/(?:(.*?):(.*?)@)?(.*?)(?::(\d+?))?(\?.*?)?(?:#(.*?))?$/,
|
||||||
);
|
);
|
||||||
|
if (port) {
|
||||||
|
port = parseInt(port, 10);
|
||||||
|
} else {
|
||||||
|
if (tls) {
|
||||||
|
port = 443;
|
||||||
|
} else if (type === 'http') {
|
||||||
|
port = 80;
|
||||||
|
} else {
|
||||||
|
$.error(`port is not present in line: ${line}`);
|
||||||
|
throw new Error(`port is not present in line: ${line}`);
|
||||||
|
}
|
||||||
|
$.info(`port is not present in line: ${line}, set to ${port}`);
|
||||||
|
}
|
||||||
|
|
||||||
const proxy = {
|
const proxy = {
|
||||||
name:
|
name:
|
||||||
@@ -62,7 +76,46 @@ function URI_PROXY() {
|
|||||||
};
|
};
|
||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
}
|
}
|
||||||
|
function URI_SOCKS() {
|
||||||
|
const name = 'URI SOCKS Parser';
|
||||||
|
const test = (line) => {
|
||||||
|
return /^socks:\/\//.test(line);
|
||||||
|
};
|
||||||
|
const parse = (line) => {
|
||||||
|
// parse url
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
let [__, type, auth, server, port, query, name] = line.match(
|
||||||
|
/^(socks)?:\/\/(?:(.*)@)?(.*?)(?::(\d+?))?(\?.*?)?(?:#(.*?))?$/,
|
||||||
|
);
|
||||||
|
if (port) {
|
||||||
|
port = parseInt(port, 10);
|
||||||
|
} else {
|
||||||
|
$.error(`port is not present in line: ${line}`);
|
||||||
|
throw new Error(`port is not present in line: ${line}`);
|
||||||
|
}
|
||||||
|
let username, password;
|
||||||
|
if (auth) {
|
||||||
|
const parsed = Base64.decode(decodeURIComponent(auth)).split(':');
|
||||||
|
username = parsed[0];
|
||||||
|
password = parsed[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxy = {
|
||||||
|
name:
|
||||||
|
name != null
|
||||||
|
? decodeURIComponent(name)
|
||||||
|
: `${type} ${server}:${port}`,
|
||||||
|
type: 'socks5',
|
||||||
|
server,
|
||||||
|
port,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
};
|
||||||
|
|
||||||
|
return proxy;
|
||||||
|
};
|
||||||
|
return { name, test, parse };
|
||||||
|
}
|
||||||
// Parse SS URI format (only supports new SIP002, legacy format is depreciated).
|
// Parse SS URI format (only supports new SIP002, legacy format is depreciated).
|
||||||
// reference: https://github.com/shadowsocks/shadowsocks-org/wiki/SIP002-URI-Scheme
|
// reference: https://github.com/shadowsocks/shadowsocks-org/wiki/SIP002-URI-Scheme
|
||||||
function URI_SS() {
|
function URI_SS() {
|
||||||
@@ -99,6 +152,7 @@ function URI_SS() {
|
|||||||
query = parsed[2];
|
query = parsed[2];
|
||||||
}
|
}
|
||||||
content = Base64.decode(content);
|
content = Base64.decode(content);
|
||||||
|
|
||||||
if (query) {
|
if (query) {
|
||||||
if (/(&|\?)v2ray-plugin=/.test(query)) {
|
if (/(&|\?)v2ray-plugin=/.test(query)) {
|
||||||
const parsed = query.match(/(&|\?)v2ray-plugin=(.*?)(&|$)/);
|
const parsed = query.match(/(&|\?)v2ray-plugin=(.*?)(&|$)/);
|
||||||
@@ -112,8 +166,11 @@ function URI_SS() {
|
|||||||
}
|
}
|
||||||
content = `${content}${query}`;
|
content = `${content}${query}`;
|
||||||
}
|
}
|
||||||
userInfoStr = content.split('@')[0];
|
userInfoStr = content.match(/(^.*)@/)?.[1];
|
||||||
serverAndPortArray = content.match(/@([^/]*)(\/|$)/);
|
serverAndPortArray = content.match(/@([^/@]*)(\/|$)/);
|
||||||
|
} else if (content.includes('?')) {
|
||||||
|
const parsed = content.match(/(\?.*)$/);
|
||||||
|
query = parsed[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverAndPort = serverAndPortArray[1];
|
const serverAndPort = serverAndPortArray[1];
|
||||||
@@ -132,11 +189,12 @@ function URI_SS() {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
// handle obfs
|
// handle obfs
|
||||||
const idx = content.indexOf('?plugin=');
|
const pluginMatch = content.match(/[?&]plugin=([^&]+)/);
|
||||||
if (idx !== -1) {
|
const shadowTlsMatch = content.match(/[?&]shadow-tls=([^&]+)/);
|
||||||
|
|
||||||
|
if (pluginMatch) {
|
||||||
const pluginInfo = (
|
const pluginInfo = (
|
||||||
'plugin=' +
|
'plugin=' + decodeURIComponent(pluginMatch[1])
|
||||||
decodeURIComponent(content.split('?plugin=')[1].split('&')[0])
|
|
||||||
).split(';');
|
).split(';');
|
||||||
const params = {};
|
const params = {};
|
||||||
for (const item of pluginInfo) {
|
for (const item of pluginInfo) {
|
||||||
@@ -161,12 +219,41 @@ function URI_SS() {
|
|||||||
tls: getIfPresent(params.tls),
|
tls: getIfPresent(params.tls),
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
case 'shadow-tls': {
|
||||||
|
proxy.plugin = 'shadow-tls';
|
||||||
|
const version = getIfNotBlank(params['version']);
|
||||||
|
proxy['plugin-opts'] = {
|
||||||
|
host: getIfNotBlank(params['host']),
|
||||||
|
password: getIfNotBlank(params['password']),
|
||||||
|
version: version ? parseInt(version, 10) : undefined,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unsupported plugin option: ${params.plugin}`,
|
`Unsupported plugin option: ${params.plugin}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Shadowrocket
|
||||||
|
if (shadowTlsMatch) {
|
||||||
|
const params = JSON.parse(Base64.decode(shadowTlsMatch[1]));
|
||||||
|
const version = getIfNotBlank(params['version']);
|
||||||
|
const address = getIfNotBlank(params['address']);
|
||||||
|
const port = getIfNotBlank(params['port']);
|
||||||
|
proxy.plugin = 'shadow-tls';
|
||||||
|
proxy['plugin-opts'] = {
|
||||||
|
host: getIfNotBlank(params['host']),
|
||||||
|
password: getIfNotBlank(params['password']),
|
||||||
|
version: version ? parseInt(version, 10) : undefined,
|
||||||
|
};
|
||||||
|
if (address) {
|
||||||
|
proxy.server = address;
|
||||||
|
}
|
||||||
|
if (port) {
|
||||||
|
proxy.port = parseInt(port, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (/(&|\?)uot=(1|true)/i.test(query)) {
|
if (/(&|\?)uot=(1|true)/i.test(query)) {
|
||||||
proxy['udp-over-tcp'] = true;
|
proxy['udp-over-tcp'] = true;
|
||||||
}
|
}
|
||||||
@@ -529,6 +616,9 @@ function URI_VLESS() {
|
|||||||
if (params.sid) {
|
if (params.sid) {
|
||||||
opts['short-id'] = params.sid;
|
opts['short-id'] = params.sid;
|
||||||
}
|
}
|
||||||
|
if (params.spx) {
|
||||||
|
opts['_spider-x'] = params.spx;
|
||||||
|
}
|
||||||
if (Object.keys(opts).length > 0) {
|
if (Object.keys(opts).length > 0) {
|
||||||
// proxy[`${params.security}-opts`] = opts;
|
// proxy[`${params.security}-opts`] = opts;
|
||||||
proxy[`${params.security}-opts`] = opts;
|
proxy[`${params.security}-opts`] = opts;
|
||||||
@@ -596,6 +686,13 @@ function URI_VLESS() {
|
|||||||
// mKCP 的伪装头部类型。当前可选值有 none / srtp / utp / wechat-video / dtls / wireguard。省略时默认值为 none,即不使用伪装头部,但不可以为空字符串。
|
// mKCP 的伪装头部类型。当前可选值有 none / srtp / utp / wechat-video / dtls / wireguard。省略时默认值为 none,即不使用伪装头部,但不可以为空字符串。
|
||||||
proxy.headerType = params.headerType || 'none';
|
proxy.headerType = params.headerType || 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (params.mode) {
|
||||||
|
proxy._mode = params.mode;
|
||||||
|
}
|
||||||
|
if (params.extra) {
|
||||||
|
proxy._extra = params.extra;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return proxy;
|
return proxy;
|
||||||
@@ -763,8 +860,11 @@ function URI_TUIC() {
|
|||||||
const parse = (line) => {
|
const parse = (line) => {
|
||||||
line = line.split(/tuic:\/\//)[1];
|
line = line.split(/tuic:\/\//)[1];
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
let [__, uuid, password, server, ___, port, ____, addons = '', name] =
|
let [__, auth, server, port, addons = '', name] =
|
||||||
/^(.*?):(.*?)@(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line);
|
/^(.*?)@(.*?)(?::(\d+))?\/?(?:\?(.*?))?(?:#(.*?))?$/.exec(line);
|
||||||
|
auth = decodeURIComponent(auth);
|
||||||
|
let [uuid, ...passwordParts] = auth.split(':');
|
||||||
|
let password = passwordParts.join(':');
|
||||||
port = parseInt(`${port}`, 10);
|
port = parseInt(`${port}`, 10);
|
||||||
if (isNaN(port)) {
|
if (isNaN(port)) {
|
||||||
port = 443;
|
port = 443;
|
||||||
@@ -786,12 +886,14 @@ function URI_TUIC() {
|
|||||||
|
|
||||||
for (const addon of addons.split('&')) {
|
for (const addon of addons.split('&')) {
|
||||||
let [key, value] = addon.split('=');
|
let [key, value] = addon.split('=');
|
||||||
key = key.replace(/_/, '-');
|
key = key.replace(/_/g, '-');
|
||||||
value = decodeURIComponent(value);
|
value = decodeURIComponent(value);
|
||||||
if (['alpn'].includes(key)) {
|
if (['alpn'].includes(key)) {
|
||||||
proxy[key] = value ? value.split(',') : undefined;
|
proxy[key] = value ? value.split(',') : undefined;
|
||||||
} else if (['allow-insecure'].includes(key)) {
|
} else if (['allow-insecure'].includes(key)) {
|
||||||
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(value);
|
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(value);
|
||||||
|
} else if (['fast-open'].includes(key)) {
|
||||||
|
proxy.tfo = true;
|
||||||
} else if (['disable-sni', 'reduce-rtt'].includes(key)) {
|
} else if (['disable-sni', 'reduce-rtt'].includes(key)) {
|
||||||
proxy[key] = /(TRUE)|1/i.test(value);
|
proxy[key] = /(TRUE)|1/i.test(value);
|
||||||
} else {
|
} else {
|
||||||
@@ -929,6 +1031,7 @@ function Clash_All() {
|
|||||||
const proxy = JSON.parse(line);
|
const proxy = JSON.parse(line);
|
||||||
if (
|
if (
|
||||||
![
|
![
|
||||||
|
'anytls',
|
||||||
'mieru',
|
'mieru',
|
||||||
'juicity',
|
'juicity',
|
||||||
'ss',
|
'ss',
|
||||||
@@ -1431,6 +1534,7 @@ function isIP(ip) {
|
|||||||
|
|
||||||
export default [
|
export default [
|
||||||
URI_PROXY(),
|
URI_PROXY(),
|
||||||
|
URI_SOCKS(),
|
||||||
URI_SS(),
|
URI_SS(),
|
||||||
URI_SSR(),
|
URI_SSR(),
|
||||||
URI_VMess(),
|
URI_VMess(),
|
||||||
|
|||||||
@@ -39,12 +39,12 @@ start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/socks5/hysteria2
|
|||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/others)*{
|
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/others)*{
|
||||||
proxy.type = "ssr";
|
proxy.type = "ssr";
|
||||||
// handle ssr obfs
|
// handle ssr obfs
|
||||||
proxy.obfs = obfs.type;
|
proxy.obfs = obfs.type;
|
||||||
}
|
}
|
||||||
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/others)* {
|
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/others)* {
|
||||||
proxy.type = "ss";
|
proxy.type = "ss";
|
||||||
// handle ss obfs
|
// handle ss obfs
|
||||||
if (obfs.type == "http" || obfs.type === "tls") {
|
if (obfs.type == "http" || obfs.type === "tls") {
|
||||||
@@ -54,31 +54,31 @@ shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs
|
|||||||
$set(proxy, "plugin-opts.path", obfs.path);
|
$set(proxy, "plugin-opts.path", obfs.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/others)* {
|
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/ip_mode/others)* {
|
||||||
proxy.type = "vmess";
|
proxy.type = "vmess";
|
||||||
proxy.cipher = proxy.cipher || "none";
|
proxy.cipher = proxy.cipher || "none";
|
||||||
proxy.alterId = proxy.alterId || 0;
|
proxy.alterId = proxy.alterId || 0;
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
|
||||||
proxy.type = "vless";
|
proxy.type = "vless";
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
|
||||||
proxy.type = "trojan";
|
proxy.type = "trojan";
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/others)* {
|
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/ip_mode/others)* {
|
||||||
proxy.type = "hysteria2";
|
proxy.type = "hysteria2";
|
||||||
}
|
}
|
||||||
https = tag equals "https"i address (username password)? (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
https = tag equals "https"i address (username password)? (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
}
|
}
|
||||||
http = tag equals "http"i address (username password)? (fast_open/udp_relay/others)* {
|
http = tag equals "http"i address (username password)? (fast_open/udp_relay/ip_mode/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
}
|
}
|
||||||
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
|
||||||
proxy.type = "socks5";
|
proxy.type = "socks5";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,6 +169,11 @@ ssr_protocol_param = comma "protocol-param" equals param:$[^=,]+ { proxy["protoc
|
|||||||
|
|
||||||
vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseInt(alterId); }
|
vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseInt(alterId); }
|
||||||
|
|
||||||
|
udp_port = comma "udp-port" equals match:$[0-9]+ { proxy["udp-port"] = parseInt(match.trim()); }
|
||||||
|
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(""); }
|
||||||
|
|
||||||
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
||||||
tls_host = comma sni:("tls-name"/"sni") equals host:domain { proxy.sni = host; }
|
tls_host = comma sni:("tls-name"/"sni") equals host:domain { proxy.sni = host; }
|
||||||
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
|
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
|
||||||
@@ -177,6 +182,7 @@ tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pu
|
|||||||
|
|
||||||
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; }
|
||||||
|
ip_mode = comma "ip-mode" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }
|
||||||
|
|
||||||
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
||||||
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
||||||
|
|||||||
@@ -37,12 +37,12 @@ start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/socks5/hysteria2
|
|||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/others)*{
|
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/others)*{
|
||||||
proxy.type = "ssr";
|
proxy.type = "ssr";
|
||||||
// handle ssr obfs
|
// handle ssr obfs
|
||||||
proxy.obfs = obfs.type;
|
proxy.obfs = obfs.type;
|
||||||
}
|
}
|
||||||
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/others)* {
|
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/others)* {
|
||||||
proxy.type = "ss";
|
proxy.type = "ss";
|
||||||
// handle ss obfs
|
// handle ss obfs
|
||||||
if (obfs.type == "http" || obfs.type === "tls") {
|
if (obfs.type == "http" || obfs.type === "tls") {
|
||||||
@@ -52,31 +52,31 @@ shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs
|
|||||||
$set(proxy, "plugin-opts.path", obfs.path);
|
$set(proxy, "plugin-opts.path", obfs.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/others)* {
|
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/ip_mode/others)* {
|
||||||
proxy.type = "vmess";
|
proxy.type = "vmess";
|
||||||
proxy.cipher = proxy.cipher || "none";
|
proxy.cipher = proxy.cipher || "none";
|
||||||
proxy.alterId = proxy.alterId || 0;
|
proxy.alterId = proxy.alterId || 0;
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
|
||||||
proxy.type = "vless";
|
proxy.type = "vless";
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
|
||||||
proxy.type = "trojan";
|
proxy.type = "trojan";
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/others)* {
|
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/ip_mode/others)* {
|
||||||
proxy.type = "hysteria2";
|
proxy.type = "hysteria2";
|
||||||
}
|
}
|
||||||
https = tag equals "https"i address (username password)? (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
https = tag equals "https"i address (username password)? (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
}
|
}
|
||||||
http = tag equals "http"i address (username password)? (fast_open/udp_relay/others)* {
|
http = tag equals "http"i address (username password)? (fast_open/udp_relay/ip_mode/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
}
|
}
|
||||||
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
|
||||||
proxy.type = "socks5";
|
proxy.type = "socks5";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,6 +167,11 @@ ssr_protocol_param = comma "protocol-param" equals param:$[^=,]+ { proxy["protoc
|
|||||||
|
|
||||||
vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseInt(alterId); }
|
vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseInt(alterId); }
|
||||||
|
|
||||||
|
udp_port = comma "udp-port" equals match:$[0-9]+ { proxy["udp-port"] = parseInt(match.trim()); }
|
||||||
|
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(""); }
|
||||||
|
|
||||||
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
||||||
tls_host = comma sni:("tls-name"/"sni") equals host:domain { proxy.sni = host; }
|
tls_host = comma sni:("tls-name"/"sni") equals host:domain { proxy.sni = host; }
|
||||||
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
|
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
|
||||||
@@ -175,6 +180,7 @@ tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pu
|
|||||||
|
|
||||||
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; }
|
||||||
|
ip_mode = comma "ip-mode" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }
|
||||||
|
|
||||||
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
||||||
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v
|
|||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/udp_port/others)* {
|
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/udp_port/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") {
|
||||||
@@ -52,7 +52,7 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
|
|||||||
}
|
}
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls/sni/tls_fingerprint/tls_verification/fast_open/tfo/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "vmess";
|
proxy.type = "vmess";
|
||||||
proxy.cipher = proxy.cipher || "none";
|
proxy.cipher = proxy.cipher || "none";
|
||||||
if (proxy.aead) {
|
if (proxy.aead) {
|
||||||
@@ -63,25 +63,25 @@ vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/
|
|||||||
handleWebsocket();
|
handleWebsocket();
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "trojan";
|
proxy.type = "trojan";
|
||||||
handleWebsocket();
|
handleWebsocket();
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
https = tag equals "https" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
https = tag equals "https" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
ssh = tag equals "ssh" address (username password)? (usernamek passwordk)? (server_fingerprint/idle_timeout/private_key/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
ssh = tag equals "ssh" address (username password)? (usernamek passwordk)? (server_fingerprint/idle_timeout/private_key/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "ssh";
|
proxy.type = "ssh";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/udp_relay/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/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") {
|
||||||
@@ -104,20 +104,20 @@ wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/under
|
|||||||
proxy.type = "wireguard-surge";
|
proxy.type = "wireguard-surge";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/port_hopping_interval/others)* {
|
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/fast_open/tfo/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/port_hopping_interval/others)* {
|
||||||
proxy.type = "hysteria2";
|
proxy.type = "hysteria2";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "socks5";
|
proxy.type = "socks5";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_fingerprint/tls_verification/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_fingerprint/tls_verification/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "socks5";
|
proxy.type = "socks5";
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
direct = tag equals "direct" (udp_relay/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/block_quic/others)* {
|
direct = tag equals "direct" (udp_relay/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/block_quic/others)* {
|
||||||
proxy.type = "direct";
|
proxy.type = "direct";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v
|
|||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/udp_port/others)* {
|
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/udp_port/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") {
|
||||||
@@ -50,7 +50,7 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
|
|||||||
}
|
}
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls/sni/tls_fingerprint/tls_verification/fast_open/tfo/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "vmess";
|
proxy.type = "vmess";
|
||||||
proxy.cipher = proxy.cipher || "none";
|
proxy.cipher = proxy.cipher || "none";
|
||||||
if (proxy.aead) {
|
if (proxy.aead) {
|
||||||
@@ -61,25 +61,25 @@ vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/
|
|||||||
handleWebsocket();
|
handleWebsocket();
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "trojan";
|
proxy.type = "trojan";
|
||||||
handleWebsocket();
|
handleWebsocket();
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
https = tag equals "https" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
https = tag equals "https" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
ssh = tag equals "ssh" address (username password)? (usernamek passwordk)? (server_fingerprint/idle_timeout/private_key/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
ssh = tag equals "ssh" address (username password)? (usernamek passwordk)? (server_fingerprint/idle_timeout/private_key/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "ssh";
|
proxy.type = "ssh";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/udp_relay/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/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") {
|
||||||
@@ -106,16 +106,16 @@ hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying
|
|||||||
proxy.type = "hysteria2";
|
proxy.type = "hysteria2";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "socks5";
|
proxy.type = "socks5";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_fingerprint/tls_verification/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_fingerprint/tls_verification/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "socks5";
|
proxy.type = "socks5";
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
direct = tag equals "direct" (udp_relay/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/block_quic/others)* {
|
direct = tag equals "direct" (udp_relay/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/block_quic/others)* {
|
||||||
proxy.type = "direct";
|
proxy.type = "direct";
|
||||||
}
|
}
|
||||||
address = comma server:server comma port:port {
|
address = comma server:server comma port:port {
|
||||||
|
|||||||
@@ -80,6 +80,9 @@ port = digits:[0-9]+ {
|
|||||||
}
|
}
|
||||||
|
|
||||||
params = "?" head:param tail:("&"@param)* {
|
params = "?" head:param tail:("&"@param)* {
|
||||||
|
for (const [key, value] of Object.entries(params)) {
|
||||||
|
params[key] = decodeURIComponent(value);
|
||||||
|
}
|
||||||
proxy["skip-cert-verify"] = toBool(params["allowInsecure"]);
|
proxy["skip-cert-verify"] = toBool(params["allowInsecure"]);
|
||||||
proxy.sni = params["sni"] || params["peer"];
|
proxy.sni = params["sni"] || params["peer"];
|
||||||
proxy['client-fingerprint'] = params.fp;
|
proxy['client-fingerprint'] = params.fp;
|
||||||
@@ -115,6 +118,27 @@ params = "?" head:param tail:("&"@param)* {
|
|||||||
$set(proxy, proxy.network+"-opts.v2ray-http-upgrade-fast-open", true);
|
$set(proxy, proxy.network+"-opts.v2ray-http-upgrade-fast-open", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (['reality'].includes(params.security)) {
|
||||||
|
const opts = {};
|
||||||
|
if (params.pbk) {
|
||||||
|
opts['public-key'] = params.pbk;
|
||||||
|
}
|
||||||
|
if (params.sid) {
|
||||||
|
opts['short-id'] = params.sid;
|
||||||
|
}
|
||||||
|
if (params.spx) {
|
||||||
|
opts['_spider-x'] = params.spx;
|
||||||
|
}
|
||||||
|
if (params.mode) {
|
||||||
|
proxy._mode = params.mode;
|
||||||
|
}
|
||||||
|
if (params.extra) {
|
||||||
|
proxy._extra = params.extra;
|
||||||
|
}
|
||||||
|
if (Object.keys(opts).length > 0) {
|
||||||
|
$set(proxy, params.security+"-opts", opts);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy.udp = toBool(params["udp"]);
|
proxy.udp = toBool(params["udp"]);
|
||||||
|
|||||||
@@ -78,6 +78,9 @@ port = digits:[0-9]+ {
|
|||||||
}
|
}
|
||||||
|
|
||||||
params = "?" head:param tail:("&"@param)* {
|
params = "?" head:param tail:("&"@param)* {
|
||||||
|
for (const [key, value] of Object.entries(params)) {
|
||||||
|
params[key] = decodeURIComponent(value);
|
||||||
|
}
|
||||||
proxy["skip-cert-verify"] = toBool(params["allowInsecure"]);
|
proxy["skip-cert-verify"] = toBool(params["allowInsecure"]);
|
||||||
proxy.sni = params["sni"] || params["peer"];
|
proxy.sni = params["sni"] || params["peer"];
|
||||||
proxy['client-fingerprint'] = params.fp;
|
proxy['client-fingerprint'] = params.fp;
|
||||||
@@ -113,6 +116,27 @@ params = "?" head:param tail:("&"@param)* {
|
|||||||
$set(proxy, proxy.network+"-opts.v2ray-http-upgrade-fast-open", true);
|
$set(proxy, proxy.network+"-opts.v2ray-http-upgrade-fast-open", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (['reality'].includes(params.security)) {
|
||||||
|
const opts = {};
|
||||||
|
if (params.pbk) {
|
||||||
|
opts['public-key'] = params.pbk;
|
||||||
|
}
|
||||||
|
if (params.sid) {
|
||||||
|
opts['short-id'] = params.sid;
|
||||||
|
}
|
||||||
|
if (params.spx) {
|
||||||
|
opts['_spider-x'] = params.spx;
|
||||||
|
}
|
||||||
|
if (params.mode) {
|
||||||
|
proxy._mode = params.mode;
|
||||||
|
}
|
||||||
|
if (params.extra) {
|
||||||
|
proxy._extra = params.extra;
|
||||||
|
}
|
||||||
|
if (Object.keys(opts).length > 0) {
|
||||||
|
$set(proxy, params.security+"-opts", opts);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy.udp = toBool(params["udp"]);
|
proxy.udp = toBool(params["udp"]);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { safeLoad } from '@/utils/yaml';
|
import { safeLoad } from '@/utils/yaml';
|
||||||
import { Base64 } from 'js-base64';
|
import { Base64 } from 'js-base64';
|
||||||
|
import $ from '@/core/app';
|
||||||
|
|
||||||
function HTML() {
|
function HTML() {
|
||||||
const name = 'HTML';
|
const name = 'HTML';
|
||||||
@@ -15,6 +16,7 @@ function Base64Encoded() {
|
|||||||
const keys = [
|
const keys = [
|
||||||
'dm1lc3M', // vmess
|
'dm1lc3M', // vmess
|
||||||
'c3NyOi8v', // ssr://
|
'c3NyOi8v', // ssr://
|
||||||
|
'c29ja3M6Ly', // socks://
|
||||||
'dHJvamFu', // trojan
|
'dHJvamFu', // trojan
|
||||||
'c3M6Ly', // ss:/
|
'c3M6Ly', // ss:/
|
||||||
'c3NkOi8v', // ssd://
|
'c3NkOi8v', // ssd://
|
||||||
@@ -35,8 +37,15 @@ function Base64Encoded() {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
const parse = function (raw) {
|
const parse = function (raw) {
|
||||||
raw = Base64.decode(raw);
|
const decoded = Base64.decode(raw);
|
||||||
return raw;
|
if (!/^\w+(:\/\/|\s*?=\s*?)\w+/m.test(decoded)) {
|
||||||
|
$.error(
|
||||||
|
`Base64 Pre-processor error: decoded line does not start with protocol`,
|
||||||
|
);
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
return decoded;
|
||||||
};
|
};
|
||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
}
|
}
|
||||||
@@ -48,43 +57,48 @@ function Clash() {
|
|||||||
const content = safeLoad(raw);
|
const content = safeLoad(raw);
|
||||||
return content.proxies && Array.isArray(content.proxies);
|
return content.proxies && Array.isArray(content.proxies);
|
||||||
};
|
};
|
||||||
const parse = function (raw) {
|
const parse = function (raw, includeProxies) {
|
||||||
// Clash YAML format
|
// Clash YAML format
|
||||||
|
|
||||||
// 防止 VLESS节点 reality-opts 选项中的 short-id 被解析成 Infinity
|
// 防止 VLESS节点 reality-opts 选项中的 short-id 被解析成 Infinity
|
||||||
// 匹配 short-id 冒号后面的值(包含空格和引号)
|
// 匹配 short-id 冒号后面的值(包含空格和引号)
|
||||||
const afterReplace = raw.replace(
|
const afterReplace = raw.replace(
|
||||||
/short-id:([ ]*[^,\n}]*)/g,
|
/short-id:([ \t]*[^#\n,}]*)/g,
|
||||||
(matched, value) => {
|
(matched, value) => {
|
||||||
const afterTrim = value.trim();
|
const afterTrim = value.trim();
|
||||||
|
|
||||||
// 为空
|
// 为空
|
||||||
if (!afterTrim || afterTrim === '') {
|
if (!afterTrim || afterTrim === '') {
|
||||||
return 'short-id: ""'
|
return 'short-id: ""';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 是否被引号包裹
|
// 是否被引号包裹
|
||||||
if (/^(['"]).*\1$/.test(afterTrim)) {
|
if (/^(['"]).*\1$/.test(afterTrim)) {
|
||||||
return `short-id: ${afterTrim}`;
|
return `short-id: ${afterTrim}`;
|
||||||
} else {
|
} else {
|
||||||
return `short-id: "${afterTrim}"`
|
return `short-id: "${afterTrim}"`;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
proxies,
|
proxies,
|
||||||
'global-client-fingerprint': globalClientFingerprint,
|
'global-client-fingerprint': globalClientFingerprint,
|
||||||
} = safeLoad(afterReplace);
|
} = safeLoad(afterReplace);
|
||||||
return proxies
|
return (
|
||||||
.map((p) => {
|
(includeProxies ? 'proxies:\n' : '') +
|
||||||
// https://github.com/MetaCubeX/mihomo/blob/Alpha/docs/config.yaml#L73C1-L73C26
|
proxies
|
||||||
if (globalClientFingerprint && !p['client-fingerprint']) {
|
.map((p) => {
|
||||||
p['client-fingerprint'] = globalClientFingerprint;
|
// https://github.com/MetaCubeX/mihomo/blob/Alpha/docs/config.yaml#L73C1-L73C26
|
||||||
}
|
if (globalClientFingerprint && !p['client-fingerprint']) {
|
||||||
return JSON.stringify(p);
|
p['client-fingerprint'] = globalClientFingerprint;
|
||||||
})
|
}
|
||||||
.join('\n');
|
return `${includeProxies ? ' - ' : ''}${JSON.stringify(
|
||||||
|
p,
|
||||||
|
)}\n`;
|
||||||
|
})
|
||||||
|
.join('')
|
||||||
|
);
|
||||||
};
|
};
|
||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { hex_md5 } from '@/vendor/md5';
|
|||||||
import { ProxyUtils } from '@/core/proxy-utils';
|
import { ProxyUtils } from '@/core/proxy-utils';
|
||||||
import { produceArtifact } from '@/restful/sync';
|
import { produceArtifact } from '@/restful/sync';
|
||||||
import { SETTINGS_KEY } from '@/constants';
|
import { SETTINGS_KEY } from '@/constants';
|
||||||
|
import YAML from '@/utils/yaml';
|
||||||
|
|
||||||
import env from '@/utils/env';
|
import env from '@/utils/env';
|
||||||
import {
|
import {
|
||||||
@@ -19,8 +20,49 @@ import {
|
|||||||
validCheck,
|
validCheck,
|
||||||
flowTransfer,
|
flowTransfer,
|
||||||
getRmainingDays,
|
getRmainingDays,
|
||||||
|
normalizeFlowHeader,
|
||||||
} from '@/utils/flow';
|
} from '@/utils/flow';
|
||||||
|
|
||||||
|
function isObject(item) {
|
||||||
|
return item && typeof item === 'object' && !Array.isArray(item);
|
||||||
|
}
|
||||||
|
function trimWrap(str) {
|
||||||
|
if (str.startsWith('<') && str.endsWith('>')) {
|
||||||
|
return str.slice(1, -1);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
function deepMerge(target, _other) {
|
||||||
|
const other = typeof _other === 'string' ? JSON.parse(_other) : _other;
|
||||||
|
for (const key in other) {
|
||||||
|
if (isObject(other[key])) {
|
||||||
|
if (key.endsWith('!')) {
|
||||||
|
const k = trimWrap(key.slice(0, -1));
|
||||||
|
target[k] = other[key];
|
||||||
|
} else {
|
||||||
|
const k = trimWrap(key);
|
||||||
|
if (!target[k]) Object.assign(target, { [k]: {} });
|
||||||
|
deepMerge(target[k], other[k]);
|
||||||
|
}
|
||||||
|
} else if (Array.isArray(other[key])) {
|
||||||
|
if (key.startsWith('+')) {
|
||||||
|
const k = trimWrap(key.slice(1));
|
||||||
|
if (!target[k]) Object.assign(target, { [k]: [] });
|
||||||
|
target[k] = [...other[key], ...target[k]];
|
||||||
|
} else if (key.endsWith('+')) {
|
||||||
|
const k = trimWrap(key.slice(0, -1));
|
||||||
|
if (!target[k]) Object.assign(target, { [k]: [] });
|
||||||
|
target[k] = [...target[k], ...other[key]];
|
||||||
|
} else {
|
||||||
|
const k = trimWrap(key);
|
||||||
|
Object.assign(target, { [k]: other[key] });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Object.assign(target, { [key]: other[key] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
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:
|
||||||
{
|
{
|
||||||
@@ -321,6 +363,43 @@ function ScriptOperator(script, targetPlatform, $arguments, source, $options) {
|
|||||||
name: 'Script Operator',
|
name: 'Script Operator',
|
||||||
func: async (proxies) => {
|
func: async (proxies) => {
|
||||||
let output = proxies;
|
let output = proxies;
|
||||||
|
if (output?.$file?.type === 'mihomoProfile') {
|
||||||
|
try {
|
||||||
|
let patch = YAML.safeLoad(script);
|
||||||
|
let config;
|
||||||
|
if (output?.$content) {
|
||||||
|
try {
|
||||||
|
config = YAML.safeLoad(output?.$content);
|
||||||
|
} catch (e) {
|
||||||
|
$.error(e.message ?? e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if (typeof patch !== 'object') patch = {};
|
||||||
|
if (typeof patch !== 'object')
|
||||||
|
throw new Error('patch is not an object');
|
||||||
|
output.$content = ProxyUtils.yaml.safeDump(
|
||||||
|
deepMerge(
|
||||||
|
config || {
|
||||||
|
proxies: await produceArtifact({
|
||||||
|
type:
|
||||||
|
output?.$file?.sourceType ||
|
||||||
|
'collection',
|
||||||
|
name: output?.$file?.sourceName,
|
||||||
|
platform: 'mihomo',
|
||||||
|
produceType: 'internal',
|
||||||
|
produceOpts: {
|
||||||
|
'delete-underscore-fields': true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
patch,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return output;
|
||||||
|
} catch (e) {
|
||||||
|
// console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
await (async function () {
|
await (async function () {
|
||||||
const operator = createDynamicFunction(
|
const operator = createDynamicFunction(
|
||||||
'operator',
|
'operator',
|
||||||
@@ -339,9 +418,34 @@ function ScriptOperator(script, targetPlatform, $arguments, source, $options) {
|
|||||||
'operator',
|
'operator',
|
||||||
`async function operator(input = []) {
|
`async function operator(input = []) {
|
||||||
if (input && (input.$files || input.$content)) {
|
if (input && (input.$files || input.$content)) {
|
||||||
let { $content, $files, $options } = input
|
let { $content, $files, $options, $file } = input
|
||||||
${script}
|
if($file.type === 'mihomoProfile') {
|
||||||
return { $content, $files, $options }
|
${script}
|
||||||
|
if(typeof main === 'function') {
|
||||||
|
let config;
|
||||||
|
if ($content) {
|
||||||
|
try {
|
||||||
|
config = ProxyUtils.yaml.safeLoad($content);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e.message ?? e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$content = ProxyUtils.yaml.safeDump(await main(config || {
|
||||||
|
proxies: await produceArtifact({
|
||||||
|
type: $file.sourceType || 'collection',
|
||||||
|
name: $file.sourceName,
|
||||||
|
platform: 'mihomo',
|
||||||
|
produceType: 'internal',
|
||||||
|
produceOpts: {
|
||||||
|
'delete-underscore-fields': true
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
${script}
|
||||||
|
}
|
||||||
|
return { $content, $files, $options, $file }
|
||||||
} else {
|
} else {
|
||||||
let proxies = input
|
let proxies = input
|
||||||
let list = []
|
let list = []
|
||||||
@@ -995,6 +1099,7 @@ function createDynamicFunction(name, script, $arguments, $options) {
|
|||||||
flowTransfer,
|
flowTransfer,
|
||||||
validCheck,
|
validCheck,
|
||||||
getRmainingDays,
|
getRmainingDays,
|
||||||
|
normalizeFlowHeader,
|
||||||
};
|
};
|
||||||
if ($.env.isLoon) {
|
if ($.env.isLoon) {
|
||||||
return new Function(
|
return new Function(
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ export default function Clash_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 === 'snell' && proxy.version < 3) {
|
||||||
|
delete proxy.udp;
|
||||||
} else if (proxy.type === 'vless') {
|
} else if (proxy.type === 'vless') {
|
||||||
if (isPresent(proxy, 'sni')) {
|
if (isPresent(proxy, 'sni')) {
|
||||||
proxy.servername = proxy.sni;
|
proxy.servername = proxy.sni;
|
||||||
@@ -139,6 +141,7 @@ export default function Clash_Producer() {
|
|||||||
'hysteria',
|
'hysteria',
|
||||||
'hysteria2',
|
'hysteria2',
|
||||||
'juicity',
|
'juicity',
|
||||||
|
'anytls',
|
||||||
].includes(proxy.type)
|
].includes(proxy.type)
|
||||||
) {
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
|
|||||||
@@ -32,9 +32,10 @@ export default function ClashMeta_Producer() {
|
|||||||
isPresent(proxy, 'cipher') &&
|
isPresent(proxy, 'cipher') &&
|
||||||
![
|
![
|
||||||
'auto',
|
'auto',
|
||||||
|
'none',
|
||||||
|
'zero',
|
||||||
'aes-128-gcm',
|
'aes-128-gcm',
|
||||||
'chacha20-poly1305',
|
'chacha20-poly1305',
|
||||||
'none',
|
|
||||||
].includes(proxy.cipher)
|
].includes(proxy.cipher)
|
||||||
) {
|
) {
|
||||||
proxy.cipher = 'auto';
|
proxy.cipher = 'auto';
|
||||||
@@ -86,6 +87,8 @@ 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 === 'snell' && proxy.version < 3) {
|
||||||
|
delete proxy.udp;
|
||||||
} else if (proxy.type === 'vless') {
|
} else if (proxy.type === 'vless') {
|
||||||
if (isPresent(proxy, 'sni')) {
|
if (isPresent(proxy, 'sni')) {
|
||||||
proxy.servername = proxy.sni;
|
proxy.servername = proxy.sni;
|
||||||
@@ -102,6 +105,9 @@ export default function ClashMeta_Producer() {
|
|||||||
password: proxy['shadow-tls-password'],
|
password: proxy['shadow-tls-password'],
|
||||||
version: proxy['shadow-tls-version'],
|
version: proxy['shadow-tls-version'],
|
||||||
};
|
};
|
||||||
|
delete proxy['shadow-tls-password'];
|
||||||
|
delete proxy['shadow-tls-sni'];
|
||||||
|
delete proxy['shadow-tls-version'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,6 +163,7 @@ export default function ClashMeta_Producer() {
|
|||||||
'hysteria',
|
'hysteria',
|
||||||
'hysteria2',
|
'hysteria2',
|
||||||
'juicity',
|
'juicity',
|
||||||
|
'anytls',
|
||||||
].includes(proxy.type)
|
].includes(proxy.type)
|
||||||
) {
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
@@ -180,7 +187,7 @@ export default function ClashMeta_Producer() {
|
|||||||
delete proxy.id;
|
delete proxy.id;
|
||||||
delete proxy.resolved;
|
delete proxy.resolved;
|
||||||
delete proxy['no-resolve'];
|
delete proxy['no-resolve'];
|
||||||
if (type !== 'internal') {
|
if (type !== 'internal' || opts['delete-underscore-fields']) {
|
||||||
for (const key in proxy) {
|
for (const key in proxy) {
|
||||||
if (proxy[key] == null || /^_/i.test(key)) {
|
if (proxy[key] == null || /^_/i.test(key)) {
|
||||||
delete proxy[key];
|
delete proxy[key];
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
import { isPresent } from './utils';
|
||||||
|
|
||||||
export default function Egern_Producer() {
|
export default function Egern_Producer() {
|
||||||
const type = 'ALL';
|
const type = 'ALL';
|
||||||
const produce = (proxies, type, opts = {}) => {
|
const produce = (proxies, type) => {
|
||||||
// https://egernapp.com/zh-CN/docs/configuration/proxies
|
// https://egernapp.com/zh-CN/docs/configuration/proxies
|
||||||
const list = proxies
|
const list = proxies
|
||||||
.filter((proxy) => {
|
.filter((proxy) => {
|
||||||
if (opts['include-unsupported-proxy']) return true;
|
// if (opts['include-unsupported-proxy']) return true;
|
||||||
if (
|
if (
|
||||||
![
|
![
|
||||||
'http',
|
'http',
|
||||||
@@ -14,6 +16,7 @@ export default function Egern_Producer() {
|
|||||||
'hysteria2',
|
'hysteria2',
|
||||||
'vless',
|
'vless',
|
||||||
'vmess',
|
'vmess',
|
||||||
|
'tuic',
|
||||||
].includes(proxy.type) ||
|
].includes(proxy.type) ||
|
||||||
(proxy.type === 'ss' &&
|
(proxy.type === 'ss' &&
|
||||||
((proxy.plugin === 'obfs' &&
|
((proxy.plugin === 'obfs' &&
|
||||||
@@ -47,17 +50,12 @@ export default function Egern_Producer() {
|
|||||||
'salsa20',
|
'salsa20',
|
||||||
'chacha20',
|
'chacha20',
|
||||||
'chacha20-ietf',
|
'chacha20-ietf',
|
||||||
|
'2022-blake3-aes-128-gcm',
|
||||||
|
'2022-blake3-aes-256-gcm',
|
||||||
].includes(proxy.cipher))) ||
|
].includes(proxy.cipher))) ||
|
||||||
(proxy.type === 'vmess' &&
|
(proxy.type === 'vmess' &&
|
||||||
(![
|
!['http', 'ws', 'tcp'].includes(proxy.network) &&
|
||||||
'auto',
|
proxy.network) ||
|
||||||
'aes-128-gcm',
|
|
||||||
'chacha20-poly1305',
|
|
||||||
'none',
|
|
||||||
'zero',
|
|
||||||
].includes(proxy.cipher) ||
|
|
||||||
(!['http', 'ws', 'tcp'].includes(proxy.network) &&
|
|
||||||
proxy.network))) ||
|
|
||||||
(proxy.type === 'trojan' &&
|
(proxy.type === 'trojan' &&
|
||||||
!['http', 'ws', 'tcp'].includes(proxy.network) &&
|
!['http', 'ws', 'tcp'].includes(proxy.network) &&
|
||||||
proxy.network) ||
|
proxy.network) ||
|
||||||
@@ -65,16 +63,26 @@ export default function Egern_Producer() {
|
|||||||
(typeof proxy.flow !== 'undefined' ||
|
(typeof proxy.flow !== 'undefined' ||
|
||||||
proxy['reality-opts'] ||
|
proxy['reality-opts'] ||
|
||||||
(!['http', 'ws', 'tcp'].includes(proxy.network) &&
|
(!['http', 'ws', 'tcp'].includes(proxy.network) &&
|
||||||
proxy.network)))
|
proxy.network))) ||
|
||||||
|
(proxy.type === 'tuic' &&
|
||||||
|
proxy.token &&
|
||||||
|
proxy.token.length !== 0)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
.map((proxy) => {
|
.map((proxy) => {
|
||||||
|
const original = { ...proxy };
|
||||||
if (proxy.tls && !proxy.sni) {
|
if (proxy.tls && !proxy.sni) {
|
||||||
proxy.sni = proxy.server;
|
proxy.sni = proxy.server;
|
||||||
}
|
}
|
||||||
|
const prev_hop =
|
||||||
|
proxy.prev_hop ||
|
||||||
|
proxy['underlying-proxy'] ||
|
||||||
|
proxy['dialer-proxy'] ||
|
||||||
|
proxy.detour;
|
||||||
|
|
||||||
if (proxy.type === 'http') {
|
if (proxy.type === 'http') {
|
||||||
proxy = {
|
proxy = {
|
||||||
type: 'http',
|
type: 'http',
|
||||||
@@ -133,11 +141,30 @@ export default function Egern_Producer() {
|
|||||||
next_hop: proxy.next_hop,
|
next_hop: proxy.next_hop,
|
||||||
sni: proxy.sni,
|
sni: proxy.sni,
|
||||||
skip_tls_verify: proxy['skip-cert-verify'],
|
skip_tls_verify: proxy['skip-cert-verify'],
|
||||||
|
port_hopping: proxy.ports,
|
||||||
|
port_hopping_interval: proxy['hop-interval'],
|
||||||
};
|
};
|
||||||
if (proxy['obfs-password'] && proxy.obfs == 'salamander') {
|
if (proxy['obfs-password'] && proxy.obfs == 'salamander') {
|
||||||
proxy.obfs = 'salamander';
|
proxy.obfs = 'salamander';
|
||||||
proxy.obfs_password = proxy['obfs-password'];
|
proxy.obfs_password = proxy['obfs-password'];
|
||||||
}
|
}
|
||||||
|
} else if (proxy.type === 'tuic') {
|
||||||
|
proxy = {
|
||||||
|
type: 'tuic',
|
||||||
|
name: proxy.name,
|
||||||
|
server: proxy.server,
|
||||||
|
port: proxy.port,
|
||||||
|
uuid: proxy.uuid,
|
||||||
|
password: proxy.password,
|
||||||
|
next_hop: proxy.next_hop,
|
||||||
|
sni: proxy.sni,
|
||||||
|
alpn: Array.isArray(proxy.alpn)
|
||||||
|
? proxy.alpn
|
||||||
|
: [proxy.alpn || 'h3'],
|
||||||
|
skip_tls_verify: proxy['skip-cert-verify'],
|
||||||
|
port_hopping: proxy.ports,
|
||||||
|
port_hopping_interval: proxy['hop-interval'],
|
||||||
|
};
|
||||||
} else if (proxy.type === 'trojan') {
|
} else if (proxy.type === 'trojan') {
|
||||||
if (proxy.network === 'ws') {
|
if (proxy.network === 'ws') {
|
||||||
proxy.websocket = {
|
proxy.websocket = {
|
||||||
@@ -160,6 +187,20 @@ export default function Egern_Producer() {
|
|||||||
websocket: proxy.websocket,
|
websocket: proxy.websocket,
|
||||||
};
|
};
|
||||||
} else if (proxy.type === 'vmess') {
|
} else if (proxy.type === 'vmess') {
|
||||||
|
// Egern:传输层,支持 ws/wss/http1/http2/tls,不配置则为 tcp
|
||||||
|
let security = proxy.cipher;
|
||||||
|
if (
|
||||||
|
security &&
|
||||||
|
![
|
||||||
|
'auto',
|
||||||
|
'none',
|
||||||
|
'zero',
|
||||||
|
'aes-128-gcm',
|
||||||
|
'chacha20-poly1305',
|
||||||
|
].includes(security)
|
||||||
|
) {
|
||||||
|
security = 'auto';
|
||||||
|
}
|
||||||
if (proxy.network === 'ws') {
|
if (proxy.network === 'ws') {
|
||||||
proxy.transport = {
|
proxy.transport = {
|
||||||
[proxy.tls ? 'wss' : 'ws']: {
|
[proxy.tls ? 'wss' : 'ws']: {
|
||||||
@@ -175,9 +216,11 @@ export default function Egern_Producer() {
|
|||||||
};
|
};
|
||||||
} else if (proxy.network === 'http') {
|
} else if (proxy.network === 'http') {
|
||||||
proxy.transport = {
|
proxy.transport = {
|
||||||
http: {
|
http1: {
|
||||||
method: proxy['http-opts']?.method,
|
method: proxy['http-opts']?.method,
|
||||||
path: proxy['http-opts']?.path,
|
path: Array.isArray(proxy['http-opts']?.path)
|
||||||
|
? proxy['http-opts']?.path[0]
|
||||||
|
: proxy['http-opts']?.path,
|
||||||
headers: {
|
headers: {
|
||||||
Host: Array.isArray(
|
Host: Array.isArray(
|
||||||
proxy['http-opts']?.headers?.Host,
|
proxy['http-opts']?.headers?.Host,
|
||||||
@@ -188,9 +231,29 @@ export default function Egern_Producer() {
|
|||||||
skip_tls_verify: proxy['skip-cert-verify'],
|
skip_tls_verify: proxy['skip-cert-verify'],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else if (proxy.network === 'tcp' || !proxy.network) {
|
} else if (proxy.network === 'h2') {
|
||||||
proxy.transport = {
|
proxy.transport = {
|
||||||
[proxy.tls ? 'tls' : 'tcp']: {
|
http2: {
|
||||||
|
method: proxy['h2-opts']?.method,
|
||||||
|
path: Array.isArray(proxy['h2-opts']?.path)
|
||||||
|
? proxy['h2-opts']?.path[0]
|
||||||
|
: proxy['h2-opts']?.path,
|
||||||
|
headers: {
|
||||||
|
Host: Array.isArray(
|
||||||
|
proxy['h2-opts']?.headers?.Host,
|
||||||
|
)
|
||||||
|
? proxy['h2-opts']?.headers?.Host[0]
|
||||||
|
: proxy['h2-opts']?.headers?.Host,
|
||||||
|
},
|
||||||
|
skip_tls_verify: proxy['skip-cert-verify'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (
|
||||||
|
(proxy.network === 'tcp' || !proxy.network) &&
|
||||||
|
proxy.tls
|
||||||
|
) {
|
||||||
|
proxy.transport = {
|
||||||
|
tls: {
|
||||||
sni: proxy.tls ? proxy.sni : undefined,
|
sni: proxy.tls ? proxy.sni : undefined,
|
||||||
skip_tls_verify: proxy.tls
|
skip_tls_verify: proxy.tls
|
||||||
? proxy['skip-cert-verify']
|
? proxy['skip-cert-verify']
|
||||||
@@ -204,7 +267,7 @@ export default function Egern_Producer() {
|
|||||||
server: proxy.server,
|
server: proxy.server,
|
||||||
port: proxy.port,
|
port: proxy.port,
|
||||||
user_id: proxy.uuid,
|
user_id: proxy.uuid,
|
||||||
security: proxy.cipher,
|
security,
|
||||||
tfo: proxy.tfo || proxy['fast-open'],
|
tfo: proxy.tfo || proxy['fast-open'],
|
||||||
legacy: proxy.legacy,
|
legacy: proxy.legacy,
|
||||||
udp_relay:
|
udp_relay:
|
||||||
@@ -232,7 +295,9 @@ export default function Egern_Producer() {
|
|||||||
proxy.transport = {
|
proxy.transport = {
|
||||||
http: {
|
http: {
|
||||||
method: proxy['http-opts']?.method,
|
method: proxy['http-opts']?.method,
|
||||||
path: proxy['http-opts']?.path,
|
path: Array.isArray(proxy['http-opts']?.path)
|
||||||
|
? proxy['http-opts']?.path[0]
|
||||||
|
: proxy['http-opts']?.path,
|
||||||
headers: {
|
headers: {
|
||||||
Host: Array.isArray(
|
Host: Array.isArray(
|
||||||
proxy['http-opts']?.headers?.Host,
|
proxy['http-opts']?.headers?.Host,
|
||||||
@@ -270,6 +335,39 @@ export default function Egern_Producer() {
|
|||||||
// skip_tls_verify: proxy['skip-cert-verify'],
|
// skip_tls_verify: proxy['skip-cert-verify'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
'http',
|
||||||
|
'socks5',
|
||||||
|
'ss',
|
||||||
|
'trojan',
|
||||||
|
'vless',
|
||||||
|
'vmess',
|
||||||
|
].includes(original.type)
|
||||||
|
) {
|
||||||
|
if (isPresent(original, 'shadow-tls-password')) {
|
||||||
|
if (original['shadow-tls-version'] != 3)
|
||||||
|
throw new Error(
|
||||||
|
`shadow-tls version ${original['shadow-tls-version']} is not supported`,
|
||||||
|
);
|
||||||
|
proxy.shadow_tls = {
|
||||||
|
password: original['shadow-tls-password'],
|
||||||
|
sni: original['shadow-tls-sni'],
|
||||||
|
};
|
||||||
|
} else if (
|
||||||
|
['shadow-tls'].includes(original.plugin) &&
|
||||||
|
original['plugin-opts']
|
||||||
|
) {
|
||||||
|
if (original['plugin-opts'].version != 3)
|
||||||
|
throw new Error(
|
||||||
|
`shadow-tls version ${original['plugin-opts'].version} is not supported`,
|
||||||
|
);
|
||||||
|
proxy.shadow_tls = {
|
||||||
|
password: original['plugin-opts'].password,
|
||||||
|
sni: original['plugin-opts'].host,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
delete proxy.subName;
|
delete proxy.subName;
|
||||||
delete proxy.collectionName;
|
delete proxy.collectionName;
|
||||||
@@ -287,6 +385,7 @@ export default function Egern_Producer() {
|
|||||||
[proxy.type]: {
|
[proxy.type]: {
|
||||||
...proxy,
|
...proxy,
|
||||||
type: undefined,
|
type: undefined,
|
||||||
|
prev_hop,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,20 +20,37 @@ function JSON_Producer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
qx: QX_Producer(),
|
||||||
QX: QX_Producer(),
|
QX: QX_Producer(),
|
||||||
QuantumultX: QX_Producer(),
|
QuantumultX: QX_Producer(),
|
||||||
|
surge: Surge_Producer(),
|
||||||
Surge: Surge_Producer(),
|
Surge: Surge_Producer(),
|
||||||
SurgeMac: SurgeMac_Producer(),
|
SurgeMac: SurgeMac_Producer(),
|
||||||
Loon: Loon_Producer(),
|
Loon: Loon_Producer(),
|
||||||
Clash: Clash_Producer(),
|
Clash: Clash_Producer(),
|
||||||
|
meta: ClashMeta_Producer(),
|
||||||
|
clashmeta: ClashMeta_Producer(),
|
||||||
|
'clash.meta': ClashMeta_Producer(),
|
||||||
|
'Clash.Meta': ClashMeta_Producer(),
|
||||||
ClashMeta: ClashMeta_Producer(),
|
ClashMeta: ClashMeta_Producer(),
|
||||||
|
mihomo: ClashMeta_Producer(),
|
||||||
|
Mihomo: ClashMeta_Producer(),
|
||||||
|
uri: URI_Producer(),
|
||||||
URI: URI_Producer(),
|
URI: URI_Producer(),
|
||||||
|
v2: V2Ray_Producer(),
|
||||||
|
v2ray: V2Ray_Producer(),
|
||||||
V2Ray: V2Ray_Producer(),
|
V2Ray: V2Ray_Producer(),
|
||||||
|
json: JSON_Producer(),
|
||||||
JSON: JSON_Producer(),
|
JSON: JSON_Producer(),
|
||||||
|
stash: Stash_Producer(),
|
||||||
Stash: Stash_Producer(),
|
Stash: Stash_Producer(),
|
||||||
|
shadowrocket: Shadowrocket_Producer(),
|
||||||
Shadowrocket: Shadowrocket_Producer(),
|
Shadowrocket: Shadowrocket_Producer(),
|
||||||
ShadowRocket: Shadowrocket_Producer(),
|
ShadowRocket: Shadowrocket_Producer(),
|
||||||
|
surfboard: Surfboard_Producer(),
|
||||||
Surfboard: Surfboard_Producer(),
|
Surfboard: Surfboard_Producer(),
|
||||||
|
singbox: singbox_Producer(),
|
||||||
'sing-box': singbox_Producer(),
|
'sing-box': singbox_Producer(),
|
||||||
|
egern: Egern_Producer(),
|
||||||
Egern: Egern_Producer(),
|
Egern: Egern_Producer(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,11 +3,19 @@ const targetPlatform = 'Loon';
|
|||||||
import { isPresent, Result } from './utils';
|
import { isPresent, Result } from './utils';
|
||||||
import { isIPv4, isIPv6 } from '@/utils';
|
import { isIPv4, isIPv6 } from '@/utils';
|
||||||
|
|
||||||
|
const ipVersions = {
|
||||||
|
dual: 'dual',
|
||||||
|
ipv4: 'v4-only',
|
||||||
|
ipv6: 'v6-only',
|
||||||
|
'ipv4-prefer': 'prefer-v4',
|
||||||
|
'ipv6-prefer': 'prefer-v6',
|
||||||
|
};
|
||||||
|
|
||||||
export default function Loon_Producer() {
|
export default function Loon_Producer() {
|
||||||
const produce = (proxy, type, opts = {}) => {
|
const produce = (proxy, type, opts = {}) => {
|
||||||
switch (proxy.type) {
|
switch (proxy.type) {
|
||||||
case 'ss':
|
case 'ss':
|
||||||
return shadowsocks(proxy, opts['include-unsupported-proxy']);
|
return shadowsocks(proxy);
|
||||||
case 'ssr':
|
case 'ssr':
|
||||||
return shadowsocksr(proxy);
|
return shadowsocksr(proxy);
|
||||||
case 'trojan':
|
case 'trojan':
|
||||||
@@ -32,7 +40,7 @@ export default function Loon_Producer() {
|
|||||||
return { produce };
|
return { produce };
|
||||||
}
|
}
|
||||||
|
|
||||||
function shadowsocks(proxy, includeUnsupportedProxy) {
|
function shadowsocks(proxy) {
|
||||||
const result = new Result(proxy);
|
const result = new Result(proxy);
|
||||||
if (
|
if (
|
||||||
![
|
![
|
||||||
@@ -56,9 +64,8 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
|
|||||||
'aes-256-gcm',
|
'aes-256-gcm',
|
||||||
'chacha20-ietf-poly1305',
|
'chacha20-ietf-poly1305',
|
||||||
'xchacha20-ietf-poly1305',
|
'xchacha20-ietf-poly1305',
|
||||||
...(includeUnsupportedProxy
|
'2022-blake3-aes-128-gcm',
|
||||||
? ['2022-blake3-aes-128-gcm', '2022-blake3-aes-256-gcm']
|
'2022-blake3-aes-256-gcm',
|
||||||
: []),
|
|
||||||
].includes(proxy.cipher)
|
].includes(proxy.cipher)
|
||||||
) {
|
) {
|
||||||
throw new Error(`cipher ${proxy.cipher} is not supported`);
|
throw new Error(`cipher ${proxy.cipher} is not supported`);
|
||||||
@@ -79,11 +86,50 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
|
|||||||
`,obfs-uri=${proxy['plugin-opts'].path}`,
|
`,obfs-uri=${proxy['plugin-opts'].path}`,
|
||||||
'plugin-opts.path',
|
'plugin-opts.path',
|
||||||
);
|
);
|
||||||
} else {
|
} else if (!['shadow-tls'].includes(proxy.plugin)) {
|
||||||
throw new Error(`plugin ${proxy.plugin} is not supported`);
|
throw new Error(`plugin ${proxy.plugin} is not supported`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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',
|
||||||
|
);
|
||||||
|
// udp-port
|
||||||
|
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
|
||||||
|
} else if (['shadow-tls'].includes(proxy.plugin) && proxy['plugin-opts']) {
|
||||||
|
const password = proxy['plugin-opts'].password;
|
||||||
|
const host = proxy['plugin-opts'].host;
|
||||||
|
const version = proxy['plugin-opts'].version;
|
||||||
|
if (password) {
|
||||||
|
result.append(`,shadow-tls-password=${password}`);
|
||||||
|
if (host) {
|
||||||
|
result.append(`,shadow-tls-sni=${host}`);
|
||||||
|
}
|
||||||
|
if (version) {
|
||||||
|
if (version < 2) {
|
||||||
|
throw new Error(
|
||||||
|
`shadow-tls version ${version} is not supported`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
result.append(`,shadow-tls-version=${version}`);
|
||||||
|
}
|
||||||
|
// udp-port
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,udp-port=${proxy['udp-port']}`,
|
||||||
|
'udp-port',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// tfo
|
// tfo
|
||||||
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
||||||
|
|
||||||
@@ -92,10 +138,13 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
|
|||||||
result.append(`,udp=true`);
|
result.append(`,udp=true`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
|
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function shadowsocksr(proxy) {
|
function shadowsocksr(proxy, includeUnsupportedProxy) {
|
||||||
const result = new Result(proxy);
|
const result = new Result(proxy);
|
||||||
result.append(
|
result.append(
|
||||||
`${proxy.name}=shadowsocksr,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"`,
|
`${proxy.name}=shadowsocksr,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"`,
|
||||||
@@ -112,6 +161,45 @@ function shadowsocksr(proxy) {
|
|||||||
result.appendIfPresent(`,obfs=${proxy.obfs}`, 'obfs');
|
result.appendIfPresent(`,obfs=${proxy.obfs}`, 'obfs');
|
||||||
result.appendIfPresent(`,obfs-param=${proxy['obfs-param']}`, 'obfs-param');
|
result.appendIfPresent(`,obfs-param=${proxy['obfs-param']}`, 'obfs-param');
|
||||||
|
|
||||||
|
// 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',
|
||||||
|
);
|
||||||
|
// udp-port
|
||||||
|
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
|
||||||
|
} else if (['shadow-tls'].includes(proxy.plugin) && proxy['plugin-opts']) {
|
||||||
|
const password = proxy['plugin-opts'].password;
|
||||||
|
const host = proxy['plugin-opts'].host;
|
||||||
|
const version = proxy['plugin-opts'].version;
|
||||||
|
if (password) {
|
||||||
|
result.append(`,shadow-tls-password=${password}`);
|
||||||
|
if (host) {
|
||||||
|
result.append(`,shadow-tls-sni=${host}`);
|
||||||
|
}
|
||||||
|
if (version) {
|
||||||
|
if (version < 2) {
|
||||||
|
throw new Error(
|
||||||
|
`shadow-tls version ${version} is not supported`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
result.append(`,shadow-tls-version=${version}`);
|
||||||
|
}
|
||||||
|
// udp-port
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,udp-port=${proxy['udp-port']}`,
|
||||||
|
'udp-port',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// tfo
|
// tfo
|
||||||
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
||||||
|
|
||||||
@@ -120,6 +208,9 @@ function shadowsocksr(proxy) {
|
|||||||
result.append(`,udp=true`);
|
result.append(`,udp=true`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
|
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,6 +263,8 @@ function trojan(proxy) {
|
|||||||
if (proxy.udp) {
|
if (proxy.udp) {
|
||||||
result.append(`,udp=true`);
|
result.append(`,udp=true`);
|
||||||
}
|
}
|
||||||
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
|
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
@@ -249,12 +342,14 @@ function vmess(proxy) {
|
|||||||
if (proxy.udp) {
|
if (proxy.udp) {
|
||||||
result.append(`,udp=true`);
|
result.append(`,udp=true`);
|
||||||
}
|
}
|
||||||
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
|
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function vless(proxy) {
|
function vless(proxy) {
|
||||||
if (proxy['reality-opts']) {
|
if (typeof proxy.flow !== 'undefined' || proxy['reality-opts']) {
|
||||||
throw new Error(`VLESS REALITY is unsupported`);
|
throw new Error(`VLESS XTLS/REALITY is not supported`);
|
||||||
}
|
}
|
||||||
const result = new Result(proxy);
|
const result = new Result(proxy);
|
||||||
result.append(
|
result.append(
|
||||||
@@ -321,6 +416,8 @@ function vless(proxy) {
|
|||||||
if (proxy.udp) {
|
if (proxy.udp) {
|
||||||
result.append(`,udp=true`);
|
result.append(`,udp=true`);
|
||||||
}
|
}
|
||||||
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
|
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,6 +439,8 @@ function http(proxy) {
|
|||||||
|
|
||||||
// tfo
|
// tfo
|
||||||
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
|
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
|
||||||
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
|
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
@@ -370,6 +469,8 @@ function socks5(proxy) {
|
|||||||
if (proxy.udp) {
|
if (proxy.udp) {
|
||||||
result.append(`,udp=true`);
|
result.append(`,udp=true`);
|
||||||
}
|
}
|
||||||
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
|
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
@@ -435,6 +536,8 @@ function wireguard(proxy) {
|
|||||||
presharedKey ?? ''
|
presharedKey ?? ''
|
||||||
}}]`,
|
}}]`,
|
||||||
);
|
);
|
||||||
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
|
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
@@ -482,6 +585,8 @@ function hysteria2(proxy) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
result.appendIfPresent(`,ecn=${proxy.ecn}`, 'ecn');
|
result.appendIfPresent(`,ecn=${proxy.ecn}`, 'ecn');
|
||||||
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
|
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { isPresent } from '@/core/proxy-utils/producers/utils';
|
import { isPresent } from '@/core/proxy-utils/producers/utils';
|
||||||
|
|
||||||
export default function ShadowRocket_Producer() {
|
export default function Shadowrocket_Producer() {
|
||||||
const type = 'ALL';
|
const type = 'ALL';
|
||||||
const produce = (proxies, type, opts = {}) => {
|
const produce = (proxies, type, opts = {}) => {
|
||||||
const list = proxies
|
const list = proxies
|
||||||
@@ -8,7 +8,7 @@ export default function ShadowRocket_Producer() {
|
|||||||
if (opts['include-unsupported-proxy']) return true;
|
if (opts['include-unsupported-proxy']) return true;
|
||||||
if (proxy.type === 'snell' && String(proxy.version) === '4') {
|
if (proxy.type === 'snell' && String(proxy.version) === '4') {
|
||||||
return false;
|
return false;
|
||||||
} else if (['mieru'].includes(proxy.type)) {
|
} else if (['mieru', 'anytls'].includes(proxy.type)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -32,9 +32,10 @@ export default function ShadowRocket_Producer() {
|
|||||||
isPresent(proxy, 'cipher') &&
|
isPresent(proxy, 'cipher') &&
|
||||||
![
|
![
|
||||||
'auto',
|
'auto',
|
||||||
|
'none',
|
||||||
|
'zero',
|
||||||
'aes-128-gcm',
|
'aes-128-gcm',
|
||||||
'chacha20-poly1305',
|
'chacha20-poly1305',
|
||||||
'none',
|
|
||||||
].includes(proxy.cipher)
|
].includes(proxy.cipher)
|
||||||
) {
|
) {
|
||||||
proxy.cipher = 'auto';
|
proxy.cipher = 'auto';
|
||||||
@@ -102,11 +103,28 @@ export default function ShadowRocket_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 === 'snell' && proxy.version < 3) {
|
||||||
|
delete proxy.udp;
|
||||||
} else if (proxy.type === 'vless') {
|
} else if (proxy.type === 'vless') {
|
||||||
if (isPresent(proxy, 'sni')) {
|
if (isPresent(proxy, 'sni')) {
|
||||||
proxy.servername = proxy.sni;
|
proxy.servername = proxy.sni;
|
||||||
delete proxy.sni;
|
delete proxy.sni;
|
||||||
}
|
}
|
||||||
|
} else if (proxy.type === 'ss') {
|
||||||
|
if (
|
||||||
|
isPresent(proxy, 'shadow-tls-password') &&
|
||||||
|
!isPresent(proxy, 'plugin')
|
||||||
|
) {
|
||||||
|
proxy.plugin = 'shadow-tls';
|
||||||
|
proxy['plugin-opts'] = {
|
||||||
|
host: proxy['shadow-tls-sni'],
|
||||||
|
password: proxy['shadow-tls-password'],
|
||||||
|
version: proxy['shadow-tls-version'],
|
||||||
|
};
|
||||||
|
delete proxy['shadow-tls-password'];
|
||||||
|
delete proxy['shadow-tls-sni'];
|
||||||
|
delete proxy['shadow-tls-version'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -160,6 +178,7 @@ export default function ShadowRocket_Producer() {
|
|||||||
'hysteria',
|
'hysteria',
|
||||||
'hysteria2',
|
'hysteria2',
|
||||||
'juicity',
|
'juicity',
|
||||||
|
'anytls',
|
||||||
].includes(proxy.type)
|
].includes(proxy.type)
|
||||||
) {
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import { isIPv4, isIPv6 } from '@/utils';
|
|||||||
const detourParser = (proxy, parsedProxy) => {
|
const detourParser = (proxy, parsedProxy) => {
|
||||||
parsedProxy.detour = proxy['dialer-proxy'] || proxy.detour;
|
parsedProxy.detour = proxy['dialer-proxy'] || proxy.detour;
|
||||||
};
|
};
|
||||||
|
const networkParser = (proxy, parsedProxy) => {
|
||||||
|
if (['tcp', 'udp'].includes(proxy._network))
|
||||||
|
parsedProxy.network = proxy._network;
|
||||||
|
};
|
||||||
const tfoParser = (proxy, parsedProxy) => {
|
const tfoParser = (proxy, parsedProxy) => {
|
||||||
parsedProxy.tcp_fast_open = false;
|
parsedProxy.tcp_fast_open = false;
|
||||||
if (proxy.tfo) parsedProxy.tcp_fast_open = true;
|
if (proxy.tfo) parsedProxy.tcp_fast_open = true;
|
||||||
@@ -305,6 +309,7 @@ const socks5Parser = (proxy = {}) => {
|
|||||||
if (proxy.uot) parsedProxy.udp_over_tcp = true;
|
if (proxy.uot) parsedProxy.udp_over_tcp = true;
|
||||||
if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true;
|
if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true;
|
||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
|
networkParser(proxy, parsedProxy);
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
detourParser(proxy, parsedProxy);
|
detourParser(proxy, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
@@ -356,6 +361,7 @@ const ssParser = (proxy = {}) => {
|
|||||||
if (proxy.uot) parsedProxy.udp_over_tcp = true;
|
if (proxy.uot) parsedProxy.udp_over_tcp = true;
|
||||||
if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true;
|
if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true;
|
||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
|
networkParser(proxy, parsedProxy);
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
detourParser(proxy, parsedProxy);
|
detourParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
@@ -470,7 +476,7 @@ const vmessParser = (proxy = {}) => {
|
|||||||
if (proxy.network === 'h2') h2Parser(proxy, parsedProxy);
|
if (proxy.network === 'h2') h2Parser(proxy, parsedProxy);
|
||||||
if (proxy.network === 'http') h1Parser(proxy, parsedProxy);
|
if (proxy.network === 'http') h1Parser(proxy, parsedProxy);
|
||||||
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
||||||
|
networkParser(proxy, parsedProxy);
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
detourParser(proxy, parsedProxy);
|
detourParser(proxy, parsedProxy);
|
||||||
tlsParser(proxy, parsedProxy);
|
tlsParser(proxy, parsedProxy);
|
||||||
@@ -493,7 +499,7 @@ const vlessParser = (proxy = {}) => {
|
|||||||
if (proxy.flow === 'xtls-rprx-vision') parsedProxy.flow = proxy.flow;
|
if (proxy.flow === 'xtls-rprx-vision') parsedProxy.flow = proxy.flow;
|
||||||
if (proxy.network === 'ws') wsParser(proxy, parsedProxy);
|
if (proxy.network === 'ws') wsParser(proxy, parsedProxy);
|
||||||
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
||||||
|
networkParser(proxy, parsedProxy);
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
detourParser(proxy, parsedProxy);
|
detourParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
@@ -514,7 +520,7 @@ const trojanParser = (proxy = {}) => {
|
|||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
||||||
if (proxy.network === 'ws') wsParser(proxy, parsedProxy);
|
if (proxy.network === 'ws') wsParser(proxy, parsedProxy);
|
||||||
|
networkParser(proxy, parsedProxy);
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
detourParser(proxy, parsedProxy);
|
detourParser(proxy, parsedProxy);
|
||||||
tlsParser(proxy, parsedProxy);
|
tlsParser(proxy, parsedProxy);
|
||||||
@@ -562,13 +568,14 @@ const hysteriaParser = (proxy = {}) => {
|
|||||||
parsedProxy.disable_mtu_discovery = true;
|
parsedProxy.disable_mtu_discovery = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
networkParser(proxy, parsedProxy);
|
||||||
tlsParser(proxy, parsedProxy);
|
tlsParser(proxy, parsedProxy);
|
||||||
detourParser(proxy, parsedProxy);
|
detourParser(proxy, parsedProxy);
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
};
|
};
|
||||||
const hysteria2Parser = (proxy = {}) => {
|
const hysteria2Parser = (proxy = {}, includeUnsupportedProxy) => {
|
||||||
const parsedProxy = {
|
const parsedProxy = {
|
||||||
tag: proxy.name,
|
tag: proxy.name,
|
||||||
type: 'hysteria2',
|
type: 'hysteria2',
|
||||||
@@ -580,12 +587,23 @@ const hysteria2Parser = (proxy = {}) => {
|
|||||||
};
|
};
|
||||||
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
|
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
|
||||||
throw 'invalid port';
|
throw 'invalid port';
|
||||||
|
if (includeUnsupportedProxy) {
|
||||||
|
if (proxy['hop-interval'])
|
||||||
|
parsedProxy.hop_interval = /^\d+$/.test(proxy['hop-interval'])
|
||||||
|
? `${proxy['hop-interval']}s`
|
||||||
|
: proxy['hop-interval'];
|
||||||
|
if (proxy['ports'])
|
||||||
|
parsedProxy.server_ports = proxy['ports']
|
||||||
|
.split(/\s*,\s*/)
|
||||||
|
.map((p) => p.replace(/\s*-\s*/g, ':'));
|
||||||
|
}
|
||||||
if (proxy.up) parsedProxy.up_mbps = parseInt(`${proxy.up}`, 10);
|
if (proxy.up) parsedProxy.up_mbps = parseInt(`${proxy.up}`, 10);
|
||||||
if (proxy.down) parsedProxy.down_mbps = parseInt(`${proxy.down}`, 10);
|
if (proxy.down) parsedProxy.down_mbps = parseInt(`${proxy.down}`, 10);
|
||||||
if (proxy.obfs === 'salamander') parsedProxy.obfs.type = 'salamander';
|
if (proxy.obfs === 'salamander') parsedProxy.obfs.type = 'salamander';
|
||||||
if (proxy['obfs-password'])
|
if (proxy['obfs-password'])
|
||||||
parsedProxy.obfs.password = proxy['obfs-password'];
|
parsedProxy.obfs.password = proxy['obfs-password'];
|
||||||
if (!parsedProxy.obfs.type) delete parsedProxy.obfs;
|
if (!parsedProxy.obfs.type) delete parsedProxy.obfs;
|
||||||
|
networkParser(proxy, parsedProxy);
|
||||||
tlsParser(proxy, parsedProxy);
|
tlsParser(proxy, parsedProxy);
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
detourParser(proxy, parsedProxy);
|
detourParser(proxy, parsedProxy);
|
||||||
@@ -616,12 +634,30 @@ const tuic5Parser = (proxy = {}) => {
|
|||||||
if (proxy['udp-over-stream']) parsedProxy.udp_over_stream = true;
|
if (proxy['udp-over-stream']) parsedProxy.udp_over_stream = true;
|
||||||
if (proxy['heartbeat-interval'])
|
if (proxy['heartbeat-interval'])
|
||||||
parsedProxy.heartbeat = `${proxy['heartbeat-interval']}ms`;
|
parsedProxy.heartbeat = `${proxy['heartbeat-interval']}ms`;
|
||||||
|
networkParser(proxy, parsedProxy);
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
detourParser(proxy, parsedProxy);
|
detourParser(proxy, parsedProxy);
|
||||||
tlsParser(proxy, parsedProxy);
|
tlsParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
};
|
};
|
||||||
|
const anytlsParser = (proxy = {}) => {
|
||||||
|
const parsedProxy = {
|
||||||
|
tag: proxy.name,
|
||||||
|
type: 'anytls',
|
||||||
|
server: proxy.server,
|
||||||
|
server_port: parseInt(`${proxy.port}`, 10),
|
||||||
|
password: proxy.password,
|
||||||
|
tls: { enabled: true, server_name: proxy.server, insecure: false },
|
||||||
|
};
|
||||||
|
if (/^\d+$/.test(proxy['idle-session-check-interval']))
|
||||||
|
parsedProxy.idle_session_check_interval = `${proxy['idle-session-check-interval']}s`;
|
||||||
|
if (/^\d+$/.test(proxy['idle-session-timeout']))
|
||||||
|
parsedProxy.idle_session_timeout = `${proxy['idle-session-timeout']}s`;
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
|
tlsParser(proxy, parsedProxy);
|
||||||
|
return parsedProxy;
|
||||||
|
};
|
||||||
|
|
||||||
const wireguardParser = (proxy = {}) => {
|
const wireguardParser = (proxy = {}) => {
|
||||||
const local_address = ['ip', 'ipv6']
|
const local_address = ['ip', 'ipv6']
|
||||||
@@ -673,6 +709,7 @@ const wireguardParser = (proxy = {}) => {
|
|||||||
parsedProxy.peers.push(peer);
|
parsedProxy.peers.push(peer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
networkParser(proxy, parsedProxy);
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
detourParser(proxy, parsedProxy);
|
detourParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
@@ -790,7 +827,12 @@ export default function singbox_Producer() {
|
|||||||
list.push(hysteriaParser(proxy));
|
list.push(hysteriaParser(proxy));
|
||||||
break;
|
break;
|
||||||
case 'hysteria2':
|
case 'hysteria2':
|
||||||
list.push(hysteria2Parser(proxy));
|
list.push(
|
||||||
|
hysteria2Parser(
|
||||||
|
proxy,
|
||||||
|
opts['include-unsupported-proxy'],
|
||||||
|
),
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'tuic':
|
case 'tuic':
|
||||||
if (!proxy.token || proxy.token.length === 0) {
|
if (!proxy.token || proxy.token.length === 0) {
|
||||||
@@ -804,6 +846,9 @@ export default function singbox_Producer() {
|
|||||||
case 'wireguard':
|
case 'wireguard':
|
||||||
list.push(wireguardParser(proxy));
|
list.push(wireguardParser(proxy));
|
||||||
break;
|
break;
|
||||||
|
case 'anytls':
|
||||||
|
list.push(anytlsParser(proxy));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Platform sing-box does not support proxy type: ${proxy.type}`,
|
`Platform sing-box does not support proxy type: ${proxy.type}`,
|
||||||
|
|||||||
@@ -187,6 +187,8 @@ export default function Stash_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 === 'snell' && proxy.version < 3) {
|
||||||
|
delete proxy.udp;
|
||||||
} else if (proxy.type === 'vless') {
|
} else if (proxy.type === 'vless') {
|
||||||
if (isPresent(proxy, 'sni')) {
|
if (isPresent(proxy, 'sni')) {
|
||||||
proxy.servername = proxy.sni;
|
proxy.servername = proxy.sni;
|
||||||
@@ -245,6 +247,7 @@ export default function Stash_Producer() {
|
|||||||
'hysteria',
|
'hysteria',
|
||||||
'hysteria2',
|
'hysteria2',
|
||||||
'juicity',
|
'juicity',
|
||||||
|
'anytls',
|
||||||
].includes(proxy.type)
|
].includes(proxy.type)
|
||||||
) {
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export default function Surge_Producer() {
|
|||||||
return { produce };
|
return { produce };
|
||||||
}
|
}
|
||||||
|
|
||||||
function shadowsocks(proxy, includeUnsupportedProxy) {
|
function shadowsocks(proxy) {
|
||||||
const result = new Result(proxy);
|
const result = new Result(proxy);
|
||||||
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
|
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
|
||||||
if (!proxy.cipher) {
|
if (!proxy.cipher) {
|
||||||
@@ -87,9 +87,8 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
|
|||||||
'chacha20',
|
'chacha20',
|
||||||
'chacha20-ietf',
|
'chacha20-ietf',
|
||||||
'none',
|
'none',
|
||||||
...(includeUnsupportedProxy
|
'2022-blake3-aes-128-gcm',
|
||||||
? ['2022-blake3-aes-128-gcm', '2022-blake3-aes-256-gcm']
|
'2022-blake3-aes-256-gcm',
|
||||||
: []),
|
|
||||||
].includes(proxy.cipher)
|
].includes(proxy.cipher)
|
||||||
) {
|
) {
|
||||||
throw new Error(`cipher ${proxy.cipher} is not supported`);
|
throw new Error(`cipher ${proxy.cipher} is not supported`);
|
||||||
@@ -127,8 +126,6 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
|
|||||||
|
|
||||||
// udp
|
// udp
|
||||||
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
||||||
// udp-port
|
|
||||||
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
|
|
||||||
|
|
||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||||
@@ -160,6 +157,8 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
|
|||||||
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
|
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
|
||||||
'shadow-tls-sni',
|
'shadow-tls-sni',
|
||||||
);
|
);
|
||||||
|
// udp-port
|
||||||
|
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
|
||||||
} else if (['shadow-tls'].includes(proxy.plugin) && proxy['plugin-opts']) {
|
} else if (['shadow-tls'].includes(proxy.plugin) && proxy['plugin-opts']) {
|
||||||
const password = proxy['plugin-opts'].password;
|
const password = proxy['plugin-opts'].password;
|
||||||
const host = proxy['plugin-opts'].host;
|
const host = proxy['plugin-opts'].host;
|
||||||
@@ -177,6 +176,11 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
|
|||||||
}
|
}
|
||||||
result.append(`,shadow-tls-version=${version}`);
|
result.append(`,shadow-tls-version=${version}`);
|
||||||
}
|
}
|
||||||
|
// udp-port
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,udp-port=${proxy['udp-port']}`,
|
||||||
|
'udp-port',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ export default function URI_Producer() {
|
|||||||
proxy.server = `[${proxy.server}]`;
|
proxy.server = `[${proxy.server}]`;
|
||||||
}
|
}
|
||||||
switch (proxy.type) {
|
switch (proxy.type) {
|
||||||
|
case 'socks5':
|
||||||
|
result = `socks://${encodeURIComponent(
|
||||||
|
Base64.encode(`${proxy.username}:${proxy.password}`),
|
||||||
|
)}@${proxy.server}:${proxy.port}#${proxy.name}`;
|
||||||
|
break;
|
||||||
case 'ss':
|
case 'ss':
|
||||||
const userinfo = `${proxy.cipher}:${proxy.password}`;
|
const userinfo = `${proxy.cipher}:${proxy.password}`;
|
||||||
result = `ss://${
|
result = `ss://${
|
||||||
@@ -54,6 +59,11 @@ export default function URI_Producer() {
|
|||||||
}${opts.tls ? ';tls' : ''}`,
|
}${opts.tls ? ';tls' : ''}`,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case 'shadow-tls':
|
||||||
|
result += encodeURIComponent(
|
||||||
|
`shadow-tls;host=${opts.host};password=${opts.password};version=${opts.version}`,
|
||||||
|
);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unsupported plugin option: ${proxy.plugin}`,
|
`Unsupported plugin option: ${proxy.plugin}`,
|
||||||
@@ -149,6 +159,7 @@ export default function URI_Producer() {
|
|||||||
const isReality = proxy['reality-opts'];
|
const isReality = proxy['reality-opts'];
|
||||||
let sid = '';
|
let sid = '';
|
||||||
let pbk = '';
|
let pbk = '';
|
||||||
|
let spx = '';
|
||||||
if (isReality) {
|
if (isReality) {
|
||||||
security = 'reality';
|
security = 'reality';
|
||||||
const publicKey = proxy['reality-opts']?.['public-key'];
|
const publicKey = proxy['reality-opts']?.['public-key'];
|
||||||
@@ -159,6 +170,10 @@ export default function URI_Producer() {
|
|||||||
if (shortId) {
|
if (shortId) {
|
||||||
sid = `&sid=${encodeURIComponent(shortId)}`;
|
sid = `&sid=${encodeURIComponent(shortId)}`;
|
||||||
}
|
}
|
||||||
|
const spiderX = proxy['reality-opts']?.['_spider-x'];
|
||||||
|
if (spiderX) {
|
||||||
|
spx = `&spx=${encodeURIComponent(spiderX)}`;
|
||||||
|
}
|
||||||
} else if (proxy.tls) {
|
} else if (proxy.tls) {
|
||||||
security = 'tls';
|
security = 'tls';
|
||||||
}
|
}
|
||||||
@@ -188,6 +203,14 @@ export default function URI_Producer() {
|
|||||||
if (proxy.flow) {
|
if (proxy.flow) {
|
||||||
flow = `&flow=${encodeURIComponent(proxy.flow)}`;
|
flow = `&flow=${encodeURIComponent(proxy.flow)}`;
|
||||||
}
|
}
|
||||||
|
let extra = '';
|
||||||
|
if (proxy._extra) {
|
||||||
|
extra = `&extra=${encodeURIComponent(proxy._extra)}`;
|
||||||
|
}
|
||||||
|
let mode = '';
|
||||||
|
if (proxy._mode) {
|
||||||
|
mode = `&mode=${encodeURIComponent(proxy._mode)}`;
|
||||||
|
}
|
||||||
let vlessType = proxy.network;
|
let vlessType = proxy.network;
|
||||||
if (
|
if (
|
||||||
proxy.network === 'ws' &&
|
proxy.network === 'ws' &&
|
||||||
@@ -254,7 +277,7 @@ export default function URI_Producer() {
|
|||||||
proxy.port
|
proxy.port
|
||||||
}?security=${encodeURIComponent(
|
}?security=${encodeURIComponent(
|
||||||
security,
|
security,
|
||||||
)}${vlessTransport}${alpn}${allowInsecure}${sni}${fp}${flow}${sid}${pbk}#${encodeURIComponent(
|
)}${vlessTransport}${alpn}${allowInsecure}${sni}${fp}${flow}${sid}${spx}${pbk}${mode}${extra}#${encodeURIComponent(
|
||||||
proxy.name,
|
proxy.name,
|
||||||
)}`;
|
)}`;
|
||||||
break;
|
break;
|
||||||
@@ -324,11 +347,41 @@ export default function URI_Producer() {
|
|||||||
: proxy.alpn.join(','),
|
: proxy.alpn.join(','),
|
||||||
)}`;
|
)}`;
|
||||||
}
|
}
|
||||||
|
const trojanIsReality = proxy['reality-opts'];
|
||||||
|
let trojanSid = '';
|
||||||
|
let trojanPbk = '';
|
||||||
|
let trojanSpx = '';
|
||||||
|
let trojanSecurity = '';
|
||||||
|
let trojanMode = '';
|
||||||
|
let trojanExtra = '';
|
||||||
|
if (trojanIsReality) {
|
||||||
|
trojanSecurity = `&security=reality`;
|
||||||
|
const publicKey = proxy['reality-opts']?.['public-key'];
|
||||||
|
if (publicKey) {
|
||||||
|
trojanPbk = `&pbk=${encodeURIComponent(publicKey)}`;
|
||||||
|
}
|
||||||
|
const shortId = proxy['reality-opts']?.['short-id'];
|
||||||
|
if (shortId) {
|
||||||
|
trojanSid = `&sid=${encodeURIComponent(shortId)}`;
|
||||||
|
}
|
||||||
|
const spiderX = proxy['reality-opts']?.['_spider-x'];
|
||||||
|
if (spiderX) {
|
||||||
|
trojanSpx = `&spx=${encodeURIComponent(spiderX)}`;
|
||||||
|
}
|
||||||
|
if (proxy._extra) {
|
||||||
|
trojanExtra = `&extra=${encodeURIComponent(
|
||||||
|
proxy._extra,
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
if (proxy._mode) {
|
||||||
|
trojanMode = `&mode=${encodeURIComponent(proxy._mode)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
result = `trojan://${proxy.password}@${proxy.server}:${
|
result = `trojan://${proxy.password}@${proxy.server}:${
|
||||||
proxy.port
|
proxy.port
|
||||||
}?sni=${encodeURIComponent(proxy.sni || proxy.server)}${
|
}?sni=${encodeURIComponent(proxy.sni || proxy.server)}${
|
||||||
proxy['skip-cert-verify'] ? '&allowInsecure=1' : ''
|
proxy['skip-cert-verify'] ? '&allowInsecure=1' : ''
|
||||||
}${trojanTransport}${trojanAlpn}${trojanFp}#${encodeURIComponent(
|
}${trojanTransport}${trojanAlpn}${trojanFp}${trojanSecurity}${trojanSid}${trojanPbk}${trojanSpx}${trojanMode}${trojanExtra}#${encodeURIComponent(
|
||||||
proxy.name,
|
proxy.name,
|
||||||
)}`;
|
)}`;
|
||||||
break;
|
break;
|
||||||
@@ -468,10 +521,13 @@ export default function URI_Producer() {
|
|||||||
['disable-sni', 'reduce-rtt'].includes(key) &&
|
['disable-sni', 'reduce-rtt'].includes(key) &&
|
||||||
proxy[key]
|
proxy[key]
|
||||||
) {
|
) {
|
||||||
tuicParams.push(`${i}=1`);
|
tuicParams.push(`${i.replace(/-/g, '_')}=1`);
|
||||||
} else if (proxy[key]) {
|
} else if (proxy[key]) {
|
||||||
tuicParams.push(
|
tuicParams.push(
|
||||||
`${i}=${encodeURIComponent(proxy[key])}`,
|
`${i.replace(
|
||||||
|
/-/g,
|
||||||
|
'_',
|
||||||
|
)}=${encodeURIComponent(proxy[key])}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ async function doSync() {
|
|||||||
const files = {};
|
const files = {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const valid = [];
|
||||||
const invalid = [];
|
const invalid = [];
|
||||||
const allSubs = $.read(SUBS_KEY);
|
const allSubs = $.read(SUBS_KEY);
|
||||||
const allCols = $.read(COLLECTIONS_KEY);
|
const allCols = $.read(COLLECTIONS_KEY);
|
||||||
@@ -156,27 +157,46 @@ async function doSync() {
|
|||||||
files[encodeURIComponent(artifact.name)] = {
|
files[encodeURIComponent(artifact.name)] = {
|
||||||
content: output,
|
content: output,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
valid.push(artifact.name);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
$.error(
|
$.error(
|
||||||
`同步配置 ${artifact.name} 发生错误: ${e.message ?? e}`,
|
`生成同步配置 ${artifact.name} 发生错误: ${
|
||||||
|
e.message ?? e
|
||||||
|
}`,
|
||||||
);
|
);
|
||||||
invalid.push(artifact.name);
|
invalid.push(artifact.name);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (invalid.length > 0) {
|
$.info(`${valid.length} 个同步配置生成成功: ${valid.join(', ')}`);
|
||||||
|
$.info(`${invalid.length} 个同步配置生成失败: ${invalid.join(', ')}`);
|
||||||
|
|
||||||
|
if (valid.length === 0) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`同步配置 ${invalid.join(', ')} 发生错误 详情请查看日志`,
|
`同步配置 ${invalid.join(', ')} 生成失败 详情请查看日志`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const resp = await syncToGist(files);
|
const resp = await syncToGist(files);
|
||||||
const body = JSON.parse(resp.body);
|
const body = JSON.parse(resp.body);
|
||||||
|
delete body.history;
|
||||||
|
delete body.forks;
|
||||||
|
delete body.owner;
|
||||||
|
Object.values(body.files).forEach((file) => {
|
||||||
|
delete file.content;
|
||||||
|
});
|
||||||
|
$.info('上传配置响应:');
|
||||||
|
$.info(JSON.stringify(body, null, 2));
|
||||||
|
|
||||||
for (const artifact of allArtifacts) {
|
for (const artifact of allArtifacts) {
|
||||||
if (artifact.sync) {
|
if (
|
||||||
|
artifact.sync &&
|
||||||
|
artifact.source &&
|
||||||
|
valid.includes(artifact.name)
|
||||||
|
) {
|
||||||
artifact.updated = new Date().getTime();
|
artifact.updated = new Date().getTime();
|
||||||
// extract real url from gist
|
// extract real url from gist
|
||||||
let files = body.files;
|
let files = body.files;
|
||||||
@@ -204,9 +224,18 @@ async function doSync() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$.write(allArtifacts, ARTIFACTS_KEY);
|
$.write(allArtifacts, ARTIFACTS_KEY);
|
||||||
$.notify('🌍 Sub-Store', '全部订阅同步成功!');
|
$.info('上传配置成功');
|
||||||
|
|
||||||
|
if (invalid.length > 0) {
|
||||||
|
$.notify(
|
||||||
|
'🌍 Sub-Store',
|
||||||
|
`同步配置成功 ${valid.length} 个, 失败 ${invalid.length} 个, 详情请查看日志`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$.notify('🌍 Sub-Store', '同步配置完成');
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
$.notify('🌍 Sub-Store', '同步订阅失败', `原因:${e.message ?? e}`);
|
$.notify('🌍 Sub-Store', '同步配置失败', `原因:${e.message ?? e}`);
|
||||||
$.error(`无法同步订阅配置到 Gist,原因:${e}`);
|
$.error(`无法同步配置到 Gist,原因:${e}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,11 +50,32 @@ function createCollection(req, res) {
|
|||||||
|
|
||||||
function getCollection(req, res) {
|
function getCollection(req, res) {
|
||||||
let { name } = req.params;
|
let { name } = req.params;
|
||||||
|
let { raw } = req.query;
|
||||||
name = decodeURIComponent(name);
|
name = decodeURIComponent(name);
|
||||||
const allCols = $.read(COLLECTIONS_KEY);
|
const allCols = $.read(COLLECTIONS_KEY);
|
||||||
const collection = findByName(allCols, name);
|
const collection = findByName(allCols, name);
|
||||||
if (collection) {
|
if (collection) {
|
||||||
success(res, collection);
|
if (raw) {
|
||||||
|
res.set('content-type', 'application/json')
|
||||||
|
.set(
|
||||||
|
'content-disposition',
|
||||||
|
`attachment; filename="${encodeURIComponent(
|
||||||
|
`sub-store_collection_${name}_${new Date()
|
||||||
|
.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
second: 'numeric',
|
||||||
|
})
|
||||||
|
.replace(/\D/g, '')}.json`,
|
||||||
|
)}"`,
|
||||||
|
)
|
||||||
|
.send(JSON.stringify(collection));
|
||||||
|
} else {
|
||||||
|
success(res, collection);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
failed(
|
failed(
|
||||||
res,
|
res,
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { getPlatformFromHeaders } from '@/utils/user-agent';
|
import {
|
||||||
|
getPlatformFromHeaders,
|
||||||
|
shouldIncludeUnsupportedProxy,
|
||||||
|
} from '@/utils/user-agent';
|
||||||
import { ProxyUtils } from '@/core/proxy-utils';
|
import { ProxyUtils } from '@/core/proxy-utils';
|
||||||
import { COLLECTIONS_KEY, SUBS_KEY } from '@/constants';
|
import { COLLECTIONS_KEY, SUBS_KEY } from '@/constants';
|
||||||
import { findByName } from '@/utils/database';
|
import { findByName } from '@/utils/database';
|
||||||
import { getFlowHeaders } from '@/utils/flow';
|
import { getFlowHeaders, normalizeFlowHeader } from '@/utils/flow';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
import { failed } from '@/restful/response';
|
import { failed } from '@/restful/response';
|
||||||
import { InternalServerError, ResourceNotFoundError } from '@/restful/errors';
|
import { InternalServerError, ResourceNotFoundError } from '@/restful/errors';
|
||||||
@@ -13,11 +16,44 @@ import { getISO } from '@/utils/geo';
|
|||||||
import env from '@/utils/env';
|
import env from '@/utils/env';
|
||||||
|
|
||||||
export default function register($app) {
|
export default function register($app) {
|
||||||
|
$app.get('/share/col/:name/:target', async (req, res) => {
|
||||||
|
const { target } = req.params;
|
||||||
|
if (target) {
|
||||||
|
req.query.target = target;
|
||||||
|
$.info(`使用路由指定目标: ${target}`);
|
||||||
|
}
|
||||||
|
await downloadCollection(req, res);
|
||||||
|
});
|
||||||
$app.get('/share/col/:name', downloadCollection);
|
$app.get('/share/col/:name', downloadCollection);
|
||||||
|
$app.get('/share/sub/:name/:target', async (req, res) => {
|
||||||
|
const { target } = req.params;
|
||||||
|
if (target) {
|
||||||
|
req.query.target = target;
|
||||||
|
$.info(`使用路由指定目标: ${target}`);
|
||||||
|
}
|
||||||
|
await downloadSubscription(req, res);
|
||||||
|
});
|
||||||
$app.get('/share/sub/:name', downloadSubscription);
|
$app.get('/share/sub/:name', downloadSubscription);
|
||||||
|
|
||||||
|
$app.get('/download/collection/:name/:target', async (req, res) => {
|
||||||
|
const { target } = req.params;
|
||||||
|
if (target) {
|
||||||
|
req.query.target = target;
|
||||||
|
$.info(`使用路由指定目标: ${target}`);
|
||||||
|
}
|
||||||
|
await downloadCollection(req, res);
|
||||||
|
});
|
||||||
$app.get('/download/collection/:name', downloadCollection);
|
$app.get('/download/collection/:name', downloadCollection);
|
||||||
|
$app.get('/download/:name/:target', async (req, res) => {
|
||||||
|
const { target } = req.params;
|
||||||
|
if (target) {
|
||||||
|
req.query.target = target;
|
||||||
|
$.info(`使用路由指定目标: ${target}`);
|
||||||
|
}
|
||||||
|
await downloadSubscription(req, res);
|
||||||
|
});
|
||||||
$app.get('/download/:name', downloadSubscription);
|
$app.get('/download/:name', downloadSubscription);
|
||||||
|
|
||||||
$app.get(
|
$app.get(
|
||||||
'/download/collection/:name/api/v1/server/details',
|
'/download/collection/:name/api/v1/server/details',
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
@@ -59,11 +95,9 @@ async function downloadSubscription(req, res) {
|
|||||||
|
|
||||||
const platform =
|
const platform =
|
||||||
req.query.target || getPlatformFromHeaders(req.headers) || 'JSON';
|
req.query.target || getPlatformFromHeaders(req.headers) || 'JSON';
|
||||||
|
const reqUA = req.headers['user-agent'] || req.headers['User-Agent'];
|
||||||
$.info(
|
$.info(
|
||||||
`正在下载订阅:${name}\n请求 User-Agent: ${
|
`正在下载订阅:${name}\n请求 User-Agent: ${reqUA}\n请求 target: ${req.query.target}\n实际输出: ${platform}`,
|
||||||
req.headers['user-agent'] || req.headers['User-Agent']
|
|
||||||
}\n请求 target: ${req.query.target}\n实际输出: ${platform}`,
|
|
||||||
);
|
);
|
||||||
let {
|
let {
|
||||||
url,
|
url,
|
||||||
@@ -98,6 +132,14 @@ async function downloadSubscription(req, res) {
|
|||||||
if (url) {
|
if (url) {
|
||||||
url = decodeURIComponent(url);
|
url = decodeURIComponent(url);
|
||||||
$.info(`指定远程订阅 URL: ${url}`);
|
$.info(`指定远程订阅 URL: ${url}`);
|
||||||
|
if (!/^https?:\/\//.test(url)) {
|
||||||
|
content = url;
|
||||||
|
$.info(`URL 不是链接,视为本地订阅`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (content) {
|
||||||
|
content = decodeURIComponent(content);
|
||||||
|
$.info(`指定本地订阅: ${content}`);
|
||||||
}
|
}
|
||||||
if (proxy) {
|
if (proxy) {
|
||||||
proxy = decodeURIComponent(proxy);
|
proxy = decodeURIComponent(proxy);
|
||||||
@@ -107,10 +149,7 @@ async function downloadSubscription(req, res) {
|
|||||||
ua = decodeURIComponent(ua);
|
ua = decodeURIComponent(ua);
|
||||||
$.info(`指定远程订阅 User-Agent: ${ua}`);
|
$.info(`指定远程订阅 User-Agent: ${ua}`);
|
||||||
}
|
}
|
||||||
if (content) {
|
|
||||||
content = decodeURIComponent(content);
|
|
||||||
$.info(`指定本地订阅: ${content}`);
|
|
||||||
}
|
|
||||||
if (mergeSources) {
|
if (mergeSources) {
|
||||||
mergeSources = decodeURIComponent(mergeSources);
|
mergeSources = decodeURIComponent(mergeSources);
|
||||||
$.info(`指定合并来源: ${mergeSources}`);
|
$.info(`指定合并来源: ${mergeSources}`);
|
||||||
@@ -125,7 +164,19 @@ async function downloadSubscription(req, res) {
|
|||||||
}
|
}
|
||||||
if (includeUnsupportedProxy) {
|
if (includeUnsupportedProxy) {
|
||||||
includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy);
|
includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy);
|
||||||
$.info(`包含不支持的节点: ${includeUnsupportedProxy}`);
|
$.info(
|
||||||
|
`包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!includeUnsupportedProxy &&
|
||||||
|
shouldIncludeUnsupportedProxy(platform, reqUA)
|
||||||
|
) {
|
||||||
|
includeUnsupportedProxy = true;
|
||||||
|
$.info(
|
||||||
|
`当前客户端可包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useMihomoExternal) {
|
if (useMihomoExternal) {
|
||||||
@@ -140,6 +191,13 @@ async function downloadSubscription(req, res) {
|
|||||||
const sub = findByName(allSubs, name);
|
const sub = findByName(allSubs, name);
|
||||||
if (sub) {
|
if (sub) {
|
||||||
try {
|
try {
|
||||||
|
const passThroughUA = sub.passThroughUA;
|
||||||
|
if (passThroughUA) {
|
||||||
|
$.info(
|
||||||
|
`订阅开启了透传 User-Agent, 使用请求的 User-Agent: ${reqUA}`,
|
||||||
|
);
|
||||||
|
ua = reqUA;
|
||||||
|
}
|
||||||
let output = await produceArtifact({
|
let output = await produceArtifact({
|
||||||
type: 'subscription',
|
type: 'subscription',
|
||||||
name,
|
name,
|
||||||
@@ -201,7 +259,10 @@ async function downloadSubscription(req, res) {
|
|||||||
$arguments.flowUrl,
|
$arguments.flowUrl,
|
||||||
);
|
);
|
||||||
if (flowInfo) {
|
if (flowInfo) {
|
||||||
res.set('subscription-userinfo', flowInfo);
|
res.set(
|
||||||
|
'subscription-userinfo',
|
||||||
|
normalizeFlowHeader(flowInfo),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -235,7 +296,9 @@ async function downloadSubscription(req, res) {
|
|||||||
}
|
}
|
||||||
res.set(
|
res.set(
|
||||||
'subscription-userinfo',
|
'subscription-userinfo',
|
||||||
[subUserInfo, flowInfo].filter((i) => i).join('; '),
|
normalizeFlowHeader(
|
||||||
|
[subUserInfo, flowInfo].filter((i) => i).join(';'),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,11 +362,9 @@ async function downloadCollection(req, res) {
|
|||||||
|
|
||||||
const allCols = $.read(COLLECTIONS_KEY);
|
const allCols = $.read(COLLECTIONS_KEY);
|
||||||
const collection = findByName(allCols, name);
|
const collection = findByName(allCols, name);
|
||||||
|
const reqUA = req.headers['user-agent'] || req.headers['User-Agent'];
|
||||||
$.info(
|
$.info(
|
||||||
`正在下载组合订阅:${name}\n请求 User-Agent: ${
|
`正在下载组合订阅:${name}\n请求 User-Agent: ${reqUA}\n请求 target: ${req.query.target}\n实际输出: ${platform}`,
|
||||||
req.headers['user-agent'] || req.headers['User-Agent']
|
|
||||||
}\n请求 target: ${req.query.target}\n实际输出: ${platform}`,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@@ -350,7 +411,18 @@ async function downloadCollection(req, res) {
|
|||||||
|
|
||||||
if (includeUnsupportedProxy) {
|
if (includeUnsupportedProxy) {
|
||||||
includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy);
|
includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy);
|
||||||
$.info(`包含不支持的节点: ${includeUnsupportedProxy}`);
|
$.info(
|
||||||
|
`包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!includeUnsupportedProxy &&
|
||||||
|
shouldIncludeUnsupportedProxy(platform, reqUA)
|
||||||
|
) {
|
||||||
|
includeUnsupportedProxy = true;
|
||||||
|
$.info(
|
||||||
|
`当前客户端可包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (useMihomoExternal) {
|
if (useMihomoExternal) {
|
||||||
$.info(`手动指定了 target 为 SurgeMac, 将使用 Mihomo External`);
|
$.info(`手动指定了 target 为 SurgeMac, 将使用 Mihomo External`);
|
||||||
@@ -374,6 +446,7 @@ async function downloadCollection(req, res) {
|
|||||||
$options,
|
$options,
|
||||||
proxy,
|
proxy,
|
||||||
noCache,
|
noCache,
|
||||||
|
ua: reqUA,
|
||||||
});
|
});
|
||||||
let subUserInfoOfSub;
|
let subUserInfoOfSub;
|
||||||
// forward flow header from the first subscription in this collection
|
// forward flow header from the first subscription in this collection
|
||||||
@@ -479,13 +552,15 @@ async function downloadCollection(req, res) {
|
|||||||
} else {
|
} else {
|
||||||
subUserInfoOfCol = collection.subUserinfo;
|
subUserInfoOfCol = collection.subUserinfo;
|
||||||
}
|
}
|
||||||
res.set(
|
const subUserInfo = [subUserInfoOfCol, subUserInfoOfSub]
|
||||||
'subscription-userinfo',
|
.filter((i) => i)
|
||||||
[subUserInfoOfCol, subUserInfoOfSub]
|
.join('; ');
|
||||||
.filter((i) => i)
|
if (subUserInfo) {
|
||||||
.join('; '),
|
res.set(
|
||||||
);
|
'subscription-userinfo',
|
||||||
|
normalizeFlowHeader(subUserInfo),
|
||||||
|
);
|
||||||
|
}
|
||||||
if (platform === 'JSON') {
|
if (platform === 'JSON') {
|
||||||
if (resultFormat === 'nezha') {
|
if (resultFormat === 'nezha') {
|
||||||
output = nezhaTransform(output);
|
output = nezhaTransform(output);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
||||||
import { getFlowHeaders } from '@/utils/flow';
|
import { getFlowHeaders, normalizeFlowHeader } from '@/utils/flow';
|
||||||
import { FILES_KEY } from '@/constants';
|
import { FILES_KEY } from '@/constants';
|
||||||
import { failed, success } from '@/restful/response';
|
import { failed, success } from '@/restful/response';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
@@ -146,7 +146,10 @@ async function getFile(req, res) {
|
|||||||
proxy || file.proxy,
|
proxy || file.proxy,
|
||||||
);
|
);
|
||||||
if (flowInfo) {
|
if (flowInfo) {
|
||||||
res.set('subscription-userinfo', flowInfo);
|
res.set(
|
||||||
|
'subscription-userinfo',
|
||||||
|
normalizeFlowHeader(flowInfo),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -197,11 +200,32 @@ async function getFile(req, res) {
|
|||||||
}
|
}
|
||||||
function getWholeFile(req, res) {
|
function getWholeFile(req, res) {
|
||||||
let { name } = req.params;
|
let { name } = req.params;
|
||||||
|
let { raw } = req.query;
|
||||||
name = decodeURIComponent(name);
|
name = decodeURIComponent(name);
|
||||||
const allFiles = $.read(FILES_KEY);
|
const allFiles = $.read(FILES_KEY);
|
||||||
const file = findByName(allFiles, name);
|
const file = findByName(allFiles, name);
|
||||||
if (file) {
|
if (file) {
|
||||||
success(res, file);
|
if (raw) {
|
||||||
|
res.set('content-type', 'application/json')
|
||||||
|
.set(
|
||||||
|
'content-disposition',
|
||||||
|
`attachment; filename="${encodeURIComponent(
|
||||||
|
`sub-store_file_${name}_${new Date()
|
||||||
|
.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
second: 'numeric',
|
||||||
|
})
|
||||||
|
.replace(/\D/g, '')}.json`,
|
||||||
|
)}"`,
|
||||||
|
)
|
||||||
|
.send(JSON.stringify(file));
|
||||||
|
} else {
|
||||||
|
success(res, file);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
failed(
|
failed(
|
||||||
res,
|
res,
|
||||||
|
|||||||
@@ -27,7 +27,18 @@ export default function register($app) {
|
|||||||
res.set('content-type', 'application/json')
|
res.set('content-type', 'application/json')
|
||||||
.set(
|
.set(
|
||||||
'content-disposition',
|
'content-disposition',
|
||||||
'attachment; filename="sub-store.json"',
|
`attachment; filename="${encodeURIComponent(
|
||||||
|
`sub-store_data_${new Date()
|
||||||
|
.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
second: 'numeric',
|
||||||
|
})
|
||||||
|
.replace(/\D/g, '')}.json`,
|
||||||
|
)}"`,
|
||||||
)
|
)
|
||||||
.send(
|
.send(
|
||||||
$.env.isNode
|
$.env.isNode
|
||||||
@@ -98,6 +109,21 @@ async function gistBackupAction(action) {
|
|||||||
const updated = settings.syncTime;
|
const updated = settings.syncTime;
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'upload':
|
case 'upload':
|
||||||
|
try {
|
||||||
|
content = $.read('#sub-store');
|
||||||
|
if ($.env.isNode) content = JSON.stringify($.cache, null, ` `);
|
||||||
|
$.info(`下载备份, 与本地内容对比...`);
|
||||||
|
const onlineContent = await gist.download(
|
||||||
|
GIST_BACKUP_FILE_NAME,
|
||||||
|
);
|
||||||
|
if (onlineContent === content) {
|
||||||
|
$.info(`内容一致, 无需上传备份`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
$.error(`${error.message ?? error}`);
|
||||||
|
}
|
||||||
|
|
||||||
// update syncTime
|
// update syncTime
|
||||||
settings.syncTime = new Date().getTime();
|
settings.syncTime = new Date().getTime();
|
||||||
$.write(settings, SETTINGS_KEY);
|
$.write(settings, SETTINGS_KEY);
|
||||||
|
|||||||
@@ -15,46 +15,48 @@ export default function register($app) {
|
|||||||
async function previewFile(req, res) {
|
async function previewFile(req, res) {
|
||||||
try {
|
try {
|
||||||
const file = req.body;
|
const file = req.body;
|
||||||
let content;
|
let content = '';
|
||||||
if (
|
if (file.type !== 'mihomoProfile') {
|
||||||
file.source === 'local' &&
|
|
||||||
!['localFirst', 'remoteFirst'].includes(file.mergeSources)
|
|
||||||
) {
|
|
||||||
content = file.content;
|
|
||||||
} else {
|
|
||||||
const errors = {};
|
|
||||||
content = await Promise.all(
|
|
||||||
file.url
|
|
||||||
.split(/[\r\n]+/)
|
|
||||||
.map((i) => i.trim())
|
|
||||||
.filter((i) => i.length)
|
|
||||||
.map(async (url) => {
|
|
||||||
try {
|
|
||||||
return await download(url, file.ua);
|
|
||||||
} catch (err) {
|
|
||||||
errors[url] = err;
|
|
||||||
$.error(
|
|
||||||
`文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`,
|
|
||||||
);
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!file.ignoreFailedRemoteFile &&
|
file.source === 'local' &&
|
||||||
Object.keys(errors).length > 0
|
!['localFirst', 'remoteFirst'].includes(file.mergeSources)
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
content = file.content;
|
||||||
`文件 ${file.name} 的远程文件 ${Object.keys(errors).join(
|
} else {
|
||||||
', ',
|
const errors = {};
|
||||||
)} 发生错误, 请查看日志`,
|
content = await Promise.all(
|
||||||
|
file.url
|
||||||
|
.split(/[\r\n]+/)
|
||||||
|
.map((i) => i.trim())
|
||||||
|
.filter((i) => i.length)
|
||||||
|
.map(async (url) => {
|
||||||
|
try {
|
||||||
|
return await download(url, file.ua);
|
||||||
|
} catch (err) {
|
||||||
|
errors[url] = err;
|
||||||
|
$.error(
|
||||||
|
`文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`,
|
||||||
|
);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
if (file.mergeSources === 'localFirst') {
|
if (
|
||||||
content.unshift(file.content);
|
!file.ignoreFailedRemoteFile &&
|
||||||
} else if (file.mergeSources === 'remoteFirst') {
|
Object.keys(errors).length > 0
|
||||||
content.push(file.content);
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`文件 ${file.name} 的远程文件 ${Object.keys(
|
||||||
|
errors,
|
||||||
|
).join(', ')} 发生错误, 请查看日志`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (file.mergeSources === 'localFirst') {
|
||||||
|
content.unshift(file.content);
|
||||||
|
} else if (file.mergeSources === 'remoteFirst') {
|
||||||
|
content.push(file.content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// parse proxies
|
// parse proxies
|
||||||
@@ -67,7 +69,7 @@ async function previewFile(req, res) {
|
|||||||
const processed =
|
const processed =
|
||||||
Array.isArray(file.process) && file.process.length > 0
|
Array.isArray(file.process) && file.process.length > 0
|
||||||
? await ProxyUtils.process(
|
? await ProxyUtils.process(
|
||||||
{ $files: files, $content: filesContent },
|
{ $files: files, $content: filesContent, $file: file },
|
||||||
file.process,
|
file.process,
|
||||||
)
|
)
|
||||||
: { $content: filesContent, $files: files };
|
: { $content: filesContent, $files: files };
|
||||||
|
|||||||
@@ -264,7 +264,18 @@ function getSubscription(req, res) {
|
|||||||
res.set('content-type', 'application/json')
|
res.set('content-type', 'application/json')
|
||||||
.set(
|
.set(
|
||||||
'content-disposition',
|
'content-disposition',
|
||||||
`attachment; filename="${encodeURIComponent(name)}.json"`,
|
`attachment; filename="${encodeURIComponent(
|
||||||
|
`sub-store_subscription_${name}_${new Date()
|
||||||
|
.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
second: 'numeric',
|
||||||
|
})
|
||||||
|
.replace(/\D/g, '')}.json`,
|
||||||
|
)}"`,
|
||||||
)
|
)
|
||||||
.send(JSON.stringify(sub));
|
.send(JSON.stringify(sub));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ async function produceArtifact({
|
|||||||
proxy || sub.proxy,
|
proxy || sub.proxy,
|
||||||
undefined,
|
undefined,
|
||||||
awaitCustomCache,
|
awaitCustomCache,
|
||||||
noCache,
|
noCache || sub.noCache,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -122,7 +122,7 @@ async function produceArtifact({
|
|||||||
proxy || sub.proxy,
|
proxy || sub.proxy,
|
||||||
undefined,
|
undefined,
|
||||||
awaitCustomCache,
|
awaitCustomCache,
|
||||||
noCache,
|
noCache || sub.noCache,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -216,6 +216,14 @@ async function produceArtifact({
|
|||||||
await Promise.all(
|
await Promise.all(
|
||||||
subnames.map(async (name) => {
|
subnames.map(async (name) => {
|
||||||
const sub = findByName(allSubs, name);
|
const sub = findByName(allSubs, name);
|
||||||
|
const passThroughUA = sub.passThroughUA;
|
||||||
|
let reqUA = sub.ua;
|
||||||
|
if (passThroughUA) {
|
||||||
|
$.info(
|
||||||
|
`订阅开启了透传 User-Agent, 使用请求的 User-Agent: ${ua}`,
|
||||||
|
);
|
||||||
|
reqUA = ua;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
$.info(`正在处理子订阅:${sub.name}...`);
|
$.info(`正在处理子订阅:${sub.name}...`);
|
||||||
let raw;
|
let raw;
|
||||||
@@ -237,14 +245,14 @@ async function produceArtifact({
|
|||||||
try {
|
try {
|
||||||
return await download(
|
return await download(
|
||||||
url,
|
url,
|
||||||
sub.ua,
|
reqUA,
|
||||||
undefined,
|
undefined,
|
||||||
proxy ||
|
proxy ||
|
||||||
sub.proxy ||
|
sub.proxy ||
|
||||||
collection.proxy,
|
collection.proxy,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
noCache,
|
noCache || sub.noCache,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -402,105 +410,117 @@ async function produceArtifact({
|
|||||||
const allFiles = $.read(FILES_KEY);
|
const allFiles = $.read(FILES_KEY);
|
||||||
const file = findByName(allFiles, name);
|
const file = findByName(allFiles, name);
|
||||||
if (!file) throw new Error(`找不到文件 ${name}`);
|
if (!file) throw new Error(`找不到文件 ${name}`);
|
||||||
let raw;
|
let raw = '';
|
||||||
if (content && !['localFirst', 'remoteFirst'].includes(mergeSources)) {
|
console.log(file);
|
||||||
raw = content;
|
if (file.type !== 'mihomoProfile') {
|
||||||
} else if (url) {
|
|
||||||
const errors = {};
|
|
||||||
raw = await Promise.all(
|
|
||||||
url
|
|
||||||
.split(/[\r\n]+/)
|
|
||||||
.map((i) => i.trim())
|
|
||||||
.filter((i) => i.length)
|
|
||||||
.map(async (url) => {
|
|
||||||
try {
|
|
||||||
return await download(
|
|
||||||
url,
|
|
||||||
ua || file.ua,
|
|
||||||
undefined,
|
|
||||||
file.proxy || proxy,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
noCache,
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
errors[url] = err;
|
|
||||||
$.error(
|
|
||||||
`文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`,
|
|
||||||
);
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
let fileIgnoreFailedRemoteFile = file.ignoreFailedRemoteFile;
|
|
||||||
if (
|
if (
|
||||||
ignoreFailedRemoteFile != null &&
|
content &&
|
||||||
ignoreFailedRemoteFile !== ''
|
!['localFirst', 'remoteFirst'].includes(mergeSources)
|
||||||
) {
|
) {
|
||||||
fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile;
|
raw = content;
|
||||||
}
|
} else if (url) {
|
||||||
if (!fileIgnoreFailedRemoteFile && Object.keys(errors).length > 0) {
|
const errors = {};
|
||||||
throw new Error(
|
raw = await Promise.all(
|
||||||
`文件 ${file.name} 的远程文件 ${Object.keys(errors).join(
|
url
|
||||||
', ',
|
.split(/[\r\n]+/)
|
||||||
)} 发生错误, 请查看日志`,
|
.map((i) => i.trim())
|
||||||
|
.filter((i) => i.length)
|
||||||
|
.map(async (url) => {
|
||||||
|
try {
|
||||||
|
return await download(
|
||||||
|
url,
|
||||||
|
ua || file.ua,
|
||||||
|
undefined,
|
||||||
|
file.proxy || proxy,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
noCache,
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
errors[url] = err;
|
||||||
|
$.error(
|
||||||
|
`文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`,
|
||||||
|
);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
let fileIgnoreFailedRemoteFile = file.ignoreFailedRemoteFile;
|
||||||
if (mergeSources === 'localFirst') {
|
if (
|
||||||
raw.unshift(content);
|
ignoreFailedRemoteFile != null &&
|
||||||
} else if (mergeSources === 'remoteFirst') {
|
ignoreFailedRemoteFile !== ''
|
||||||
raw.push(content);
|
) {
|
||||||
}
|
fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile;
|
||||||
} else if (
|
}
|
||||||
file.source === 'local' &&
|
if (
|
||||||
!['localFirst', 'remoteFirst'].includes(file.mergeSources)
|
!fileIgnoreFailedRemoteFile &&
|
||||||
) {
|
Object.keys(errors).length > 0
|
||||||
raw = file.content;
|
) {
|
||||||
} else {
|
throw new Error(
|
||||||
const errors = {};
|
`文件 ${file.name} 的远程文件 ${Object.keys(
|
||||||
raw = await Promise.all(
|
errors,
|
||||||
file.url
|
).join(', ')} 发生错误, 请查看日志`,
|
||||||
.split(/[\r\n]+/)
|
);
|
||||||
.map((i) => i.trim())
|
}
|
||||||
.filter((i) => i.length)
|
if (mergeSources === 'localFirst') {
|
||||||
.map(async (url) => {
|
raw.unshift(content);
|
||||||
try {
|
} else if (mergeSources === 'remoteFirst') {
|
||||||
return await download(
|
raw.push(content);
|
||||||
url,
|
}
|
||||||
ua || file.ua,
|
} else if (
|
||||||
undefined,
|
file.source === 'local' &&
|
||||||
file.proxy || proxy,
|
!['localFirst', 'remoteFirst'].includes(file.mergeSources)
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
noCache,
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
errors[url] = err;
|
|
||||||
$.error(
|
|
||||||
`文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`,
|
|
||||||
);
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
let fileIgnoreFailedRemoteFile = file.ignoreFailedRemoteFile;
|
|
||||||
if (
|
|
||||||
ignoreFailedRemoteFile != null &&
|
|
||||||
ignoreFailedRemoteFile !== ''
|
|
||||||
) {
|
) {
|
||||||
fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile;
|
raw = file.content;
|
||||||
}
|
} else {
|
||||||
if (!fileIgnoreFailedRemoteFile && Object.keys(errors).length > 0) {
|
const errors = {};
|
||||||
throw new Error(
|
raw = await Promise.all(
|
||||||
`文件 ${file.name} 的远程文件 ${Object.keys(errors).join(
|
file.url
|
||||||
', ',
|
.split(/[\r\n]+/)
|
||||||
)} 发生错误, 请查看日志`,
|
.map((i) => i.trim())
|
||||||
|
.filter((i) => i.length)
|
||||||
|
.map(async (url) => {
|
||||||
|
try {
|
||||||
|
return await download(
|
||||||
|
url,
|
||||||
|
ua || file.ua,
|
||||||
|
undefined,
|
||||||
|
file.proxy || proxy,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
noCache,
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
errors[url] = err;
|
||||||
|
$.error(
|
||||||
|
`文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`,
|
||||||
|
);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
let fileIgnoreFailedRemoteFile = file.ignoreFailedRemoteFile;
|
||||||
if (file.mergeSources === 'localFirst') {
|
if (
|
||||||
raw.unshift(file.content);
|
ignoreFailedRemoteFile != null &&
|
||||||
} else if (file.mergeSources === 'remoteFirst') {
|
ignoreFailedRemoteFile !== ''
|
||||||
raw.push(file.content);
|
) {
|
||||||
|
fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!fileIgnoreFailedRemoteFile &&
|
||||||
|
Object.keys(errors).length > 0
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`文件 ${file.name} 的远程文件 ${Object.keys(
|
||||||
|
errors,
|
||||||
|
).join(', ')} 发生错误, 请查看日志`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (file.mergeSources === 'localFirst') {
|
||||||
|
raw.unshift(file.content);
|
||||||
|
} else if (file.mergeSources === 'remoteFirst') {
|
||||||
|
raw.push(file.content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const files = (Array.isArray(raw) ? raw : [raw]).flat();
|
const files = (Array.isArray(raw) ? raw : [raw]).flat();
|
||||||
@@ -512,7 +532,12 @@ async function produceArtifact({
|
|||||||
const processed =
|
const processed =
|
||||||
Array.isArray(file.process) && file.process.length > 0
|
Array.isArray(file.process) && file.process.length > 0
|
||||||
? await ProxyUtils.process(
|
? await ProxyUtils.process(
|
||||||
{ $files: files, $content: filesContent, $options },
|
{
|
||||||
|
$files: files,
|
||||||
|
$content: filesContent,
|
||||||
|
$options,
|
||||||
|
$file: file,
|
||||||
|
},
|
||||||
file.process,
|
file.process,
|
||||||
)
|
)
|
||||||
: { $content: filesContent, $files: files, $options };
|
: { $content: filesContent, $files: files, $options };
|
||||||
@@ -527,6 +552,7 @@ async function syncArtifacts() {
|
|||||||
const files = {};
|
const files = {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const valid = [];
|
||||||
const invalid = [];
|
const invalid = [];
|
||||||
const allSubs = $.read(SUBS_KEY);
|
const allSubs = $.read(SUBS_KEY);
|
||||||
const allCols = $.read(COLLECTIONS_KEY);
|
const allCols = $.read(COLLECTIONS_KEY);
|
||||||
@@ -601,27 +627,47 @@ async function syncArtifacts() {
|
|||||||
files[encodeURIComponent(artifact.name)] = {
|
files[encodeURIComponent(artifact.name)] = {
|
||||||
content: output,
|
content: output,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
valid.push(artifact.name);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
$.error(
|
$.error(
|
||||||
`同步配置 ${artifact.name} 发生错误: ${e.message ?? e}`,
|
`生成同步配置 ${artifact.name} 发生错误: ${
|
||||||
|
e.message ?? e
|
||||||
|
}`,
|
||||||
);
|
);
|
||||||
invalid.push(artifact.name);
|
invalid.push(artifact.name);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (invalid.length > 0) {
|
$.info(`${valid.length} 个同步配置生成成功: ${valid.join(', ')}`);
|
||||||
|
$.info(`${invalid.length} 个同步配置生成失败: ${invalid.join(', ')}`);
|
||||||
|
|
||||||
|
if (valid.length === 0) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`同步配置 ${invalid.join(', ')} 发生错误 详情请查看日志`,
|
`同步配置 ${invalid.join(', ')} 生成失败 详情请查看日志`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const resp = await syncToGist(files);
|
const resp = await syncToGist(files);
|
||||||
const body = JSON.parse(resp.body);
|
const body = JSON.parse(resp.body);
|
||||||
|
|
||||||
|
delete body.history;
|
||||||
|
delete body.forks;
|
||||||
|
delete body.owner;
|
||||||
|
Object.values(body.files).forEach((file) => {
|
||||||
|
delete file.content;
|
||||||
|
});
|
||||||
|
$.info('上传配置响应:');
|
||||||
|
$.info(JSON.stringify(body, null, 2));
|
||||||
|
|
||||||
for (const artifact of allArtifacts) {
|
for (const artifact of allArtifacts) {
|
||||||
if (artifact.sync) {
|
if (
|
||||||
|
artifact.sync &&
|
||||||
|
artifact.source &&
|
||||||
|
valid.includes(artifact.name)
|
||||||
|
) {
|
||||||
artifact.updated = new Date().getTime();
|
artifact.updated = new Date().getTime();
|
||||||
// extract real url from gist
|
// extract real url from gist
|
||||||
let files = body.files;
|
let files = body.files;
|
||||||
@@ -649,9 +695,17 @@ async function syncArtifacts() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$.write(allArtifacts, ARTIFACTS_KEY);
|
$.write(allArtifacts, ARTIFACTS_KEY);
|
||||||
$.info('全部订阅同步成功!');
|
$.info('上传配置成功');
|
||||||
|
|
||||||
|
if (invalid.length > 0) {
|
||||||
|
throw new Error(
|
||||||
|
`同步配置成功 ${valid.length} 个, 失败 ${invalid.length} 个, 详情请查看日志`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$.info(`同步配置成功 ${valid.length} 个`);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
$.error(`同步订阅失败,原因:${e.message ?? e}`);
|
$.error(`同步配置失败,原因:${e.message ?? e}`);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -661,7 +715,7 @@ async function syncAllArtifacts(_, res) {
|
|||||||
await syncArtifacts();
|
await syncArtifacts();
|
||||||
success(res);
|
success(res);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
$.error(`同步订阅失败,原因:${e.message ?? e}`);
|
$.error(`同步配置失败,原因:${e.message ?? e}`);
|
||||||
failed(
|
failed(
|
||||||
res,
|
res,
|
||||||
new InternalServerError(
|
new InternalServerError(
|
||||||
@@ -738,6 +792,16 @@ async function syncArtifact(req, res) {
|
|||||||
});
|
});
|
||||||
artifact.updated = new Date().getTime();
|
artifact.updated = new Date().getTime();
|
||||||
const body = JSON.parse(resp.body);
|
const body = JSON.parse(resp.body);
|
||||||
|
|
||||||
|
delete body.history;
|
||||||
|
delete body.forks;
|
||||||
|
delete body.owner;
|
||||||
|
Object.values(body.files).forEach((file) => {
|
||||||
|
delete file.content;
|
||||||
|
});
|
||||||
|
$.info('上传配置响应:');
|
||||||
|
$.info(JSON.stringify(body, null, 2));
|
||||||
|
|
||||||
let files = body.files;
|
let files = body.files;
|
||||||
let isGitLab;
|
let isGitLab;
|
||||||
if (Array.isArray(files)) {
|
if (Array.isArray(files)) {
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ export default async function download(
|
|||||||
if (preprocess) {
|
if (preprocess) {
|
||||||
try {
|
try {
|
||||||
if (clashPreprocessor.test(body)) {
|
if (clashPreprocessor.test(body)) {
|
||||||
body = clashPreprocessor.parse(body);
|
body = clashPreprocessor.parse(body, true);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
$.error(`Clash Pre-processor error: ${e}`);
|
$.error(`Clash Pre-processor error: ${e}`);
|
||||||
|
|||||||
@@ -313,3 +313,41 @@ export function getRmainingDays(opt = {}) {
|
|||||||
$.error(`getRmainingDays failed: ${e.message ?? e}`);
|
$.error(`getRmainingDays failed: ${e.message ?? e}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function normalizeFlowHeader(flowHeaders) {
|
||||||
|
try {
|
||||||
|
// 使用 Map 保持顺序并处理重复键
|
||||||
|
const kvMap = new Map();
|
||||||
|
|
||||||
|
flowHeaders
|
||||||
|
.split(';')
|
||||||
|
.map((p) => p.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.forEach((pair) => {
|
||||||
|
const eqIndex = pair.indexOf('=');
|
||||||
|
if (eqIndex === -1) return;
|
||||||
|
|
||||||
|
const key = pair.slice(0, eqIndex).trim();
|
||||||
|
const encodedValue = pair.slice(eqIndex + 1).trim();
|
||||||
|
|
||||||
|
// 只保留第一个出现的 key
|
||||||
|
if (!kvMap.has(key)) {
|
||||||
|
try {
|
||||||
|
// 解码 URI 组件并保留原始值作为 fallback
|
||||||
|
const decodedValue = decodeURIComponent(encodedValue);
|
||||||
|
kvMap.set(key, decodedValue);
|
||||||
|
} catch (e) {
|
||||||
|
kvMap.set(key, encodedValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 拼接标准化字符串
|
||||||
|
return Array.from(kvMap.entries())
|
||||||
|
.map(([k, v]) => `${k}=${encodeURIComponent(v)}`) // 重新编码保持兼容性
|
||||||
|
.join('; ');
|
||||||
|
} catch (e) {
|
||||||
|
$.error(`normalizeFlowHeader failed: ${e.message ?? e}`);
|
||||||
|
return flowHeaders;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ const ISOFlags = {
|
|||||||
'🇧🇬': ['BG', 'BGR'],
|
'🇧🇬': ['BG', 'BGR'],
|
||||||
'🇧🇭': ['BH', 'BHR'],
|
'🇧🇭': ['BH', 'BHR'],
|
||||||
'🇧🇴': ['BO', 'BOL'],
|
'🇧🇴': ['BO', 'BOL'],
|
||||||
|
'🇧🇳': ['BN', 'BRN'],
|
||||||
'🇧🇷': ['BR', 'BRA'],
|
'🇧🇷': ['BR', 'BRA'],
|
||||||
|
'🇧🇹': ['BT', 'BTN'],
|
||||||
'🇧🇾': ['BY', 'BLR'],
|
'🇧🇾': ['BY', 'BLR'],
|
||||||
'🇨🇦': ['CA', 'CAN'],
|
'🇨🇦': ['CA', 'CAN'],
|
||||||
'🇨🇭': ['CH', 'CHE'],
|
'🇨🇭': ['CH', 'CHE'],
|
||||||
@@ -40,6 +42,7 @@ const ISOFlags = {
|
|||||||
'🇬🇪': ['GE', 'GEO'],
|
'🇬🇪': ['GE', 'GEO'],
|
||||||
'🇬🇷': ['GR', 'GRC'],
|
'🇬🇷': ['GR', 'GRC'],
|
||||||
'🇬🇹': ['GT', 'GTM'],
|
'🇬🇹': ['GT', 'GTM'],
|
||||||
|
'🇬🇺': ['GU', 'GUM'],
|
||||||
'🇭🇰': ['HK', 'HKG', 'HKT', 'HKBN', 'HGC', 'WTT', 'CMI'],
|
'🇭🇰': ['HK', 'HKG', 'HKT', 'HKBN', 'HGC', 'WTT', 'CMI'],
|
||||||
'🇭🇷': ['HR', 'HRV'],
|
'🇭🇷': ['HR', 'HRV'],
|
||||||
'🇭🇺': ['HU', 'HUN'],
|
'🇭🇺': ['HU', 'HUN'],
|
||||||
@@ -59,12 +62,15 @@ const ISOFlags = {
|
|||||||
'🇮🇷': ['IR', 'IRN'],
|
'🇮🇷': ['IR', 'IRN'],
|
||||||
'🇮🇸': ['IS', 'ISL'],
|
'🇮🇸': ['IS', 'ISL'],
|
||||||
'🇮🇹': ['IT', 'ITA'],
|
'🇮🇹': ['IT', 'ITA'],
|
||||||
|
'🇱🇦': ['LA', 'LAO'],
|
||||||
|
'🇱🇰': ['LK', 'LKA'],
|
||||||
'🇱🇹': ['LT', 'LTU'],
|
'🇱🇹': ['LT', 'LTU'],
|
||||||
'🇱🇺': ['LU', 'LUX'],
|
'🇱🇺': ['LU', 'LUX'],
|
||||||
'🇱🇻': ['LV', 'LVA'],
|
'🇱🇻': ['LV', 'LVA'],
|
||||||
'🇲🇦': ['MA', 'MAR'],
|
'🇲🇦': ['MA', 'MAR'],
|
||||||
'🇲🇩': ['MD', 'MDA'],
|
'🇲🇩': ['MD', 'MDA'],
|
||||||
'🇳🇬': ['NG', 'NGA'],
|
'🇳🇬': ['NG', 'NGA'],
|
||||||
|
'🇲🇲': ['MM', 'MMR'],
|
||||||
'🇲🇰': ['MK', 'MKD'],
|
'🇲🇰': ['MK', 'MKD'],
|
||||||
'🇲🇳': ['MN', 'MNG'],
|
'🇲🇳': ['MN', 'MNG'],
|
||||||
'🇲🇴': ['MO', 'MAC', 'CTM'],
|
'🇲🇴': ['MO', 'MAC', 'CTM'],
|
||||||
@@ -83,6 +89,7 @@ const ISOFlags = {
|
|||||||
'🇵🇷': ['PR', 'PRI'],
|
'🇵🇷': ['PR', 'PRI'],
|
||||||
'🇵🇹': ['PT', 'PRT'],
|
'🇵🇹': ['PT', 'PRT'],
|
||||||
'🇵🇾': ['PY', 'PRY'],
|
'🇵🇾': ['PY', 'PRY'],
|
||||||
|
'🇵🇬': ['PG', 'PNG'],
|
||||||
'🇷🇴': ['RO', 'ROU'],
|
'🇷🇴': ['RO', 'ROU'],
|
||||||
'🇷🇸': ['RS', 'SRB'],
|
'🇷🇸': ['RS', 'SRB'],
|
||||||
'🇷🇪': ['RE', 'REU'],
|
'🇷🇪': ['RE', 'REU'],
|
||||||
@@ -142,8 +149,10 @@ export function getFlag(name) {
|
|||||||
'🇧🇬': ['Bulgaria', '保加利亚', '保加利亞'],
|
'🇧🇬': ['Bulgaria', '保加利亚', '保加利亞'],
|
||||||
'🇧🇭': ['Bahrain', '巴林'],
|
'🇧🇭': ['Bahrain', '巴林'],
|
||||||
'🇧🇷': ['Brazil', '巴西', '圣保罗'],
|
'🇧🇷': ['Brazil', '巴西', '圣保罗'],
|
||||||
|
'🇧🇳': ['Brunei', '文莱', '汶萊'],
|
||||||
'🇧🇾': ['Belarus', '白俄罗斯', '白俄'],
|
'🇧🇾': ['Belarus', '白俄罗斯', '白俄'],
|
||||||
'🇧🇴': ['Bolivia', '玻利维亚'],
|
'🇧🇴': ['Bolivia', '玻利维亚'],
|
||||||
|
'🇧🇹': ['Bhutan', '不丹', '不丹王国'],
|
||||||
'🇨🇦': [
|
'🇨🇦': [
|
||||||
'Canada',
|
'Canada',
|
||||||
'加拿大',
|
'加拿大',
|
||||||
@@ -194,6 +203,7 @@ export function getFlag(name) {
|
|||||||
],
|
],
|
||||||
'🇬🇪': ['Georgia', '格鲁吉亚', '格魯吉亞'],
|
'🇬🇪': ['Georgia', '格鲁吉亚', '格魯吉亞'],
|
||||||
'🇬🇷': ['Greece', '希腊', '希臘'],
|
'🇬🇷': ['Greece', '希腊', '希臘'],
|
||||||
|
'🇬🇺': ['Guam', '关岛', '關島'],
|
||||||
'🇬🇹': ['Guatemala', '危地马拉'],
|
'🇬🇹': ['Guatemala', '危地马拉'],
|
||||||
'🇭🇰': [
|
'🇭🇰': [
|
||||||
'Hongkong',
|
'Hongkong',
|
||||||
@@ -254,11 +264,14 @@ export function getFlag(name) {
|
|||||||
'🇮🇷': ['Iran', '伊朗'],
|
'🇮🇷': ['Iran', '伊朗'],
|
||||||
'🇮🇸': ['Iceland', '冰岛', '冰島'],
|
'🇮🇸': ['Iceland', '冰岛', '冰島'],
|
||||||
'🇮🇹': ['Italy', '意大利', '義大利', '米兰', 'Nachash'],
|
'🇮🇹': ['Italy', '意大利', '義大利', '米兰', 'Nachash'],
|
||||||
|
'🇱🇰': ['Sri Lanka', '斯里兰卡', '斯里蘭卡'],
|
||||||
|
'🇱🇦': ['Laos', '老挝', '老撾'],
|
||||||
'🇱🇹': ['Lithuania', '立陶宛'],
|
'🇱🇹': ['Lithuania', '立陶宛'],
|
||||||
'🇱🇺': ['Luxembourg', '卢森堡'],
|
'🇱🇺': ['Luxembourg', '卢森堡'],
|
||||||
'🇱🇻': ['Latvia', '拉脱维亚', 'Latvija'],
|
'🇱🇻': ['Latvia', '拉脱维亚', 'Latvija'],
|
||||||
'🇲🇦': ['Morocco', '摩洛哥'],
|
'🇲🇦': ['Morocco', '摩洛哥'],
|
||||||
'🇲🇩': ['Moldova', '摩尔多瓦', '摩爾多瓦'],
|
'🇲🇩': ['Moldova', '摩尔多瓦', '摩爾多瓦'],
|
||||||
|
'🇲🇲': ['Myanmar', '缅甸', '緬甸'],
|
||||||
'🇳🇬': ['Nigeria', '尼日利亚', '尼日利亞'],
|
'🇳🇬': ['Nigeria', '尼日利亚', '尼日利亞'],
|
||||||
'🇲🇰': ['Macedonia', '马其顿', '馬其頓'],
|
'🇲🇰': ['Macedonia', '马其顿', '馬其頓'],
|
||||||
'🇲🇳': ['Mongolia', '蒙古'],
|
'🇲🇳': ['Mongolia', '蒙古'],
|
||||||
@@ -284,6 +297,7 @@ export function getFlag(name) {
|
|||||||
'🇵🇱': ['Poland', '波兰', '波蘭', '华沙', 'Warsaw'],
|
'🇵🇱': ['Poland', '波兰', '波蘭', '华沙', 'Warsaw'],
|
||||||
'🇵🇷': ['Puerto Rico', '波多黎各'],
|
'🇵🇷': ['Puerto Rico', '波多黎各'],
|
||||||
'🇵🇹': ['Portugal', '葡萄牙'],
|
'🇵🇹': ['Portugal', '葡萄牙'],
|
||||||
|
'🇵🇬': ['Papua New Guinea', '巴布亚新几内亚'],
|
||||||
'🇵🇾': ['Paraguay', '巴拉圭'],
|
'🇵🇾': ['Paraguay', '巴拉圭'],
|
||||||
'🇷🇴': ['Romania', '罗马尼亚'],
|
'🇷🇴': ['Romania', '罗马尼亚'],
|
||||||
'🇷🇸': ['Serbia', '塞尔维亚'],
|
'🇷🇸': ['Serbia', '塞尔维亚'],
|
||||||
|
|||||||
@@ -114,15 +114,18 @@ export default class Gist {
|
|||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return this.http.get('/gists').then((response) => {
|
return this.http
|
||||||
const gists = JSON.parse(response.body);
|
.get('/gists?per_page=100&page=1')
|
||||||
for (let g of gists) {
|
.then((response) => {
|
||||||
if (g.description === this.key) {
|
const gists = JSON.parse(response.body);
|
||||||
return g;
|
$.info(`获取到当前 GitHub 用户的 gist: ${gists.length} 个`);
|
||||||
|
for (let g of gists) {
|
||||||
|
if (g.description === this.key) {
|
||||||
|
return g;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return;
|
||||||
return;
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -117,7 +117,17 @@ function numberToString(value) {
|
|||||||
: BigInt(value).toString();
|
: BigInt(value).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isValidUUID(uuid) {
|
||||||
|
return (
|
||||||
|
typeof uuid === 'string' &&
|
||||||
|
/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(
|
||||||
|
uuid,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
isValidUUID,
|
||||||
ipAddress,
|
ipAddress,
|
||||||
isIPv4,
|
isIPv4,
|
||||||
isIPv6,
|
isIPv6,
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
import gte from 'semver/functions/gte';
|
||||||
|
import coerce from 'semver/functions/coerce';
|
||||||
|
import $ from '@/core/app';
|
||||||
|
|
||||||
export function getUserAgentFromHeaders(headers) {
|
export function getUserAgentFromHeaders(headers) {
|
||||||
const keys = Object.keys(headers);
|
const keys = Object.keys(headers);
|
||||||
let UA = '';
|
let UA = '';
|
||||||
@@ -56,3 +60,32 @@ export function getPlatformFromHeaders(headers) {
|
|||||||
const { UA, ua, accept } = getUserAgentFromHeaders(headers);
|
const { UA, ua, accept } = getUserAgentFromHeaders(headers);
|
||||||
return getPlatformFromUserAgent({ ua, UA, accept });
|
return getPlatformFromUserAgent({ ua, UA, accept });
|
||||||
}
|
}
|
||||||
|
export function shouldIncludeUnsupportedProxy(platform, ua) {
|
||||||
|
try {
|
||||||
|
const target = getPlatformFromUserAgent({
|
||||||
|
UA: ua,
|
||||||
|
ua: ua.toLowerCase(),
|
||||||
|
});
|
||||||
|
if (!['Stash', 'Egern'].includes(target)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const version = coerce(ua).version;
|
||||||
|
if (
|
||||||
|
platform === 'Stash' &&
|
||||||
|
target === 'Stash' &&
|
||||||
|
gte(version, '2.8.0')
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
platform === 'Egern' &&
|
||||||
|
target === 'Egern' &&
|
||||||
|
gte(version, '1.29.0')
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$.error(`获取版本号失败: ${e}`);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,8 +21,13 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
// 13. `test-url` 为测延迟链接, `test-timeout` 为测延迟超时
|
// 13. `test-url` 为测延迟链接, `test-timeout` 为测延迟超时
|
||||||
// 14. `ports` 为端口跳跃, `hop-interval` 变换端口号的时间间隔
|
// 14. `ports` 为端口跳跃, `hop-interval` 变换端口号的时间间隔
|
||||||
// 15. `ip-version` 设置节点使用 IP 版本,可选:dual,ipv4,ipv6,ipv4-prefer,ipv6-prefer. 会进行内部转换, 若无法匹配则使用原始值
|
// 15. `ip-version` 设置节点使用 IP 版本,可选:dual,ipv4,ipv6,ipv4-prefer,ipv6-prefer. 会进行内部转换, 若无法匹配则使用原始值
|
||||||
|
// 16. `sing-box` 支持使用 `_network` 来设置 `network`, 例如 `tcp`, `udp`
|
||||||
|
|
||||||
// require 为 Node.js 的 require, 在 Node.js 运行环境下 可以用来引入模块
|
// require 为 Node.js 的 require, 在 Node.js 运行环境下 可以用来引入模块
|
||||||
|
// 例如在 Node.js 环境下, 将文件内容写入 /tmp/1.txt 文件
|
||||||
|
// const fs = eval(`require("fs")`)
|
||||||
|
// // const path = eval(`require("path")`)
|
||||||
|
// fs.writeFileSync('/tmp/1.txt', $content, "utf8");
|
||||||
|
|
||||||
// $arguments 为传入的脚本参数
|
// $arguments 为传入的脚本参数
|
||||||
|
|
||||||
@@ -68,6 +73,7 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
// Gist, // Gist 类
|
// Gist, // Gist 类
|
||||||
// download, // 内部的下载方法, 见 backend/src/utils/download.js
|
// download, // 内部的下载方法, 见 backend/src/utils/download.js
|
||||||
// MMDB, // Node.js 环境 可用于模拟 Surge/Loon 的 $utils.ipasn, $utils.ipaso, $utils.geoip. 具体见 https://t.me/zhetengsha/1269
|
// MMDB, // Node.js 环境 可用于模拟 Surge/Loon 的 $utils.ipasn, $utils.ipaso, $utils.geoip. 具体见 https://t.me/zhetengsha/1269
|
||||||
|
// isValidUUID, // 辅助判断是否为有效的 UUID
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// 如果只是为了快速修改或者筛选 可以参考 脚本操作支持节点快捷脚本 https://t.me/zhetengsha/970 和 脚本筛选支持节点快捷脚本 https://t.me/zhetengsha/1009
|
// 如果只是为了快速修改或者筛选 可以参考 脚本操作支持节点快捷脚本 https://t.me/zhetengsha/970 和 脚本筛选支持节点快捷脚本 https://t.me/zhetengsha/1009
|
||||||
|
|||||||
Reference in New Issue
Block a user