/** * Convert proxy URIs (ss://, vmess://, trojan://) to Clash YAML flow-style proxy lines. * Returns a string like ` - {name: "NodeName", type: ss, server: ..., port: ..., ...}` * or null if the URI is unsupported/invalid. */ export function uriToClashLine(uri: string): string | null { try { if (uri.startsWith('ss://')) return ssToClash(uri); if (uri.startsWith('vmess://')) return vmessToClash(uri); if (uri.startsWith('trojan://')) return trojanToClash(uri); return null; } catch { return null; } } /** Escape a string value for use inside double-quoted YAML flow style */ function esc(s: string): string { return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); } function ssToClash(uri: string): string | null { const url = new URL(uri); const name = decodeURIComponent(url.hash.slice(1)); const server = url.hostname; const port = url.port; // userinfo is base64(method:password) const decoded = Buffer.from(url.username, 'base64').toString(); const firstColon = decoded.indexOf(':'); if (firstColon === -1) return null; const cipher = decoded.slice(0, firstColon); const password = decoded.slice(firstColon + 1); return ` - {name: "${esc(name)}", type: ss, server: ${server}, port: ${port}, cipher: ${cipher}, password: "${esc(password)}"}`; } function vmessToClash(uri: string): string | null { const b64 = uri.replace('vmess://', ''); const json = JSON.parse(Buffer.from(b64, 'base64').toString()); const name = json.ps || 'VMess'; const server = json.add; const port = json.port; const uuid = json.id; if (!server || !port || !uuid) return null; let line = ` - {name: "${esc(name)}", type: vmess, server: ${server}, port: ${port}, uuid: ${uuid}, alterId: 0, cipher: auto`; if (json.tls === 'tls') { line += ', tls: true'; if (json.sni) line += `, sni: ${json.sni}`; } if (json.net === 'ws') { line += ', network: ws'; const wsOpts: string[] = []; if (json.path) wsOpts.push(`path: "${esc(json.path)}"`); if (json.host) wsOpts.push(`headers: {Host: ${json.host}}`); if (wsOpts.length > 0) { line += `, ws-opts: {${wsOpts.join(', ')}}`; } } line += '}'; return line; } function trojanToClash(uri: string): string | null { const url = new URL(uri); const name = decodeURIComponent(url.hash.slice(1)); const server = url.hostname; const port = url.port; const password = url.username; const sni = url.searchParams.get('sni') || server; return ` - {name: "${esc(name)}", type: trojan, server: ${server}, port: ${port}, password: "${esc(password)}", sni: ${sni}}`; }