/** # Quantumult X 资源解析器 (2020-06-05: 12:59 ) 解析器作者: Shawn(请勿私聊问怎么用) 有bug请反馈: @Shawn_KOP_bot 更新请关注tg频道: https://t.me/QuanX_API 主要功能: 将各类服务器订阅解析成 QuantumultX 格式引用(支持 V2RayN/SSR/SS/Trojan/QuanX(conf&list)/Surge(conf&list)/https(仅部分格式) 订阅),并提供 1⃣️ 中的可选参数; 附加功能: rewrite(重写) /filter(分流) 过滤, 可用于解决无法单独禁用远程引用中某(几)条 rewrite/hostname/filter, 以及直接导入 Surge 类型规则 list 的问题 0️⃣ 请在“订阅链接”后加入 "#" 后再加参数, 不同参数间请使用 "&" 来连接, 如: "https://mysub.com#in=香港+台湾&emoji=1&tfo=1" (如是本地资源引用,请将参数"#in=xxx"填入资源文件第一行) 1️⃣ "节点" 订阅--参数说明: - in, out, 分别为 保留/排除, 多参数用 "+" 连接(逻辑"或"), 逻辑"与"请用"."连接,可直接用中文, 空格用"%20"代替 (如 "in=香港.IPLC.04+台湾&out=香港%20BGP" ); - emoji=1,2 或 -1, 为添加/删除节点名中的 emoji 旗帜 (国行设备请用 emoji=2 ); - udp=1, tfo=1 参数开启 udp-relay 及 fast-open (默认关闭, 此参数对源类型为 QuanX/Surge 的链接无效); - rename 重命名、删除字段, rename=旧名@新名, 以及 "前缀@", "@后缀","删除字段☠️" 用 "+" 连接, 如 "rename=香港@HK+[SS]@+@[1X]+倍率☠️"; - cert=0,跳过证书验证(vmess/trojan),即强制"tls-verification=false"; - tls13=1, 开启 "tls13=true"(vmess/trojan), 请自行确认服务端是否支持; - sort=1 或 sort=-1, 排序参数,分别根据节点名 正序/逆序 排列; - info=1, 开启通知提示流量信息(前提:原订阅链接有返回该信息),默认关闭; - b64=1, 由于QuanX的特性,整体 base64-encode 后导入时,QuanX 会自动解码检查并忽略错误节点(所以可在解析提示"内容无效/invalid..."时,尝试使用此参数) 2⃣️ "rewrite(重写)/filter(分流)" 引用--参数说明: - in, out, 分别为 保留/排除, 多个参数用 "+" 连接(谨慎使用); - 分流规则额外支持 "policy=xx" 参数, 可用于直接指定策略组,或者为 Surge 格式的 rule-set 生成策略组(默认"Shawn"策略组) ⚠️⚠️ 由于商店版本中 rewrite/filter 的 UI 暂时没有提供解析器开关,想使用的请自行去配置文件中的相关行,添加参数"opt-parser=true"以开启,如: https://Advertising.list#policy=MineGroup&out=aweme, tag=🚦去广告,update-interval=86400, opt-parser=true, enabled=true 3⃣️ 通用参数: ntf=1, 用于打开资源解析器的提示通知 (默认关闭), - rewrite/filter 类型则会强制在有 in/out 参数时开启通知提示被删除(禁用)的内容,以防止规则误删除 */ /** * 使用说明, 0️⃣ 在QuantumultX 配置文件中[general] 部分,加入 resource_parser_url=https://raw.githubusercontent.com/KOP-XIAO/QuantumultX/master/Scripts/resource-parser.js ⚠️⚠️如提示"没有自定义解析器",请长按右下角图标后点击左侧刷新按钮,更新资源,后台退出 app,直到出现解析器说明 1️⃣ 假设原始订阅连接为: https://raw.githubusercontent.com/crossutility/Quantumult-X/master/server-complete.txt , 2️⃣ 假设你想要保留的参数为 in=tls+ss, 想要过滤的参数为 out=http+2, 请注意下面订阅链接后一定要加 ”#“ 符号 3️⃣ 则填入 Quanx 节点引用的的总链接为 https://raw.githubusercontent.com/crossutility/Quantumult-X/master/server-complete.txt#in=tls+ss&out=http+2 4️⃣ 填入上述链接, 并打开的资源解析器开关 ------------------------------ ⚠️⚠️ 由于 rewrite/filter 的 UI 中暂时没有提供解析器开关,想使用的请自行去配置文件中的相关行,添加参数"opt-parser=true"以开启,如: https://Advertising.list#policy=Shawn&out=aweme, tag=🚦去广告,update-interval=86400, opt-parser=true, enabled=true */ //$notify("test",$resource.link) var content0=$resource.content; var link0=$resource.link; //$notify(link0,"tt",content0) var para=(link0.indexOf("http")!=-1 && link0.indexOf("://")!=-1)?decodeURIComponent(link0):content0.split("\n")[0]; var mark0=para.indexOf("#")!=-1? true:false; var type0=Type_Check(content0); //$notify(link0,"type",type0) var Pin0=mark0 && para.indexOf("in=")!=-1? para.split("#")[1].split("in=")[1].split("&")[0].split("+"):null; var Pout0=mark0 && para.indexOf("out=")!=-1? para.split("#")[1].split("out=")[1].split("&")[0].split("+"):null; var Pemoji=mark0 && para.indexOf("emoji=")!=-1? para.split("#")[1].split("emoji=")[1].split("&")[0].split("+"):null; var Pudp0=mark0 && para.indexOf("udp=")!=-1? para.split("#")[1].split("udp=")[1].split("&")[0].split("+"):0; var Ptfo0=mark0 && para.indexOf("tfo=")!=-1? para.split("#")[1].split("tfo=")[1].split("&")[0].split("+"):0; var Pinfo=mark0 && para.indexOf("info=")!=-1? para.split("#")[1].split("info=")[1].split("&")[0].split("+"):0; var Prname=mark0 && para.indexOf("rename=")!=-1? para.split("#")[1].split("rename=")[1].split("&")[0].split("+"):null; var Ppolicy=mark0 && para.indexOf("policy=")!=-1? para.split("#")[1].split("policy=")[1].split("&")[0].split("+"):"Shawn"; var Pcert0=mark0 && para.indexOf("cert=")!=-1? para.split("#")[1].split("cert=")[1].split("&")[0].split("+"):1; var Psort0=mark0 && para.indexOf("sort=")!=-1? para.split("#")[1].split("sort=")[1].split("&")[0].split("+"):0; var PTls13=mark0 && para.indexOf("tls13=")!=-1? para.split("#")[1].split("tls13=")[1].split("&")[0].split("+"):0; var Pntf0= mark0 && para.indexOf("ntf=")!=-1? para.split("#")[1].split("ntf=")[1].split("&")[0].split("+"):0; var Pb64= mark0 && para.indexOf("b64=")!=-1? para.split("#")[1].split("b64=")[1].split("&")[0].split("+"):0; var emojino=["0️⃣","1⃣️","2⃣️","3⃣️","4⃣️","5⃣️","6⃣️","7⃣️","8⃣️","9⃣️","🔟"] const subinfo=$resource.info; const subtag=$resource.tag!=undefined? $resource.tag:""; const Base64=new Base64Code(); //响应头流量处理部分 if(Pinfo==1 && subinfo){ var sinfo=subinfo.replace(/ /g,"").toLowerCase(); var total="总流量: "+(parseFloat(sinfo.split("total=")[1].split(",")[0])/(1024**3)).toFixed(2)+"GB"; var usd="已用流量: "+((parseFloat(sinfo.split("upload=")[1].split(",")[0])+parseFloat(sinfo.split("download=")[1].split(",")[0]))/(1024**3)).toFixed(2)+"GB" var left="剩余流量: "+((parseFloat(sinfo.split("total=")[1].split(",")[0])/(1024**3))-((parseFloat(sinfo.split("upload=")[1].split(",")[0])+parseFloat(sinfo.split("download=")[1].split(",")[0]))/(1024**3))).toFixed(2)+"GB" if(sinfo.indexOf("expire=")!=-1){ var epr= new Date(parseFloat(sinfo.split("expire=")[1].split(",")[0])*1000); var year=epr.getFullYear(); // 获取完整的年份(4位,1970) var mth=epr.getMonth()+1 < 10 ? '0'+(epr.getMonth()+1):(epr.getMonth()+1); // 获取月份(0-11,0代表1月,用的时候记得加上1) var day=epr.getDate()<10 ? "0"+(epr.getDate()):epr.getDate(); epr="过期时间: "+year+"-"+mth+"-"+day } else{ epr=""; //"过期时间: ✈️ 未提供該信息" //没过期时间的显示订阅链接 } var message=total+"\n"+usd+", "+left; $notify("流量信息: "+subtag, epr, message) } if(type0=="Subs-B64Encode"){ total=SubsEd2QX(content0,Pudp0,Ptfo0,Pcert0,PTls13); flag=1; }else if(type0=="Subs"){ total=Subs2QX(content0,Pudp0,Ptfo0,Pcert0,PTls13); flag=1; }else if(type0=="QuanX"){ total=isQuanX(content0); flag=1; }else if(type0=="Surge"){ total=Surge2QX(content0); flag=1; }else if(type0=="rewrite"){ flag=2; content0=content0.split("\n"); total=Rewrite_Filter(content0,Pin0,Pout0); }else if(type0=="Rule"){ flag=3; total=content0.split("\n"); total=Rule_Handle(total,Pout0,Pin0); }else if(content0.trim()==""){ $notify("‼️ "+"["+subtag+"]"+" 链接返回內容为空","⁉️请自行复制原始链接到浏览器, 确认链接是否失效",para.split("#")[0]); flag=0; $done({content : ""}) }else if(type0=="unknown"){ $notify("😭 太难写了 "+"["+subtag+"]", "👻 本解析器 暂未支持/未能识别 该订阅格式", "☠️ 已尝试直接导入Quantumult X"); $done({content : content0}); flag=-1; }else { flag=0 } if(flag==3){ $done({content : total.join("\n")}); }else if(flag==2){ $done({content:total.join("\n")}); }else if(flag==1){ if(Pin0||Pout0){ if(Pntf0!=0){ $notify("👥 "+"["+subtag+"]"+" 开始转换节点订阅","🐶 您已添加节点筛选参数,如下","👍️ 保留的关键字:"+Pin0+"\n👎️ 排除的关键字:"+Pout0);} total=filter(total,Pin0,Pout0) } else { if(Pntf0!=0){ $notify("🐷 "+"["+subtag+"]"+" 开始转换节点订阅","🐼️ 如需筛选节点请使用in/out及其他参数,可参考此示范:","👉 https://t.me/QuanXNews/110");} } if(Pemoji){ if(Pntf0!=0){ $notify("🏳️‍🌈 "+"["+subtag+"]"+" 开始更改旗帜 emoji","清除emoji请用参数 -1, 国行设备添加emoji请使用参数 2","你当前所用的参数为 emoji="+Pemoji)}; total=emoji_handle(total,Pemoji); } if(Prname){ if(Pntf0!=0){ $notify("🏳️‍🌈 "+"["+subtag+"]"+" 开始节点重命名","格式为 \"旧名字@新名字\"","你当前所用的参数为"+Prname);} var Prn=Prname; total=total.map(Rename); } if(Psort0==1 || Psort0==-1){ total=QXSort(total,Psort0); } total=TagCheck_QX(total) if(total.length==0){ $notify("‼️ "+"["+subtag+"]"+"无有效节点","⁉️请自行检查原始链接以及过滤参数",para) }; //$notify("Final","List",total) total=total.join("\n"); if(flag==1 && Pb64==1){ total=Base64.encode(total)} $done({content : total}); } //判断订阅类型 function Type_Check(subs){ var type="unknown" var RuleK=["host","domain","ip-cidr","geoip","user-agent","ip6-cidr"]; var QuanXK=["shadowsocks=","trojan=","vmess=","http="]; var SurgeK=["=ss,","=vmess,","=trojan,","=http,","=custom,","=https,"]; var SubK=["dm1lc3M6Ly","c3NyOi8v","dHJvamFu","c3M6Ly"]; var RewriteK=[" url "] var SubK2=["ss://","vmess://","ssr://","trojan://"]; var html="DOCTYPE html" var subi=subs.replace(/ /g,"") const RuleCheck = (item) => subs.toLowerCase().indexOf(item)!=-1; const QuanXCheck = (item) => subi.toLowerCase().indexOf(item)!=-1; const SurgeCheck = (item) => subi.toLowerCase().indexOf(item)!=-1; const SubCheck = (item) => subs.indexOf(item)!=-1; const RewriteCheck = (item) => subs.indexOf(item)!=-1; var subsn=subs.split("\n") //$notify("Subs","cnt",subs) if(subs.indexOf(html)!=-1){ $notify("‼️ 该链接返回内容有误","⁉️ 请自行复制原始链接到浏览器, 确认链接是否失效",link0); type="web"; } else if(SubK.some(SubCheck)){ //b64加密的订阅类型 type="Subs-B64Encode" } else if(subsn.length>=1 && SubK2.some(SubCheck)){ //未b64加密的多行URI 组合订阅 type="Subs" } else if(subi.indexOf("tag=")!=-1 && QuanXK.some(QuanXCheck)){ type="QuanX" } else if(subs.indexOf("[Proxy]")!=-1){ type="Surge"; } else if(SurgeK.some(SurgeCheck)){ type="Surge" } else if(subi.indexOf("hostname=")!=-1 || RewriteK.some(RewriteCheck)){ type="rewrite" } else if(RuleK.some(RuleCheck) && subs.indexOf(html)==-1){ type="Rule"; } return type } function Trim(item){ return item.trim() } //Rewrite过滤,使用+连接多个关键词(逻辑"或"):in 为保留,out 为排除 function Rewrite_Filter(subs,Pin,Pout){ //var subs=subs0.split("\n") var Nlist=[]; var noteK=["//","#",";"]; var hnc=0; var dwrite=[] var hostname="" for(var i=0;i subi.indexOf(item)==0 if(noteK.some(notecheck)){ // 注释项跳过 //console.log("notion") //Nlist.push(subs[i]) continue; }else if(hnc==0 && subii.indexOf("hostname=")==0){ //host name 部分 //console.log("hostname"); hostname=HostNamecheck(subi,Pin,Pout); }else if(subii.indexOf("hostname=")!=0){ //rewrite 部分 var inflag=Rcheck(subi,Pin); var outflag=Rcheck(subi,Pout); if(outflag==1){ dwrite.push(subi); //out 命中 }else if(outflag==0 && inflag!=0){ //out 未命中 && in 未排除 Nlist.push(subi); }else if(outflag==2 && inflag!=0){ //无 out 参数 && in 未排除 Nlist.push(subi); }// } } } if(dwrite.length>0){ $notify("🤖 "+"["+subtag+"]"+" 复写 [rewrite] 过滤关键词为:","🚫 禁用: "+Pout0.join(", "),"☠️ 复写 rewrite 中已禁用以下"+dwrite.length+"个匹配项:"+"\n"+dwrite.join("\n") ) } if(Nlist.length==0){$notify("🤖 "+"["+subtag+"]"+" 筛选关键词为:","✅ 保留: "+Pin+",🚫 删除: "+Pout,"☠️ 筛选后剩余rewrite规则数为 0️⃣ 条, 请检查参数及原始链接" )} if(hostname!=""){Nlist.push(hostname)} return Nlist } // 主机名处理 function HostNamecheck(content,parain,paraout){ var hname=content.replace(/ /g,"").split("=")[1].split(","); var nname=[]; var dname=[]; //删除项 for(i=0;i dd.indexOf(item)!=-1; if(paraout && paraout!=""){ if(!paraout.some(excludehn)){ if(parain && parain!=""){ if(parain.some(excludehn)){ //Pin nname.push(hname[i]) } }else{nname.push(hname[i])} }else{dname.push(hname[i])} }else if(parain && parain!=""){ if(parain.some(excludehn)){ //Pin nname.push(hname[i]) } }else { nname.push(hname[i]) } } //for j hname="hostname="+nname.join(", "); if(dname.length>0){ if(paraout && paraout!=""){ $notify("🤖 "+"["+subtag+"]"+" 复写 [rewrite] 过滤关键词为:","🚫 删除: "+paraout,"☠️ 主机名 hostname 中已删除以下"+dname.length+"个匹配项:"+"\n"+dname.join(",") ) } } if(nname.length==0){ $notify("🤖 "+"["+subtag+"]"+" 复写 [rewrite] 筛选关键词为:","✅ 保留: "+parain+",🚫 删除: "+paraout,"☠️ 主机名 hostname 中剩余项为 0️⃣, 请检查参数及原始链接" ) } return hname } //Rewrite 筛选的函数 function Rcheck(content,param){ name=content.toUpperCase() if(param){ var flag=0; //没命中 const checkpara= (item) => name.indexOf(item.toUpperCase()) !=-1; if(param.some(checkpara)){ flag=1 //命中 } return flag }else { //if param return 2} //无参数 } //分流规则转换及过滤,可用于 surge 及 quanx 的 rule-list function Rule_Handle(subs,Pout,Pin){ cnt=subs //.split("\n"); Tin=Pin; //保留参数 Tout=Pout; //过滤参数 ply=Ppolicy; //策略组 var nlist=[] var RuleK=["//","#",";"]; if(Tout!="" && Tout!=null){ var dlist=[]; for(var i=0;icc.indexOf(item)!=-1; // 删除项 const RuleCheck = (item) => cc.indexOf(item)!=-1; //无视注释行 if(Tout.some(exclude) && !RuleK.some(RuleCheck)){ dlist.push(cnt[i]) } else if(!RuleK.some(RuleCheck) && cc ){ //if Pout.some, 不操作注释项 dd=Rule_Policy(cc); if(Tin!="" && Tin!=null){ const include = (item) =>dd.indexOf(item)!=-1; // 保留项 if(Tin.some(include)){ nlist.push(dd); } }else{nlist.push(dd); } } //else if cc }//for cnt var no=dlist.length if(dlist.length>0){$notify("🤖 "+"["+subtag+"]"+" 分流 [filter] 筛选删除关键词为:","🚫 "+ Tout,"☠️ 已删除以下 "+no+"条匹配规则:"+"\n"+dlist.join("\n")) }else{$notify("🤖 "+"["+subtag+"]"+" 分流 [filter] 筛选删除关键词为:","🚫 "+Tout,"☠️ 没有发现任何匹配项, , 请检查参数或原始链接")} if(Tin!="" && Tin!=null){ //有 in 跟 out 参数时 if(nlist.length>0 ){ $notify("🤖 "+"["+subtag+"]"+" 分流 [filter] 筛选保留关键词为:","✅ "+Tin,"🎯 已保留以下 "+nlist.length+"条匹配规则:"+"\n"+nlist.join("\n")) } else{$notify("🤖 "+"["+subtag+"]"+" 分流 [filter] 筛选关键词为:","✅ 保留:"+Tin+",🚫 删除:"+Tout,"☠️ 筛选后剩余规则数为 0️⃣ 条, 请检查参数及原始链接") } } else {// if Tin (No Tin) if(nlist.length==0 ){ $notify("🤖 "+"["+subtag+"]"+" 分流 [filter] 筛选删除关键词为:","🚫 "+Tout,"☠️ 筛选后剩余规则数为 0️⃣ 条, 请检查参数及原始链接") } } return nlist; } else if(Tin!="" && Tin!=null){ //if Tout var dlist=[]; for(var i=0;i cc.indexOf(item)!=-1; //无视注释行 if(!RuleK.some(RuleCheck) && cc ){ //if Pout.some, 不操作注释项 dd=Rule_Policy(cc); const include = (item) =>dd.indexOf(item)!=-1; // 保留项 if(Tin.some(include)){ nlist.push(dd); } } } // for cnt if(nlist.length>0){ $notify("🤖 "+"["+subtag+"]"+" 分流 [filter] 保留筛选关键词为:","✅ "+Tin,"🎯 已保留以下 "+nlist.length+"条匹配规则:"+"\n"+nlist.join("\n")) } else{$notify("🤖 "+"["+subtag+"]"+" 分流 [filter] 保留筛选关键词为:","✅ "+Tin,"☠️ 筛选后剩余规则数为 0️⃣ 条, 请检查参数及原始链接")} return nlist; } else { //if Tin return cnt.map(Rule_Policy) } } function Rule_Policy(content){ //增加、替换 policy var cnt=content.split(","); var RuleK=["//","#",";"]; var RuleK1=["host","domain","ip-cidr","geoip","user-agent","ip6-cidr"]; const RuleCheck = (item) => cnt[0].toLowerCase().indexOf(item)!=-1; //无视注释行 const RuleCheck1 = (item) => cnt[0].toLowerCase().indexOf(item)!=-1; //无视 quanx 不支持的规则类别 if(RuleK1.some(RuleCheck1)){ if(cnt.length==3 && cnt.indexOf("no-resolve")==-1){ ply0 = Ppolicy!="Shawn"? Ppolicy:cnt[2] nn=cnt[0]+", "+cnt[1]+", "+ply0 } else if(cnt.length==2){ //Surge rule-set ply0 = Ppolicy!="Shawn"? Ppolicy:"Shawn" nn=cnt[0]+", "+cnt[1]+", "+ply0 }else if(cnt.length==3 && cnt[2].indexOf("no-resolve")!=-1){ ply0 = Ppolicy!="Shawn"? Ppolicy:"Shawn" nn=cnt[0]+", "+cnt[1]+", "+ply0+", "+cnt[2] }else if(cnt.length==4 && cnt[3].indexOf("no-resolve")!=-1){ ply0 = Ppolicy!="Shawn"? Ppolicy:cnt[2] nn=cnt[0]+", "+cnt[1]+", "+ply0+", "+cnt[3] }else if(!RuleK.some(RuleCheck)&& content){ $notify("未能解析"+"["+subtag+"]"+"其中部分规则:",content); return "" }else{return ""} if(cnt[0].indexOf("URL-REGEX")!=-1 || cnt[0].indexOf("PROCESS")!=-1){ nn="" } else {nn=nn.replace("IP-CIDR6","ip6-cidr")} return nn } else{return ""}//if RuleK1 check } //混合订阅类型,用于整体进行了 base64 encode 后的类型 function SubsEd2QX(subs,Pudp,Ptfo,Pcert,Ptls13){ var list0=Base64.decode(subs).split("\n"); //$notify("After B64","lists",list0) var QuanXK=["shadowsocks=","trojan=","vmess=","http="]; var SurgeK=["=ss","=vmess","=trojan","=http","=custom"]; var QXlist=[]; for(i=0;i3){ var type=list0[i].split("://")[0].trim() //$notify(type) var listi=list0[i].replace(/ /g,"") const QuanXCheck = (item) => listi.toLowerCase().indexOf(item)!=-1; const SurgeCheck = (item) => listi.toLowerCase().indexOf(item)!=-1; if(type=="vmess"){ node= V2QX(list0[i],Pudp,Ptfo,Pcert,Ptls13) }else if(type=="ssr"){ node= SSR2QX(list0[i],Pudp,Ptfo) }else if(type=="ss"){ node = SS2QX(list0[i],Pudp,Ptfo) }else if(type=="trojan"){ node = TJ2QX(list0[i],Pudp,Ptfo,Pcert,Ptls13) }else if(type=="https"){ //subs,Ptfo,Pcert,Ptls13 node = HPS2QX(list0[i],Ptfo,Pcert,Ptls13) }else if(QuanXK.some(QuanXCheck)){ node = list0[i] }else if(SurgeK.some(SurgeCheck)){ node = Surge2QX(list0[i]) } //$notify("Final","results",node) if(node!=""){ QXlist.push(node)} } } return QXlist } //混合订阅类型,用于未整体进行 base64 encode 的类型 function Subs2QX(subs,Pudp,Ptfo,Pcert,Ptls13){ //$notify("start","cnt",subs) var list0=subs.split("\n"); var QuanXK=["shadowsocks=","trojan=","vmess=","http="]; var SurgeK=["=ss","=vmess","=trojan","=http"]; var QXlist=[]; for(i=0;i3){ var type=list0[i].split("://")[0].trim() var listi=list0[i].replace(/ /g,"") const QuanXCheck = (item) => listi.toLowerCase().indexOf(item)!=-1; const SurgeCheck = (item) => listi.toLowerCase().indexOf(item)!=-1; if(type=="vmess"){ node= V2QX(list0[i],Pudp,Ptfo,Pcert,Ptls13) }else if(type=="ssr"){ node= SSR2QX(list0[i],Pudp,Ptfo) }else if(type=="ss"){ node = SS2QX(list0[i],Pudp,Ptfo) }else if(type=="trojan"){ node = TJ2QX(list0[i],Pudp,Ptfo,Pcert,Ptls13) }else if(type=="https"){ node = HPS2QX(list0[i],Ptfo,Pcert,Ptls13) }else if(QuanXK.some(QuanXCheck)){ node = list0[i] }else if(SurgeK.some(SurgeCheck)){ node = Surge2QX(list0[i]) } if(node!=""){ QXlist.push(node)} } } //$notify("final", "list", QXlist) return QXlist } // 检查节点名字(重复以及空名)等QuanX 不允许的情形 function TagCheck_QX(content){ var Olist=content var Nlist=[] var nmlist=[] var nulllist=[]; //记录空名字节点 var duplist=[]; //记录重名节点 var no=0; for(i=0;i=1){ no= nulllist.length<=10? emojino[nulllist.length]:nulllist.length ; $notify("⚠️ "+"["+subtag+"]"+" 订阅内出现"+no+"个空节点名 ", "✅ 已自动将节点“类型+IP”作为节点名",nulllist.join("\n"))} if(duplist.length>=1){ no= duplist.length<=10? emojino[duplist.length]:duplist.length ; $notify("⚠️ "+"["+subtag+"]"+" 订阅内出现"+no+"个重复节点名 ", "✅ 已自动添加⌘符号作为区分:", duplist.join("\n"))} return Nlist } //http=example.com:443, username=name, password=pwd, over-tls=true, tls-host=example.com, tls-verification=true, tls13=true, fast-open=false, udp-relay=false, tag=http-tls-02 //HTTPS 类型 URI 转换成 QUANX 格式 function HPS2QX(subs,Ptfo,Pcert,Ptls13){ var server=Base64.decode(subs.replace("https://","")).trim().split("\u0000")[0]; var nss=[] if(server!=""){ var ipport="http="+server.split("@")[1].split("#")[0].split("/")[0]; var uname="username="+server.split(":")[0]; var pwd="password="+server.split("@")[0].split(":")[1]; var tag="tag="+server.split("#")[1]; var tls="over-tls=true"; var cert=Pcert!=0? "tls-verification=true":"tls-verification=false"; var tfo=Ptfo==1? "fast-open=true":"fast-open=false"; var tls13=Ptls13==1? "tls13=true":"tls13=false"; nss.push(ipport,uname,pwd,tls,cert,tfo,tls13,tag) } var QX=nss.join(","); return QX //$notify("ts","content",QX) } //V2RayN uri转换成 QUANX 格式 function V2QX(subs,Pudp,Ptfo,Pcert,Ptls13){ var cert=Pcert var tls13=Ptls13 var server=String(Base64.decode(subs.replace("vmess://","")).trim()).split("\u0000")[0]; var nss=[]; if(server!=""){ ss=JSON.parse(server); ip="vmess="+ss.add+":"+ss.port; pwd="password="+ss.id; mtd="method=aes-128-gcm" tag="tag="+decodeURIComponent(ss.ps); udp= Pudp==1? "udp-relay=true":"udp-relay=false"; tfo= Ptfo==1? "fast-open=true":"fast-open=false"; obfs=Pobfs(ss,cert,tls13); if(obfs=="" || obfs==undefined){ nss.push(ip,mtd,pwd,tfo,udp,tag) }else { nss.push(ip,mtd,pwd,obfs,tfo,udp,tag);} QX=nss.join(", "); } return QX } // Vmess obfs 参数 function Pobfs(jsonl,Pcert,Ptls13){ var obfsi=[]; var cert=Pcert; tcert= cert==0? "tls-verification=false":"tls-verification=true"; tls13= Ptls13==1? "tls13=true":"tls13=false" if(jsonl.net=="ws" && jsonl.tls=="tls"){ obfs0="obfs=wss, "+tcert+", "+tls13+", "; uri0= jsonl.path && jsonl.path!=""? "obfs-uri="+jsonl.path:"obfs-uri=/"; host0= jsonl.host && jsonl.host!=""? "obfs-host="+jsonl.host+",":""; obfsi.push(obfs0+host0+uri0) return obfsi.join(", ") }else if(jsonl.net=="ws"){ obfs0="obfs=ws"; uri0= jsonl.path && jsonl.path!=""? "obfs-uri="+jsonl.path:"obfs-uri=/"; host0= jsonl.host && jsonl.host!=""? "obfs-host="+jsonl.host+",":""; obfsi.push(obfs0,host0+uri0); return obfsi.join(", ") }else if(jsonl.tls=="tls"){ obfs0="obfs=over-tls, "+tcert+", "+tls13; uri0=jsonl.path && jsonl.path!=""? "obfs-uri="+jsonl.path:""; host0=jsonl.host && jsonl.host!=""? ", obfs-host="+jsonl.host:""; obfsi.push(obfs0+host0) return obfsi.join(", ") } } ////节点过滤,使用+连接多个关键词:in 为保留,out 为排除 //function filter(Servers,Pin,Pout){ // var NList=[]; // for(var i=0;i name.indexOf(item.toUpperCase()) != -1; // const exclude = (item) => name.indexOf(item.toUpperCase()) != -1; // if(Pin){ // if(Pin.some(include)&&Pout){ // if(!Pout.some(exclude)){ // NList.push(Servers[i]) // } // } else if(Pin.some(include)&&!Pout) {NList.push(Servers[i])} // } else{ // if(!Pout.some(exclude)){ // NList.push(Servers[i]) // } // } // } // } // return NList //} // 判断节点过滤的函数 function Scheck(content,param){ name=content.split("tag=")[1].toUpperCase() if(param){ var flag=0; for(i=0;i name.indexOf(item.toUpperCase()) !=-1; if(params.every(checkpara)){ flag=1 } }//for return flag }else { //if param return 2} } //节点过滤,使用+连接多个关键词(逻辑"或"):in 为保留,out 为排除, "与"逻辑请用符号"."连接 function filter(servers,Pin,Pout){ var Nlist=[]; for(var i=0;itag2? 1:-1 return res } //逆序 function ToTagR(elem1,elem2){ var tag1=emoji_del(elem1.split("tag")[1].split("=")[1].trim()) var tag2=emoji_del(elem2.split("tag")[1].split("=")[1].trim()) res = tag1>tag2? -1:1 return res } //节点重命名 function Rename(str){ var server=str; if(server.indexOf("tag=")!=-1){ hd=server.split("tag=")[0] name=server.split("tag=")[1].trim() for(i=0;i>> 6)) + fromCharCode(0x80 | (cc & 0x3f))) : (fromCharCode(0xe0 | ((cc >>> 12) & 0x0f)) + fromCharCode(0x80 | ((cc >>> 6) & 0x3f)) + fromCharCode(0x80 | ( cc & 0x3f))); } else { var cc = 0x10000 + (c.charCodeAt(0) - 0xD800) * 0x400 + (c.charCodeAt(1) - 0xDC00); return (fromCharCode(0xf0 | ((cc >>> 18) & 0x07)) + fromCharCode(0x80 | ((cc >>> 12) & 0x3f)) + fromCharCode(0x80 | ((cc >>> 6) & 0x3f)) + fromCharCode(0x80 | ( cc & 0x3f))); } }; var re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g; var utob = function(u) { return u.replace(re_utob, cb_utob); }; var cb_encode = function(ccc) { var padlen = [0, 2, 1][ccc.length % 3], ord = ccc.charCodeAt(0) << 16 | ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8) | ((ccc.length > 2 ? ccc.charCodeAt(2) : 0)), chars = [ b64chars.charAt( ord >>> 18), b64chars.charAt((ord >>> 12) & 63), padlen >= 2 ? '=' : b64chars.charAt((ord >>> 6) & 63), padlen >= 1 ? '=' : b64chars.charAt(ord & 63) ]; return chars.join(''); }; var btoa = function(b) { return b.replace(/[\s\S]{1,3}/g, cb_encode); }; // var _encode = function(u) { // var isUint8Array = Object.prototype.toString.call(u) === '[object Uint8Array]'; // return isUint8Array ? u.toString('base64') // : btoa(utob(String(u))); // } this.encode=function(u){ var isUint8Array = Object.prototype.toString.call(u) === '[object Uint8Array]'; return isUint8Array ? u.toString('base64') : btoa(utob(String(u))); } var uriencode = function(u, urisafe) { return !urisafe ? _encode(u) : _encode(String(u)).replace(/[+\/]/g, function(m0) { return m0 == '+' ? '-' : '_'; }).replace(/=/g, ''); }; var encodeURI = function(u) { return uriencode(u, true) }; // decoder stuff var re_btou = /[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3}/g; var cb_btou = function(cccc) { switch(cccc.length) { case 4: var cp = ((0x07 & cccc.charCodeAt(0)) << 18) | ((0x3f & cccc.charCodeAt(1)) << 12) | ((0x3f & cccc.charCodeAt(2)) << 6) | (0x3f & cccc.charCodeAt(3)), offset = cp - 0x10000; return (fromCharCode((offset >>> 10) + 0xD800) + fromCharCode((offset & 0x3FF) + 0xDC00)); case 3: return fromCharCode( ((0x0f & cccc.charCodeAt(0)) << 12) | ((0x3f & cccc.charCodeAt(1)) << 6) | (0x3f & cccc.charCodeAt(2)) ); default: return fromCharCode( ((0x1f & cccc.charCodeAt(0)) << 6) | (0x3f & cccc.charCodeAt(1)) ); } }; var btou = function(b) { return b.replace(re_btou, cb_btou); }; var cb_decode = function(cccc) { var len = cccc.length, padlen = len % 4, n = (len > 0 ? b64tab[cccc.charAt(0)] << 18 : 0) | (len > 1 ? b64tab[cccc.charAt(1)] << 12 : 0) | (len > 2 ? b64tab[cccc.charAt(2)] << 6 : 0) | (len > 3 ? b64tab[cccc.charAt(3)] : 0), chars = [ fromCharCode( n >>> 16), fromCharCode((n >>> 8) & 0xff), fromCharCode( n & 0xff) ]; chars.length -= [0, 0, 2, 1][padlen]; return chars.join(''); }; var _atob = function(a){ return a.replace(/\S{1,4}/g, cb_decode); }; var atob = function(a) { return _atob(String(a).replace(/[^A-Za-z0-9\+\/]/g, '')); }; // var _decode = buffer ? // buffer.from && Uint8Array && buffer.from !== Uint8Array.from // ? function(a) { // return (a.constructor === buffer.constructor // ? a : buffer.from(a, 'base64')).toString(); // } // : function(a) { // return (a.constructor === buffer.constructor // ? a : new buffer(a, 'base64')).toString(); // } // : function(a) { return btou(_atob(a)) }; var _decode=function(u){ return btou(_atob(u)) } this.decode = function(a){ return _decode( String(a).replace(/[-_]/g, function(m0) { return m0 == '-' ? '+' : '/' }) .replace(/[^A-Za-z0-9\+\/]/g, '') ); }; }