diff --git a/backend/package.json b/backend/package.json index 067e090..71e7100 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "sub-store", - "version": "2.14.203", + "version": "2.14.204", "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 9b5317d..c2b7cc3 100644 --- a/backend/src/core/proxy-utils/parsers/index.js +++ b/backend/src/core/proxy-utils/parsers/index.js @@ -545,6 +545,75 @@ function URI_Hysteria2() { }; return { name, test, parse }; } +function URI_Hysteria() { + const name = 'URI Hysteria Parser'; + const test = (line) => { + return /^(hysteria|hy):\/\//.test(line); + }; + const parse = (line) => { + line = line.split(/(hysteria|hy):\/\//)[2]; + // eslint-disable-next-line no-unused-vars + let [__, server, ___, port, ____, addons = '', name] = + /^(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line); + port = parseInt(`${port}`, 10); + if (isNaN(port)) { + port = 443; + } + if (name != null) { + name = decodeURIComponent(name); + } + name = name ?? `Hysteria ${server}:${port}`; + + const proxy = { + type: 'hysteria', + name, + server, + port, + }; + const params = {}; + for (const addon of addons.split('&')) { + let [key, value] = addon.split('='); + key = key.replace(/_/, '-'); + 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 if (['auth'].includes(key)) { + proxy['auth-str'] = value; + } else if (['mport'].includes(key)) { + proxy['ports'] = value; + } else if (['obfsParam'].includes(key)) { + proxy['obfs'] = value; + } else if (['upmbps'].includes(key)) { + proxy['up'] = value; + } else if (['downmbps'].includes(key)) { + proxy['down'] = value; + } else if (['obfs'].includes(key)) { + // obfs: Obfuscation mode (optional, empty or "xplus") + proxy['_obfs'] = value || ''; + } else if (['fast-open', 'peer'].includes(key)) { + params[key] = value; + } else { + proxy[key] = value; + } + } + + if (!proxy.sni && params.peer) { + proxy.sni = params.peer; + } + if (!proxy['fast-open'] && params.fastopen) { + proxy['fast-open'] = true; + } + if (!proxy.protocol) { + // protocol: protocol to use ("udp", "wechat-video", "faketcp") (optional, default: "udp") + proxy.protocol = 'udp'; + } + + return proxy; + }; + return { name, test, parse }; +} function URI_TUIC() { const name = 'URI TUIC Parser'; const test = (line) => { @@ -552,7 +621,6 @@ function URI_TUIC() { }; const parse = (line) => { line = line.split(/tuic:\/\//)[1]; - console.log(line); // eslint-disable-next-line no-unused-vars let [__, uuid, password, server, ___, port, ____, addons = '', name] = /^(.*?):(.*?)@(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line); @@ -1101,6 +1169,7 @@ export default [ URI_VMess(), URI_VLESS(), URI_TUIC(), + URI_Hysteria(), URI_Hysteria2(), URI_Trojan(), Clash_All(), diff --git a/backend/src/core/proxy-utils/producers/uri.js b/backend/src/core/proxy-utils/producers/uri.js index c6511e1..4fab2ae 100644 --- a/backend/src/core/proxy-utils/producers/uri.js +++ b/backend/src/core/proxy-utils/producers/uri.js @@ -6,6 +6,11 @@ export default function URI_Producer() { const type = 'SINGLE'; const produce = (proxy) => { let result = ''; + delete proxy.subName; + delete proxy.collectionName; + if (['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(proxy.type)) { + delete proxy.tls; + } if (proxy.server && isIPv6(proxy.server)) { proxy.server = `[${proxy.server}]`; } @@ -285,6 +290,61 @@ export default function URI_Producer() { '&', )}#${encodeURIComponent(proxy.name)}`; break; + case 'hysteria': + let hysteriaParams = []; + Object.keys(proxy).forEach((key) => { + if (!['name', 'type', 'server', 'port'].includes(key)) { + const i = key.replace(/-/, '_'); + if (['alpn'].includes(key)) { + if (proxy[key]) { + hysteriaParams.push( + `${i}=${encodeURIComponent( + Array.isArray(proxy[key]) + ? proxy[key][0] + : proxy[key], + )}`, + ); + } + } else if (['skip-cert-verify'].includes(key)) { + if (proxy[key]) { + hysteriaParams.push(`insecure=1`); + } + } else if (['tfo', 'fast-open'].includes(key)) { + if ( + proxy[key] && + !hysteriaParams.includes('fastopen=1') + ) { + hysteriaParams.push(`fastopen=1`); + } + } else if (['ports'].includes(key)) { + hysteriaParams.push(`mport=${proxy[key]}`); + } else if (['auth-str'].includes(key)) { + hysteriaParams.push(`auth=${proxy[key]}`); + } else if (['up'].includes(key)) { + hysteriaParams.push(`upmbps=${proxy[key]}`); + } else if (['down'].includes(key)) { + hysteriaParams.push(`downmbps=${proxy[key]}`); + } else if (['_obfs'].includes(key)) { + hysteriaParams.push(`obfs=${proxy[key]}`); + } else if (['obfs'].includes(key)) { + hysteriaParams.push(`obfsParam=${proxy[key]}`); + } else if (['sni'].includes(key)) { + hysteriaParams.push(`peer=${proxy[key]}`); + } else if (proxy[key]) { + hysteriaParams.push( + `${i}=${encodeURIComponent(proxy[key])}`, + ); + } + } + }); + + result = `hysteria://${proxy.server}:${ + proxy.port + }?${hysteriaParams.join('&')}#${encodeURIComponent( + proxy.name, + )}`; + break; + case 'tuic': if (!proxy.token || proxy.token.length === 0) { let tuicParams = [];