diff --git a/README.md b/README.md index 41ff7533a..6286f747e 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.

[![Build](https://github.com/Peng-YM/Sub-Store/actions/workflows/main.yml/badge.svg)](https://github.com/Peng-YM/Sub-Store/actions/workflows/main.yml) ![GitHub](https://img.shields.io/github/license/Peng-YM/Sub-Store) ![GitHub issues](https://img.shields.io/github/issues/Peng-YM/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/Peng-YM/Sub-Store) ![Size](https://img.shields.io/github/languages/code-size/Peng-YM/Sub-Store) - + [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/PengYM) Core functionalities: @@ -19,6 +19,8 @@ Core functionalities: 1. Conversion among various formats. 2. Subscription formatting. 3. Collect multiple subscriptions in one URL. + +> The following descriptions of features may not be updated in real-time. Please refer to the actual available features for accurate information. ## 1. Subscription Conversion @@ -29,17 +31,25 @@ Core functionalities: - [x] SSD URI - [x] V2RayN URI - [x] QX (SS, SSR, VMess, Trojan, HTTP) -- [x] Loon (SS, SSR, VMess, Trojan, HTTP) -- [x] Surge (SS, VMess, Trojan, HTTP) -- [x] Stash & Clash (SS, SSR, VMess, Trojan, HTTP) +- [x] Loon (SS, SSR, VMess, Trojan, HTTP, WireGuard, VLESS) +- [x] Surge (SS, VMess, Trojan, HTTP, TUIC, Snell) +- [x] ShadowRocket (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, WireGuard, Hysteria) +- [x] Clash.Meta (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, WireGuard, Hysteria) +- [x] Stash (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, WireGuard, Hysteria) +- [x] Clash (SS, SSR, VMess, Trojan, HTTP, Snell) ### Supported Target Platforms - [x] QX - [x] Loon - [x] Surge -- [x] Stash & Clash +- [x] Stash +- [x] Clash.Meta +- [x] Clash - [x] ShadowRocket +- [x] V2Ray +- [x] V2Ray URI +- [x] Plain JSON ## 2. Subscription Formatting @@ -61,6 +71,7 @@ Core functionalities: - [x] **Regex rename operator**: replace by regex in proxy names. - [x] **Regex delete operator**: delete by regex in proxy names. - [x] **Script operator**: modify proxy by script. +- [x] **Resolve Domain Operator**: resolve the domain of nodes to an IP address. ### Development diff --git a/backend/package.json b/backend/package.json index 728bdf0ec..4abf0b643 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "sub-store", - "version": "2.14.34", + "version": "2.14.35", "description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.", "main": "src/main.js", "scripts": { diff --git a/backend/src/core/proxy-utils/producers/clashmeta.js b/backend/src/core/proxy-utils/producers/clashmeta.js new file mode 100644 index 000000000..c6283124b --- /dev/null +++ b/backend/src/core/proxy-utils/producers/clashmeta.js @@ -0,0 +1,117 @@ +import { isPresent } from '@/core/proxy-utils/producers/utils'; + +export default function ClashMeta_Producer() { + const type = 'ALL'; + const produce = (proxies) => { + return ( + 'proxies:\n' + + proxies + .filter((proxy) => { + if ( + proxy.type === 'snell' && + String(proxy.version) === '4' + ) { + return false; + } + return true; + }) + .map((proxy) => { + if (proxy.type === 'vmess') { + // handle vmess aead + if (isPresent(proxy, 'aead')) { + if (proxy.aead) { + proxy.alterId = 0; + } + delete proxy.aead; + } + if (isPresent(proxy, 'sni')) { + proxy.servername = proxy.sni; + delete proxy.sni; + } + // https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml#L400 + // https://stash.wiki/proxy-protocols/proxy-types#vmess + if ( + isPresent(proxy, 'cipher') && + ![ + 'auto', + 'aes-128-gcm', + 'chacha20-poly1305', + 'none', + ].includes(proxy.cipher) + ) { + proxy.cipher = 'auto'; + } + } else if (proxy.type === 'tuic') { + if (isPresent(proxy, 'alpn')) { + proxy.alpn = Array.isArray(proxy.alpn) + ? proxy.alpn + : [proxy.alpn]; + } else { + proxy.alpn = ['h3']; + } + if ( + isPresent(proxy, 'tfo') && + !isPresent(proxy, 'fast-open') + ) { + proxy['fast-open'] = proxy.tfo; + } + // https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/adapter/outbound/tuic.go#L197 + if ( + (!proxy.token || proxy.token.length === 0) && + !isPresent(proxy, 'version') + ) { + proxy.version = 5; + } + } else if (proxy.type === 'hysteria') { + if (isPresent(proxy, 'alpn')) { + proxy.alpn = Array.isArray(proxy.alpn) + ? proxy.alpn + : [proxy.alpn]; + } + if ( + isPresent(proxy, 'tfo') && + !isPresent(proxy, 'fast-open') + ) { + proxy['fast-open'] = proxy.tfo; + } + } else if (proxy.type === 'wireguard') { + proxy.keepalive = + proxy.keepalive ?? proxy['persistent-keepalive']; + proxy['persistent-keepalive'] = proxy.keepalive; + proxy['preshared-key'] = + proxy['preshared-key'] ?? proxy['pre-shared-key']; + proxy['pre-shared-key'] = proxy['preshared-key']; + } + + if ( + ['vmess', 'vless'].includes(proxy.type) && + proxy.network === 'http' + ) { + let httpPath = proxy['http-opts']?.path; + if ( + isPresent(proxy, 'http-opts.path') && + !Array.isArray(httpPath) + ) { + proxy['http-opts'].path = [httpPath]; + } + let httpHost = proxy['http-opts']?.headers?.Host; + if ( + isPresent(proxy, 'http-opts.headers.Host') && + !Array.isArray(httpHost) + ) { + proxy['http-opts'].headers.Host = [httpHost]; + } + } + + if (['trojan', 'tuic', 'hysteria'].includes(proxy.type)) { + delete proxy.tls; + } + + delete proxy['tls-fingerprint']; + return ' - ' + 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 0d5dd07c0..ba703abd3 100644 --- a/backend/src/core/proxy-utils/producers/index.js +++ b/backend/src/core/proxy-utils/producers/index.js @@ -1,5 +1,6 @@ import Surge_Producer from './surge'; import Clash_Producer from './clash'; +import ClashMeta_Producer from './clashmeta'; import Stash_Producer from './stash'; import Loon_Producer from './loon'; import URI_Producer from './uri'; @@ -18,6 +19,7 @@ export default { Surge: Surge_Producer(), Loon: Loon_Producer(), Clash: Clash_Producer(), + ClashMeta: ClashMeta_Producer(), URI: URI_Producer(), V2Ray: V2Ray_Producer(), JSON: JSON_Producer(), diff --git a/backend/src/core/proxy-utils/producers/shadowrocket.js b/backend/src/core/proxy-utils/producers/shadowrocket.js index d6cbc2deb..fccdedb5f 100644 --- a/backend/src/core/proxy-utils/producers/shadowrocket.js +++ b/backend/src/core/proxy-utils/producers/shadowrocket.js @@ -1,6 +1,6 @@ import { isPresent } from '@/core/proxy-utils/producers/utils'; -export default function Stash_Producer() { +export default function ShadowRocket_Producer() { const type = 'ALL'; const produce = (proxies) => { return ( diff --git a/backend/src/utils/platform.js b/backend/src/utils/platform.js index b556bc545..ee5168309 100644 --- a/backend/src/utils/platform.js +++ b/backend/src/utils/platform.js @@ -1,9 +1,11 @@ export function getPlatformFromHeaders(headers) { const keys = Object.keys(headers); let UA = ''; + let ua = ''; for (let k of keys) { if (/USER-AGENT/i.test(k)) { UA = headers[k]; + ua = UA.toLowerCase(); break; } } @@ -17,6 +19,15 @@ export function getPlatformFromHeaders(headers) { return 'ShadowRocket'; } else if (UA.indexOf('Stash') !== -1) { return 'Stash'; + } else if ( + ua === 'meta' || + (ua.indexOf('clash') !== -1 && ua.indexOf('meta') !== -1) + ) { + return 'ClashMeta'; + } else if (ua.indexOf('clash') !== -1) { + return 'Clash'; + } else if (ua.indexOf('v2ray') !== -1) { + return 'V2Ray'; } else { return 'JSON'; }