Compare commits

..

1 Commits

Author SHA1 Message Date
xream
5fce134a0a feat: sing-box includeUnsupportedProxy 开启支持 Hysteria2 端口跳跃 2024-12-29 16:03:42 +08:00
24 changed files with 7457 additions and 7028 deletions

View File

@@ -1,6 +1,5 @@
name: build name: build
on: on:
workflow_dispatch:
push: push:
branches: branches:
- master - master
@@ -27,18 +26,18 @@ jobs:
run: | run: |
npm install -g pnpm npm install -g pnpm
cd backend && pnpm i --no-frozen-lockfile cd backend && pnpm i --no-frozen-lockfile
# - name: Test - name: Test
# run: | run: |
# cd backend cd backend
# pnpm test pnpm test
# - name: Build - name: Build
# run: | run: |
# cd backend cd backend
# pnpm run build pnpm run build
- name: Bundle - name: Bundle
run: | run: |
cd backend cd backend
pnpm bundle:esbuild pnpm run bundle
- id: tag - id: tag
name: Generate release tag name: Generate release tag
run: | run: |

View File

@@ -1,6 +1,6 @@
{ {
"name": "sub-store", "name": "sub-store",
"version": "2.16.18", "version": "2.15.4",
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.", "description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
"main": "src/main.js", "main": "src/main.js",
"scripts": { "scripts": {
@@ -12,7 +12,6 @@
"dev:run": "nodemon -w sub-store.min.js sub-store.min.js", "dev:run": "nodemon -w sub-store.min.js sub-store.min.js",
"build": "gulp", "build": "gulp",
"bundle": "node bundle.js", "bundle": "node bundle.js",
"bundle:esbuild": "node bundle-esbuild.js",
"changelog": "conventional-changelog -p cli -i CHANGELOG.md -s" "changelog": "conventional-changelog -p cli -i CHANGELOG.md -s"
}, },
"author": "Peng-YM", "author": "Peng-YM",
@@ -34,7 +33,6 @@
"ms": "^2.1.3", "ms": "^2.1.3",
"nanoid": "^3.3.3", "nanoid": "^3.3.3",
"request": "^2.88.2", "request": "^2.88.2",
"semver": "^7.6.3",
"static-js-yaml": "^1.0.0" "static-js-yaml": "^1.0.0"
}, },
"devDependencies": { "devDependencies": {

13812
backend/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -343,14 +343,6 @@ function lastParse(proxy) {
if (typeof proxy.password === 'number') { if (typeof proxy.password === 'number') {
proxy.password = numberToString(proxy.password); proxy.password = numberToString(proxy.password);
} }
if (
['ss'].includes(proxy.type) &&
proxy.cipher === 'none' &&
!proxy.password
) {
// https://github.com/MetaCubeX/mihomo/issues/1677
proxy.password = '';
}
if (proxy.interface) { if (proxy.interface) {
proxy['interface-name'] = proxy.interface; proxy['interface-name'] = proxy.interface;
delete proxy.interface; delete proxy.interface;

View File

@@ -39,12 +39,12 @@ start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/socks5/hysteria2
return proxy; return proxy;
} }
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/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/others)*{
proxy.type = "ssr"; proxy.type = "ssr";
// handle ssr obfs // handle ssr obfs
proxy.obfs = obfs.type; proxy.obfs = obfs.type;
} }
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/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/others)* {
proxy.type = "ss"; proxy.type = "ss";
// handle ss obfs // handle ss obfs
if (obfs.type == "http" || obfs.type === "tls") { if (obfs.type == "http" || obfs.type === "tls") {
@@ -54,31 +54,31 @@ shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs
$set(proxy, "plugin-opts.path", obfs.path); $set(proxy, "plugin-opts.path", obfs.path);
} }
} }
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/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.type = "vmess";
proxy.cipher = proxy.cipher || "none"; proxy.cipher = proxy.cipher || "none";
proxy.alterId = proxy.alterId || 0; proxy.alterId = proxy.alterId || 0;
handleTransport(); handleTransport();
} }
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/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"; proxy.type = "vless";
handleTransport(); handleTransport();
} }
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/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"; proxy.type = "trojan";
handleTransport(); handleTransport();
} }
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/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"; 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.type = "http";
proxy.tls = true; 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"; 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"; proxy.type = "socks5";
} }
@@ -169,11 +169,6 @@ ssr_protocol_param = comma "protocol-param" equals param:$[^=,]+ { proxy["protoc
vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseInt(alterId); } vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseInt(alterId); }
udp_port = comma "udp-port" equals match:$[0-9]+ { proxy["udp-port"] = parseInt(match.trim()); }
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-sni"] = match.join(""); }
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join(""); }
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; } over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
tls_host = comma sni:("tls-name"/"sni") equals host:domain { proxy.sni = host; } tls_host = comma sni:("tls-name"/"sni") equals host:domain { proxy.sni = host; }
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; } tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
@@ -182,7 +177,6 @@ tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pu
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; } fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; } udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
ip_mode = comma "ip-mode" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; } ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); } download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }

View File

@@ -37,12 +37,12 @@ start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/socks5/hysteria2
return proxy; return proxy;
} }
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/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/others)*{
proxy.type = "ssr"; proxy.type = "ssr";
// handle ssr obfs // handle ssr obfs
proxy.obfs = obfs.type; proxy.obfs = obfs.type;
} }
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/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/others)* {
proxy.type = "ss"; proxy.type = "ss";
// handle ss obfs // handle ss obfs
if (obfs.type == "http" || obfs.type === "tls") { if (obfs.type == "http" || obfs.type === "tls") {
@@ -52,31 +52,31 @@ shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs
$set(proxy, "plugin-opts.path", obfs.path); $set(proxy, "plugin-opts.path", obfs.path);
} }
} }
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/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.type = "vmess";
proxy.cipher = proxy.cipher || "none"; proxy.cipher = proxy.cipher || "none";
proxy.alterId = proxy.alterId || 0; proxy.alterId = proxy.alterId || 0;
handleTransport(); handleTransport();
} }
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/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"; proxy.type = "vless";
handleTransport(); handleTransport();
} }
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/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"; proxy.type = "trojan";
handleTransport(); handleTransport();
} }
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/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"; 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.type = "http";
proxy.tls = true; 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"; 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"; proxy.type = "socks5";
} }
@@ -167,11 +167,6 @@ ssr_protocol_param = comma "protocol-param" equals param:$[^=,]+ { proxy["protoc
vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseInt(alterId); } vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseInt(alterId); }
udp_port = comma "udp-port" equals match:$[0-9]+ { proxy["udp-port"] = parseInt(match.trim()); }
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-sni"] = match.join(""); }
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join(""); }
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; } over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
tls_host = comma sni:("tls-name"/"sni") equals host:domain { proxy.sni = host; } tls_host = comma sni:("tls-name"/"sni") equals host:domain { proxy.sni = host; }
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; } tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
@@ -180,7 +175,6 @@ tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pu
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; } fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; } udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
ip_mode = comma "ip-mode" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; } ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); } download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }

