mirror of
https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-10 00:52:40 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25a232190d |
@@ -26,20 +26,20 @@ Core functionalities:
|
||||
|
||||
### Supported Input Formats
|
||||
|
||||
> ⚠️ 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.
|
||||
> ⚠️ Do not use `Shadowrocket` to export URI and then import it as input. It is not a standard URI.
|
||||
|
||||
- [x] Proxy URI Scheme(`socks5`, `socks5+tls`, `http`, `https`(it's ok))
|
||||
- [x] Normal Proxy(`socks5`, `socks5+tls`, `http`, `https`(it's ok))
|
||||
|
||||
example: `socks5+tls://user:pass@ip:port#name`
|
||||
|
||||
- [x] URI(AnyTLS, SOCKS, SS, SSR, VMess, VLESS, Trojan, Hysteria, Hysteria 2, TUIC v5, WireGuard)
|
||||
- [x] URI(SS, SSR, VMess, VLESS, Trojan, Hysteria, Hysteria 2, TUIC v5, WireGuard)
|
||||
- [x] Clash Proxies YAML
|
||||
- [x] Clash Proxy JSON(single line)
|
||||
- [x] QX (SS, SSR, VMess, Trojan, HTTP, SOCKS5, VLESS)
|
||||
- [x] Loon (SS, SSR, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, WireGuard, VLESS, Hysteria 2)
|
||||
- [x] Surge (Direct, SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, TUIC, Snell, Hysteria 2, SSH(Password authentication only), External Proxy Program(only for macOS), WireGuard(Surge to Surge))
|
||||
- [x] Surfboard (SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, WireGuard(Surfboard to Surfboard))
|
||||
- [x] Clash.Meta (Direct, SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC, SSH, mieru, AnyTLS)
|
||||
- [x] Clash.Meta (Direct, SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC, SSH, mieru)
|
||||
- [x] Stash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, TUIC, Juicity, SSH)
|
||||
- [x] Clash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard)
|
||||
|
||||
|
||||
@@ -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 %>
|
||||
* @version: <%= pkg.version %>
|
||||
* @author: Peng-YM
|
||||
|
||||
0
backend/dist/.gitkeep
vendored
0
backend/dist/.gitkeep
vendored
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "sub-store",
|
||||
"version": "2.17.6",
|
||||
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket.",
|
||||
"version": "2.16.12",
|
||||
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
isIPv4,
|
||||
isIPv6,
|
||||
isValidPortNumber,
|
||||
isValidUUID,
|
||||
isNotBlank,
|
||||
ipAddress,
|
||||
getRandomPort,
|
||||
@@ -22,8 +21,6 @@ import { findByName } from '@/utils/database';
|
||||
import { produceArtifact } from '@/restful/sync';
|
||||
import { getFlag, removeFlag, getISO, MMDB } from '@/utils/geo';
|
||||
import Gist from '@/utils/gist';
|
||||
import { isPresent } from './producers/utils';
|
||||
import { doh } from '@/utils/dns';
|
||||
|
||||
function preprocess(raw) {
|
||||
for (const processor of PROXY_PREPROCESSORS) {
|
||||
@@ -78,16 +75,7 @@ function parse(raw) {
|
||||
$.error(`Failed to parse line: ${line}`);
|
||||
}
|
||||
}
|
||||
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;
|
||||
});
|
||||
return proxies;
|
||||
}
|
||||
|
||||
async function processFn(
|
||||
@@ -185,7 +173,6 @@ async function processFn(
|
||||
}
|
||||
} else {
|
||||
script = content;
|
||||
$arguments = item.args.arguments || {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,22 +214,10 @@ function produce(proxies, targetPlatform, type, opts = {}) {
|
||||
);
|
||||
|
||||
// filter unsupported proxies
|
||||
proxies = proxies.filter((proxy) => {
|
||||
// 检查代理是否支持目标平台
|
||||
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.filter(
|
||||
(proxy) =>
|
||||
!(proxy.supported && proxy.supported[targetPlatform] === false),
|
||||
);
|
||||
|
||||
proxies = proxies.map((proxy) => {
|
||||
proxy._resolved = proxy.resolved;
|
||||
@@ -328,8 +303,6 @@ export const ProxyUtils = {
|
||||
MMDB,
|
||||
Gist,
|
||||
download,
|
||||
isValidUUID,
|
||||
doh,
|
||||
};
|
||||
|
||||
function tryParse(parser, line) {
|
||||
@@ -370,14 +343,6 @@ function lastParse(proxy) {
|
||||
if (typeof proxy.password === 'number') {
|
||||
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) {
|
||||
proxy['interface-name'] = proxy.interface;
|
||||
delete proxy.interface;
|
||||
@@ -427,14 +392,9 @@ function lastParse(proxy) {
|
||||
}
|
||||
}
|
||||
if (
|
||||
[
|
||||
'trojan',
|
||||
'tuic',
|
||||
'hysteria',
|
||||
'hysteria2',
|
||||
'juicity',
|
||||
'anytls',
|
||||
].includes(proxy.type)
|
||||
['trojan', 'tuic', 'hysteria', 'hysteria2', 'juicity'].includes(
|
||||
proxy.type,
|
||||
)
|
||||
) {
|
||||
proxy.tls = true;
|
||||
}
|
||||
@@ -604,20 +564,6 @@ function lastParse(proxy) {
|
||||
if (!proxy['tls-fingerprint'] && 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import getSurgeParser from './peggy/surge';
|
||||
import getLoonParser from './peggy/loon';
|
||||
import getQXParser from './peggy/qx';
|
||||
import getTrojanURIParser from './peggy/trojan-uri';
|
||||
import $ from '@/core/app';
|
||||
|
||||
import { Base64 } from 'js-base64';
|
||||
|
||||
@@ -41,21 +40,8 @@ function URI_PROXY() {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let [__, type, tls, username, password, server, port, query, name] =
|
||||
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 = {
|
||||
name:
|
||||
@@ -76,46 +62,7 @@ function URI_PROXY() {
|
||||
};
|
||||
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).
|
||||
// reference: https://github.com/shadowsocks/shadowsocks-org/wiki/SIP002-URI-Scheme
|
||||
function URI_SS() {
|
||||
@@ -152,7 +99,6 @@ function URI_SS() {
|
||||
query = parsed[2];
|
||||
}
|
||||
content = Base64.decode(content);
|
||||
|
||||
if (query) {
|
||||
if (/(&|\?)v2ray-plugin=/.test(query)) {
|
||||
const parsed = query.match(/(&|\?)v2ray-plugin=(.*?)(&|$)/);
|
||||
@@ -166,11 +112,8 @@ function URI_SS() {
|
||||
}
|
||||
content = `${content}${query}`;
|
||||
}
|
||||
userInfoStr = content.match(/(^.*)@/)?.[1];
|
||||
serverAndPortArray = content.match(/@([^/@]*)(\/|$)/);
|
||||
} else if (content.includes('?')) {
|
||||
const parsed = content.match(/(\?.*)$/);
|
||||
query = parsed[1];
|
||||
userInfoStr = content.split('@')[0];
|
||||
serverAndPortArray = content.match(/@([^/]*)(\/|$)/);
|
||||
}
|
||||
|
||||
const serverAndPort = serverAndPortArray[1];
|
||||
@@ -189,12 +132,11 @@ function URI_SS() {
|
||||
// }
|
||||
|
||||
// handle obfs
|
||||
const pluginMatch = content.match(/[?&]plugin=([^&]+)/);
|
||||
const shadowTlsMatch = content.match(/[?&]shadow-tls=([^&]+)/);
|
||||
|
||||
if (pluginMatch) {
|
||||
const idx = content.indexOf('?plugin=');
|
||||
if (idx !== -1) {
|
||||
const pluginInfo = (
|
||||
'plugin=' + decodeURIComponent(pluginMatch[1])
|
||||
'plugin=' +
|
||||
decodeURIComponent(content.split('?plugin=')[1].split('&')[0])
|
||||
).split(';');
|
||||
const params = {};
|
||||
for (const item of pluginInfo) {
|
||||
@@ -219,41 +161,12 @@ function URI_SS() {
|
||||
tls: getIfPresent(params.tls),
|
||||
};
|
||||
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:
|
||||
throw new Error(
|
||||
`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)) {
|
||||
proxy['udp-over-tcp'] = true;
|
||||
}
|
||||
@@ -616,9 +529,6 @@ function URI_VLESS() {
|
||||
if (params.sid) {
|
||||
opts['short-id'] = params.sid;
|
||||
}
|
||||
if (params.spx) {
|
||||
opts['_spider-x'] = params.spx;
|
||||
}
|
||||
if (Object.keys(opts).length > 0) {
|
||||
// proxy[`${params.security}-opts`] = opts;
|
||||
proxy[`${params.security}-opts`] = opts;
|
||||
@@ -686,59 +596,6 @@ function URI_VLESS() {
|
||||
// mKCP 的伪装头部类型。当前可选值有 none / srtp / utp / wechat-video / dtls / wireguard。省略时默认值为 none,即不使用伪装头部,但不可以为空字符串。
|
||||
proxy.headerType = params.headerType || 'none';
|
||||
}
|
||||
|
||||
if (params.mode) {
|
||||
proxy._mode = params.mode;
|
||||
}
|
||||
if (params.extra) {
|
||||
proxy._extra = params.extra;
|
||||
}
|
||||
}
|
||||
|
||||
return proxy;
|
||||
};
|
||||
return { name, test, parse };
|
||||
}
|
||||
function URI_AnyTLS() {
|
||||
const name = 'URI AnyTLS Parser';
|
||||
const test = (line) => {
|
||||
return /^anytls:\/\//.test(line);
|
||||
};
|
||||
const parse = (line) => {
|
||||
line = line.split(/anytls:\/\//)[1];
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let [__, password, server, port, addons = '', name] =
|
||||
/^(.*?)@(.*?)(?::(\d+))?\/?(?:\?(.*?))?(?:#(.*?))?$/.exec(line);
|
||||
password = decodeURIComponent(password);
|
||||
port = parseInt(`${port}`, 10);
|
||||
if (isNaN(port)) {
|
||||
port = 443;
|
||||
}
|
||||
password = decodeURIComponent(password);
|
||||
if (name != null) {
|
||||
name = decodeURIComponent(name);
|
||||
}
|
||||
name = name ?? `AnyTLS ${server}:${port}`;
|
||||
|
||||
const proxy = {
|
||||
type: 'anytls',
|
||||
name,
|
||||
server,
|
||||
port,
|
||||
password,
|
||||
};
|
||||
|
||||
for (const addon of addons.split('&')) {
|
||||
let [key, value] = addon.split('=');
|
||||
key = key.replace(/_/g, '-');
|
||||
value = decodeURIComponent(value);
|
||||
if (['alpn'].includes(key)) {
|
||||
proxy[key] = value ? value.split(',') : undefined;
|
||||
} else if (['insecure'].includes(key)) {
|
||||
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(value);
|
||||
} else {
|
||||
proxy[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return proxy;
|
||||
@@ -906,11 +763,8 @@ function URI_TUIC() {
|
||||
const parse = (line) => {
|
||||
line = line.split(/tuic:\/\//)[1];
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let [__, auth, server, port, addons = '', name] =
|
||||
/^(.*?)@(.*?)(?::(\d+))?\/?(?:\?(.*?))?(?:#(.*?))?$/.exec(line);
|
||||
auth = decodeURIComponent(auth);
|
||||
let [uuid, ...passwordParts] = auth.split(':');
|
||||
let password = passwordParts.join(':');
|
||||
let [__, uuid, password, server, ___, port, ____, addons = '', name] =
|
||||
/^(.*?):(.*?)@(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line);
|
||||
port = parseInt(`${port}`, 10);
|
||||
if (isNaN(port)) {
|
||||
port = 443;
|
||||
@@ -932,14 +786,12 @@ function URI_TUIC() {
|
||||
|
||||
for (const addon of addons.split('&')) {
|
||||
let [key, value] = addon.split('=');
|
||||
key = key.replace(/_/g, '-');
|
||||
key = key.replace(/_/, '-');
|
||||
value = decodeURIComponent(value);
|
||||
if (['alpn'].includes(key)) {
|
||||
proxy[key] = value ? value.split(',') : undefined;
|
||||
} else if (['allow-insecure'].includes(key)) {
|
||||
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(value);
|
||||
} else if (['fast-open'].includes(key)) {
|
||||
proxy.tfo = true;
|
||||
} else if (['disable-sni', 'reduce-rtt'].includes(key)) {
|
||||
proxy[key] = /(TRUE)|1/i.test(value);
|
||||
} else {
|
||||
@@ -1077,7 +929,6 @@ function Clash_All() {
|
||||
const proxy = JSON.parse(line);
|
||||
if (
|
||||
![
|
||||
'anytls',
|
||||
'mieru',
|
||||
'juicity',
|
||||
'ss',
|
||||
@@ -1580,7 +1431,6 @@ function isIP(ip) {
|
||||
|
||||
export default [
|
||||
URI_PROXY(),
|
||||
URI_SOCKS(),
|
||||
URI_SS(),
|
||||
URI_SSR(),
|
||||
URI_VMess(),
|
||||
@@ -1590,7 +1440,6 @@ export default [
|
||||
URI_Hysteria(),
|
||||
URI_Hysteria2(),
|
||||
URI_Trojan(),
|
||||
URI_AnyTLS(),
|
||||
Clash_All(),
|
||||
Surge_Direct(),
|
||||
Surge_SSH(),
|
||||
|
||||
@@ -39,12 +39,12 @@ start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/socks5/hysteria2
|
||||
return proxy;
|
||||
}
|
||||
|
||||
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/others)*{
|
||||
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)*{
|
||||
proxy.type = "ssr";
|
||||
// handle ssr obfs
|
||||
proxy.obfs = obfs.type;
|
||||
}
|
||||
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/others)* {
|
||||
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
|
||||
proxy.type = "ss";
|
||||
// handle ss obfs
|
||||
if (obfs.type == "http" || obfs.type === "tls") {
|
||||
@@ -54,31 +54,31 @@ shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs
|
||||
$set(proxy, "plugin-opts.path", obfs.path);
|
||||
}
|
||||
}
|
||||
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/ip_mode/others)* {
|
||||
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/others)* {
|
||||
proxy.type = "vmess";
|
||||
proxy.cipher = proxy.cipher || "none";
|
||||
proxy.alterId = proxy.alterId || 0;
|
||||
handleTransport();
|
||||
}
|
||||
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/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/others)* {
|
||||
proxy.type = "vless";
|
||||
handleTransport();
|
||||
}
|
||||
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
|
||||
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
||||
proxy.type = "trojan";
|
||||
handleTransport();
|
||||
}
|
||||
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/ip_mode/others)* {
|
||||
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/others)* {
|
||||
proxy.type = "hysteria2";
|
||||
}
|
||||
https = tag equals "https"i address (username password)? (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
|
||||
https = tag equals "https"i address (username password)? (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
||||
proxy.type = "http";
|
||||
proxy.tls = true;
|
||||
}
|
||||
http = tag equals "http"i address (username password)? (fast_open/udp_relay/ip_mode/others)* {
|
||||
http = tag equals "http"i address (username password)? (fast_open/udp_relay/others)* {
|
||||
proxy.type = "http";
|
||||
}
|
||||
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
|
||||
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
||||
proxy.type = "socks5";
|
||||
}
|
||||
|
||||
@@ -182,7 +182,6 @@ tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pu
|
||||
|
||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = 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; }
|
||||
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;
|
||||
}
|
||||
|
||||
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/others)*{
|
||||
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)*{
|
||||
proxy.type = "ssr";
|
||||
// handle ssr obfs
|
||||
proxy.obfs = obfs.type;
|
||||
}
|
||||
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/others)* {
|
||||
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
|
||||
proxy.type = "ss";
|
||||
// handle ss obfs
|
||||
if (obfs.type == "http" || obfs.type === "tls") {
|
||||
@@ -52,31 +52,31 @@ shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs
|
||||
$set(proxy, "plugin-opts.path", obfs.path);
|
||||
}
|
||||
}
|
||||
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/ip_mode/others)* {
|
||||
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/others)* {
|
||||
proxy.type = "vmess";
|
||||
proxy.cipher = proxy.cipher || "none";
|
||||
proxy.alterId = proxy.alterId || 0;
|
||||
handleTransport();
|
||||
}
|
||||
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/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/others)* {
|
||||
proxy.type = "vless";
|
||||
handleTransport();
|
||||
}
|
||||
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
|
||||
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
||||
proxy.type = "trojan";
|
||||
handleTransport();
|
||||
}
|
||||
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/ip_mode/others)* {
|
||||
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/others)* {
|
||||
proxy.type = "hysteria2";
|
||||
}
|
||||
https = tag equals "https"i address (username password)? (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
|
||||
https = tag equals "https"i address (username password)? (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
||||
proxy.type = "http";
|
||||
proxy.tls = true;
|
||||
}
|
||||
http = tag equals "http"i address (username password)? (fast_open/udp_relay/ip_mode/others)* {
|
||||
http = tag equals "http"i address (username password)? (fast_open/udp_relay/others)* {
|
||||
proxy.type = "http";
|
||||
}
|
||||
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
|
||||
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
||||
proxy.type = "socks5";
|
||||
}
|
||||
|
||||
@@ -180,7 +180,6 @@ tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pu
|
||||
|
||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = 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; }
|
||||
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;
|
||||
}
|
||||
|
||||
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)* {
|
||||
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)* {
|
||||
proxy.type = "ss";
|
||||
// handle obfs
|
||||
if (obfs.type == "http" || obfs.type === "tls") {
|
||||
@@ -52,7 +52,7 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
|
||||
}
|
||||
handleShadowTLS();
|
||||
}
|
||||
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/underlying_proxy/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)* {
|
||||
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)* {
|
||||
proxy.type = "vmess";
|
||||
proxy.cipher = proxy.cipher || "none";
|
||||
if (proxy.aead) {
|
||||
@@ -63,25 +63,25 @@ vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/
|
||||
handleWebsocket();
|
||||
handleShadowTLS();
|
||||
}
|
||||
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/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)* {
|
||||
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)* {
|
||||
proxy.type = "trojan";
|
||||
handleWebsocket();
|
||||
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/tfo/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/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||
proxy.type = "http";
|
||||
proxy.tls = true;
|
||||
handleShadowTLS();
|
||||
}
|
||||
http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/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)* {
|
||||
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)* {
|
||||
proxy.type = "http";
|
||||
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/tfo/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/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||
proxy.type = "ssh";
|
||||
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/tfo/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/udp_relay/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||
proxy.type = "snell";
|
||||
// handle obfs
|
||||
if (obfs.type == "http" || obfs.type === "tls") {
|
||||
@@ -104,23 +104,22 @@ wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/under
|
||||
proxy.type = "wireguard-surge";
|
||||
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/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)* {
|
||||
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)* {
|
||||
proxy.type = "hysteria2";
|
||||
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/tfo/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/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||
proxy.type = "socks5";
|
||||
handleShadowTLS();
|
||||
}
|
||||
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (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)* {
|
||||
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)* {
|
||||
proxy.type = "socks5";
|
||||
proxy.tls = true;
|
||||
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/tfo/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/block_quic/others)* {
|
||||
proxy.type = "direct";
|
||||
}
|
||||
|
||||
address = comma server:server comma port:port {
|
||||
proxy.server = server;
|
||||
proxy.port = port;
|
||||
@@ -246,7 +245,7 @@ block_quic = comma "block-quic" equals match:[^,]+ { proxy["block-quic"] = match
|
||||
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("").replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
|
||||
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join(""); }
|
||||
token = comma "token" equals match:[^,]+ { proxy.token = match.join(""); }
|
||||
alpn = comma "alpn" equals match:[^,]+ { proxy.alpn = match.join(""); }
|
||||
uuidk = comma "uuid" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
||||
|
||||
@@ -39,7 +39,7 @@ start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v
|
||||
return proxy;
|
||||
}
|
||||
|
||||
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/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)* {
|
||||
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)* {
|
||||
proxy.type = "ss";
|
||||
// handle obfs
|
||||
if (obfs.type == "http" || obfs.type === "tls") {
|
||||
@@ -50,7 +50,7 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
|
||||
}
|
||||
handleShadowTLS();
|
||||
}
|
||||
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/underlying_proxy/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)* {
|
||||
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)* {
|
||||
proxy.type = "vmess";
|
||||
proxy.cipher = proxy.cipher || "none";
|
||||
if (proxy.aead) {
|
||||
@@ -61,25 +61,25 @@ vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/
|
||||
handleWebsocket();
|
||||
handleShadowTLS();
|
||||
}
|
||||
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/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)* {
|
||||
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)* {
|
||||
proxy.type = "trojan";
|
||||
handleWebsocket();
|
||||
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/tfo/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/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||
proxy.type = "http";
|
||||
proxy.tls = true;
|
||||
handleShadowTLS();
|
||||
}
|
||||
http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/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)* {
|
||||
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)* {
|
||||
proxy.type = "http";
|
||||
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/tfo/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/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||
proxy.type = "ssh";
|
||||
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/tfo/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/udp_relay/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||
proxy.type = "snell";
|
||||
// handle obfs
|
||||
if (obfs.type == "http" || obfs.type === "tls") {
|
||||
@@ -106,16 +106,16 @@ hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying
|
||||
proxy.type = "hysteria2";
|
||||
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/tfo/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/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||
proxy.type = "socks5";
|
||||
handleShadowTLS();
|
||||
}
|
||||
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (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)* {
|
||||
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)* {
|
||||
proxy.type = "socks5";
|
||||
proxy.tls = true;
|
||||
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/tfo/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/block_quic/others)* {
|
||||
proxy.type = "direct";
|
||||
}
|
||||
address = comma server:server comma port:port {
|
||||
@@ -243,7 +243,7 @@ block_quic = comma "block-quic" equals match:[^,]+ { proxy["block-quic"] = match
|
||||
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("").replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
|
||||
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join(""); }
|
||||
token = comma "token" equals match:[^,]+ { proxy.token = match.join(""); }
|
||||
alpn = comma "alpn" equals match:[^,]+ { proxy.alpn = match.join(""); }
|
||||
uuidk = comma "uuid" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
||||
|
||||
@@ -80,9 +80,6 @@ port = digits:[0-9]+ {
|
||||
}
|
||||
|
||||
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.sni = params["sni"] || params["peer"];
|
||||
proxy['client-fingerprint'] = params.fp;
|
||||
@@ -118,27 +115,6 @@ params = "?" head:param tail:("&"@param)* {
|
||||
$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"]);
|
||||
|
||||
@@ -78,9 +78,6 @@ port = digits:[0-9]+ {
|
||||
}
|
||||
|
||||
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.sni = params["sni"] || params["peer"];
|
||||
proxy['client-fingerprint'] = params.fp;
|
||||
@@ -116,27 +113,6 @@ params = "?" head:param tail:("&"@param)* {
|
||||
$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"]);
|
||||
|
||||
@@ -16,7 +16,6 @@ function Base64Encoded() {
|
||||
const keys = [
|
||||
'dm1lc3M', // vmess
|
||||
'c3NyOi8v', // ssr://
|
||||
'c29ja3M6Ly', // socks://
|
||||
'dHJvamFu', // trojan
|
||||
'c3M6Ly', // ss:/
|
||||
'c3NkOi8v', // ssd://
|
||||
@@ -38,7 +37,7 @@ function Base64Encoded() {
|
||||
};
|
||||
const parse = function (raw) {
|
||||
const decoded = Base64.decode(raw);
|
||||
if (!/^\w+(:\/\/|\s*?=\s*?)\w+/m.test(decoded)) {
|
||||
if (!/^\w+:\/\/\w+/m.test(decoded)) {
|
||||
$.error(
|
||||
`Base64 Pre-processor error: decoded line does not start with protocol`,
|
||||
);
|
||||
@@ -63,7 +62,7 @@ function Clash() {
|
||||
// 防止 VLESS节点 reality-opts 选项中的 short-id 被解析成 Infinity
|
||||
// 匹配 short-id 冒号后面的值(包含空格和引号)
|
||||
const afterReplace = raw.replace(
|
||||
/short-id:([ \t]*[^#\n,}]*)/g,
|
||||
/short-id:([ ]*[^,\n}]*)/g,
|
||||
(matched, value) => {
|
||||
const afterTrim = value.trim();
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
validCheck,
|
||||
flowTransfer,
|
||||
getRmainingDays,
|
||||
normalizeFlowHeader,
|
||||
} from '@/utils/flow';
|
||||
|
||||
function isObject(item) {
|
||||
@@ -366,35 +365,22 @@ function ScriptOperator(script, targetPlatform, $arguments, source, $options) {
|
||||
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');
|
||||
if (typeof patch !== 'object') patch = {};
|
||||
output.$content = ProxyUtils.yaml.safeDump(
|
||||
deepMerge(
|
||||
config ||
|
||||
(output?.$file?.sourceType === 'none'
|
||||
? {}
|
||||
: {
|
||||
proxies: await produceArtifact({
|
||||
type:
|
||||
output?.$file?.sourceType ||
|
||||
'collection',
|
||||
name: output?.$file?.sourceName,
|
||||
platform: 'mihomo',
|
||||
produceType: 'internal',
|
||||
produceOpts: {
|
||||
'delete-underscore-fields': true,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
{
|
||||
proxies: await produceArtifact({
|
||||
type:
|
||||
output?.$file?.sourceType ||
|
||||
'collection',
|
||||
name: output?.$file?.sourceName,
|
||||
platform: 'mihomo',
|
||||
produceType: 'internal',
|
||||
produceOpts: {
|
||||
'delete-underscore-fields': true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
patch,
|
||||
),
|
||||
);
|
||||
@@ -425,15 +411,7 @@ function ScriptOperator(script, targetPlatform, $arguments, source, $options) {
|
||||
if($file.type === 'mihomoProfile') {
|
||||
${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 || ($file.sourceType === 'none' ? {} : {
|
||||
const config = {
|
||||
proxies: await produceArtifact({
|
||||
type: $file.sourceType || 'collection',
|
||||
name: $file.sourceName,
|
||||
@@ -443,7 +421,8 @@ function ScriptOperator(script, targetPlatform, $arguments, source, $options) {
|
||||
'delete-underscore-fields': true
|
||||
}
|
||||
}),
|
||||
})))
|
||||
}
|
||||
$content = ProxyUtils.yaml.safeDump(await main(config))
|
||||
}
|
||||
} else {
|
||||
${script}
|
||||
@@ -849,12 +828,7 @@ function UselessFilter() {
|
||||
}
|
||||
|
||||
// filter by regions
|
||||
function RegionFilter(input) {
|
||||
let regions = input?.value || input;
|
||||
if (!Array.isArray(regions)) {
|
||||
regions = [];
|
||||
}
|
||||
const keep = input?.keep ?? true;
|
||||
function RegionFilter(regions) {
|
||||
const REGION_MAP = {
|
||||
HK: '🇭🇰',
|
||||
TW: '🇹🇼',
|
||||
@@ -871,8 +845,7 @@ function RegionFilter(input) {
|
||||
// this would be high memory usage
|
||||
return proxies.map((proxy) => {
|
||||
const flag = getFlag(proxy.name);
|
||||
const selected = regions.some((r) => REGION_MAP[r] === flag);
|
||||
return keep ? selected : !selected;
|
||||
return regions.some((r) => REGION_MAP[r] === flag);
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -904,19 +877,11 @@ function buildRegex(str, ...options) {
|
||||
}
|
||||
|
||||
// filter by proxy types
|
||||
function TypeFilter(input) {
|
||||
let types = input?.value || input;
|
||||
if (!Array.isArray(types)) {
|
||||
types = [];
|
||||
}
|
||||
const keep = input?.keep ?? true;
|
||||
function TypeFilter(types) {
|
||||
return {
|
||||
name: 'Type Filter',
|
||||
func: (proxies) => {
|
||||
return proxies.map((proxy) => {
|
||||
const selected = types.some((t) => proxy.type === t);
|
||||
return keep ? selected : !selected;
|
||||
});
|
||||
return proxies.map((proxy) => types.some((t) => proxy.type === t));
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1116,7 +1081,6 @@ function createDynamicFunction(name, script, $arguments, $options) {
|
||||
flowTransfer,
|
||||
validCheck,
|
||||
getRmainingDays,
|
||||
normalizeFlowHeader,
|
||||
};
|
||||
if ($.env.isLoon) {
|
||||
return new Function(
|
||||
|
||||
@@ -81,8 +81,6 @@ export default function Clash_Producer() {
|
||||
proxy['preshared-key'] =
|
||||
proxy['preshared-key'] ?? proxy['pre-shared-key'];
|
||||
proxy['pre-shared-key'] = proxy['preshared-key'];
|
||||
} else if (proxy.type === 'snell' && proxy.version < 3) {
|
||||
delete proxy.udp;
|
||||
} else if (proxy.type === 'vless') {
|
||||
if (isPresent(proxy, 'sni')) {
|
||||
proxy.servername = proxy.sni;
|
||||
@@ -141,7 +139,6 @@ export default function Clash_Producer() {
|
||||
'hysteria',
|
||||
'hysteria2',
|
||||
'juicity',
|
||||
'anytls',
|
||||
].includes(proxy.type)
|
||||
) {
|
||||
delete proxy.tls;
|
||||
|
||||
@@ -10,47 +10,6 @@ export default function ClashMeta_Producer() {
|
||||
return false;
|
||||
} else if (['juicity'].includes(proxy.type)) {
|
||||
return false;
|
||||
} else if (
|
||||
['ss'].includes(proxy.type) &&
|
||||
![
|
||||
'aes-128-ctr',
|
||||
'aes-192-ctr',
|
||||
'aes-256-ctr',
|
||||
'aes-128-cfb',
|
||||
'aes-192-cfb',
|
||||
'aes-256-cfb',
|
||||
'aes-128-gcm',
|
||||
'aes-192-gcm',
|
||||
'aes-256-gcm',
|
||||
'aes-128-ccm',
|
||||
'aes-192-ccm',
|
||||
'aes-256-ccm',
|
||||
'aes-128-gcm-siv',
|
||||
'aes-256-gcm-siv',
|
||||
'chacha20-ietf',
|
||||
'chacha20',
|
||||
'xchacha20',
|
||||
'chacha20-ietf-poly1305',
|
||||
'xchacha20-ietf-poly1305',
|
||||
'chacha8-ietf-poly1305',
|
||||
'xchacha8-ietf-poly1305',
|
||||
'2022-blake3-aes-128-gcm',
|
||||
'2022-blake3-aes-256-gcm',
|
||||
'2022-blake3-chacha20-poly1305',
|
||||
'lea-128-gcm',
|
||||
'lea-192-gcm',
|
||||
'lea-256-gcm',
|
||||
'rabbit128-poly1305',
|
||||
'aegis-128l',
|
||||
'aegis-256',
|
||||
'aez-384',
|
||||
'deoxys-ii-256-128',
|
||||
'rc4-md5',
|
||||
'none',
|
||||
].includes(proxy.cipher)
|
||||
) {
|
||||
// https://wiki.metacubex.one/config/proxies/ss/#cipher
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
@@ -73,10 +32,9 @@ export default function ClashMeta_Producer() {
|
||||
isPresent(proxy, 'cipher') &&
|
||||
![
|
||||
'auto',
|
||||
'none',
|
||||
'zero',
|
||||
'aes-128-gcm',
|
||||
'chacha20-poly1305',
|
||||
'none',
|
||||
].includes(proxy.cipher)
|
||||
) {
|
||||
proxy.cipher = 'auto';
|
||||
@@ -128,8 +86,6 @@ export default function ClashMeta_Producer() {
|
||||
proxy['preshared-key'] =
|
||||
proxy['preshared-key'] ?? proxy['pre-shared-key'];
|
||||
proxy['pre-shared-key'] = proxy['preshared-key'];
|
||||
} else if (proxy.type === 'snell' && proxy.version < 3) {
|
||||
delete proxy.udp;
|
||||
} else if (proxy.type === 'vless') {
|
||||
if (isPresent(proxy, 'sni')) {
|
||||
proxy.servername = proxy.sni;
|
||||
@@ -146,9 +102,6 @@ export default function ClashMeta_Producer() {
|
||||
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'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +157,6 @@ export default function ClashMeta_Producer() {
|
||||
'hysteria',
|
||||
'hysteria2',
|
||||
'juicity',
|
||||
'anytls',
|
||||
].includes(proxy.type)
|
||||
) {
|
||||
delete proxy.tls;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { isPresent } from './utils';
|
||||
|
||||
export default function Egern_Producer() {
|
||||
const type = 'ALL';
|
||||
const produce = (proxies, type) => {
|
||||
const produce = (proxies, type, opts = {}) => {
|
||||
// https://egernapp.com/zh-CN/docs/configuration/proxies
|
||||
const list = proxies
|
||||
.filter((proxy) => {
|
||||
@@ -16,7 +14,6 @@ export default function Egern_Producer() {
|
||||
'hysteria2',
|
||||
'vless',
|
||||
'vmess',
|
||||
'tuic',
|
||||
].includes(proxy.type) ||
|
||||
(proxy.type === 'ss' &&
|
||||
((proxy.plugin === 'obfs' &&
|
||||
@@ -50,12 +47,23 @@ export default function Egern_Producer() {
|
||||
'salsa20',
|
||||
'chacha20',
|
||||
'chacha20-ietf',
|
||||
'2022-blake3-aes-128-gcm',
|
||||
'2022-blake3-aes-256-gcm',
|
||||
...(opts['include-unsupported-proxy']
|
||||
? [
|
||||
'2022-blake3-aes-128-gcm',
|
||||
'2022-blake3-aes-256-gcm',
|
||||
]
|
||||
: []),
|
||||
].includes(proxy.cipher))) ||
|
||||
(proxy.type === 'vmess' &&
|
||||
!['http', 'ws', 'tcp'].includes(proxy.network) &&
|
||||
proxy.network) ||
|
||||
(![
|
||||
'auto',
|
||||
'aes-128-gcm',
|
||||
'chacha20-poly1305',
|
||||
'none',
|
||||
'zero',
|
||||
].includes(proxy.cipher) ||
|
||||
(!['http', 'ws', 'tcp'].includes(proxy.network) &&
|
||||
proxy.network))) ||
|
||||
(proxy.type === 'trojan' &&
|
||||
!['http', 'ws', 'tcp'].includes(proxy.network) &&
|
||||
proxy.network) ||
|
||||
@@ -63,17 +71,13 @@ export default function Egern_Producer() {
|
||||
(typeof proxy.flow !== 'undefined' ||
|
||||
proxy['reality-opts'] ||
|
||||
(!['http', 'ws', 'tcp'].includes(proxy.network) &&
|
||||
proxy.network))) ||
|
||||
(proxy.type === 'tuic' &&
|
||||
proxy.token &&
|
||||
proxy.token.length !== 0)
|
||||
proxy.network)))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map((proxy) => {
|
||||
const original = { ...proxy };
|
||||
if (proxy.tls && !proxy.sni) {
|
||||
proxy.sni = proxy.server;
|
||||
}
|
||||
@@ -123,10 +127,10 @@ export default function Egern_Producer() {
|
||||
proxy.udp || proxy.udp_relay || proxy.udp_relay,
|
||||
next_hop: proxy.next_hop,
|
||||
};
|
||||
if (original.plugin === 'obfs') {
|
||||
proxy.obfs = original['plugin-opts'].mode;
|
||||
proxy.obfs_host = original['plugin-opts'].host;
|
||||
proxy.obfs_uri = original['plugin-opts'].path;
|
||||
if (proxy.plugin === 'obfs') {
|
||||
proxy.obfs = proxy['plugin-opts'].mode;
|
||||
proxy.obfs_host = proxy['plugin-opts'].host;
|
||||
proxy.obfs_uri = proxy['plugin-opts'].path;
|
||||
}
|
||||
} else if (proxy.type === 'hysteria2') {
|
||||
proxy = {
|
||||
@@ -144,30 +148,10 @@ export default function Egern_Producer() {
|
||||
port_hopping: proxy.ports,
|
||||
port_hopping_interval: proxy['hop-interval'],
|
||||
};
|
||||
if (
|
||||
original['obfs-password'] &&
|
||||
original.obfs == 'salamander'
|
||||
) {
|
||||
if (proxy['obfs-password'] && proxy.obfs == 'salamander') {
|
||||
proxy.obfs = 'salamander';
|
||||
proxy.obfs_password = original['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') {
|
||||
if (proxy.network === 'ws') {
|
||||
proxy.websocket = {
|
||||
@@ -190,20 +174,6 @@ export default function Egern_Producer() {
|
||||
websocket: proxy.websocket,
|
||||
};
|
||||
} 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') {
|
||||
proxy.transport = {
|
||||
[proxy.tls ? 'wss' : 'ws']: {
|
||||
@@ -219,11 +189,9 @@ export default function Egern_Producer() {
|
||||
};
|
||||
} else if (proxy.network === 'http') {
|
||||
proxy.transport = {
|
||||
http1: {
|
||||
http: {
|
||||
method: proxy['http-opts']?.method,
|
||||
path: Array.isArray(proxy['http-opts']?.path)
|
||||
? proxy['http-opts']?.path[0]
|
||||
: proxy['http-opts']?.path,
|
||||
path: proxy['http-opts']?.path,
|
||||
headers: {
|
||||
Host: Array.isArray(
|
||||
proxy['http-opts']?.headers?.Host,
|
||||
@@ -234,29 +202,9 @@ export default function Egern_Producer() {
|
||||
skip_tls_verify: proxy['skip-cert-verify'],
|
||||
},
|
||||
};
|
||||
} else if (proxy.network === 'h2') {
|
||||
} else if (proxy.network === 'tcp' || !proxy.network) {
|
||||
proxy.transport = {
|
||||
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: {
|
||||
[proxy.tls ? 'tls' : 'tcp']: {
|
||||
sni: proxy.tls ? proxy.sni : undefined,
|
||||
skip_tls_verify: proxy.tls
|
||||
? proxy['skip-cert-verify']
|
||||
@@ -270,7 +218,7 @@ export default function Egern_Producer() {
|
||||
server: proxy.server,
|
||||
port: proxy.port,
|
||||
user_id: proxy.uuid,
|
||||
security,
|
||||
security: proxy.cipher,
|
||||
tfo: proxy.tfo || proxy['fast-open'],
|
||||
legacy: proxy.legacy,
|
||||
udp_relay:
|
||||
@@ -298,9 +246,7 @@ export default function Egern_Producer() {
|
||||
proxy.transport = {
|
||||
http: {
|
||||
method: proxy['http-opts']?.method,
|
||||
path: Array.isArray(proxy['http-opts']?.path)
|
||||
? proxy['http-opts']?.path[0]
|
||||
: proxy['http-opts']?.path,
|
||||
path: proxy['http-opts']?.path,
|
||||
headers: {
|
||||
Host: Array.isArray(
|
||||
proxy['http-opts']?.headers?.Host,
|
||||
@@ -338,39 +284,6 @@ export default function Egern_Producer() {
|
||||
// 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.collectionName;
|
||||
|
||||
@@ -3,21 +3,13 @@ const targetPlatform = 'Loon';
|
||||
import { isPresent, Result } 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() {
|
||||
const produce = (proxy, type, opts = {}) => {
|
||||
switch (proxy.type) {
|
||||
case 'ss':
|
||||
return shadowsocks(proxy);
|
||||
return shadowsocks(proxy, opts['include-unsupported-proxy']);
|
||||
case 'ssr':
|
||||
return shadowsocksr(proxy);
|
||||
return shadowsocksr(proxy, opts['include-unsupported-proxy']);
|
||||
case 'trojan':
|
||||
return trojan(proxy);
|
||||
case 'vmess':
|
||||
@@ -40,7 +32,7 @@ export default function Loon_Producer() {
|
||||
return { produce };
|
||||
}
|
||||
|
||||
function shadowsocks(proxy) {
|
||||
function shadowsocks(proxy, includeUnsupportedProxy) {
|
||||
const result = new Result(proxy);
|
||||
if (
|
||||
![
|
||||
@@ -74,6 +66,8 @@ function shadowsocks(proxy) {
|
||||
`${proxy.name}=shadowsocks,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"`,
|
||||
);
|
||||
|
||||
let isShadowTLS;
|
||||
|
||||
// obfs
|
||||
if (isPresent(proxy, 'plugin')) {
|
||||
if (proxy.plugin === 'obfs') {
|
||||
@@ -105,6 +99,7 @@ function shadowsocks(proxy) {
|
||||
);
|
||||
// udp-port
|
||||
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
|
||||
isShadowTLS = true;
|
||||
} else if (['shadow-tls'].includes(proxy.plugin) && proxy['plugin-opts']) {
|
||||
const password = proxy['plugin-opts'].password;
|
||||
const host = proxy['plugin-opts'].host;
|
||||
@@ -127,6 +122,7 @@ function shadowsocks(proxy) {
|
||||
`,udp-port=${proxy['udp-port']}`,
|
||||
'udp-port',
|
||||
);
|
||||
isShadowTLS = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,8 +134,11 @@ function shadowsocks(proxy) {
|
||||
result.append(`,udp=true`);
|
||||
}
|
||||
|
||||
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
||||
if (!includeUnsupportedProxy && isShadowTLS) {
|
||||
throw new Error(
|
||||
`shadow-tls is not supported(请使用 includeUnsupportedProxy 参数)`,
|
||||
);
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
@@ -161,6 +160,8 @@ function shadowsocksr(proxy, includeUnsupportedProxy) {
|
||||
result.appendIfPresent(`,obfs=${proxy.obfs}`, 'obfs');
|
||||
result.appendIfPresent(`,obfs-param=${proxy['obfs-param']}`, 'obfs-param');
|
||||
|
||||
let isShadowTLS;
|
||||
|
||||
// shadow-tls
|
||||
if (isPresent(proxy, 'shadow-tls-password')) {
|
||||
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
|
||||
@@ -175,6 +176,7 @@ function shadowsocksr(proxy, includeUnsupportedProxy) {
|
||||
);
|
||||
// udp-port
|
||||
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
|
||||
isShadowTLS = true;
|
||||
} else if (['shadow-tls'].includes(proxy.plugin) && proxy['plugin-opts']) {
|
||||
const password = proxy['plugin-opts'].password;
|
||||
const host = proxy['plugin-opts'].host;
|
||||
@@ -197,6 +199,7 @@ function shadowsocksr(proxy, includeUnsupportedProxy) {
|
||||
`,udp-port=${proxy['udp-port']}`,
|
||||
'udp-port',
|
||||
);
|
||||
isShadowTLS = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,8 +211,11 @@ function shadowsocksr(proxy, includeUnsupportedProxy) {
|
||||
result.append(`,udp=true`);
|
||||
}
|
||||
|
||||
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
||||
if (!includeUnsupportedProxy && isShadowTLS) {
|
||||
throw new Error(
|
||||
`shadow-tls is not supported(请使用 includeUnsupportedProxy 参数)`,
|
||||
);
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
@@ -263,8 +269,6 @@ function trojan(proxy) {
|
||||
if (proxy.udp) {
|
||||
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();
|
||||
}
|
||||
@@ -342,8 +346,6 @@ function vmess(proxy) {
|
||||
if (proxy.udp) {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -416,8 +418,6 @@ function vless(proxy) {
|
||||
if (proxy.udp) {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -439,8 +439,6 @@ function http(proxy) {
|
||||
|
||||
// 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();
|
||||
}
|
||||
@@ -469,8 +467,6 @@ function socks5(proxy) {
|
||||
if (proxy.udp) {
|
||||
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();
|
||||
}
|
||||
@@ -536,8 +532,6 @@ function wireguard(proxy) {
|
||||
presharedKey ?? ''
|
||||
}}]`,
|
||||
);
|
||||
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
@@ -585,8 +579,6 @@ function hysteria2(proxy) {
|
||||
);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { isPresent } from '@/core/proxy-utils/producers/utils';
|
||||
|
||||
export default function Shadowrocket_Producer() {
|
||||
export default function ShadowRocket_Producer() {
|
||||
const type = 'ALL';
|
||||
const produce = (proxies, type, opts = {}) => {
|
||||
const list = proxies
|
||||
@@ -8,7 +8,7 @@ export default function Shadowrocket_Producer() {
|
||||
if (opts['include-unsupported-proxy']) return true;
|
||||
if (proxy.type === 'snell' && String(proxy.version) === '4') {
|
||||
return false;
|
||||
} else if (['mieru', 'anytls'].includes(proxy.type)) {
|
||||
} else if (['mieru'].includes(proxy.type)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -32,10 +32,9 @@ export default function Shadowrocket_Producer() {
|
||||
isPresent(proxy, 'cipher') &&
|
||||
![
|
||||
'auto',
|
||||
'none',
|
||||
'zero',
|
||||
'aes-128-gcm',
|
||||
'chacha20-poly1305',
|
||||
'none',
|
||||
].includes(proxy.cipher)
|
||||
) {
|
||||
proxy.cipher = 'auto';
|
||||
@@ -103,28 +102,11 @@ export default function Shadowrocket_Producer() {
|
||||
proxy['preshared-key'] =
|
||||
proxy['preshared-key'] ?? proxy['pre-shared-key'];
|
||||
proxy['pre-shared-key'] = proxy['preshared-key'];
|
||||
} else if (proxy.type === 'snell' && proxy.version < 3) {
|
||||
delete proxy.udp;
|
||||
} else if (proxy.type === 'vless') {
|
||||
if (isPresent(proxy, 'sni')) {
|
||||
proxy.servername = 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 (
|
||||
@@ -178,7 +160,6 @@ export default function Shadowrocket_Producer() {
|
||||
'hysteria',
|
||||
'hysteria2',
|
||||
'juicity',
|
||||
'anytls',
|
||||
].includes(proxy.type)
|
||||
) {
|
||||
delete proxy.tls;
|
||||
|
||||
@@ -641,23 +641,6 @@ const tuic5Parser = (proxy = {}) => {
|
||||
smuxParser(proxy.smux, 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 local_address = ['ip', 'ipv6']
|
||||
@@ -846,9 +829,6 @@ export default function singbox_Producer() {
|
||||
case 'wireguard':
|
||||
list.push(wireguardParser(proxy));
|
||||
break;
|
||||
case 'anytls':
|
||||
list.push(anytlsParser(proxy));
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
`Platform sing-box does not support proxy type: ${proxy.type}`,
|
||||
|
||||
@@ -187,8 +187,6 @@ export default function Stash_Producer() {
|
||||
proxy['preshared-key'] =
|
||||
proxy['preshared-key'] ?? proxy['pre-shared-key'];
|
||||
proxy['pre-shared-key'] = proxy['preshared-key'];
|
||||
} else if (proxy.type === 'snell' && proxy.version < 3) {
|
||||
delete proxy.udp;
|
||||
} else if (proxy.type === 'vless') {
|
||||
if (isPresent(proxy, 'sni')) {
|
||||
proxy.servername = proxy.sni;
|
||||
@@ -247,7 +245,6 @@ export default function Stash_Producer() {
|
||||
'hysteria',
|
||||
'hysteria2',
|
||||
'juicity',
|
||||
'anytls',
|
||||
].includes(proxy.type)
|
||||
) {
|
||||
delete proxy.tls;
|
||||
|
||||
@@ -433,9 +433,6 @@ function ssh(proxy) {
|
||||
return result.toString();
|
||||
}
|
||||
function http(proxy) {
|
||||
if (proxy.headers && Object.keys(proxy.headers).length > 0) {
|
||||
throw new Error(`headers is unsupported`);
|
||||
}
|
||||
const result = new Result(proxy);
|
||||
const type = proxy.tls ? 'https' : 'http';
|
||||
result.append(`${proxy.name}=${type},${proxy.server},${proxy.port}`);
|
||||
|
||||
@@ -141,19 +141,10 @@ function mihomo(proxy, type, opts) {
|
||||
dns: {
|
||||
enable: true,
|
||||
ipv6,
|
||||
'default-nameserver': opts?.defaultNameserver ||
|
||||
proxy._defaultNameserver || [
|
||||
'180.76.76.76',
|
||||
'52.80.52.52',
|
||||
'119.28.28.28',
|
||||
'223.6.6.6',
|
||||
],
|
||||
nameserver: opts?.nameserver ||
|
||||
proxy._nameserver || [
|
||||
'https://doh.pub/dns-query',
|
||||
'https://dns.alidns.com/dns-query',
|
||||
'https://doh-pure.onedns.net/dns-query',
|
||||
],
|
||||
nameserver: [
|
||||
'https://223.6.6.6/dns-query',
|
||||
'https://120.53.53.53/dns-query',
|
||||
],
|
||||
},
|
||||
proxies: [
|
||||
{
|
||||
|
||||
@@ -27,11 +27,6 @@ export default function URI_Producer() {
|
||||
proxy.server = `[${proxy.server}]`;
|
||||
}
|
||||
switch (proxy.type) {
|
||||
case 'socks5':
|
||||
result = `socks://${encodeURIComponent(
|
||||
Base64.encode(`${proxy.username}:${proxy.password}`),
|
||||
)}@${proxy.server}:${proxy.port}#${proxy.name}`;
|
||||
break;
|
||||
case 'ss':
|
||||
const userinfo = `${proxy.cipher}:${proxy.password}`;
|
||||
result = `ss://${
|
||||
@@ -59,11 +54,6 @@ export default function URI_Producer() {
|
||||
}${opts.tls ? ';tls' : ''}`,
|
||||
);
|
||||
break;
|
||||
case 'shadow-tls':
|
||||
result += encodeURIComponent(
|
||||
`shadow-tls;host=${opts.host};password=${opts.password};version=${opts.version}`,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
`Unsupported plugin option: ${proxy.plugin}`,
|
||||
@@ -159,7 +149,6 @@ export default function URI_Producer() {
|
||||
const isReality = proxy['reality-opts'];
|
||||
let sid = '';
|
||||
let pbk = '';
|
||||
let spx = '';
|
||||
if (isReality) {
|
||||
security = 'reality';
|
||||
const publicKey = proxy['reality-opts']?.['public-key'];
|
||||
@@ -170,10 +159,6 @@ export default function URI_Producer() {
|
||||
if (shortId) {
|
||||
sid = `&sid=${encodeURIComponent(shortId)}`;
|
||||
}
|
||||
const spiderX = proxy['reality-opts']?.['_spider-x'];
|
||||
if (spiderX) {
|
||||
spx = `&spx=${encodeURIComponent(spiderX)}`;
|
||||
}
|
||||
} else if (proxy.tls) {
|
||||
security = 'tls';
|
||||
}
|
||||
@@ -203,14 +188,6 @@ export default function URI_Producer() {
|
||||
if (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;
|
||||
if (
|
||||
proxy.network === 'ws' &&
|
||||
@@ -277,7 +254,7 @@ export default function URI_Producer() {
|
||||
proxy.port
|
||||
}?security=${encodeURIComponent(
|
||||
security,
|
||||
)}${vlessTransport}${alpn}${allowInsecure}${sni}${fp}${flow}${sid}${spx}${pbk}${mode}${extra}#${encodeURIComponent(
|
||||
)}${vlessTransport}${alpn}${allowInsecure}${sni}${fp}${flow}${sid}${pbk}#${encodeURIComponent(
|
||||
proxy.name,
|
||||
)}`;
|
||||
break;
|
||||
@@ -347,41 +324,11 @@ export default function URI_Producer() {
|
||||
: 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}:${
|
||||
proxy.port
|
||||
}?sni=${encodeURIComponent(proxy.sni || proxy.server)}${
|
||||
proxy['skip-cert-verify'] ? '&allowInsecure=1' : ''
|
||||
}${trojanTransport}${trojanAlpn}${trojanFp}${trojanSecurity}${trojanSid}${trojanPbk}${trojanSpx}${trojanMode}${trojanExtra}#${encodeURIComponent(
|
||||
}${trojanTransport}${trojanAlpn}${trojanFp}#${encodeURIComponent(
|
||||
proxy.name,
|
||||
)}`;
|
||||
break;
|
||||
@@ -493,7 +440,6 @@ export default function URI_Producer() {
|
||||
'password',
|
||||
'server',
|
||||
'port',
|
||||
'tls',
|
||||
].includes(key)
|
||||
) {
|
||||
const i = key.replace(/-/, '_');
|
||||
@@ -522,13 +468,10 @@ export default function URI_Producer() {
|
||||
['disable-sni', 'reduce-rtt'].includes(key) &&
|
||||
proxy[key]
|
||||
) {
|
||||
tuicParams.push(`${i.replace(/-/g, '_')}=1`);
|
||||
tuicParams.push(`${i}=1`);
|
||||
} else if (proxy[key]) {
|
||||
tuicParams.push(
|
||||
`${i.replace(
|
||||
/-/g,
|
||||
'_',
|
||||
)}=${encodeURIComponent(proxy[key])}`,
|
||||
`${i}=${encodeURIComponent(proxy[key])}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -543,50 +486,6 @@ export default function URI_Producer() {
|
||||
)}`;
|
||||
}
|
||||
break;
|
||||
case 'anytls':
|
||||
let anytlsParams = [];
|
||||
Object.keys(proxy).forEach((key) => {
|
||||
if (
|
||||
![
|
||||
'name',
|
||||
'type',
|
||||
'password',
|
||||
'server',
|
||||
'port',
|
||||
'tls',
|
||||
].includes(key)
|
||||
) {
|
||||
const i = key.replace(/-/, '_');
|
||||
if (['alpn'].includes(key)) {
|
||||
if (proxy[key]) {
|
||||
anytlsParams.push(
|
||||
`${i}=${encodeURIComponent(
|
||||
Array.isArray(proxy[key])
|
||||
? proxy[key][0]
|
||||
: proxy[key],
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
} else if (['skip-cert-verify'].includes(key)) {
|
||||
if (proxy[key]) {
|
||||
anytlsParams.push(`insecure=1`);
|
||||
}
|
||||
} else if (proxy[key]) {
|
||||
anytlsParams.push(
|
||||
`${i.replace(/-/g, '_')}=${encodeURIComponent(
|
||||
proxy[key],
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
result = `anytls://${encodeURIComponent(proxy.password)}@${
|
||||
proxy.server
|
||||
}:${proxy.port}/?${anytlsParams.join('&')}#${encodeURIComponent(
|
||||
proxy.name,
|
||||
)}`;
|
||||
break;
|
||||
case 'wireguard':
|
||||
let wireguardParams = [];
|
||||
|
||||
|
||||
@@ -84,7 +84,6 @@ async function doSync() {
|
||||
const files = {};
|
||||
|
||||
try {
|
||||
const valid = [];
|
||||
const invalid = [];
|
||||
const allSubs = $.read(SUBS_KEY);
|
||||
const allCols = $.read(COLLECTIONS_KEY);
|
||||
@@ -157,26 +156,19 @@ async function doSync() {
|
||||
files[encodeURIComponent(artifact.name)] = {
|
||||
content: output,
|
||||
};
|
||||
|
||||
valid.push(artifact.name);
|
||||
}
|
||||
} catch (e) {
|
||||
$.error(
|
||||
`生成同步配置 ${artifact.name} 发生错误: ${
|
||||
e.message ?? e
|
||||
}`,
|
||||
`同步配置 ${artifact.name} 发生错误: ${e.message ?? e}`,
|
||||
);
|
||||
invalid.push(artifact.name);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
$.info(`${valid.length} 个同步配置生成成功: ${valid.join(', ')}`);
|
||||
$.info(`${invalid.length} 个同步配置生成失败: ${invalid.join(', ')}`);
|
||||
|
||||
if (valid.length === 0) {
|
||||
if (invalid.length > 0) {
|
||||
throw new Error(
|
||||
`同步配置 ${invalid.join(', ')} 生成失败 详情请查看日志`,
|
||||
`同步配置 ${invalid.join(', ')} 发生错误 详情请查看日志`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -192,11 +184,7 @@ async function doSync() {
|
||||
$.info(JSON.stringify(body, null, 2));
|
||||
|
||||
for (const artifact of allArtifacts) {
|
||||
if (
|
||||
artifact.sync &&
|
||||
artifact.source &&
|
||||
valid.includes(artifact.name)
|
||||
) {
|
||||
if (artifact.sync) {
|
||||
artifact.updated = new Date().getTime();
|
||||
// extract real url from gist
|
||||
let files = body.files;
|
||||
@@ -224,18 +212,9 @@ async function doSync() {
|
||||
}
|
||||
|
||||
$.write(allArtifacts, ARTIFACTS_KEY);
|
||||
$.info('上传配置成功');
|
||||
|
||||
if (invalid.length > 0) {
|
||||
$.notify(
|
||||
'🌍 Sub-Store',
|
||||
`同步配置成功 ${valid.length} 个, 失败 ${invalid.length} 个, 详情请查看日志`,
|
||||
);
|
||||
} else {
|
||||
$.notify('🌍 Sub-Store', '同步配置完成');
|
||||
}
|
||||
$.notify('🌍 Sub-Store', '全部订阅同步成功!');
|
||||
} catch (e) {
|
||||
$.notify('🌍 Sub-Store', '同步配置失败', `原因:${e.message ?? e}`);
|
||||
$.error(`无法同步配置到 Gist,原因:${e}`);
|
||||
$.notify('🌍 Sub-Store', '同步订阅失败', `原因:${e.message ?? e}`);
|
||||
$.error(`无法同步订阅配置到 Gist,原因:${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,18 +59,7 @@ function getCollection(req, res) {
|
||||
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`,
|
||||
)}"`,
|
||||
`attachment; filename="${encodeURIComponent(name)}.json"`,
|
||||
)
|
||||
.send(JSON.stringify(collection));
|
||||
} else {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
import { ProxyUtils } from '@/core/proxy-utils';
|
||||
import { COLLECTIONS_KEY, SUBS_KEY } from '@/constants';
|
||||
import { findByName } from '@/utils/database';
|
||||
import { getFlowHeaders, normalizeFlowHeader } from '@/utils/flow';
|
||||
import { getFlowHeaders } from '@/utils/flow';
|
||||
import $ from '@/core/app';
|
||||
import { failed } from '@/restful/response';
|
||||
import { InternalServerError, ResourceNotFoundError } from '@/restful/errors';
|
||||
@@ -259,10 +259,7 @@ async function downloadSubscription(req, res) {
|
||||
$arguments.flowUrl,
|
||||
);
|
||||
if (flowInfo) {
|
||||
res.set(
|
||||
'subscription-userinfo',
|
||||
normalizeFlowHeader(flowInfo),
|
||||
);
|
||||
res.set('subscription-userinfo', flowInfo);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -296,9 +293,7 @@ async function downloadSubscription(req, res) {
|
||||
}
|
||||
res.set(
|
||||
'subscription-userinfo',
|
||||
normalizeFlowHeader(
|
||||
[subUserInfo, flowInfo].filter((i) => i).join(';'),
|
||||
),
|
||||
[subUserInfo, flowInfo].filter((i) => i).join('; '),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -446,7 +441,6 @@ async function downloadCollection(req, res) {
|
||||
$options,
|
||||
proxy,
|
||||
noCache,
|
||||
ua: reqUA,
|
||||
});
|
||||
let subUserInfoOfSub;
|
||||
// forward flow header from the first subscription in this collection
|
||||
@@ -552,15 +546,13 @@ async function downloadCollection(req, res) {
|
||||
} else {
|
||||
subUserInfoOfCol = collection.subUserinfo;
|
||||
}
|
||||
const subUserInfo = [subUserInfoOfCol, subUserInfoOfSub]
|
||||
.filter((i) => i)
|
||||
.join('; ');
|
||||
if (subUserInfo) {
|
||||
res.set(
|
||||
'subscription-userinfo',
|
||||
normalizeFlowHeader(subUserInfo),
|
||||
);
|
||||
}
|
||||
res.set(
|
||||
'subscription-userinfo',
|
||||
[subUserInfoOfCol, subUserInfoOfSub]
|
||||
.filter((i) => i)
|
||||
.join('; '),
|
||||
);
|
||||
|
||||
if (platform === 'JSON') {
|
||||
if (resultFormat === 'nezha') {
|
||||
output = nezhaTransform(output);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
||||
import { getFlowHeaders, normalizeFlowHeader } from '@/utils/flow';
|
||||
import { getFlowHeaders } from '@/utils/flow';
|
||||
import { FILES_KEY } from '@/constants';
|
||||
import { failed, success } from '@/restful/response';
|
||||
import $ from '@/core/app';
|
||||
@@ -146,10 +146,7 @@ async function getFile(req, res) {
|
||||
proxy || file.proxy,
|
||||
);
|
||||
if (flowInfo) {
|
||||
res.set(
|
||||
'subscription-userinfo',
|
||||
normalizeFlowHeader(flowInfo),
|
||||
);
|
||||
res.set('subscription-userinfo', flowInfo);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -209,18 +206,7 @@ function getWholeFile(req, res) {
|
||||
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`,
|
||||
)}"`,
|
||||
`attachment; filename="${encodeURIComponent(name)}.json"`,
|
||||
)
|
||||
.send(JSON.stringify(file));
|
||||
} else {
|
||||
|
||||
@@ -2,7 +2,7 @@ import express from '@/vendor/express';
|
||||
import $ from '@/core/app';
|
||||
import migrate from '@/utils/migration';
|
||||
import download from '@/utils/download';
|
||||
import { syncArtifacts, produceArtifact } from '@/restful/sync';
|
||||
import { syncArtifacts } from '@/restful/sync';
|
||||
import { gistBackupAction } from '@/restful/miscs';
|
||||
import { TOKENS_KEY } from '@/constants';
|
||||
|
||||
@@ -75,39 +75,6 @@ export default function serve() {
|
||||
// 'Asia/Shanghai' // timeZone
|
||||
);
|
||||
}
|
||||
// 格式: 0 */2 * * *,sub,a;0 */3 * * *,col,b
|
||||
// 每 2 小时处理一次单条订阅 a, 每 3 小时处理一次组合订阅 b
|
||||
const produce_cron = eval('process.env.SUB_STORE_PRODUCE_CRON');
|
||||
if (produce_cron) {
|
||||
$.info(`[PRODUCE CRON] ${produce_cron} enabled`);
|
||||
const { CronJob } = eval(`require("cron")`);
|
||||
produce_cron.split(/\s*;\s*/).map((item) => {
|
||||
const [cron, type, name] = item.split(/\s*,\s*/);
|
||||
new CronJob(
|
||||
cron.trim(),
|
||||
async function () {
|
||||
try {
|
||||
$.info(
|
||||
`[PRODUCE CRON] ${type} ${name} ${cron} started`,
|
||||
);
|
||||
await produceArtifact({ type, name });
|
||||
$.info(
|
||||
`[PRODUCE CRON] ${type} ${name} ${cron} finished`,
|
||||
);
|
||||
} catch (e) {
|
||||
$.error(
|
||||
`[PRODUCE CRON] ${type} ${name} ${cron} error: ${
|
||||
e.message ?? e
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}, // onTick
|
||||
null, // onComplete
|
||||
true, // start
|
||||
// 'Asia/Shanghai' // timeZone
|
||||
);
|
||||
});
|
||||
}
|
||||
const backend_download_cron = eval(
|
||||
'process.env.SUB_STORE_BACKEND_DOWNLOAD_CRON',
|
||||
);
|
||||
|
||||
@@ -27,18 +27,7 @@ export default function register($app) {
|
||||
res.set('content-type', 'application/json')
|
||||
.set(
|
||||
'content-disposition',
|
||||
`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`,
|
||||
)}"`,
|
||||
'attachment; filename="sub-store.json"',
|
||||
)
|
||||
.send(
|
||||
$.env.isNode
|
||||
@@ -109,21 +98,6 @@ async function gistBackupAction(action) {
|
||||
const updated = settings.syncTime;
|
||||
switch (action) {
|
||||
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
|
||||
settings.syncTime = new Date().getTime();
|
||||
$.write(settings, SETTINGS_KEY);
|
||||
|
||||
@@ -15,48 +15,46 @@ export default function register($app) {
|
||||
async function previewFile(req, res) {
|
||||
try {
|
||||
const file = req.body;
|
||||
let content = '';
|
||||
if (file.type !== 'mihomoProfile') {
|
||||
if (
|
||||
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 '';
|
||||
}
|
||||
}),
|
||||
);
|
||||
let content;
|
||||
if (
|
||||
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 (
|
||||
!file.ignoreFailedRemoteFile &&
|
||||
Object.keys(errors).length > 0
|
||||
) {
|
||||
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);
|
||||
}
|
||||
if (
|
||||
!file.ignoreFailedRemoteFile &&
|
||||
Object.keys(errors).length > 0
|
||||
) {
|
||||
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
|
||||
|
||||
@@ -264,18 +264,7 @@ function getSubscription(req, res) {
|
||||
res.set('content-type', 'application/json')
|
||||
.set(
|
||||
'content-disposition',
|
||||
`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`,
|
||||
)}"`,
|
||||
`attachment; filename="${encodeURIComponent(name)}.json"`,
|
||||
)
|
||||
.send(JSON.stringify(sub));
|
||||
} else {
|
||||
|
||||
@@ -43,7 +43,7 @@ async function produceArtifact({
|
||||
}) {
|
||||
platform = platform || 'JSON';
|
||||
|
||||
if (['subscription', 'sub'].includes(type)) {
|
||||
if (type === 'subscription') {
|
||||
let sub;
|
||||
if (name) {
|
||||
const allSubs = $.read(SUBS_KEY);
|
||||
@@ -190,7 +190,7 @@ async function produceArtifact({
|
||||
}
|
||||
// produce
|
||||
return ProxyUtils.produce(proxies, platform, produceType, produceOpts);
|
||||
} else if (['collection', 'col'].includes(type)) {
|
||||
} else if (type === 'collection') {
|
||||
const allSubs = $.read(SUBS_KEY);
|
||||
const allCols = $.read(COLLECTIONS_KEY);
|
||||
const collection = findByName(allCols, name);
|
||||
@@ -216,14 +216,6 @@ async function produceArtifact({
|
||||
await Promise.all(
|
||||
subnames.map(async (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 {
|
||||
$.info(`正在处理子订阅:${sub.name}...`);
|
||||
let raw;
|
||||
@@ -245,7 +237,7 @@ async function produceArtifact({
|
||||
try {
|
||||
return await download(
|
||||
url,
|
||||
reqUA,
|
||||
sub.ua,
|
||||
undefined,
|
||||
proxy ||
|
||||
sub.proxy ||
|
||||
@@ -410,117 +402,105 @@ async function produceArtifact({
|
||||
const allFiles = $.read(FILES_KEY);
|
||||
const file = findByName(allFiles, name);
|
||||
if (!file) throw new Error(`找不到文件 ${name}`);
|
||||
let raw = '';
|
||||
console.log(file);
|
||||
if (file.type !== 'mihomoProfile') {
|
||||
let raw;
|
||||
if (content && !['localFirst', 'remoteFirst'].includes(mergeSources)) {
|
||||
raw = content;
|
||||
} 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 (
|
||||
content &&
|
||||
!['localFirst', 'remoteFirst'].includes(mergeSources)
|
||||
ignoreFailedRemoteFile != null &&
|
||||
ignoreFailedRemoteFile !== ''
|
||||
) {
|
||||
raw = content;
|
||||
} 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 '';
|
||||
}
|
||||
}),
|
||||
fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile;
|
||||
}
|
||||
if (!fileIgnoreFailedRemoteFile && Object.keys(errors).length > 0) {
|
||||
throw new Error(
|
||||
`文件 ${file.name} 的远程文件 ${Object.keys(errors).join(
|
||||
', ',
|
||||
)} 发生错误, 请查看日志`,
|
||||
);
|
||||
let fileIgnoreFailedRemoteFile = file.ignoreFailedRemoteFile;
|
||||
if (
|
||||
ignoreFailedRemoteFile != null &&
|
||||
ignoreFailedRemoteFile !== ''
|
||||
) {
|
||||
fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile;
|
||||
}
|
||||
if (
|
||||
!fileIgnoreFailedRemoteFile &&
|
||||
Object.keys(errors).length > 0
|
||||
) {
|
||||
throw new Error(
|
||||
`文件 ${file.name} 的远程文件 ${Object.keys(
|
||||
errors,
|
||||
).join(', ')} 发生错误, 请查看日志`,
|
||||
);
|
||||
}
|
||||
if (mergeSources === 'localFirst') {
|
||||
raw.unshift(content);
|
||||
} else if (mergeSources === 'remoteFirst') {
|
||||
raw.push(content);
|
||||
}
|
||||
} else if (
|
||||
file.source === 'local' &&
|
||||
!['localFirst', 'remoteFirst'].includes(file.mergeSources)
|
||||
}
|
||||
if (mergeSources === 'localFirst') {
|
||||
raw.unshift(content);
|
||||
} else if (mergeSources === 'remoteFirst') {
|
||||
raw.push(content);
|
||||
}
|
||||
} else if (
|
||||
file.source === 'local' &&
|
||||
!['localFirst', 'remoteFirst'].includes(file.mergeSources)
|
||||
) {
|
||||
raw = file.content;
|
||||
} else {
|
||||
const errors = {};
|
||||
raw = await Promise.all(
|
||||
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 (
|
||||
ignoreFailedRemoteFile != null &&
|
||||
ignoreFailedRemoteFile !== ''
|
||||
) {
|
||||
raw = file.content;
|
||||
} else {
|
||||
const errors = {};
|
||||
raw = await Promise.all(
|
||||
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 '';
|
||||
}
|
||||
}),
|
||||
fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile;
|
||||
}
|
||||
if (!fileIgnoreFailedRemoteFile && Object.keys(errors).length > 0) {
|
||||
throw new Error(
|
||||
`文件 ${file.name} 的远程文件 ${Object.keys(errors).join(
|
||||
', ',
|
||||
)} 发生错误, 请查看日志`,
|
||||
);
|
||||
let fileIgnoreFailedRemoteFile = file.ignoreFailedRemoteFile;
|
||||
if (
|
||||
ignoreFailedRemoteFile != null &&
|
||||
ignoreFailedRemoteFile !== ''
|
||||
) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
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();
|
||||
@@ -552,7 +532,6 @@ async function syncArtifacts() {
|
||||
const files = {};
|
||||
|
||||
try {
|
||||
const valid = [];
|
||||
const invalid = [];
|
||||
const allSubs = $.read(SUBS_KEY);
|
||||
const allCols = $.read(COLLECTIONS_KEY);
|
||||
@@ -627,26 +606,19 @@ async function syncArtifacts() {
|
||||
files[encodeURIComponent(artifact.name)] = {
|
||||
content: output,
|
||||
};
|
||||
|
||||
valid.push(artifact.name);
|
||||
}
|
||||
} catch (e) {
|
||||
$.error(
|
||||
`生成同步配置 ${artifact.name} 发生错误: ${
|
||||
e.message ?? e
|
||||
}`,
|
||||
`同步配置 ${artifact.name} 发生错误: ${e.message ?? e}`,
|
||||
);
|
||||
invalid.push(artifact.name);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
$.info(`${valid.length} 个同步配置生成成功: ${valid.join(', ')}`);
|
||||
$.info(`${invalid.length} 个同步配置生成失败: ${invalid.join(', ')}`);
|
||||
|
||||
if (valid.length === 0) {
|
||||
if (invalid.length > 0) {
|
||||
throw new Error(
|
||||
`同步配置 ${invalid.join(', ')} 生成失败 详情请查看日志`,
|
||||
`同步配置 ${invalid.join(', ')} 发生错误 详情请查看日志`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -663,11 +635,7 @@ async function syncArtifacts() {
|
||||
$.info(JSON.stringify(body, null, 2));
|
||||
|
||||
for (const artifact of allArtifacts) {
|
||||
if (
|
||||
artifact.sync &&
|
||||
artifact.source &&
|
||||
valid.includes(artifact.name)
|
||||
) {
|
||||
if (artifact.sync) {
|
||||
artifact.updated = new Date().getTime();
|
||||
// extract real url from gist
|
||||
let files = body.files;
|
||||
@@ -695,17 +663,9 @@ async function syncArtifacts() {
|
||||
}
|
||||
|
||||
$.write(allArtifacts, ARTIFACTS_KEY);
|
||||
$.info('上传配置成功');
|
||||
|
||||
if (invalid.length > 0) {
|
||||
throw new Error(
|
||||
`同步配置成功 ${valid.length} 个, 失败 ${invalid.length} 个, 详情请查看日志`,
|
||||
);
|
||||
} else {
|
||||
$.info(`同步配置成功 ${valid.length} 个`);
|
||||
}
|
||||
$.info('全部订阅同步成功!');
|
||||
} catch (e) {
|
||||
$.error(`同步配置失败,原因:${e.message ?? e}`);
|
||||
$.error(`同步订阅失败,原因:${e.message ?? e}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -715,7 +675,7 @@ async function syncAllArtifacts(_, res) {
|
||||
await syncArtifacts();
|
||||
success(res);
|
||||
} catch (e) {
|
||||
$.error(`同步配置失败,原因:${e.message ?? e}`);
|
||||
$.error(`同步订阅失败,原因:${e.message ?? e}`);
|
||||
failed(
|
||||
res,
|
||||
new InternalServerError(
|
||||
|
||||
@@ -313,41 +313,3 @@ export function getRmainingDays(opt = {}) {
|
||||
$.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,9 +18,7 @@ const ISOFlags = {
|
||||
'🇧🇬': ['BG', 'BGR'],
|
||||
'🇧🇭': ['BH', 'BHR'],
|
||||
'🇧🇴': ['BO', 'BOL'],
|
||||
'🇧🇳': ['BN', 'BRN'],
|
||||
'🇧🇷': ['BR', 'BRA'],
|
||||
'🇧🇹': ['BT', 'BTN'],
|
||||
'🇧🇾': ['BY', 'BLR'],
|
||||
'🇨🇦': ['CA', 'CAN'],
|
||||
'🇨🇭': ['CH', 'CHE'],
|
||||
@@ -42,7 +40,6 @@ const ISOFlags = {
|
||||
'🇬🇪': ['GE', 'GEO'],
|
||||
'🇬🇷': ['GR', 'GRC'],
|
||||
'🇬🇹': ['GT', 'GTM'],
|
||||
'🇬🇺': ['GU', 'GUM'],
|
||||
'🇭🇰': ['HK', 'HKG', 'HKT', 'HKBN', 'HGC', 'WTT', 'CMI'],
|
||||
'🇭🇷': ['HR', 'HRV'],
|
||||
'🇭🇺': ['HU', 'HUN'],
|
||||
@@ -62,15 +59,12 @@ const ISOFlags = {
|
||||
'🇮🇷': ['IR', 'IRN'],
|
||||
'🇮🇸': ['IS', 'ISL'],
|
||||
'🇮🇹': ['IT', 'ITA'],
|
||||
'🇱🇦': ['LA', 'LAO'],
|
||||
'🇱🇰': ['LK', 'LKA'],
|
||||
'🇱🇹': ['LT', 'LTU'],
|
||||
'🇱🇺': ['LU', 'LUX'],
|
||||
'🇱🇻': ['LV', 'LVA'],
|
||||
'🇲🇦': ['MA', 'MAR'],
|
||||
'🇲🇩': ['MD', 'MDA'],
|
||||
'🇳🇬': ['NG', 'NGA'],
|
||||
'🇲🇲': ['MM', 'MMR'],
|
||||
'🇲🇰': ['MK', 'MKD'],
|
||||
'🇲🇳': ['MN', 'MNG'],
|
||||
'🇲🇴': ['MO', 'MAC', 'CTM'],
|
||||
@@ -89,7 +83,6 @@ const ISOFlags = {
|
||||
'🇵🇷': ['PR', 'PRI'],
|
||||
'🇵🇹': ['PT', 'PRT'],
|
||||
'🇵🇾': ['PY', 'PRY'],
|
||||
'🇵🇬': ['PG', 'PNG'],
|
||||
'🇷🇴': ['RO', 'ROU'],
|
||||
'🇷🇸': ['RS', 'SRB'],
|
||||
'🇷🇪': ['RE', 'REU'],
|
||||
@@ -149,10 +142,8 @@ export function getFlag(name) {
|
||||
'🇧🇬': ['Bulgaria', '保加利亚', '保加利亞'],
|
||||
'🇧🇭': ['Bahrain', '巴林'],
|
||||
'🇧🇷': ['Brazil', '巴西', '圣保罗'],
|
||||
'🇧🇳': ['Brunei', '文莱', '汶萊'],
|
||||
'🇧🇾': ['Belarus', '白俄罗斯', '白俄'],
|
||||
'🇧🇴': ['Bolivia', '玻利维亚'],
|
||||
'🇧🇹': ['Bhutan', '不丹', '不丹王国'],
|
||||
'🇨🇦': [
|
||||
'Canada',
|
||||
'加拿大',
|
||||
@@ -203,7 +194,6 @@ export function getFlag(name) {
|
||||
],
|
||||
'🇬🇪': ['Georgia', '格鲁吉亚', '格魯吉亞'],
|
||||
'🇬🇷': ['Greece', '希腊', '希臘'],
|
||||
'🇬🇺': ['Guam', '关岛', '關島'],
|
||||
'🇬🇹': ['Guatemala', '危地马拉'],
|
||||
'🇭🇰': [
|
||||
'Hongkong',
|
||||
@@ -264,14 +254,11 @@ export function getFlag(name) {
|
||||
'🇮🇷': ['Iran', '伊朗'],
|
||||
'🇮🇸': ['Iceland', '冰岛', '冰島'],
|
||||
'🇮🇹': ['Italy', '意大利', '義大利', '米兰', 'Nachash'],
|
||||
'🇱🇰': ['Sri Lanka', '斯里兰卡', '斯里蘭卡'],
|
||||
'🇱🇦': ['Laos', '老挝', '老撾'],
|
||||
'🇱🇹': ['Lithuania', '立陶宛'],
|
||||
'🇱🇺': ['Luxembourg', '卢森堡'],
|
||||
'🇱🇻': ['Latvia', '拉脱维亚', 'Latvija'],
|
||||
'🇲🇦': ['Morocco', '摩洛哥'],
|
||||
'🇲🇩': ['Moldova', '摩尔多瓦', '摩爾多瓦'],
|
||||
'🇲🇲': ['Myanmar', '缅甸', '緬甸'],
|
||||
'🇳🇬': ['Nigeria', '尼日利亚', '尼日利亞'],
|
||||
'🇲🇰': ['Macedonia', '马其顿', '馬其頓'],
|
||||
'🇲🇳': ['Mongolia', '蒙古'],
|
||||
@@ -297,7 +284,6 @@ export function getFlag(name) {
|
||||
'🇵🇱': ['Poland', '波兰', '波蘭', '华沙', 'Warsaw'],
|
||||
'🇵🇷': ['Puerto Rico', '波多黎各'],
|
||||
'🇵🇹': ['Portugal', '葡萄牙'],
|
||||
'🇵🇬': ['Papua New Guinea', '巴布亚新几内亚'],
|
||||
'🇵🇾': ['Paraguay', '巴拉圭'],
|
||||
'🇷🇴': ['Romania', '罗马尼亚'],
|
||||
'🇷🇸': ['Serbia', '塞尔维亚'],
|
||||
|
||||
@@ -118,7 +118,6 @@ export default class Gist {
|
||||
.get('/gists?per_page=100&page=1')
|
||||
.then((response) => {
|
||||
const gists = JSON.parse(response.body);
|
||||
$.info(`获取到当前 GitHub 用户的 gist: ${gists.length} 个`);
|
||||
for (let g of gists) {
|
||||
if (g.description === this.key) {
|
||||
return g;
|
||||
|
||||
@@ -117,17 +117,7 @@ function numberToString(value) {
|
||||
: 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 {
|
||||
isValidUUID,
|
||||
ipAddress,
|
||||
isIPv4,
|
||||
isIPv6,
|
||||
|
||||
@@ -24,7 +24,7 @@ class ResourceCache {
|
||||
this._cleanup();
|
||||
}
|
||||
|
||||
_cleanup(prefix, expires) {
|
||||
_cleanup() {
|
||||
// clear obsolete cached resource
|
||||
let clear = false;
|
||||
Object.entries(this.resourceCache).forEach((entry) => {
|
||||
@@ -35,11 +35,7 @@ class ResourceCache {
|
||||
$.delete(`#${id}`);
|
||||
clear = true;
|
||||
}
|
||||
if (
|
||||
new Date().getTime() - updated.time >
|
||||
(expires ?? this.expires) ||
|
||||
(prefix && id.startsWith(prefix))
|
||||
) {
|
||||
if (new Date().getTime() - updated.time > this.expires) {
|
||||
delete this.resourceCache[id];
|
||||
clear = true;
|
||||
}
|
||||
@@ -56,15 +52,10 @@ class ResourceCache {
|
||||
$.write(JSON.stringify(this.resourceCache), SCRIPT_RESOURCE_CACHE_KEY);
|
||||
}
|
||||
|
||||
get(id, expires, remove) {
|
||||
get(id) {
|
||||
const updated = this.resourceCache[id] && this.resourceCache[id].time;
|
||||
if (updated) {
|
||||
if (new Date().getTime() - updated <= (expires ?? this.expires))
|
||||
return this.resourceCache[id].data;
|
||||
if (remove) {
|
||||
delete this.resourceCache[id];
|
||||
this._persist();
|
||||
}
|
||||
if (updated && new Date().getTime() - updated <= this.expires) {
|
||||
return this.resourceCache[id].data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -62,26 +62,11 @@ export function getPlatformFromHeaders(headers) {
|
||||
}
|
||||
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')
|
||||
) {
|
||||
if (platform === 'Stash' && gte(version, '2.8.0')) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
platform === 'Egern' &&
|
||||
target === 'Egern' &&
|
||||
gte(version, '1.29.0')
|
||||
) {
|
||||
if (platform === 'Egern' && gte(version, '1.29.0')) {
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
7
backend/src/vendor/express.js
vendored
7
backend/src/vendor/express.js
vendored
@@ -17,12 +17,7 @@ export default function express({ substore: $, port, host }) {
|
||||
const express_ = eval(`require("express")`);
|
||||
const bodyParser = eval(`require("body-parser")`);
|
||||
const app = express_();
|
||||
app.use(
|
||||
bodyParser.json({
|
||||
verify: rawBodySaver,
|
||||
limit: eval('process.env.SUB_STORE_BODY_JSON_LIMIT') || '1mb',
|
||||
}),
|
||||
);
|
||||
app.use(bodyParser.json({ verify: rawBodySaver, limit: '1mb' }));
|
||||
app.use(
|
||||
bodyParser.urlencoded({ verify: rawBodySaver, extended: true }),
|
||||
);
|
||||
|
||||
@@ -24,10 +24,6 @@ function operator(proxies = [], targetPlatform, context) {
|
||||
// 16. `sing-box` 支持使用 `_network` 来设置 `network`, 例如 `tcp`, `udp`
|
||||
|
||||
// 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 为传入的脚本参数
|
||||
|
||||
@@ -52,32 +48,8 @@ function operator(proxies = [], targetPlatform, context) {
|
||||
// scriptResourceCache 缓存
|
||||
// 可参考 https://t.me/zhetengsha/1003
|
||||
// const cache = scriptResourceCache
|
||||
// 设置
|
||||
// cache.set('a:1', 1)
|
||||
// cache.set('a:2', 2)
|
||||
// 获取
|
||||
// cache.get('a:1')
|
||||
// 支持第二个参数: 自定义过期时间
|
||||
// 支持第三个参数: 是否删除过期项
|
||||
// cache.get('a:2', 1000, true)
|
||||
|
||||
// 清理
|
||||
// cache._cleanup()
|
||||
// 支持第一个参数: 匹配前缀的项也一起删除
|
||||
// 支持第二个参数: 自定义过期时间
|
||||
// cache._cleanup('a:', 1000)
|
||||
|
||||
// 关于缓存时长
|
||||
|
||||
// 拉取 Sub-Store 订阅时, 会自动拉取远程订阅
|
||||
|
||||
// 远程订阅缓存是 1 小时, 缓存的唯一 key 为 url+ user agent. 可通过前端的刷新按钮刷新缓存. 或使用参数 noCache 来禁用缓存. 例: 内部配置订阅链接时使用 http://a.com#noCache, 外部使用 sub-store 链接时使用 https://sub.store/download/1?noCache=true
|
||||
|
||||
// 当使用相关脚本时, 若在对应的脚本中使用参数开启缓存, 可设置持久化缓存 sub-store-csr-expiration-time 的值来自定义默认缓存时长, 默认为 172800000 (48 * 3600 * 1000, 即 48 小时)
|
||||
|
||||
// 🎈Loon 可在插件中设置
|
||||
|
||||
// 其他平台同理, 持久化缓存数据在 JSON 里
|
||||
// cache.set(id, data)
|
||||
// cache.get(id)
|
||||
|
||||
// ProxyUtils 为节点处理工具
|
||||
// 可参考 https://t.me/zhetengsha/1066
|
||||
@@ -97,7 +69,6 @@ function operator(proxies = [], targetPlatform, context) {
|
||||
// Gist, // Gist 类
|
||||
// download, // 内部的下载方法, 见 backend/src/utils/download.js
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user