mirror of
https://github.com/lush2020/edgetunnel.git
synced 2026-03-21 17:12:33 +08:00
Merge pull request #913 from cmliu/beta2.0
fix: 添加Clash订阅配置文件热补丁,支持ECH配置,优化Singbox处理逻辑
This commit is contained in:
141
_worker.js
141
_worker.js
@@ -324,6 +324,7 @@ export default {
|
||||
订阅内容 = Singbox订阅配置文件热补丁(订阅内容, config_JSON.UUID, config_JSON.Fingerprint, config_JSON.ECH ? await getECH(host) : null);
|
||||
responseHeaders["content-type"] = 'application/json; charset=utf-8';
|
||||
} else if (订阅类型 === 'clash') {
|
||||
订阅内容 = Clash订阅配置文件热补丁(订阅内容, config_JSON.UUID, config_JSON.ECH ? await getECH(host) : null);
|
||||
responseHeaders["content-type"] = 'application/x-yaml; charset=utf-8';
|
||||
}
|
||||
return new Response(订阅内容, { status: 200, headers: responseHeaders });
|
||||
@@ -784,6 +785,142 @@ async function httpConnect(targetHost, targetPort, initialData) {
|
||||
}
|
||||
}
|
||||
//////////////////////////////////////////////////功能性函数///////////////////////////////////////////////
|
||||
function Clash订阅配置文件热补丁(clash_yaml, uuid = null, ech_config = null) {
|
||||
// 判断uuid字段是否为真
|
||||
if (!uuid) {
|
||||
return clash_yaml;
|
||||
}
|
||||
|
||||
// 如果ech_config为null,直接返回
|
||||
if (!ech_config) return clash_yaml;
|
||||
|
||||
const lines = clash_yaml.split('\n');
|
||||
const processedLines = [];
|
||||
let i = 0;
|
||||
|
||||
while (i < lines.length) {
|
||||
const line = lines[i];
|
||||
const trimmedLine = line.trim();
|
||||
|
||||
// 处理行格式(Flow):- {name: ..., uuid: ..., ...}
|
||||
if (trimmedLine.startsWith('- {') && (trimmedLine.includes('uuid:') || trimmedLine.includes('password:'))) {
|
||||
let fullNode = line;
|
||||
let braceCount = (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length;
|
||||
|
||||
// 如果括号不匹配,继续读取下一行
|
||||
while (braceCount > 0 && i + 1 < lines.length) {
|
||||
i++;
|
||||
fullNode += '\n' + lines[i];
|
||||
braceCount += (lines[i].match(/\{/g) || []).length - (lines[i].match(/\}/g) || []).length;
|
||||
}
|
||||
|
||||
// 获取代理类型
|
||||
const typeMatch = fullNode.match(/type:\s*(\w+)/);
|
||||
const proxyType = typeMatch ? typeMatch[1] : 'vless';
|
||||
|
||||
// 根据代理类型确定要查找的字段
|
||||
let credentialField = 'uuid';
|
||||
if (proxyType === 'trojan') {
|
||||
credentialField = 'password';
|
||||
}
|
||||
|
||||
// 检查对应字段的值是否匹配
|
||||
const credentialPattern = new RegExp(`${credentialField}:\\s*([^,}\\n]+)`);
|
||||
const credentialMatch = fullNode.match(credentialPattern);
|
||||
|
||||
if (credentialMatch && credentialMatch[1].trim() === uuid.trim()) {
|
||||
// 在最后一个}前添加ech-opts
|
||||
fullNode = fullNode.replace(/\}(\s*)$/, `, ech-opts: {enable: true, config: "${ech_config}"}}$1`);
|
||||
}
|
||||
|
||||
processedLines.push(fullNode);
|
||||
i++;
|
||||
}
|
||||
// 处理块格式(Block):- name: ..., 后续行为属性
|
||||
else if (trimmedLine.startsWith('- name:')) {
|
||||
// 收集完整的代理节点定义
|
||||
let nodeLines = [line];
|
||||
let baseIndent = line.search(/\S/);
|
||||
let topLevelIndent = baseIndent + 2; // 顶级属性的缩进
|
||||
i++;
|
||||
|
||||
// 继续读取这个节点的所有属性
|
||||
while (i < lines.length) {
|
||||
const nextLine = lines[i];
|
||||
const nextTrimmed = nextLine.trim();
|
||||
|
||||
// 如果是空行,包含它但不继续
|
||||
if (!nextTrimmed) {
|
||||
nodeLines.push(nextLine);
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
|
||||
const nextIndent = nextLine.search(/\S/);
|
||||
|
||||
// 如果缩进小于等于基础缩进且不是空行,说明节点结束了
|
||||
if (nextIndent <= baseIndent && nextTrimmed.startsWith('- ')) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 如果缩进更小,节点也结束了
|
||||
if (nextIndent < baseIndent && nextTrimmed) {
|
||||
break;
|
||||
}
|
||||
|
||||
nodeLines.push(nextLine);
|
||||
i++;
|
||||
}
|
||||
|
||||
// 获取代理类型
|
||||
const nodeText = nodeLines.join('\n');
|
||||
const typeMatch = nodeText.match(/type:\s*(\w+)/);
|
||||
const proxyType = typeMatch ? typeMatch[1] : 'vless';
|
||||
|
||||
// 根据代理类型确定要查找的字段
|
||||
let credentialField = 'uuid';
|
||||
if (proxyType === 'trojan') {
|
||||
credentialField = 'password';
|
||||
}
|
||||
|
||||
// 检查这个节点的对应字段是否匹配
|
||||
const credentialPattern = new RegExp(`${credentialField}:\\s*([^\\n]+)`);
|
||||
const credentialMatch = nodeText.match(credentialPattern);
|
||||
|
||||
if (credentialMatch && credentialMatch[1].trim() === uuid.trim()) {
|
||||
// 找到在哪里插入ech-opts
|
||||
// 策略:在最后一个顶级属性后面插入,或在ws-opts之前插入
|
||||
let insertIndex = -1;
|
||||
|
||||
for (let j = nodeLines.length - 1; j >= 0; j--) {
|
||||
// 跳过空行,找到节点中最后一个非空行(可能是顶级属性或其子项)
|
||||
if (nodeLines[j].trim()) {
|
||||
insertIndex = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (insertIndex >= 0) {
|
||||
const indent = ' '.repeat(topLevelIndent);
|
||||
// 在节点末尾(最后一个属性块之后)插入 ech-opts 属性
|
||||
nodeLines.splice(insertIndex + 1, 0,
|
||||
`${indent}ech-opts:`,
|
||||
`${indent} enable: true`,
|
||||
`${indent} config: "${ech_config}"`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
processedLines.push(...nodeLines);
|
||||
} else {
|
||||
processedLines.push(line);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return processedLines.join('\n');
|
||||
}
|
||||
|
||||
function Singbox订阅配置文件热补丁(sb_json_text, uuid = null, fingerprint = "chrome", ech_config = null) {
|
||||
try {
|
||||
let config = JSON.parse(sb_json_text);
|
||||
@@ -932,8 +1069,8 @@ function Singbox订阅配置文件热补丁(sb_json_text, uuid = null, fingerpri
|
||||
// --- 4. UUID 匹配节点的 TLS 热补丁 (utls & ech) ---
|
||||
if (uuid) {
|
||||
config.outbounds.forEach(outbound => {
|
||||
// 仅处理包含 uuid 且匹配的节点
|
||||
if (outbound.uuid && outbound.uuid === uuid) {
|
||||
// 仅处理包含 uuid 或 password 且匹配的节点
|
||||
if ((outbound.uuid && outbound.uuid === uuid) || (outbound.password && outbound.password === uuid)) {
|
||||
// 确保 tls 对象存在
|
||||
if (!outbound.tls) {
|
||||
outbound.tls = { enabled: true };
|
||||
|
||||
Reference in New Issue
Block a user