View File

@@ -1,6 +1,5 @@
import { safeLoad } from '@/utils/yaml'; import { safeLoad } from '@/utils/yaml';
import { Base64 } from 'js-base64'; import { Base64 } from 'js-base64';
import $ from '@/core/app';
function HTML() { function HTML() {
const name = 'HTML'; const name = 'HTML';
@@ -36,15 +35,8 @@ function Base64Encoded() {
); );
}; };
const parse = function (raw) { const parse = function (raw) {
const decoded = Base64.decode(raw); raw = Base64.decode(raw);
if (!/^\w+(:\/\/|\s*?=\s*?)\w+/m.test(decoded)) { return raw;
$.error(
`Base64 Pre-processor error: decoded line does not start with protocol`,
);
return raw;
}
return decoded;
}; };
return { name, test, parse }; return { name, test, parse };
} }
@@ -56,7 +48,7 @@ function Clash() {
const content = safeLoad(raw); const content = safeLoad(raw);
return content.proxies && Array.isArray(content.proxies); return content.proxies && Array.isArray(content.proxies);
}; };
const parse = function (raw, includeProxies) { const parse = function (raw) {
// Clash YAML format // Clash YAML format
// 防止 VLESS节点 reality-opts 选项中的 short-id 被解析成 Infinity // 防止 VLESS节点 reality-opts 选项中的 short-id 被解析成 Infinity
@@ -64,40 +56,35 @@ function Clash() {
const afterReplace = raw.replace( const afterReplace = raw.replace(
/short-id:([ ]*[^,\n}]*)/g, /short-id:([ ]*[^,\n}]*)/g,
(matched, value) => { (matched, value) => {
const afterTrim = value.trim(); const afterTrim = value.trim();
// 为空 // 为空
if (!afterTrim || afterTrim === '') { if (!afterTrim || afterTrim === '') {
return 'short-id: ""'; return 'short-id: ""'
} }
// 是否被引号包裹 // 是否被引号包裹
if (/^(['"]).*\1$/.test(afterTrim)) { if (/^(['"]).*\1$/.test(afterTrim)) {
return `short-id: ${afterTrim}`; return `short-id: ${afterTrim}`;
} else { } else {
return `short-id: "${afterTrim}"`; return `short-id: "${afterTrim}"`
} }
}, }
); );
const { const {
proxies, proxies,
'global-client-fingerprint': globalClientFingerprint, 'global-client-fingerprint': globalClientFingerprint,
} = safeLoad(afterReplace); } = safeLoad(afterReplace);
return ( return proxies
(includeProxies ? 'proxies:\n' : '') + .map((p) => {
proxies // https://github.com/MetaCubeX/mihomo/blob/Alpha/docs/config.yaml#L73C1-L73C26
.map((p) => { if (globalClientFingerprint && !p['client-fingerprint']) {
// https://github.com/MetaCubeX/mihomo/blob/Alpha/docs/config.yaml#L73C1-L73C26 p['client-fingerprint'] = globalClientFingerprint;
if (globalClientFingerprint && !p['client-fingerprint']) { }
p['client-fingerprint'] = globalClientFingerprint; return JSON.stringify(p);
} })
return `${includeProxies ? ' - ' : ''}${JSON.stringify( .join('\n');
p,
)}\n`;
})
.join('')
);
}; };
return { name, test, parse }; return { name, test, parse };
} }

View File

