Compare commits

..

5 Commits

Author SHA1 Message Date
xream
4c6ba2cdc8 feat: hysteria 2023-08-25 16:19:08 +08:00
Hsiaoyi
9cbbd0e86f Merge pull request #233 from eltociear/master-1
Fix typo in README.md
2023-08-24 21:46:03 +08:00
xream
0320a77451 feat: producers adjustments, VMess URI formats 2023-08-24 21:43:58 +08:00
xream
afb9296158 feat: Added support for VMess URI in other formats and VMess without transport settings 2023-08-24 20:23:48 +08:00
Ikko Eltociear Ashimine
0734a3d563 Fix typo in README.md
Speicial -> Special
2023-08-24 00:46:24 +09:00
12 changed files with 273 additions and 45 deletions

View File

@@ -91,4 +91,4 @@ This project is under the GPL V3 LICENSE.
## Acknowledgements ## Acknowledgements
- Special thanks to @KOP-XIAO for his awesome resource-parser. Please give a [star](https://github.com/KOP-XIAO/QuantumultX) for his great work! - Special thanks to @KOP-XIAO for his awesome resource-parser. Please give a [star](https://github.com/KOP-XIAO/QuantumultX) for his great work!
- Speicial thanks to @Orz-3 and @58xinian for their awesome icons. - Special thanks to @Orz-3 and @58xinian for their awesome icons.

View File

@@ -1,6 +1,6 @@
{ {
"name": "sub-store", "name": "sub-store",
"version": "2.14.24", "version": "2.14.27",
"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": {

View File

@@ -188,6 +188,8 @@ function lastParse(proxy) {
if (proxy.network === 'tcp') { if (proxy.network === 'tcp') {
delete proxy.network; delete proxy.network;
} }
}
if (['trojan', 'tuic', 'hysteria'].includes(proxy.type)) {
proxy.tls = true; proxy.tls = true;
} }
if (proxy.tls && !proxy.sni) { if (proxy.tls && !proxy.sni) {

View File

@@ -157,7 +157,7 @@ function URI_VMess() {
}; };
const parse = (line) => { const parse = (line) => {
line = line.split('vmess://')[1]; line = line.split('vmess://')[1];
const content = Base64.decode(line); let content = Base64.decode(line);
if (/=\s*vmess/.test(content)) { if (/=\s*vmess/.test(content)) {
// Quantumult VMess URI format // Quantumult VMess URI format
const partitions = content.split(',').map((p) => p.trim()); const partitions = content.split(',').map((p) => p.trim());
@@ -209,17 +209,49 @@ function URI_VMess() {
} }
return proxy; return proxy;
} else { } else {
// V2rayN URI format let params = {};
const params = JSON.parse(content);
try {
// V2rayN URI format
params = JSON.parse(content);
} catch (e) {
// console.error(e);
// Shadowrocket URI format
// eslint-disable-next-line no-unused-vars
let [__, base64Line, qs] = /(^[^?]+?)\/?\?(.*)$/.exec(line);
content = Base64.decode(base64Line);
for (const addon of qs.split('&')) {
const [key, valueRaw] = addon.split('=');
let value = valueRaw;
value = decodeURIComponent(valueRaw);
if (value.indexOf(',') === -1) {
params[key] = value;
} else {
params[key] = value.split(',');
}
}
// eslint-disable-next-line no-unused-vars
let [___, cipher, uuid, server, port] =
/(^[^:]+?):([^:]+?)@(.*):(\d+)$/.exec(content);
params.scy = cipher;
params.id = uuid;
params.port = port;
params.add = server;
}
const proxy = { const proxy = {
name: params.ps, name: params.ps ?? params.remark,
type: 'vmess', type: 'vmess',
server: params.add, server: params.add,
port: params.port, port: parseInt(getIfPresent(params.port), 10),
cipher: getIfPresent(params.scy, 'auto'), cipher: getIfPresent(params.scy, 'auto'),
uuid: params.id, uuid: params.id,
alterId: parseInt(getIfPresent(params.aid, 0)), alterId: parseInt(
tls: params.tls === 'tls' || params.tls === true, getIfPresent(params.aid ?? params.alterId, 0),
10,
),
tls: ['tls', true, 1, '1'].includes(params.tls),
'skip-cert-verify': isPresent(params.verify_cert) 'skip-cert-verify': isPresent(params.verify_cert)
? !params.verify_cert ? !params.verify_cert
: undefined, : undefined,
@@ -229,16 +261,43 @@ function URI_VMess() {
proxy.sni = params.sni; proxy.sni = params.sni;
} }
// handle obfs // handle obfs
if (params.net === 'ws') { if (params.net === 'ws' || params.obfs === 'websocket') {
proxy.network = 'ws'; proxy.network = 'ws';
proxy['ws-opts'] = { } else if (
path: getIfNotBlank(params.path), ['tcp', 'http'].includes(params.net) ||
headers: { Host: getIfNotBlank(params.host) }, params.obfs === 'http'
}; ) {
proxy.network = 'http';
}
if (proxy.network) {
let transportHost = params.host ?? params.obfsParam;
let transportPath = params.path;
if (proxy.network === 'http') {
if (transportHost) {
transportHost = Array.isArray(transportHost)
? transportHost[0]
: transportHost;
}
if (transportPath) {
transportPath = Array.isArray(transportPath)
? transportPath[0]
: transportPath;
}
}
if (transportPath || transportHost) {
proxy[`${proxy.network}-opts`] = {
path: getIfNotBlank(transportPath),
headers: { Host: getIfNotBlank(transportHost) },
};
} else {
delete proxy.network;
}
// https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml#L413 // https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml#L413
// sni 优先级应高于 host // sni 优先级应高于 host
if (proxy.tls && !proxy.sni && params.host) { if (proxy.tls && !proxy.sni && transportHost) {
proxy.sni = params.host; proxy.sni = transportHost;
} }
} }
return proxy; return proxy;
@@ -284,6 +343,8 @@ function Clash_All() {
'snell', 'snell',
'trojan', 'trojan',
'tuic', 'tuic',
'vless',
'hysteria',
].includes(proxy.type) ].includes(proxy.type)
) { ) {
throw new Error( throw new Error(
@@ -292,7 +353,7 @@ function Clash_All() {
} }
// handle vmess sni // handle vmess sni
if (proxy.type === 'vmess') { if (['vmess', 'vless'].includes(proxy.type)) {
proxy.sni = proxy.servername; proxy.sni = proxy.servername;
delete proxy.servername; delete proxy.servername;
if (proxy.tls && !proxy.sni) { if (proxy.tls && !proxy.sni) {

View File

@@ -87,6 +87,7 @@ params = "/"? "?" head:param tail:("&"@param)* {
proxy.network = "ws"; proxy.network = "ws";
$set(proxy, "ws-opts.path", params["wspath"]); $set(proxy, "ws-opts.path", params["wspath"]);
} }
if (params["type"]) { if (params["type"]) {
proxy.network = params["type"] proxy.network = params["type"]
if (params["path"]) { if (params["path"]) {

View File

@@ -74,7 +74,9 @@ export default function Clash_Producer() {
proxy['http-opts'].headers.Host = [httpHost]; proxy['http-opts'].headers.Host = [httpHost];
} }
} }
if (['trojan', 'tuic', 'hysteria'].includes(proxy.type)) {
delete proxy.tls;
}
delete proxy['tls-fingerprint']; delete proxy['tls-fingerprint'];
return ' - ' + JSON.stringify(proxy) + '\n'; return ' - ' + JSON.stringify(proxy) + '\n';
}) })

View File

@@ -5,6 +5,7 @@ import Loon_Producer from './loon';
import URI_Producer from './uri'; import URI_Producer from './uri';
import V2Ray_Producer from './v2ray'; import V2Ray_Producer from './v2ray';
import QX_Producer from './qx'; import QX_Producer from './qx';
import ShadowRocket_Producer from './shadowrocket';
function JSON_Producer() { function JSON_Producer() {
const type = 'ALL'; const type = 'ALL';
@@ -21,4 +22,5 @@ export default {
V2Ray: V2Ray_Producer(), V2Ray: V2Ray_Producer(),
JSON: JSON_Producer(), JSON: JSON_Producer(),
Stash: Stash_Producer(), Stash: Stash_Producer(),
ShadowRocket: ShadowRocket_Producer(),
}; };

View File

@@ -189,6 +189,9 @@ function vmess(proxy) {
} }
function vless(proxy) { function vless(proxy) {
if (proxy['reality-opts']) {
throw new Error(`reality is unsupported`);
}
const result = new Result(proxy); const result = new Result(proxy);
result.append( result.append(
`${proxy.name}=vless,${proxy.server},${proxy.port},"${proxy.uuid}"`, `${proxy.name}=vless,${proxy.server},${proxy.port},"${proxy.uuid}"`,

View File

@@ -0,0 +1,110 @@
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'];
}
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;
}
}
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 };
}

View File

@@ -8,8 +8,20 @@ export default function Stash_Producer() {
proxies proxies
.filter((proxy) => { .filter((proxy) => {
if ( 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; return false;
} }
@@ -49,6 +61,12 @@ export default function Stash_Producer() {
} else { } else {
proxy.alpn = ['h3']; 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 // https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/adapter/outbound/tuic.go#L197
if ( if (
(!proxy.token || proxy.token.length === 0) && (!proxy.token || proxy.token.length === 0) &&
@@ -56,6 +74,18 @@ export default function Stash_Producer() {
) { ) {
proxy.version = 5; 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;
}
} }
if ( if (
@@ -77,7 +107,9 @@ export default function Stash_Producer() {
proxy['http-opts'].headers.Host = [httpHost]; proxy['http-opts'].headers.Host = [httpHost];
} }
} }
if (['trojan', 'tuic', 'hysteria'].includes(proxy.type)) {
delete proxy.tls;
}
delete proxy['tls-fingerprint']; delete proxy['tls-fingerprint'];
return ' - ' + JSON.stringify(proxy) + '\n'; return ' - ' + JSON.stringify(proxy) + '\n';
}) })

View File

@@ -55,47 +55,65 @@ export default function URI_Producer() {
break; break;
case 'vmess': case 'vmess':
// V2RayN URI format // V2RayN URI format
let type = '';
let net = proxy.network || 'tcp';
if (proxy.network === 'http') {
net = 'tcp';
type = 'http';
}
result = { result = {
v: '2',
ps: proxy.name, ps: proxy.name,
add: proxy.server, add: proxy.server,
port: proxy.port, port: proxy.port,
id: proxy.uuid, id: proxy.uuid,
type: '', type,
aid: 0, aid: 0,
net: proxy.network || 'tcp', net,
tls: proxy.tls ? 'tls' : '', tls: proxy.tls ? 'tls' : '',
}; };
if (proxy.tls && proxy.sni) { if (proxy.tls && proxy.sni) {
result.sni = proxy.sni; result.sni = proxy.sni;
} }
// obfs // obfs
if (proxy.network === 'ws') { if (proxy.network) {
result.path = proxy['ws-opts'].path || '/'; let vmessTransportPath =
if (proxy['ws-opts'].headers.Host) { proxy[`${proxy.network}-opts`]?.path;
result.host = proxy['ws-opts'].headers.Host; 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)); result = 'vmess://' + Base64.encode(JSON.stringify(result));
break; break;
case 'trojan': case 'trojan':
let transport = ''; let trojanTransport = '';
if (proxy.network) { if (proxy.network) {
transport = `&type=${proxy.network}`; trojanTransport = `&type=${proxy.network}`;
let transportPath = proxy[`${proxy.network}-opts`]?.path; let trojanTransportPath =
let transportHost = proxy[`${proxy.network}-opts`]?.path;
let trojanTransportHost =
proxy[`${proxy.network}-opts`]?.headers?.Host; proxy[`${proxy.network}-opts`]?.headers?.Host;
if (transportPath) { if (trojanTransportPath) {
transport += `&path=${encodeURIComponent( trojanTransport += `&path=${encodeURIComponent(
Array.isArray(transportPath) Array.isArray(trojanTransportPath)
? transportPath[0] ? trojanTransportPath[0]
: transportPath, : trojanTransportPath,
)}`; )}`;
} }
if (transportHost) { if (trojanTransportHost) {
transport += `&host=${encodeURIComponent( trojanTransport += `&host=${encodeURIComponent(
Array.isArray(transportHost) Array.isArray(trojanTransportHost)
? transportHost[0] ? trojanTransportHost[0]
: transportHost, : trojanTransportHost,
)}`; )}`;
} }
} }
@@ -103,7 +121,7 @@ export default function URI_Producer() {
proxy.port proxy.port
}?sni=${encodeURIComponent(proxy.sni || proxy.server)}${ }?sni=${encodeURIComponent(proxy.sni || proxy.server)}${
proxy['skip-cert-verify'] ? '&allowInsecure=1' : '' proxy['skip-cert-verify'] ? '&allowInsecure=1' : ''
}${transport}#${encodeURIComponent(proxy.name)}`; }${trojanTransport}#${encodeURIComponent(proxy.name)}`;
break; break;
} }
return result; return result;

View File

@@ -25,9 +25,6 @@ export default function register($app) {
async function produceArtifact({ type, name, platform }) { async function produceArtifact({ type, name, platform }) {
platform = platform || 'JSON'; platform = platform || 'JSON';
// produce Clash node format for ShadowRocket
if (platform === 'ShadowRocket') platform = 'Clash';
if (type === 'subscription') { if (type === 'subscription') {
const allSubs = $.read(SUBS_KEY); const allSubs = $.read(SUBS_KEY);
const sub = findByName(allSubs, name); const sub = findByName(allSubs, name);