const http = require('http'); const https = require('https'); const PORT = 3456; const UPSTREAM_URL = 'https://iplcsub.com/subscribe/36769/gVZJsFgUA7/surge/'; // 额外节点的 URI(VLESS 不被 Surge 支持,跳过) const EXTRA_NODES_RAW = [ // VLESS-Reality — Surge 不支持,以注释形式保留 // 'vless://d42937a5-cf84-46c9-a304-36e0b6753c4c@coding.njcqtechaicoding.com:8443?...', 'ss://MjAyMi1ibGFrZTMtYWVzLTEyOC1nY206L3lzcGo3RFFGSm9za0MvbkFvSDRNUT09Oi95c3BqN0RRRkpvc2tDL25Bb0g0TVE9PQ@coding.njcqtechaicoding.com:8446?type=tcp#Shadowsocks-2022-ss-2022%40x3ui', 'vmess://ewogICJ2IjogIjIiLAogICJwcyI6ICJWTWVzcy1XUy1UTFMtdm1lc3Mtd3NAeDN1aSIsCiAgImFkZCI6ICJjb2RpbmcubmpjcXRlY2hhaWNvZGluZy5jb20iLAogICJwb3J0IjogMjA4MywKICAiaWQiOiAiNTI4ODkyMDUtNmY2ZC00ZWY2LTg4ZjItMmM2YTRmYThmOTJkIiwKICAic2N5IjogIiIsCiAgIm5ldCI6ICJ3cyIsCiAgInRscyI6ICJ0bHMiLAogICJwYXRoIjogIi92bWVzc3dzIiwKICAiaG9zdCI6ICJjb2RpbmcubmpjcXRlY2hhaWNvZGluZy5jb20iLAogICJzbmkiOiAiY29kaW5nLm5qY3F0ZWNoYWljb2RpbmcuY29tIiwKICAiZnAiOiAiY2hyb21lIiwKICAiYWxwbiI6ICJoMixodHRwLzEuMSIKfQ==', 'trojan://1d834ec2-a09b-4f59-978e-bbd557f1ee3a@coding.njcqtechaicoding.com:2087?type=tcp&security=tls&fp=chrome&alpn=h2%2Chttp%2F1.1&sni=coding.njcqtechaicoding.com#Trojan-TLS-trojan-tls%40x3ui', ]; /** * 解析 ss:// URI 为 Surge 代理行 */ function parseSS(uri) { const url = new URL(uri); const name = decodeURIComponent(url.hash.slice(1)); const server = url.hostname; const port = url.port; // userinfo 是 base64(method:password) const decoded = Buffer.from(url.username, 'base64').toString(); // SS 2022 格式: method:serverKey:userKey 或 method:password const firstColon = decoded.indexOf(':'); const method = decoded.slice(0, firstColon); const password = decoded.slice(firstColon + 1); return `${name} = ss, ${server}, ${port}, encrypt-method=${method}, password=${password}`; } /** * 解析 vmess:// URI 为 Surge 代理行 */ function parseVMess(uri) { 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; let line = `${name} = vmess, ${server}, ${port}, username=${uuid}, vmess-aead=true`; if (json.tls === 'tls') { line += ', tls=true'; if (json.sni) line += `, sni=${json.sni}`; line += ', skip-cert-verify=true'; } if (json.net === 'ws') { line += ', ws=true'; if (json.path) line += `, ws-path=${json.path}`; if (json.host) line += `, ws-headers=Host:${json.host}`; } return line; } /** * 解析 trojan:// URI 为 Surge 代理行 */ function parseTrojan(uri) { 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 params = url.searchParams; const sni = params.get('sni') || server; let line = `${name} = trojan, ${server}, ${port}, password=${password}, sni=${sni}, skip-cert-verify=true`; return line; } /** * 将各协议的 URI 转为 Surge 代理行 */ function convertToSurgeLine(uri) { if (uri.startsWith('ss://')) return parseSS(uri); if (uri.startsWith('vmess://')) return parseVMess(uri); if (uri.startsWith('trojan://')) return parseTrojan(uri); return null; } /** * 通过 HTTPS 获取上游订阅内容 */ function fetchUpstream() { return new Promise((resolve, reject) => { https.get(UPSTREAM_URL, (res) => { let data = ''; res.on('data', (chunk) => (data += chunk)); res.on('end', () => resolve(data)); res.on('error', reject); }).on('error', reject); }); } /** * 将额外节点注入到 Surge 配置中 */ function mergeNodes(config, extraLines) { if (extraLines.length === 0) return config; const extraNames = extraLines.map((l) => l.split(' = ')[0].trim()); const lines = config.split('\n'); const result = []; let inProxySection = false; let inProxyGroupSection = false; let proxyGroupHandled = false; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const trimmed = line.trim(); // 检测 section 边界 if (trimmed.startsWith('[') && trimmed.endsWith(']')) { // 如果正在 [Proxy] section,插入额外节点 if (inProxySection) { result.push('# --- 自定义节点 ---'); extraLines.forEach((l) => result.push(l)); result.push(''); inProxySection = false; } if (trimmed === '[Proxy]') { inProxySection = true; } else if (trimmed === '[Proxy Group]') { inProxyGroupSection = true; } else { inProxyGroupSection = false; } } // 处理 Proxy Group,在第一个 select 组中追加自定义节点名 if (inProxyGroupSection && !proxyGroupHandled && trimmed.includes('= select,')) { const appended = line + ',' + extraNames.join(','); result.push(appended); proxyGroupHandled = true; continue; } result.push(line); } // 如果 [Proxy] 是最后一个 section if (inProxySection) { result.push('# --- 自定义节点 ---'); extraLines.forEach((l) => result.push(l)); } return result.join('\n'); } /** * 替换 MANAGED-CONFIG URL 指向本地服务 */ function rewriteManagedConfig(config, localUrl) { return config.replace( /^#!MANAGED-CONFIG\s+\S+/m, `#!MANAGED-CONFIG ${localUrl}` ); } // 创建 HTTP 服务 const server = http.createServer(async (req, res) => { if (req.url === '/' || req.url === '/surge') { try { console.log(`[${new Date().toISOString()}] Fetching upstream subscription...`); const upstream = await fetchUpstream(); // 解析额外节点 const extraLines = EXTRA_NODES_RAW.map(convertToSurgeLine).filter(Boolean); // 合并配置 let merged = mergeNodes(upstream, extraLines); // 替换 MANAGED-CONFIG URL const localUrl = `http://${req.headers.host || `127.0.0.1:${PORT}`}/surge`; merged = rewriteManagedConfig(merged, localUrl); console.log(`[${new Date().toISOString()}] Serving merged config (${extraLines.length} extra nodes)`); res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8', 'Content-Disposition': 'attachment; filename=merged.conf', }); res.end(merged); } catch (err) { console.error('Error:', err.message); res.writeHead(502, { 'Content-Type': 'text/plain' }); res.end('Failed to fetch upstream subscription'); } } else { res.writeHead(404); res.end('Not Found'); } }); server.listen(PORT, () => { console.log(`Sub-Router running at http://127.0.0.1:${PORT}/surge`); console.log('Use this URL in Surge as your subscription link.'); console.log(''); console.log('Note: VLESS-Reality node is skipped (not supported by Surge).'); });