@@ -10,7 +10,6 @@ import { hex_md5 } from '@/vendor/md5';
import { ProxyUtils } from '@/core/proxy-utils'; import { ProxyUtils } from '@/core/proxy-utils';
import { produceArtifact } from '@/restful/sync'; import { produceArtifact } from '@/restful/sync';
import { SETTINGS_KEY } from '@/constants'; import { SETTINGS_KEY } from '@/constants';
import YAML from '@/utils/yaml';
import env from '@/utils/env'; import env from '@/utils/env';
import { import {
@@ -22,46 +21,6 @@ import {
getRmainingDays, getRmainingDays,
} from '@/utils/flow'; } from '@/utils/flow';
function isObject(item) {
return item && typeof item === 'object' && !Array.isArray(item);
}
function trimWrap(str) {
if (str.startsWith('<') && str.endsWith('>')) {
return str.slice(1, -1);
}
return str;
}
function deepMerge(target, _other) {
const other = typeof _other === 'string' ? JSON.parse(_other) : _other;
for (const key in other) {
if (isObject(other[key])) {
if (key.endsWith('!')) {
const k = trimWrap(key.slice(0, -1));
target[k] = other[key];
} else {
const k = trimWrap(key);
if (!target[k]) Object.assign(target, { [k]: {} });
deepMerge(target[k], other[k]);
}
} else if (Array.isArray(other[key])) {
if (key.startsWith('+')) {
const k = trimWrap(key.slice(1));
if (!target[k]) Object.assign(target, { [k]: [] });
target[k] = [...other[key], ...target[k]];
} else if (key.endsWith('+')) {
const k = trimWrap(key.slice(0, -1));
if (!target[k]) Object.assign(target, { [k]: [] });
target[k] = [...target[k], ...other[key]];
} else {
const k = trimWrap(key);
Object.assign(target, { [k]: other[key] });
}
} else {
Object.assign(target, { [key]: other[key] });
}
}
return target;
}
/** /**
The rule "(name CONTAINS "🇨🇳") AND (port IN [80, 443])" can be expressed as follows: The rule "(name CONTAINS "🇨🇳") AND (port IN [80, 443])" can be expressed as follows:
{ {
@@ -362,33 +321,6 @@ function ScriptOperator(script, targetPlatform, $arguments, source, $options) {
name: 'Script Operator', name: 'Script Operator',
func: async (proxies) => { func: async (proxies) => {
let output = proxies; let output = proxies;
if (output?.$file?.type === 'mihomoProfile') {
try {
let patch = YAML.safeLoad(script);
if (typeof patch !== 'object') patch = {};
output.$content = ProxyUtils.yaml.safeDump(
deepMerge(
{
proxies: await produceArtifact({
type:
output?.$file?.sourceType ||
'collection',
name: output?.$file?.sourceName,
platform: 'mihomo',
produceType: 'internal',
produceOpts: {
'delete-underscore-fields': true,
},
}),
},
patch,
),
);
return output;
} catch (e) {
// console.log(e);
}
}
await (async function () { await (async function () {
const operator = createDynamicFunction( const operator = createDynamicFunction(
'operator', 'operator',
@@ -407,27 +339,9 @@ function ScriptOperator(script, targetPlatform, $arguments, source, $options) {
'operator', 'operator',
`async function operator(input = []) { `async function operator(input = []) {
if (input && (input.$files || input.$content)) { if (input && (input.$files || input.$content)) {
let { $content, $files, $options, $file } = input let { $content, $files, $options } = input
if($file.type === 'mihomoProfile') { ${script}
${script} return { $content, $files, $options }
if(typeof main === 'function') {
const config = {
proxies: await produceArtifact({
type: $file.sourceType || 'collection',
name: $file.sourceName,
platform: 'mihomo',
produceType: 'internal',
produceOpts: {
'delete-underscore-fields': true
}
}),
}
$content = ProxyUtils.yaml.safeDump(await main(config))
}
} else {
${script}
}
return { $content, $files, $options, $file }
} else { } else {
let proxies = input let proxies = input
let list = [] let list = []

View File

@@ -180,7 +180,7 @@ export default function ClashMeta_Producer() {
delete proxy.id; delete proxy.id;
delete proxy.resolved; delete proxy.resolved;
delete proxy['no-resolve']; delete proxy['no-resolve'];
if (type !== 'internal' || opts['delete-underscore-fields']) { if (type !== 'internal') {
for (const key in proxy) { for (const key in proxy) {
if (proxy[key] == null || /^_/i.test(key)) { if (proxy[key] == null || /^_/i.test(key)) {
delete proxy[key]; delete proxy[key];

View File

@@ -4,7 +4,7 @@ export default function Egern_Producer() {
// https://egernapp.com/zh-CN/docs/configuration/proxies // https://egernapp.com/zh-CN/docs/configuration/proxies
const list = proxies const list = proxies
.filter((proxy) => { .filter((proxy) => {
// if (opts['include-unsupported-proxy']) return true; if (opts['include-unsupported-proxy']) return true;
if ( if (
![ ![
'http', 'http',
@@ -47,12 +47,6 @@ export default function Egern_Producer() {
'salsa20', 'salsa20',
'chacha20', 'chacha20',
'chacha20-ietf', 'chacha20-ietf',
...(opts['include-unsupported-proxy']
? [
'2022-blake3-aes-128-gcm',
'2022-blake3-aes-256-gcm',
]
: []),
].includes(proxy.cipher))) || ].includes(proxy.cipher))) ||
(proxy.type === 'vmess' && (proxy.type === 'vmess' &&
(![ (![
@@ -81,12 +75,6 @@ export default function Egern_Producer() {
if (proxy.tls && !proxy.sni) { if (proxy.tls && !proxy.sni) {
proxy.sni = proxy.server; proxy.sni = proxy.server;
} }
const prev_hop =
proxy.prev_hop ||
proxy['underlying-proxy'] ||
proxy['dialer-proxy'] ||
proxy.detour;
if (proxy.type === 'http') { if (proxy.type === 'http') {
proxy = { proxy = {
type: 'http', type: 'http',
@@ -145,8 +133,6 @@ export default function Egern_Producer() {
next_hop: proxy.next_hop, next_hop: proxy.next_hop,
sni: proxy.sni, sni: proxy.sni,
skip_tls_verify: proxy['skip-cert-verify'], skip_tls_verify: proxy['skip-cert-verify'],
port_hopping: proxy.ports,
port_hopping_interval: proxy['hop-interval'],
}; };
if (proxy['obfs-password'] && proxy.obfs == 'salamander') { if (proxy['obfs-password'] && proxy.obfs == 'salamander') {
proxy.obfs = 'salamander'; proxy.obfs = 'salamander';
@@ -301,7 +287,6 @@ export default function Egern_Producer() {
[proxy.type]: { [proxy.type]: {
...proxy, ...proxy,
type: undefined, type: undefined,
prev_hop,
}, },
}; };
}); });

View File

@@ -20,37 +20,20 @@ function JSON_Producer() {
} }
export default { export default {
qx: QX_Producer(),
QX: QX_Producer(), QX: QX_Producer(),
QuantumultX: QX_Producer(), QuantumultX: QX_Producer(),
surge: Surge_Producer(),
Surge: Surge_Producer(), Surge: Surge_Producer(),
SurgeMac: SurgeMac_Producer(), SurgeMac: SurgeMac_Producer(),
Loon: Loon_Producer(), Loon: Loon_Producer(),
Clash: Clash_Producer(), Clash: Clash_Producer(),
meta: ClashMeta_Producer(),
clashmeta: ClashMeta_Producer(),
'clash.meta': ClashMeta_Producer(),
'Clash.Meta': ClashMeta_Producer(),
ClashMeta: ClashMeta_Producer(), ClashMeta: ClashMeta_Producer(),
mihomo: ClashMeta_Producer(),
Mihomo: ClashMeta_Producer(),
uri: URI_Producer(),
URI: URI_Producer(), URI: URI_Producer(),
v2: V2Ray_Producer(),
v2ray: V2Ray_Producer(),
V2Ray: V2Ray_Producer(), V2Ray: V2Ray_Producer(),
json: JSON_Producer(),
JSON: JSON_Producer(), JSON: JSON_Producer(),
stash: Stash_Producer(),
Stash: Stash_Producer(), Stash: Stash_Producer(),
shadowrocket: Shadowrocket_Producer(),
Shadowrocket: Shadowrocket_Producer(), Shadowrocket: Shadowrocket_Producer(),
ShadowRocket: Shadowrocket_Producer(), ShadowRocket: Shadowrocket_Producer(),
surfboard: Surfboard_Producer(),
Surfboard: Surfboard_Producer(), Surfboard: Surfboard_Producer(),
singbox: singbox_Producer(),
'sing-box': singbox_Producer(), 'sing-box': singbox_Producer(),
egern: Egern_Producer(),
Egern: Egern_Producer(), Egern: Egern_Producer(),
}; };

View File

@@ -3,21 +3,13 @@ const targetPlatform = 'Loon';
import { isPresent, Result } from './utils'; import { isPresent, Result } from './utils';
import { isIPv4, isIPv6 } from '@/utils'; import { isIPv4, isIPv6 } from '@/utils';
const ipVersions = {
dual: 'dual',
ipv4: 'v4-only',
ipv6: 'v6-only',
'ipv4-prefer': 'prefer-v4',
'ipv6-prefer': 'prefer-v6',
};
export default function Loon_Producer() { export default function Loon_Producer() {
const produce = (proxy, type, opts = {}) => { const produce = (proxy, type, opts = {}) => {
switch (proxy.type) { switch (proxy.type) {
case 'ss': case 'ss':
return shadowsocks(proxy, opts['include-unsupported-proxy']); return shadowsocks(proxy, opts['include-unsupported-proxy']);
case 'ssr': case 'ssr':
return shadowsocksr(proxy, opts['include-unsupported-proxy']); return shadowsocksr(proxy);
case 'trojan': case 'trojan':
return trojan(proxy); return trojan(proxy);
case 'vmess': case 'vmess':
@@ -64,8 +56,9 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
'aes-256-gcm', 'aes-256-gcm',
'chacha20-ietf-poly1305', 'chacha20-ietf-poly1305',
'xchacha20-ietf-poly1305', 'xchacha20-ietf-poly1305',
'2022-blake3-aes-128-gcm', ...(includeUnsupportedProxy
'2022-blake3-aes-256-gcm', ? ['2022-blake3-aes-128-gcm', '2022-blake3-aes-256-gcm']
: []),
].includes(proxy.cipher) ].includes(proxy.cipher)
) { ) {
throw new Error(`cipher ${proxy.cipher} is not supported`); throw new Error(`cipher ${proxy.cipher} is not supported`);
@@ -74,8 +67,6 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
`${proxy.name}=shadowsocks,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"`, `${proxy.name}=shadowsocks,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"`,
); );
let isShadowTLS;
// obfs // obfs
if (isPresent(proxy, 'plugin')) { if (isPresent(proxy, 'plugin')) {
if (proxy.plugin === 'obfs') { if (proxy.plugin === 'obfs') {
@@ -88,52 +79,11 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
`,obfs-uri=${proxy['plugin-opts'].path}`, `,obfs-uri=${proxy['plugin-opts'].path}`,
'plugin-opts.path', 'plugin-opts.path',
); );
} else if (!['shadow-tls'].includes(proxy.plugin)) { } else {
throw new Error(`plugin ${proxy.plugin} is not supported`); throw new Error(`plugin ${proxy.plugin} is not supported`);
} }
} }
// shadow-tls
if (isPresent(proxy, 'shadow-tls-password')) {
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
result.appendIfPresent(
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
'shadow-tls-version',
);
result.appendIfPresent(
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
'shadow-tls-sni',
);
// udp-port
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
isShadowTLS = true;
} else if (['shadow-tls'].includes(proxy.plugin) && proxy['plugin-opts']) {
const password = proxy['plugin-opts'].password;
const host = proxy['plugin-opts'].host;
const version = proxy['plugin-opts'].version;
if (password) {
result.append(`,shadow-tls-password=${password}`);
if (host) {
result.append(`,shadow-tls-sni=${host}`);
}
if (version) {
if (version < 2) {
throw new Error(
`shadow-tls version ${version} is not supported`,
);
}
result.append(`,shadow-tls-version=${version}`);
}
// udp-port
result.appendIfPresent(
`,udp-port=${proxy['udp-port']}`,
'udp-port',
);
isShadowTLS = true;
}
}
// tfo // tfo
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo'); result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
@@ -142,18 +92,10 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
result.append(`,udp=true`); result.append(`,udp=true`);
} }
if (!includeUnsupportedProxy && isShadowTLS) {
throw new Error(
`shadow-tls is not supported(请使用 includeUnsupportedProxy 参数)`,
);
}
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
return result.toString(); return result.toString();
} }
function shadowsocksr(proxy, includeUnsupportedProxy) { function shadowsocksr(proxy) {
const result = new Result(proxy); const result = new Result(proxy);
result.append( result.append(
`${proxy.name}=shadowsocksr,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"`, `${proxy.name}=shadowsocksr,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"`,
@@ -170,49 +112,6 @@ function shadowsocksr(proxy, includeUnsupportedProxy) {
result.appendIfPresent(`,obfs=${proxy.obfs}`, 'obfs'); result.appendIfPresent(`,obfs=${proxy.obfs}`, 'obfs');
result.appendIfPresent(`,obfs-param=${proxy['obfs-param']}`, 'obfs-param'); result.appendIfPresent(`,obfs-param=${proxy['obfs-param']}`, 'obfs-param');
let isShadowTLS;
// shadow-tls
if (isPresent(proxy, 'shadow-tls-password')) {
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
result.appendIfPresent(
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
'shadow-tls-version',
);
result.appendIfPresent(
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
'shadow-tls-sni',
);
// udp-port
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
isShadowTLS = true;
} else if (['shadow-tls'].includes(proxy.plugin) && proxy['plugin-opts']) {
const password = proxy['plugin-opts'].password;
const host = proxy['plugin-opts'].host;
const version = proxy['plugin-opts'].version;
if (password) {
result.append(`,shadow-tls-password=${password}`);
if (host) {
result.append(`,shadow-tls-sni=${host}`);
}
if (version) {
if (version < 2) {
throw new Error(
`shadow-tls version ${version} is not supported`,
);
}
result.append(`,shadow-tls-version=${version}`);
}
// udp-port
result.appendIfPresent(
`,udp-port=${proxy['udp-port']}`,
'udp-port',
);
isShadowTLS = true;
}
}
// tfo // tfo
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo'); result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
@@ -221,14 +120,6 @@ function shadowsocksr(proxy, includeUnsupportedProxy) {
result.append(`,udp=true`); result.append(`,udp=true`);
} }
if (!includeUnsupportedProxy && isShadowTLS) {
throw new Error(
`shadow-tls is not supported(请使用 includeUnsupportedProxy 参数)`,
);
}
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
return result.toString(); return result.toString();
} }
@@ -281,8 +172,6 @@ function trojan(proxy) {
if (proxy.udp) { if (proxy.udp) {
result.append(`,udp=true`); result.append(`,udp=true`);
} }
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
return result.toString(); return result.toString();
} }
@@ -359,16 +248,13 @@ function vmess(proxy) {
// udp // udp
if (proxy.udp) { if (proxy.udp) {
result.append(`,udp=true`); result.append(`,udp=true`);
const ip_version =
ipVersions[proxy['ip-version']] || proxy['ip-version'];
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
} }
return result.toString(); return result.toString();
} }
function vless(proxy) { function vless(proxy) {
if (typeof proxy.flow !== 'undefined' || proxy['reality-opts']) { if (proxy['reality-opts']) {
throw new Error(`VLESS XTLS/REALITY is not supported`); throw new Error(`VLESS REALITY is unsupported`);
} }
const result = new Result(proxy); const result = new Result(proxy);
result.append( result.append(
@@ -434,9 +320,6 @@ function vless(proxy) {
// udp // udp
if (proxy.udp) { if (proxy.udp) {
result.append(`,udp=true`); result.append(`,udp=true`);
const ip_version =
ipVersions[proxy['ip-version']] || proxy['ip-version'];
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
} }
return result.toString(); return result.toString();
} }
@@ -459,8 +342,6 @@ function http(proxy) {
// tfo // tfo
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo'); result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
return result.toString(); return result.toString();
} }
@@ -489,8 +370,6 @@ function socks5(proxy) {
if (proxy.udp) { if (proxy.udp) {
result.append(`,udp=true`); result.append(`,udp=true`);
} }
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
return result.toString(); return result.toString();
} }
@@ -556,8 +435,6 @@ function wireguard(proxy) {
presharedKey ?? '' presharedKey ?? ''
}}]`, }}]`,
); );
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
return result.toString(); return result.toString();
} }
@@ -605,8 +482,6 @@ function hysteria2(proxy) {
); );
result.appendIfPresent(`,ecn=${proxy.ecn}`, 'ecn'); result.appendIfPresent(`,ecn=${proxy.ecn}`, 'ecn');
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
return result.toString(); return result.toString();
} }

View File

@@ -5,10 +5,6 @@ import { isIPv4, isIPv6 } from '@/utils';
const detourParser = (proxy, parsedProxy) => { const detourParser = (proxy, parsedProxy) => {
parsedProxy.detour = proxy['dialer-proxy'] || proxy.detour; parsedProxy.detour = proxy['dialer-proxy'] || proxy.detour;
}; };
const networkParser = (proxy, parsedProxy) => {
if (['tcp', 'udp'].includes(proxy._network))
parsedProxy.network = proxy._network;
};
const tfoParser = (proxy, parsedProxy) => { const tfoParser = (proxy, parsedProxy) => {
parsedProxy.tcp_fast_open = false; parsedProxy.tcp_fast_open = false;
if (proxy.tfo) parsedProxy.tcp_fast_open = true; if (proxy.tfo) parsedProxy.tcp_fast_open = true;
@@ -309,7 +305,6 @@ const socks5Parser = (proxy = {}) => {
if (proxy.uot) parsedProxy.udp_over_tcp = true; if (proxy.uot) parsedProxy.udp_over_tcp = true;
if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true; if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true;
if (proxy['fast-open']) parsedProxy.udp_fragment = true; if (proxy['fast-open']) parsedProxy.udp_fragment = true;
networkParser(proxy, parsedProxy);
tfoParser(proxy, parsedProxy); tfoParser(proxy, parsedProxy);
detourParser(proxy, parsedProxy); detourParser(proxy, parsedProxy);
return parsedProxy; return parsedProxy;
@@ -361,7 +356,6 @@ const ssParser = (proxy = {}) => {
if (proxy.uot) parsedProxy.udp_over_tcp = true; if (proxy.uot) parsedProxy.udp_over_tcp = true;
if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true; if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true;
if (proxy['fast-open']) parsedProxy.udp_fragment = true; if (proxy['fast-open']) parsedProxy.udp_fragment = true;
networkParser(proxy, parsedProxy);
tfoParser(proxy, parsedProxy); tfoParser(proxy, parsedProxy);
detourParser(proxy, parsedProxy); detourParser(proxy, parsedProxy);
smuxParser(proxy.smux, parsedProxy); smuxParser(proxy.smux, parsedProxy);
@@ -476,7 +470,7 @@ const vmessParser = (proxy = {}) => {
if (proxy.network === 'h2') h2Parser(proxy, parsedProxy); if (proxy.network === 'h2') h2Parser(proxy, parsedProxy);
if (proxy.network === 'http') h1Parser(proxy, parsedProxy); if (proxy.network === 'http') h1Parser(proxy, parsedProxy);
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy); if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
networkParser(proxy, parsedProxy);
tfoParser(proxy, parsedProxy); tfoParser(proxy, parsedProxy);
detourParser(proxy, parsedProxy); detourParser(proxy, parsedProxy);
tlsParser(proxy, parsedProxy); tlsParser(proxy, parsedProxy);
@@ -499,7 +493,7 @@ const vlessParser = (proxy = {}) => {
if (proxy.flow === 'xtls-rprx-vision') parsedProxy.flow = proxy.flow; if (proxy.flow === 'xtls-rprx-vision') parsedProxy.flow = proxy.flow;
if (proxy.network === 'ws') wsParser(proxy, parsedProxy); if (proxy.network === 'ws') wsParser(proxy, parsedProxy);
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy); if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
networkParser(proxy, parsedProxy);
tfoParser(proxy, parsedProxy); tfoParser(proxy, parsedProxy);
detourParser(proxy, parsedProxy); detourParser(proxy, parsedProxy);
smuxParser(proxy.smux, parsedProxy); smuxParser(proxy.smux, parsedProxy);
@@ -520,7 +514,7 @@ const trojanParser = (proxy = {}) => {
if (proxy['fast-open']) parsedProxy.udp_fragment = true; if (proxy['fast-open']) parsedProxy.udp_fragment = true;
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy); if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
if (proxy.network === 'ws') wsParser(proxy, parsedProxy); if (proxy.network === 'ws') wsParser(proxy, parsedProxy);
networkParser(proxy, parsedProxy);
tfoParser(proxy, parsedProxy); tfoParser(proxy, parsedProxy);
detourParser(proxy, parsedProxy); detourParser(proxy, parsedProxy);
tlsParser(proxy, parsedProxy); tlsParser(proxy, parsedProxy);
@@ -568,7 +562,6 @@ const hysteriaParser = (proxy = {}) => {
parsedProxy.disable_mtu_discovery = true; parsedProxy.disable_mtu_discovery = true;
} }
} }
networkParser(proxy, parsedProxy);
tlsParser(proxy, parsedProxy); tlsParser(proxy, parsedProxy);
detourParser(proxy, parsedProxy); detourParser(proxy, parsedProxy);
tfoParser(proxy, parsedProxy); tfoParser(proxy, parsedProxy);
@@ -595,7 +588,7 @@ const hysteria2Parser = (proxy = {}, includeUnsupportedProxy) => {
if (proxy['ports']) if (proxy['ports'])
parsedProxy.server_ports = proxy['ports'] parsedProxy.server_ports = proxy['ports']
.split(/\s*,\s*/) .split(/\s*,\s*/)
.map((p) => p.replace(/\s*-\s*/g, ':')); .map((p) => p.replace(/\s*-\s*/, ':'));
} }
if (proxy.up) parsedProxy.up_mbps = parseInt(`${proxy.up}`, 10); if (proxy.up) parsedProxy.up_mbps = parseInt(`${proxy.up}`, 10);
if (proxy.down) parsedProxy.down_mbps = parseInt(`${proxy.down}`, 10); if (proxy.down) parsedProxy.down_mbps = parseInt(`${proxy.down}`, 10);
@@ -603,7 +596,6 @@ const hysteria2Parser = (proxy = {}, includeUnsupportedProxy) => {
if (proxy['obfs-password']) if (proxy['obfs-password'])
parsedProxy.obfs.password = proxy['obfs-password']; parsedProxy.obfs.password = proxy['obfs-password'];
if (!parsedProxy.obfs.type) delete parsedProxy.obfs; if (!parsedProxy.obfs.type) delete parsedProxy.obfs;
networkParser(proxy, parsedProxy);
tlsParser(proxy, parsedProxy); tlsParser(proxy, parsedProxy);
tfoParser(proxy, parsedProxy); tfoParser(proxy, parsedProxy);
detourParser(proxy, parsedProxy); detourParser(proxy, parsedProxy);
@@ -634,7 +626,6 @@ const tuic5Parser = (proxy = {}) => {
if (proxy['udp-over-stream']) parsedProxy.udp_over_stream = true; if (proxy['udp-over-stream']) parsedProxy.udp_over_stream = true;
if (proxy['heartbeat-interval']) if (proxy['heartbeat-interval'])
parsedProxy.heartbeat = `${proxy['heartbeat-interval']}ms`; parsedProxy.heartbeat = `${proxy['heartbeat-interval']}ms`;
networkParser(proxy, parsedProxy);
tfoParser(proxy, parsedProxy); tfoParser(proxy, parsedProxy);
detourParser(proxy, parsedProxy); detourParser(proxy, parsedProxy);
tlsParser(proxy, parsedProxy); tlsParser(proxy, parsedProxy);
@@ -692,7 +683,6 @@ const wireguardParser = (proxy = {}) => {
parsedProxy.peers.push(peer); parsedProxy.peers.push(peer);
} }
} }
networkParser(proxy, parsedProxy);
tfoParser(proxy, parsedProxy); tfoParser(proxy, parsedProxy);
detourParser(proxy, parsedProxy); detourParser(proxy, parsedProxy);
smuxParser(proxy.smux, parsedProxy); smuxParser(proxy.smux, parsedProxy);

