feat: clash/ss suport
This commit is contained in:
79
server/src/parsers/toClash.ts
Normal file
79
server/src/parsers/toClash.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* 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}}`;
|
||||
}
|
||||
Reference in New Issue
Block a user