diff --git a/backend/package.json b/backend/package.json index 68184678f7..2298bab925 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "sub-store", - "version": "2.14.25", + "version": "2.14.26", "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 b09a307674..fb39ba3d0d 100644 --- a/backend/src/core/proxy-utils/parsers/index.js +++ b/backend/src/core/proxy-utils/parsers/index.js @@ -230,8 +230,7 @@ function URI_VMess() { params[key] = value.split(','); } } - console.log(`content`, content); - console.log(`params`, params); + let [__, cipher, uuid, server, port] = /(^[^:]+?):([^:]+?)@(.*):(\d+)$/.exec(content); @@ -263,7 +262,10 @@ function URI_VMess() { // handle obfs if (params.net === 'ws' || params.obfs === 'websocket') { proxy.network = 'ws'; - } else if (params.net === 'tcp' || params.obfs === 'http') { + } else if ( + ['tcp', 'http'].includes(params.net) || + params.obfs === 'http' + ) { proxy.network = 'http'; } if (proxy.network) { @@ -340,6 +342,7 @@ function Clash_All() { 'snell', 'trojan', 'tuic', + 'vless', ].includes(proxy.type) ) { throw new Error( @@ -348,7 +351,7 @@ function Clash_All() { } // handle vmess sni - if (proxy.type === 'vmess') { + if (['vmess', 'vless'].includes(proxy.type)) { proxy.sni = proxy.servername; delete proxy.servername; if (proxy.tls && !proxy.sni) { diff --git a/backend/src/core/proxy-utils/producers/index.js b/backend/src/core/proxy-utils/producers/index.js index c8b9dcf43e..0d5dd07c05 100644 --- a/backend/src/core/proxy-utils/producers/index.js +++ b/backend/src/core/proxy-utils/producers/index.js @@ -5,6 +5,7 @@ import Loon_Producer from './loon'; import URI_Producer from './uri'; import V2Ray_Producer from './v2ray'; import QX_Producer from './qx'; +import ShadowRocket_Producer from './shadowrocket'; function JSON_Producer() { const type = 'ALL'; @@ -21,4 +22,5 @@ export default { V2Ray: V2Ray_Producer(), JSON: JSON_Producer(), Stash: Stash_Producer(), + ShadowRocket: ShadowRocket_Producer(), }; diff --git a/backend/src/core/proxy-utils/producers/loon.js b/backend/src/core/proxy-utils/producers/loon.js index 48fa02994d..86489f5e6a 100644 --- a/backend/src/core/proxy-utils/producers/loon.js +++ b/backend/src/core/proxy-utils/producers/loon.js @@ -189,6 +189,9 @@ function vmess(proxy) { } function vless(proxy) { + if (proxy['reality-opts']) { + throw new Error(`reality is unsupported`); + } const result = new Result(proxy); result.append( `${proxy.name}=vless,${proxy.server},${proxy.port},"${proxy.uuid}"`, diff --git a/backend/src/core/proxy-utils/producers/shadowrocket.js b/backend/src/core/proxy-utils/producers/shadowrocket.js new file mode 100644 index 0000000000..0174bff582 --- /dev/null +++ b/backend/src/core/proxy-utils/producers/shadowrocket.js @@ -0,0 +1,88 @@ +import { isPresent } from '@/core/proxy-utils/producers/utils'; + +export default function Stash_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']; + } + // 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; + } + } + + 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]; + } + } + + delete proxy['tls-fingerprint']; + return ' - ' + JSON.stringify(proxy) + '\n'; + }) + .join('') + ); + }; + return { type, produce }; +} diff --git a/backend/src/core/proxy-utils/producers/stash.js b/backend/src/core/proxy-utils/producers/stash.js index 0174bff582..1014f2a7c2 100644 --- a/backend/src/core/proxy-utils/producers/stash.js +++ b/backend/src/core/proxy-utils/producers/stash.js @@ -8,8 +8,20 @@ export default function Stash_Producer() { proxies .filter((proxy) => { if ( - proxy.type === 'snell' && - String(proxy.version) === '4' + ![ + 'ss', + 'ssr', + 'vmess', + 'socks', + 'http', + 'snell', + 'trojan', + 'tuic', + 'vless', + ].includes(proxy.type) || + (proxy.type === 'snell' && + String(proxy.version) === '4') || + (proxy.type === 'vless' && proxy['reality-opts']) ) { return false; } diff --git a/backend/src/core/proxy-utils/producers/uri.js b/backend/src/core/proxy-utils/producers/uri.js index fe43704c8d..a29cce0d15 100644 --- a/backend/src/core/proxy-utils/producers/uri.js +++ b/backend/src/core/proxy-utils/producers/uri.js @@ -55,47 +55,65 @@ export default function URI_Producer() { break; case 'vmess': // V2RayN URI format + let type = ''; + let net = proxy.network || 'tcp'; + if (proxy.network === 'http') { + net = 'tcp'; + type = 'http'; + } result = { + v: '2', ps: proxy.name, add: proxy.server, port: proxy.port, id: proxy.uuid, - type: '', + type, aid: 0, - net: proxy.network || 'tcp', + net, tls: proxy.tls ? 'tls' : '', }; if (proxy.tls && proxy.sni) { result.sni = proxy.sni; } // obfs - if (proxy.network === 'ws') { - result.path = proxy['ws-opts'].path || '/'; - if (proxy['ws-opts'].headers.Host) { - result.host = proxy['ws-opts'].headers.Host; + if (proxy.network) { + let vmessTransportPath = + proxy[`${proxy.network}-opts`]?.path; + let vmessTransportHost = + proxy[`${proxy.network}-opts`]?.headers?.Host; + if (vmessTransportPath) { + result.path = Array.isArray(vmessTransportPath) + ? vmessTransportPath[0] + : vmessTransportPath; + } + if (vmessTransportHost) { + result.host = Array.isArray(vmessTransportHost) + ? vmessTransportHost[0] + : vmessTransportHost; } } result = 'vmess://' + Base64.encode(JSON.stringify(result)); break; case 'trojan': - let transport = ''; + let trojanTransport = ''; if (proxy.network) { - transport = `&type=${proxy.network}`; - let transportPath = proxy[`${proxy.network}-opts`]?.path; - let transportHost = + trojanTransport = `&type=${proxy.network}`; + let trojanTransportPath = + proxy[`${proxy.network}-opts`]?.path; + let trojanTransportHost = proxy[`${proxy.network}-opts`]?.headers?.Host; - if (transportPath) { - transport += `&path=${encodeURIComponent( - Array.isArray(transportPath) - ? transportPath[0] - : transportPath, + if (trojanTransportPath) { + trojanTransport += `&path=${encodeURIComponent( + Array.isArray(trojanTransportPath) + ? trojanTransportPath[0] + : trojanTransportPath, )}`; } - if (transportHost) { - transport += `&host=${encodeURIComponent( - Array.isArray(transportHost) - ? transportHost[0] - : transportHost, + if (trojanTransportHost) { + trojanTransport += `&host=${encodeURIComponent( + Array.isArray(trojanTransportHost) + ? trojanTransportHost[0] + : trojanTransportHost, )}`; } } @@ -103,7 +121,7 @@ export default function URI_Producer() { proxy.port }?sni=${encodeURIComponent(proxy.sni || proxy.server)}${ proxy['skip-cert-verify'] ? '&allowInsecure=1' : '' - }${transport}#${encodeURIComponent(proxy.name)}`; + }${trojanTransport}#${encodeURIComponent(proxy.name)}`; break; } return result; diff --git a/backend/src/restful/sync.js b/backend/src/restful/sync.js index 92f5571466..fc88bc1801 100644 --- a/backend/src/restful/sync.js +++ b/backend/src/restful/sync.js @@ -25,9 +25,6 @@ export default function register($app) { async function produceArtifact({ type, name, platform }) { platform = platform || 'JSON'; - // produce Clash node format for ShadowRocket - if (platform === 'ShadowRocket') platform = 'Clash'; - if (type === 'subscription') { const allSubs = $.read(SUBS_KEY); const sub = findByName(allSubs, name);