View File

@@ -53,7 +53,7 @@ export default function Surge_Producer() {
return { produce }; return { produce };
} }
function shadowsocks(proxy) { function shadowsocks(proxy, includeUnsupportedProxy) {
const result = new Result(proxy); const result = new Result(proxy);
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`); result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
if (!proxy.cipher) { if (!proxy.cipher) {
@@ -87,8 +87,9 @@ function shadowsocks(proxy) {
'chacha20', 'chacha20',
'chacha20-ietf', 'chacha20-ietf',
'none', 'none',
'2022-blake3-aes-128-gcm', ...(includeUnsupportedProxy
'2022-blake3-aes-256-gcm', ? ['2022-blake3-aes-128-gcm', '2022-blake3-aes-256-gcm']
: []),
].includes(proxy.cipher) ].includes(proxy.cipher)
) { ) {
throw new Error(`cipher ${proxy.cipher} is not supported`); throw new Error(`cipher ${proxy.cipher} is not supported`);
@@ -126,6 +127,8 @@ function shadowsocks(proxy) {
// udp // udp
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp'); result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
// udp-port
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
// test-url // test-url
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url'); result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
@@ -157,8 +160,6 @@ function shadowsocks(proxy) {
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`, `,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
'shadow-tls-sni', 'shadow-tls-sni',
); );
// udp-port
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
} else if (['shadow-tls'].includes(proxy.plugin) && proxy['plugin-opts']) { } else if (['shadow-tls'].includes(proxy.plugin) && proxy['plugin-opts']) {
const password = proxy['plugin-opts'].password; const password = proxy['plugin-opts'].password;
const host = proxy['plugin-opts'].host; const host = proxy['plugin-opts'].host;
@@ -176,11 +177,6 @@ function shadowsocks(proxy) {
} }
result.append(`,shadow-tls-version=${version}`); result.append(`,shadow-tls-version=${version}`);
} }
// udp-port
result.appendIfPresent(
`,udp-port=${proxy['udp-port']}`,
'udp-port',
);
} }
} }

View File

@@ -174,14 +174,6 @@ async function doSync() {
const resp = await syncToGist(files); const resp = await syncToGist(files);
const body = JSON.parse(resp.body); const body = JSON.parse(resp.body);
delete body.history;
delete body.forks;
delete body.owner;
Object.values(body.files).forEach((file) => {
delete file.content;
});
$.info('上传配置响应:');
$.info(JSON.stringify(body, null, 2));
for (const artifact of allArtifacts) { for (const artifact of allArtifacts) {
if (artifact.sync) { if (artifact.sync) {

View File

@@ -50,21 +50,11 @@ function createCollection(req, res) {
function getCollection(req, res) { function getCollection(req, res) {
let { name } = req.params; let { name } = req.params;
let { raw } = req.query;
name = decodeURIComponent(name); name = decodeURIComponent(name);
const allCols = $.read(COLLECTIONS_KEY); const allCols = $.read(COLLECTIONS_KEY);
const collection = findByName(allCols, name); const collection = findByName(allCols, name);
if (collection) { if (collection) {
if (raw) { success(res, collection);
res.set('content-type', 'application/json')
.set(
'content-disposition',
`attachment; filename="${encodeURIComponent(name)}.json"`,
)
.send(JSON.stringify(collection));
} else {
success(res, collection);
}
} else { } else {
failed( failed(
res, res,

View File

@@ -1,7 +1,4 @@
import { import { getPlatformFromHeaders } from '@/utils/user-agent';
getPlatformFromHeaders,
shouldIncludeUnsupportedProxy,
} from '@/utils/user-agent';
import { ProxyUtils } from '@/core/proxy-utils'; import { ProxyUtils } from '@/core/proxy-utils';
import { COLLECTIONS_KEY, SUBS_KEY } from '@/constants'; import { COLLECTIONS_KEY, SUBS_KEY } from '@/constants';
import { findByName } from '@/utils/database'; import { findByName } from '@/utils/database';
@@ -16,44 +13,11 @@ import { getISO } from '@/utils/geo';
import env from '@/utils/env'; import env from '@/utils/env';
export default function register($app) { export default function register($app) {
$app.get('/share/col/:name/:target', async (req, res) => {
const { target } = req.params;
if (target) {
req.query.target = target;
$.info(`使用路由指定目标: ${target}`);
}
await downloadCollection(req, res);
});
$app.get('/share/col/:name', downloadCollection); $app.get('/share/col/:name', downloadCollection);
$app.get('/share/sub/:name/:target', async (req, res) => {
const { target } = req.params;
if (target) {
req.query.target = target;
$.info(`使用路由指定目标: ${target}`);
}
await downloadSubscription(req, res);
});
$app.get('/share/sub/:name', downloadSubscription); $app.get('/share/sub/:name', downloadSubscription);
$app.get('/download/collection/:name/:target', async (req, res) => {
const { target } = req.params;
if (target) {
req.query.target = target;
$.info(`使用路由指定目标: ${target}`);
}
await downloadCollection(req, res);
});
$app.get('/download/collection/:name', downloadCollection); $app.get('/download/collection/:name', downloadCollection);
$app.get('/download/:name/:target', async (req, res) => {
const { target } = req.params;
if (target) {
req.query.target = target;
$.info(`使用路由指定目标: ${target}`);
}
await downloadSubscription(req, res);
});
$app.get('/download/:name', downloadSubscription); $app.get('/download/:name', downloadSubscription);
$app.get( $app.get(
'/download/collection/:name/api/v1/server/details', '/download/collection/:name/api/v1/server/details',
async (req, res) => { async (req, res) => {
@@ -95,9 +59,11 @@ async function downloadSubscription(req, res) {
const platform = const platform =
req.query.target || getPlatformFromHeaders(req.headers) || 'JSON'; req.query.target || getPlatformFromHeaders(req.headers) || 'JSON';
const reqUA = req.headers['user-agent'] || req.headers['User-Agent'];
$.info( $.info(
`正在下载订阅:${name}\n请求 User-Agent: ${reqUA}\n请求 target: ${req.query.target}\n实际输出: ${platform}`, `正在下载订阅:${name}\n请求 User-Agent: ${
req.headers['user-agent'] || req.headers['User-Agent']
}\n请求 target: ${req.query.target}\n实际输出: ${platform}`,
); );
let { let {
url, url,
@@ -132,14 +98,6 @@ async function downloadSubscription(req, res) {
if (url) { if (url) {
url = decodeURIComponent(url); url = decodeURIComponent(url);
$.info(`指定远程订阅 URL: ${url}`); $.info(`指定远程订阅 URL: ${url}`);
if (!/^https?:\/\//.test(url)) {
content = url;
$.info(`URL 不是链接,视为本地订阅`);
}
}
if (content) {
content = decodeURIComponent(content);
$.info(`指定本地订阅: ${content}`);
} }
if (proxy) { if (proxy) {
proxy = decodeURIComponent(proxy); proxy = decodeURIComponent(proxy);
@@ -149,7 +107,10 @@ async function downloadSubscription(req, res) {
ua = decodeURIComponent(ua); ua = decodeURIComponent(ua);
$.info(`指定远程订阅 User-Agent: ${ua}`); $.info(`指定远程订阅 User-Agent: ${ua}`);
} }
if (content) {
content = decodeURIComponent(content);
$.info(`指定本地订阅: ${content}`);
}
if (mergeSources) { if (mergeSources) {
mergeSources = decodeURIComponent(mergeSources); mergeSources = decodeURIComponent(mergeSources);
$.info(`指定合并来源: ${mergeSources}`); $.info(`指定合并来源: ${mergeSources}`);
@@ -164,19 +125,7 @@ async function downloadSubscription(req, res) {
} }
if (includeUnsupportedProxy) { if (includeUnsupportedProxy) {
includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy); includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy);
$.info( $.info(`包含不支持的节点: ${includeUnsupportedProxy}`);
`包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`,
);
}
if (
!includeUnsupportedProxy &&
shouldIncludeUnsupportedProxy(platform, reqUA)
) {
includeUnsupportedProxy = true;
$.info(
`当前客户端可包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`,
);
} }
if (useMihomoExternal) { if (useMihomoExternal) {
@@ -191,13 +140,6 @@ async function downloadSubscription(req, res) {
const sub = findByName(allSubs, name); const sub = findByName(allSubs, name);
if (sub) { if (sub) {
try { try {
const passThroughUA = sub.passThroughUA;
if (passThroughUA) {
$.info(
`订阅开启了透传 User-Agent, 使用请求的 User-Agent: ${reqUA}`,
);
ua = reqUA;
}
let output = await produceArtifact({ let output = await produceArtifact({
type: 'subscription', type: 'subscription',
name, name,
@@ -357,9 +299,11 @@ async function downloadCollection(req, res) {
const allCols = $.read(COLLECTIONS_KEY); const allCols = $.read(COLLECTIONS_KEY);
const collection = findByName(allCols, name); const collection = findByName(allCols, name);
const reqUA = req.headers['user-agent'] || req.headers['User-Agent'];
$.info( $.info(
`正在下载组合订阅:${name}\n请求 User-Agent: ${reqUA}\n请求 target: ${req.query.target}\n实际输出: ${platform}`, `正在下载组合订阅:${name}\n请求 User-Agent: ${
req.headers['user-agent'] || req.headers['User-Agent']
}\n请求 target: ${req.query.target}\n实际输出: ${platform}`,
); );
let { let {
@@ -406,18 +350,7 @@ async function downloadCollection(req, res) {
if (includeUnsupportedProxy) { if (includeUnsupportedProxy) {
includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy); includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy);
$.info( $.info(`包含不支持的节点: ${includeUnsupportedProxy}`);
`包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`,
);
}
if (
!includeUnsupportedProxy &&
shouldIncludeUnsupportedProxy(platform, reqUA)
) {
includeUnsupportedProxy = true;
$.info(
`当前客户端可包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`,
);
} }
if (useMihomoExternal) { if (useMihomoExternal) {
$.info(`手动指定了 target 为 SurgeMac, 将使用 Mihomo External`); $.info(`手动指定了 target 为 SurgeMac, 将使用 Mihomo External`);
@@ -441,7 +374,6 @@ async function downloadCollection(req, res) {
$options, $options,
proxy, proxy,
noCache, noCache,
ua: reqUA,
}); });
let subUserInfoOfSub; let subUserInfoOfSub;
// forward flow header from the first subscription in this collection // forward flow header from the first subscription in this collection
@@ -547,12 +479,13 @@ async function downloadCollection(req, res) {
} else { } else {
subUserInfoOfCol = collection.subUserinfo; subUserInfoOfCol = collection.subUserinfo;
} }
const subUserInfo = [subUserInfoOfCol, subUserInfoOfSub] res.set(
.filter((i) => i) 'subscription-userinfo',
.join('; '); [subUserInfoOfCol, subUserInfoOfSub]
if (subUserInfo) { .filter((i) => i)
res.set('subscription-userinfo', subUserInfo); .join('; '),
} );
if (platform === 'JSON') { if (platform === 'JSON') {
if (resultFormat === 'nezha') { if (resultFormat === 'nezha') {
output = nezhaTransform(output); output = nezhaTransform(output);

View File

@@ -197,21 +197,11 @@ async function getFile(req, res) {
} }
function getWholeFile(req, res) { function getWholeFile(req, res) {
let { name } = req.params; let { name } = req.params;
let { raw } = req.query;
name = decodeURIComponent(name); name = decodeURIComponent(name);
const allFiles = $.read(FILES_KEY); const allFiles = $.read(FILES_KEY);
const file = findByName(allFiles, name); const file = findByName(allFiles, name);
if (file) { if (file) {
if (raw) { success(res, file);
res.set('content-type', 'application/json')
.set(
'content-disposition',
`attachment; filename="${encodeURIComponent(name)}.json"`,
)
.send(JSON.stringify(file));
} else {
success(res, file);
}
} else { } else {
failed( failed(
res, res,

View File

@@ -67,7 +67,7 @@ async function previewFile(req, res) {
const processed = const processed =
Array.isArray(file.process) && file.process.length > 0 Array.isArray(file.process) && file.process.length > 0
? await ProxyUtils.process( ? await ProxyUtils.process(
{ $files: files, $content: filesContent, $file: file }, { $files: files, $content: filesContent },
file.process, file.process,
) )
: { $content: filesContent, $files: files }; : { $content: filesContent, $files: files };

View File

@@ -73,7 +73,7 @@ async function produceArtifact({
proxy || sub.proxy, proxy || sub.proxy,
undefined, undefined,
awaitCustomCache, awaitCustomCache,
noCache || sub.noCache, noCache,
true, true,
); );
} catch (err) { } catch (err) {
@@ -122,7 +122,7 @@ async function produceArtifact({
proxy || sub.proxy, proxy || sub.proxy,
undefined, undefined,
awaitCustomCache, awaitCustomCache,
noCache || sub.noCache, noCache,
true, true,
); );
} catch (err) { } catch (err) {
@@ -216,14 +216,6 @@ async function produceArtifact({
await Promise.all( await Promise.all(
subnames.map(async (name) => { subnames.map(async (name) => {
const sub = findByName(allSubs, name); const sub = findByName(allSubs, name);
const passThroughUA = sub.passThroughUA;
let reqUA = sub.ua;
if (passThroughUA) {
$.info(
`订阅开启了透传 User-Agent, 使用请求的 User-Agent: ${ua}`,
);
reqUA = ua;
}
try { try {
$.info(`正在处理子订阅:${sub.name}...`); $.info(`正在处理子订阅:${sub.name}...`);
let raw; let raw;
@@ -245,14 +237,14 @@ async function produceArtifact({
try { try {
return await download( return await download(
url, url,
reqUA, sub.ua,
undefined, undefined,
proxy || proxy ||
sub.proxy || sub.proxy ||
collection.proxy, collection.proxy,
undefined, undefined,
undefined, undefined,
noCache || sub.noCache, noCache,
true, true,
); );
} catch (err) { } catch (err) {
@@ -520,12 +512,7 @@ async function produceArtifact({
const processed = const processed =
Array.isArray(file.process) && file.process.length > 0 Array.isArray(file.process) && file.process.length > 0
? await ProxyUtils.process( ? await ProxyUtils.process(
{ { $files: files, $content: filesContent, $options },
$files: files,
$content: filesContent,
$options,
$file: file,
},
file.process, file.process,
) )
: { $content: filesContent, $files: files, $options }; : { $content: filesContent, $files: files, $options };
@@ -633,15 +620,6 @@ async function syncArtifacts() {
const resp = await syncToGist(files); const resp = await syncToGist(files);
const body = JSON.parse(resp.body); const body = JSON.parse(resp.body);
delete body.history;
delete body.forks;
delete body.owner;
Object.values(body.files).forEach((file) => {
delete file.content;
});
$.info('上传配置响应:');
$.info(JSON.stringify(body, null, 2));
for (const artifact of allArtifacts) { for (const artifact of allArtifacts) {
if (artifact.sync) { if (artifact.sync) {
artifact.updated = new Date().getTime(); artifact.updated = new Date().getTime();
@@ -760,16 +738,6 @@ async function syncArtifact(req, res) {
}); });
artifact.updated = new Date().getTime(); artifact.updated = new Date().getTime();
const body = JSON.parse(resp.body); const body = JSON.parse(resp.body);
delete body.history;
delete body.forks;
delete body.owner;
Object.values(body.files).forEach((file) => {
delete file.content;
});
$.info('上传配置响应:');
$.info(JSON.stringify(body, null, 2));
let files = body.files; let files = body.files;
let isGitLab; let isGitLab;
if (Array.isArray(files)) { if (Array.isArray(files)) {

View File

@@ -207,7 +207,7 @@ export default async function download(
if (preprocess) { if (preprocess) {
try { try {
if (clashPreprocessor.test(body)) { if (clashPreprocessor.test(body)) {
body = clashPreprocessor.parse(body, true); body = clashPreprocessor.parse(body);
} }
} catch (e) { } catch (e) {
$.error(`Clash Pre-processor error: ${e}`); $.error(`Clash Pre-processor error: ${e}`);

View File

@@ -114,17 +114,15 @@ export default class Gist {
return; return;
}); });
} else { } else {
return this.http return this.http.get('/gists').then((response) => {
.get('/gists?per_page=100&page=1') const gists = JSON.parse(response.body);
.then((response) => { for (let g of gists) {
const gists = JSON.parse(response.body); if (g.description === this.key) {
for (let g of gists) { return g;
if (g.description === this.key) {
return g;
}
} }
return; }
}); return;
});
} }
} }

View File

@@ -1,7 +1,3 @@
import gte from 'semver/functions/gte';
import coerce from 'semver/functions/coerce';
import $ from '@/core/app';
export function getUserAgentFromHeaders(headers) { export function getUserAgentFromHeaders(headers) {
const keys = Object.keys(headers); const keys = Object.keys(headers);
let UA = ''; let UA = '';
@@ -60,17 +56,3 @@ export function getPlatformFromHeaders(headers) {
const { UA, ua, accept } = getUserAgentFromHeaders(headers); const { UA, ua, accept } = getUserAgentFromHeaders(headers);
return getPlatformFromUserAgent({ ua, UA, accept }); return getPlatformFromUserAgent({ ua, UA, accept });
} }
export function shouldIncludeUnsupportedProxy(platform, ua) {
try {
const version = coerce(ua).version;
if (platform === 'Stash' && gte(version, '2.8.0')) {
return true;
}
if (platform === 'Egern' && gte(version, '1.29.0')) {
return true;
}
} catch (e) {
$.error(`获取版本号失败: ${e}`);
}
return false;
}

View File

@@ -21,7 +21,6 @@ function operator(proxies = [], targetPlatform, context) {
// 13. `test-url` 为测延迟链接, `test-timeout` 为测延迟超时 // 13. `test-url` 为测延迟链接, `test-timeout` 为测延迟超时
// 14. `ports` 为端口跳跃, `hop-interval` 变换端口号的时间间隔 // 14. `ports` 为端口跳跃, `hop-interval` 变换端口号的时间间隔
// 15. `ip-version` 设置节点使用 IP 版本可选dualipv4ipv6ipv4-preferipv6-prefer. 会进行内部转换, 若无法匹配则使用原始值 // 15. `ip-version` 设置节点使用 IP 版本可选dualipv4ipv6ipv4-preferipv6-prefer. 会进行内部转换, 若无法匹配则使用原始值
// 16. `sing-box` 支持使用 `_network` 来设置 `network`, 例如 `tcp`, `udp`
// require 为 Node.js 的 require, 在 Node.js 运行环境下 可以用来引入模块 // require 为 Node.js 的 require, 在 Node.js 运行环境下 可以用来引入模块