@@ -324,6 +324,7 @@ export default {
324324 订阅内容 = Singbox订阅配置文件热补丁 ( 订阅内容 , config_JSON . UUID , config_JSON . Fingerprint , config_JSON . ECH ? await getECH ( host ) : null ) ;
325325 responseHeaders [ "content-type" ] = 'application/json; charset=utf-8' ;
326326 } else if ( 订阅类型 === 'clash' ) {
327+ 订阅内容 = Clash订阅配置文件热补丁 ( 订阅内容 , config_JSON . UUID , config_JSON . ECH ? await getECH ( host ) : null ) ;
327328 responseHeaders [ "content-type" ] = 'application/x-yaml; charset=utf-8' ;
328329 }
329330 return new Response ( 订阅内容 , { status : 200 , headers : responseHeaders } ) ;
@@ -784,6 +785,142 @@ async function httpConnect(targetHost, targetPort, initialData) {
784785 }
785786}
786787//////////////////////////////////////////////////功能性函数///////////////////////////////////////////////
788+ function Clash订阅配置文件热补丁 ( clash_yaml , uuid = null , ech_config = null ) {
789+ // 判断uuid字段是否为真
790+ if ( ! uuid ) {
791+ return clash_yaml ;
792+ }
793+
794+ // 如果ech_config为null,直接返回
795+ if ( ! ech_config ) return clash_yaml ;
796+
797+ const lines = clash_yaml . split ( '\n' ) ;
798+ const processedLines = [ ] ;
799+ let i = 0 ;
800+
801+ while ( i < lines . length ) {
802+ const line = lines [ i ] ;
803+ const trimmedLine = line . trim ( ) ;
804+
805+ // 处理行格式(Flow):- {name: ..., uuid: ..., ...}
806+ if ( trimmedLine . startsWith ( '- {' ) && ( trimmedLine . includes ( 'uuid:' ) || trimmedLine . includes ( 'password:' ) ) ) {
807+ let fullNode = line ;
808+ let braceCount = ( line . match ( / \{ / g) || [ ] ) . length - ( line . match ( / \} / g) || [ ] ) . length ;
809+
810+ // 如果括号不匹配,继续读取下一行
811+ while ( braceCount > 0 && i + 1 < lines . length ) {
812+ i ++ ;
813+ fullNode += '\n' + lines [ i ] ;
814+ braceCount += ( lines [ i ] . match ( / \{ / g) || [ ] ) . length - ( lines [ i ] . match ( / \} / g) || [ ] ) . length ;
815+ }
816+
817+ // 获取代理类型
818+ const typeMatch = fullNode . match ( / t y p e : \s * ( \w + ) / ) ;
819+ const proxyType = typeMatch ? typeMatch [ 1 ] : 'vless' ;
820+
821+ // 根据代理类型确定要查找的字段
822+ let credentialField = 'uuid' ;
823+ if ( proxyType === 'trojan' ) {
824+ credentialField = 'password' ;
825+ }
826+
827+ // 检查对应字段的值是否匹配
828+ const credentialPattern = new RegExp ( `${ credentialField } :\\s*([^,}\\n]+)` ) ;
829+ const credentialMatch = fullNode . match ( credentialPattern ) ;
830+
831+ if ( credentialMatch && credentialMatch [ 1 ] . trim ( ) === uuid . trim ( ) ) {
832+ // 在最后一个}前添加ech-opts
833+ fullNode = fullNode . replace ( / \} ( \s * ) $ / , `, ech-opts: {enable: true, config: "${ ech_config } "}}$1` ) ;
834+ }
835+
836+ processedLines . push ( fullNode ) ;
837+ i ++ ;
838+ }
839+ // 处理块格式(Block):- name: ..., 后续行为属性
840+ else if ( trimmedLine . startsWith ( '- name:' ) ) {
841+ // 收集完整的代理节点定义
842+ let nodeLines = [ line ] ;
843+ let baseIndent = line . search ( / \S / ) ;
844+ let topLevelIndent = baseIndent + 2 ; // 顶级属性的缩进
845+ i ++ ;
846+
847+ // 继续读取这个节点的所有属性
848+ while ( i < lines . length ) {
849+ const nextLine = lines [ i ] ;
850+ const nextTrimmed = nextLine . trim ( ) ;
851+
852+ // 如果是空行,包含它但不继续
853+ if ( ! nextTrimmed ) {
854+ nodeLines . push ( nextLine ) ;
855+ i ++ ;
856+ break ;
857+ }
858+
859+ const nextIndent = nextLine . search ( / \S / ) ;
860+
861+ // 如果缩进小于等于基础缩进且不是空行,说明节点结束了
862+ if ( nextIndent <= baseIndent && nextTrimmed . startsWith ( '- ' ) ) {
863+ break ;
864+ }
865+
866+ // 如果缩进更小,节点也结束了
867+ if ( nextIndent < baseIndent && nextTrimmed ) {
868+ break ;
869+ }
870+
871+ nodeLines . push ( nextLine ) ;
872+ i ++ ;
873+ }
874+
875+ // 获取代理类型
876+ const nodeText = nodeLines . join ( '\n' ) ;
877+ const typeMatch = nodeText . match ( / t y p e : \s * ( \w + ) / ) ;
878+ const proxyType = typeMatch ? typeMatch [ 1 ] : 'vless' ;
879+
880+ // 根据代理类型确定要查找的字段
881+ let credentialField = 'uuid' ;
882+ if ( proxyType === 'trojan' ) {
883+ credentialField = 'password' ;
884+ }
885+
886+ // 检查这个节点的对应字段是否匹配
887+ const credentialPattern = new RegExp ( `${ credentialField } :\\s*([^\\n]+)` ) ;
888+ const credentialMatch = nodeText . match ( credentialPattern ) ;
889+
890+ if ( credentialMatch && credentialMatch [ 1 ] . trim ( ) === uuid . trim ( ) ) {
891+ // 找到在哪里插入ech-opts
892+ // 策略:在最后一个顶级属性后面插入,或在ws-opts之前插入
893+ let insertIndex = - 1 ;
894+
895+ for ( let j = nodeLines . length - 1 ; j >= 0 ; j -- ) {
896+ // 跳过空行,找到节点中最后一个非空行(可能是顶级属性或其子项)
897+ if ( nodeLines [ j ] . trim ( ) ) {
898+ insertIndex = j ;
899+ break ;
900+ }
901+ }
902+
903+ if ( insertIndex >= 0 ) {
904+ const indent = ' ' . repeat ( topLevelIndent ) ;
905+ // 在节点末尾(最后一个属性块之后)插入 ech-opts 属性
906+ nodeLines . splice ( insertIndex + 1 , 0 ,
907+ `${ indent } ech-opts:` ,
908+ `${ indent } enable: true` ,
909+ `${ indent } config: "${ ech_config } "`
910+ ) ;
911+ }
912+ }
913+
914+ processedLines . push ( ...nodeLines ) ;
915+ } else {
916+ processedLines . push ( line ) ;
917+ i ++ ;
918+ }
919+ }
920+
921+ return processedLines . join ( '\n' ) ;
922+ }
923+
787924function Singbox订阅配置文件热补丁 ( sb_json_text , uuid = null , fingerprint = "chrome" , ech_config = null ) {
788925 try {
789926 let config = JSON . parse ( sb_json_text ) ;
@@ -932,8 +1069,8 @@ function Singbox订阅配置文件热补丁(sb_json_text, uuid = null, fingerpri
9321069 // --- 4. UUID 匹配节点的 TLS 热补丁 (utls & ech) ---
9331070 if ( uuid ) {
9341071 config . outbounds . forEach ( outbound => {
935- // 仅处理包含 uuid 且匹配的节点
936- if ( outbound . uuid && outbound . uuid === uuid ) {
1072+ // 仅处理包含 uuid 或 password 且匹配的节点
1073+ if ( ( outbound . uuid && outbound . uuid === uuid ) || ( outbound . password && outbound . password === uuid ) ) {
9371074 // 确保 tls 对象存在
9381075 if ( ! outbound . tls ) {
9391076 outbound . tls = { enabled : true } ;
0 commit comments