feat: 添加 VLESS-Reality 静态节点解析支持

支持 vless:// URI 解析,覆盖 Reality/TLS/WS/gRPC 传输方式,
自动提取 pbk、sid、sni 等参数生成 Surge 配置行。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-15 13:22:53 +08:00
parent 2df229473a
commit e268ed6d10
3 changed files with 52 additions and 2 deletions

View File

@@ -1,6 +1,7 @@
import { parseSS, type ParsedNode } from './ss.js'; import { parseSS, type ParsedNode } from './ss.js';
import { parseVMess } from './vmess.js'; import { parseVMess } from './vmess.js';
import { parseTrojan } from './trojan.js'; import { parseTrojan } from './trojan.js';
import { parseVless } from './vless.js';
export type { ParsedNode }; export type { ParsedNode };
@@ -9,6 +10,7 @@ export function parseNodeUri(uri: string): ParsedNode | null {
if (uri.startsWith('ss://')) return parseSS(uri); if (uri.startsWith('ss://')) return parseSS(uri);
if (uri.startsWith('vmess://')) return parseVMess(uri); if (uri.startsWith('vmess://')) return parseVMess(uri);
if (uri.startsWith('trojan://')) return parseTrojan(uri); if (uri.startsWith('trojan://')) return parseTrojan(uri);
if (uri.startsWith('vless://')) return parseVless(uri);
return null; return null;
} catch { } catch {
return null; return null;

View File

@@ -0,0 +1,48 @@
import type { ParsedNode } from './ss.js';
export function parseVless(uri: string): ParsedNode {
const url = new URL(uri);
const name = decodeURIComponent(url.hash.slice(1)) || 'VLESS';
const server = url.hostname;
const port = parseInt(url.port, 10);
const uuid = url.username;
const params = url.searchParams;
const security = params.get('security') || 'none';
const type = params.get('type') || 'tcp';
const sni = params.get('sni') || '';
const flow = params.get('flow') || '';
let line = `${name} = vless, ${server}, ${port}, username=${uuid}`;
if (security === 'reality') {
const pbk = params.get('pbk') || '';
const sid = params.get('sid') || '';
line += ', tls=true';
if (sni) line += `, sni=${sni}`;
if (pbk) line += `, reality-public-key=${pbk}`;
if (sid) line += `, reality-short-id=${sid}`;
line += ', skip-cert-verify=false';
} else if (security === 'tls') {
line += ', tls=true';
if (sni) line += `, sni=${sni}`;
line += ', skip-cert-verify=false';
}
if (type === 'ws') {
const path = params.get('path') || '/';
const host = params.get('host') || sni || server;
line += ', ws=true';
line += `, ws-path=${path}`;
line += `, ws-headers=Host:${host}`;
} else if (type === 'grpc') {
const serviceName = params.get('serviceName') || '';
if (serviceName) line += `, grpc-service-name=${serviceName}`;
}
if (flow === 'xtls-rprx-vision') {
line += ', flow=xtls-rprx-vision';
}
return { name, type: 'vless', server, port, surgeLine: line };
}

View File

@@ -50,7 +50,7 @@ export default function StaticNodes() {
return ( return (
<div> <div>
<h2 style={styles.title}></h2> <h2 style={styles.title}></h2>
<p style={styles.subtitle}> ss:// / vmess:// / trojan:// URI 自动解析</p> <p style={styles.subtitle}> ss:// / vmess:// / trojan:// / vless:// URI 自动解析</p>
<div style={styles.form}> <div style={styles.form}>
<input <input
@@ -60,7 +60,7 @@ export default function StaticNodes() {
style={{ width: 180 }} style={{ width: 180 }}
/> />
<input <input
placeholder="粘贴节点 URIss:// / vmess:// / trojan://" placeholder="粘贴节点 URIss:// / vmess:// / trojan:// / vless://"
value={uri} value={uri}
onChange={e => setUri(e.target.value)} onChange={e => setUri(e.target.value)}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}