feat: init proj

This commit is contained in:
2026-03-31 13:11:54 +08:00
commit 8f75ea24d6
38 changed files with 6826 additions and 0 deletions

210
index.js Normal file
View File

@@ -0,0 +1,210 @@
const http = require('http');
const https = require('https');
const PORT = 3456;
const UPSTREAM_URL = 'https://iplcsub.com/subscribe/36769/gVZJsFgUA7/surge/';
// 额外节点的 URIVLESS 不被 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).');
});