feat: fix ss

This commit is contained in:
2026-07-21 18:09:25 +08:00
parent d708240acf
commit 6b4285bc82
4 changed files with 35 additions and 11 deletions

View File

@@ -6,6 +6,12 @@ export interface ParsedNode {
surgeLine: string;
}
// Surge 使用与标准 Shadowsocks 不同的加密方式命名,需做别名映射
const ENCRYPT_METHOD_ALIASES: Record<string, string> = {
'chacha20-poly1305': 'chacha20-ietf-poly1305',
'xchacha20-poly1305': 'xchacha20-ietf-poly1305',
};
export function parseSS(uri: string): ParsedNode {
const url = new URL(uri);
const name = decodeURIComponent(url.hash.slice(1));
@@ -14,11 +20,18 @@ export function parseSS(uri: string): ParsedNode {
const decoded = Buffer.from(url.username, 'base64').toString();
const firstColon = decoded.indexOf(':');
const method = decoded.slice(0, firstColon);
const rawMethod = decoded.slice(0, firstColon);
const method = ENCRYPT_METHOD_ALIASES[rawMethod] ?? rawMethod;
const password = decoded.slice(firstColon + 1);
// SS 2022 has built-in encryption, Surge does not support SS over TLS
const surgeLine = `${name} = ss, ${server}, ${port}, encrypt-method=${method}, password=${password}`;
return { name, type: 'ss', server, port, surgeLine };
// Surge's `ss` proxy type has no WebSocket/TLS transport support at all (unlike
// trojan/vmess/vless) — this Xray-style ss+ws+tls link can never work in Surge,
// so it's tagged 'ss-ws' and excluded from Surge output (see generator.ts).
// Clash/SSR generators re-parse the raw uri independently and are unaffected.
const type = url.searchParams.get('type') === 'ws' ? 'ss-ws' : 'ss';
return { name, type, server, port, surgeLine };
}

View File

@@ -1,4 +1,5 @@
import type { ParsedNode } from './ss.js';
import { normalizeWsPath } from './util.js';
export function parseTrojan(uri: string): ParsedNode {
const url = new URL(uri);
@@ -9,7 +10,13 @@ export function parseTrojan(uri: string): ParsedNode {
const params = url.searchParams;
const sni = params.get('sni') || server;
const surgeLine = `${name} = trojan, ${server}, ${port}, password=${password}, tls=true, sni=${sni}, skip-cert-verify=false`;
let surgeLine = `${name} = trojan, ${server}, ${port}, password=${password}, tls=true, sni=${sni}, skip-cert-verify=false`;
if (params.get('type') === 'ws') {
const path = params.get('path') || '/';
const host = params.get('host') || sni;
surgeLine += `, ws=true, ws-path=${normalizeWsPath(path)}, ws-headers=Host:${host}`;
}
return { name, type: 'trojan', server, port, surgeLine };
}

View File

@@ -14,11 +14,12 @@ function renameSurgeLine(surgeLine: string, oldName: string, newName: string): s
return surgeLine;
}
const rows = db.prepare('SELECT id, name, uri, surge_line FROM static_nodes').all() as Array<{
const rows = db.prepare('SELECT id, name, uri, surge_line, type FROM static_nodes').all() as Array<{
id: number;
name: string;
uri: string;
surge_line: string;
type: string;
}>;
const update = db.prepare('UPDATE static_nodes SET surge_line = ?, type = ? WHERE id = ?');
@@ -31,7 +32,7 @@ for (const row of rows) {
continue;
}
const newLine = renameSurgeLine(node.surgeLine, node.name, row.name);
if (newLine !== row.surge_line) {
if (newLine !== row.surge_line || node.type !== row.type) {
update.run(newLine, node.type, row.id);
changed++;
console.log(`[updated] id=${row.id} "${row.name}"`);

View File

@@ -15,18 +15,21 @@ export function generateSurgeConfig(hostUrl: string, whitelist?: Set<string>): s
return '# No subscription config available. Add and fetch a subscription first.';
}
// Collect fetched nodes (exclude vless Surge doesn't support it; tuic uses `tuic-v5`)
// Exclude vless and ss-ws (Surge doesn't support either transport)
const UNSUPPORTED = "type NOT IN ('vless', 'ss-ws')";
// Collect fetched nodes (tuic uses `tuic-v5`)
const fetchedNodes = db.prepare(
whitelist
? "SELECT surge_line FROM fetched_nodes WHERE type != 'vless' ORDER BY subscription_id, sort_order, id"
: "SELECT surge_line FROM fetched_nodes WHERE enabled = 1 AND type != 'vless' ORDER BY subscription_id, sort_order, id"
? `SELECT surge_line FROM fetched_nodes WHERE ${UNSUPPORTED} ORDER BY subscription_id, sort_order, id`
: `SELECT surge_line FROM fetched_nodes WHERE enabled = 1 AND ${UNSUPPORTED} ORDER BY subscription_id, sort_order, id`
).all() as any[];
// Collect static nodes (these go FIRST, exclude vless)
// Collect static nodes (these go FIRST)
const staticNodes = db.prepare(
whitelist
? "SELECT surge_line FROM static_nodes WHERE type != 'vless' ORDER BY sort_order, id"
: "SELECT surge_line FROM static_nodes WHERE enabled = 1 AND type != 'vless' ORDER BY sort_order, id"
? `SELECT surge_line FROM static_nodes WHERE ${UNSUPPORTED} ORDER BY sort_order, id`
: `SELECT surge_line FROM static_nodes WHERE enabled = 1 AND ${UNSUPPORTED} ORDER BY sort_order, id`
).all() as any[];
// Collect enabled rules