diff --git a/server/src/parsers/index.ts b/server/src/parsers/index.ts index 7400f48..f58c5e7 100644 --- a/server/src/parsers/index.ts +++ b/server/src/parsers/index.ts @@ -1,6 +1,7 @@ import { parseSS, type ParsedNode } from './ss.js'; import { parseVMess } from './vmess.js'; import { parseTrojan } from './trojan.js'; +import { parseVless } from './vless.js'; export type { ParsedNode }; @@ -9,6 +10,7 @@ export function parseNodeUri(uri: string): ParsedNode | null { if (uri.startsWith('ss://')) return parseSS(uri); if (uri.startsWith('vmess://')) return parseVMess(uri); if (uri.startsWith('trojan://')) return parseTrojan(uri); + if (uri.startsWith('vless://')) return parseVless(uri); return null; } catch { return null; diff --git a/server/src/parsers/vless.ts b/server/src/parsers/vless.ts new file mode 100644 index 0000000..5dbb39e --- /dev/null +++ b/server/src/parsers/vless.ts @@ -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 }; +} diff --git a/web/src/components/StaticNodes.tsx b/web/src/components/StaticNodes.tsx index 5f91f7a..5f37881 100644 --- a/web/src/components/StaticNodes.tsx +++ b/web/src/components/StaticNodes.tsx @@ -50,7 +50,7 @@ export default function StaticNodes() { return (
粘贴 ss:// / vmess:// / trojan:// URI 自动解析
+粘贴 ss:// / vmess:// / trojan:// / vless:// URI 自动解析