From 28aff1399711511bb2b7971ac8920c68866397a1 Mon Sep 17 00:00:00 2001 From: xream Date: Wed, 20 Nov 2024 14:28:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=20Egern=20=E8=BE=93?= =?UTF-8?q?=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +- backend/package.json | 2 +- backend/src/core/proxy-utils/parsers/index.js | 4 + .../src/core/proxy-utils/producers/egern.js | 296 ++++++++++++++++++ .../src/core/proxy-utils/producers/index.js | 2 + backend/src/utils/user-agent.js | 4 +- 6 files changed, 308 insertions(+), 3 deletions(-) create mode 100644 backend/src/core/proxy-utils/producers/egern.js diff --git a/README.md b/README.md index 9a1e0cba5..247075dce 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@

-Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket. +Advanced Subscription Manager for QX, Loon, Surge, Stash, Egern and Shadowrocket.

[![Build](https://github.com/sub-store-org/Sub-Store/actions/workflows/main.yml/badge.svg)](https://github.com/sub-store-org/Sub-Store/actions/workflows/main.yml) ![GitHub](https://img.shields.io/github/license/sub-store-org/Sub-Store) ![GitHub issues](https://img.shields.io/github/issues/sub-store-org/Sub-Store) ![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed-raw/Peng-Ym/Sub-Store) ![Lines of code](https://img.shields.io/tokei/lines/github/sub-store-org/Sub-Store) ![Size](https://img.shields.io/github/languages/code-size/sub-store-org/Sub-Store) @@ -49,6 +49,7 @@ Core functionalities: - [x] Surge - [x] SurgeMac(Use mihomo to support protocols that are not supported by Surge itself) - [x] Loon +- [x] Egern - [x] Shadowrocket - [x] QX - [x] sing-box diff --git a/backend/package.json b/backend/package.json index 9b43438e0..33384f622 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "sub-store", - "version": "2.14.423", + "version": "2.14.424", "description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.", "main": "src/main.js", "scripts": { diff --git a/backend/src/core/proxy-utils/parsers/index.js b/backend/src/core/proxy-utils/parsers/index.js index b4c8cc5dc..070089836 100644 --- a/backend/src/core/proxy-utils/parsers/index.js +++ b/backend/src/core/proxy-utils/parsers/index.js @@ -340,6 +340,10 @@ function URI_VMess() { } else if (params.net === 'h2' || proxy.network === 'h2') { proxy.network = 'h2'; } + // ζš‚δΈζ”―ζŒ tcp + host + path + // else if (params.net === 'tcp' || proxy.network === 'tcp') { + // proxy.network = 'tcp'; + // } if (proxy.network) { let transportHost = params.host ?? params.obfsParam; try { diff --git a/backend/src/core/proxy-utils/producers/egern.js b/backend/src/core/proxy-utils/producers/egern.js new file mode 100644 index 000000000..0671310bb --- /dev/null +++ b/backend/src/core/proxy-utils/producers/egern.js @@ -0,0 +1,296 @@ +export default function Egern_Producer() { + const type = 'ALL'; + const produce = (proxies, type, opts = {}) => { + // https://egernapp.com/zh-CN/docs/configuration/proxies + const list = proxies + .filter((proxy) => { + if (opts['include-unsupported-proxy']) return true; + if ( + ![ + 'http', + 'socks5', + 'ss', + 'trojan', + 'hysteria2', + 'vless', + 'vmess', + ].includes(proxy.type) || + (proxy.type === 'ss' && + ((proxy.plugin === 'obfs' && + !['http', 'tls'].includes( + proxy['plugin-opts']?.mode, + )) || + ![ + 'chacha20-poly1305', + 'aes-256-gcm', + 'aes-128-gcm', + 'none', + 'tbale', + 'rc4', + 'rc4-md5', + 'aes-128-cfb', + 'aes-192-cfb', + 'aes-256-cfb', + 'aes-128-ctr', + 'aes-192-ctr', + 'aes-256-ctr', + 'bf-cfb', + 'camellia-128-cfb', + 'camellia-192-cfb', + 'camellia-256-cfb', + 'cast5-cfb', + 'des-cfb', + 'idea-cfb', + 'rc2-cfb', + 'seed-cfb', + 'salsa20', + 'chacha20', + 'chacha20-ietf', + ].includes(proxy.cipher))) || + (proxy.type === 'vmess' && + (![ + '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) || + (proxy.type === 'vless' && + (typeof proxy.flow !== 'undefined' || + proxy['reality-opts'] || + (!['http', 'ws', 'tcp'].includes(proxy.network) && + proxy.network))) + ) { + return false; + } + return true; + }) + .map((proxy) => { + if (proxy.type === 'http') { + proxy = { + type: 'http', + name: proxy.name, + server: proxy.server, + port: proxy.port, + username: proxy.username, + password: proxy.password, + tfo: proxy.tfo || proxy['fast-open'], + next_hop: proxy.next_hop, + }; + } else if (proxy.type === 'socks5') { + proxy = { + type: 'socks5', + name: proxy.name, + server: proxy.server, + port: proxy.port, + username: proxy.username, + password: proxy.password, + tfo: proxy.tfo || proxy['fast-open'], + udp_relay: + proxy.udp || proxy.udp_relay || proxy.udp_relay, + next_hop: proxy.next_hop, + }; + } else if (proxy.type === 'shadowsocks') { + proxy = { + type: 'shadowsocks', + name: proxy.name, + method: proxy.cipher, + server: proxy.server, + port: proxy.port, + password: proxy.password, + tfo: proxy.tfo || proxy['fast-open'], + udp_relay: + proxy.udp || proxy.udp_relay || proxy.udp_relay, + next_hop: proxy.next_hop, + }; + 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 = { + type: 'hysteria2', + name: proxy.name, + server: proxy.server, + port: proxy.port, + auth: proxy.password, + tfo: proxy.tfo || proxy['fast-open'], + udp_relay: + proxy.udp || proxy.udp_relay || proxy.udp_relay, + next_hop: proxy.next_hop, + sni: proxy.sni, + skip_tls_verify: proxy['skip-cert-verify'], + }; + if (proxy['obfs-password'] && proxy.obfs == 'salamander') { + proxy.obfs = 'salamander'; + proxy.obfs_password = proxy['obfs-password']; + } + } else if (proxy.type === 'trojan') { + if (proxy.network === 'ws') { + proxy.websocket = { + path: proxy['ws-opts']?.path, + host: proxy['ws-opts']?.headers?.Host, + }; + } + proxy = { + type: 'trojan', + name: proxy.name, + server: proxy.server, + port: proxy.port, + user_id: proxy.uuid, + security: proxy.cipher, + tfo: proxy.tfo || proxy['fast-open'], + legacy: proxy.legacy, + udp_relay: + proxy.udp || proxy.udp_relay || proxy.udp_relay, + next_hop: proxy.next_hop, + sni: proxy.sni, + skip_tls_verify: proxy['skip-cert-verify'], + websocket: proxy.websocket, + }; + } else if (proxy.type === 'vmess') { + if (proxy.network === 'ws') { + proxy.transport = { + [proxy.tls ? 'wss' : 'ws']: { + path: proxy['ws-opts']?.path, + headers: { + Host: proxy['ws-opts']?.headers?.Host, + }, + sni: proxy.tls ? proxy.sni : undefined, + skip_tls_verify: proxy.tls + ? proxy['skip-cert-verify'] + : undefined, + }, + }; + } else if (proxy.network === 'http') { + proxy.transport = { + http: { + method: proxy['http-opts']?.method, + path: proxy['http-opts']?.path, + headers: { + Host: Array.isArray( + proxy['http-opts']?.headers?.Host, + ) + ? proxy['http-opts']?.headers?.Host[0] + : proxy['http-opts']?.headers?.Host, + }, + skip_tls_verify: proxy['skip-cert-verify'], + }, + }; + } else if (proxy.network === 'tcp' || !proxy.network) { + proxy.transport = { + [proxy.tls ? 'tls' : 'tcp']: { + sni: proxy.tls ? proxy.sni : undefined, + skip_tls_verify: proxy.tls + ? proxy['skip-cert-verify'] + : undefined, + }, + }; + } + proxy = { + type: 'vmess', + name: proxy.name, + server: proxy.server, + port: proxy.port, + user_id: proxy.uuid, + security: proxy.cipher, + tfo: proxy.tfo || proxy['fast-open'], + legacy: proxy.legacy, + udp_relay: + proxy.udp || proxy.udp_relay || proxy.udp_relay, + next_hop: proxy.next_hop, + transport: proxy.transport, + // sni: proxy.sni, + // skip_tls_verify: proxy['skip-cert-verify'], + }; + } else if (proxy.type === 'vless') { + if (proxy.network === 'ws') { + proxy.transport = { + [proxy.tls ? 'wss' : 'ws']: { + path: proxy['ws-opts']?.path, + headers: { + Host: proxy['ws-opts']?.headers?.Host, + }, + sni: proxy.tls ? proxy.sni : undefined, + skip_tls_verify: proxy.tls + ? proxy['skip-cert-verify'] + : undefined, + }, + }; + } else if (proxy.network === 'http') { + proxy.transport = { + http: { + method: proxy['http-opts']?.method, + path: proxy['http-opts']?.path, + headers: { + Host: Array.isArray( + proxy['http-opts']?.headers?.Host, + ) + ? proxy['http-opts']?.headers?.Host[0] + : proxy['http-opts']?.headers?.Host, + }, + skip_tls_verify: proxy['skip-cert-verify'], + }, + }; + } else if (proxy.network === 'tcp' || !proxy.network) { + proxy.transport = { + [proxy.tls ? 'tls' : 'tcp']: { + sni: proxy.tls ? proxy.sni : undefined, + skip_tls_verify: proxy.tls + ? proxy['skip-cert-verify'] + : undefined, + }, + }; + } + proxy = { + type: 'vless', + name: proxy.name, + server: proxy.server, + port: proxy.port, + user_id: proxy.uuid, + security: proxy.cipher, + tfo: proxy.tfo || proxy['fast-open'], + legacy: proxy.legacy, + udp_relay: + proxy.udp || proxy.udp_relay || proxy.udp_relay, + next_hop: proxy.next_hop, + transport: proxy.transport, + // sni: proxy.sni, + // skip_tls_verify: proxy['skip-cert-verify'], + }; + } + + delete proxy.subName; + delete proxy.collectionName; + delete proxy.id; + delete proxy.resolved; + delete proxy['no-resolve']; + if (type !== 'internal') { + for (const key in proxy) { + if (proxy[key] == null || /^_/i.test(key)) { + delete proxy[key]; + } + } + } + return { + [proxy.type]: { + ...proxy, + type: undefined, + }, + }; + }); + return type === 'internal' + ? list + : 'proxies:\n' + + list + .map((proxy) => ' - ' + JSON.stringify(proxy) + '\n') + .join(''); + }; + return { type, produce }; +} diff --git a/backend/src/core/proxy-utils/producers/index.js b/backend/src/core/proxy-utils/producers/index.js index d72c0626e..add3b2e62 100644 --- a/backend/src/core/proxy-utils/producers/index.js +++ b/backend/src/core/proxy-utils/producers/index.js @@ -10,6 +10,7 @@ import QX_Producer from './qx'; import Shadowrocket_Producer from './shadowrocket'; import Surfboard_Producer from './surfboard'; import singbox_Producer from './sing-box'; +import Egern_Producer from './egern'; function JSON_Producer() { const type = 'ALL'; @@ -34,4 +35,5 @@ export default { ShadowRocket: Shadowrocket_Producer(), Surfboard: Surfboard_Producer(), 'sing-box': singbox_Producer(), + Egern: Egern_Producer(), }; diff --git a/backend/src/utils/user-agent.js b/backend/src/utils/user-agent.js index aabb33de0..bb3dffeb5 100644 --- a/backend/src/utils/user-agent.js +++ b/backend/src/utils/user-agent.js @@ -18,6 +18,8 @@ export function getUserAgentFromHeaders(headers) { export function getPlatformFromUserAgent({ ua, UA, accept }) { if (UA.indexOf('Quantumult%20X') !== -1) { return 'QX'; + } else if (ua.indexOf('egern') !== -1) { + return 'Egern'; } else if (UA.indexOf('Surfboard') !== -1) { return 'Surfboard'; } else if (UA.indexOf('Surge Mac') !== -1) { @@ -39,7 +41,7 @@ export function getPlatformFromUserAgent({ ua, UA, accept }) { return 'ClashMeta'; } else if (ua.indexOf('clash') !== -1) { return 'Clash'; - } else if (ua.indexOf('v2ray') !== -1 || ua.indexOf('egern') !== -1) { + } else if (ua.indexOf('v2ray') !== -1) { return 'V2Ray'; } else if (ua.indexOf('sing-box') !== -1) { return 'sing-box';