/** ☑️ 资源解析器 ©𝐒𝐡𝐚𝐰𝐧 ⟦2020-06-22 11:59⟧ ---------------------------------------------------------- 🚫 发现 𝐁𝐔𝐆 请反馈: @Shawn_KOP_bot ⛳️ 关注 🆃🅶 相关频道: https://t.me/QuanX_API 🗣 🆃🄷🄰🄽🄺🅂 🆃🄾 @Jamie CHIEN, @M**F**, @c0lada 🤖 主要功能: A. 将各格式的服务器订阅解析成 𝐐𝐮𝐚𝐧𝐭𝐮𝐦𝐮𝐥𝐭 𝐗 格式引用 ✔︎ 支持 V2RayN/SSR/SS/SSD/Trojan/QuanX/Surge/https 订阅 ✔︎ 提供说明 1⃣️ 中的可选个性化参数(筛选、重命名 等) B. rewrite(重写) /filter(分流) 的转换&筛选 ✔︎ 用于禁用远程引用中某(几)项 rewrite/hostname/filter ✔︎ Surge 类型规则 list(不含策略组)的解析与使用 ---------------------------------------------------------- 0️⃣ ⟦原始订阅链接⟧ 后加 "#" , 不同参数用 "&" 连接: ⚠️ ☞ https://mysub.com#in=香港+台湾&emoji=1&tfo=1 ❖ 本地资源引用, 请将参数 "#in=xxx.." 填入资源文件第一行 ❖ 1️⃣ ⟦server 节点⟧ ➠ 参数说明: ⦿ info=1, 开启通知提示机场 ✈️ 流量信息(如有提供); ⦿ emoji=1,2 或 -1, 为添加/删除节点名中的地区 emoji 旗帜; ❖ 国行设备请用 emoji=2 ⦿ udp=1, tfo=1, tls13=1, 分别开启 udp-relay/fast-open/tls1.3; ⦿ cert=0, 强制"tls-verification=false" 跳过证书验证; ⦿ in, out, 分别为 保留/删除 节点, 多参数用 "+" 连接(逻辑"或"), 逻辑"与"用 "." 连接; ❖ 可直接用中文, 特殊字符请 urlencode 后使用, 如 ∎ "@"☞"%40", "+"☞"%2B", 空格☞"%20", "&"☞"%26" ❖ 如 "in=香港.IPLC.04+台湾&out=香港%20BGP" ⦿ rename 重命名、删除字段, "旧名@新名", "删除字段1.删除字段2☠️", 以及 "前缀@", "@后缀",用 "+" 连接多个参数; ❖ 如 "rename=香港@HK+[SS]@+@[1X]+倍率.流量☠️" ❖ 如想删除 ".", 请用"rename=.@點+點☠️" 类似操作 ⦿ sort=1, -1, 排序参数, 分别根据节点名 正序/逆序 排列; 2⃣️ ⟦rewrite 重写⟧/⟦filter 分流⟧ ➠ 参数说明: ⦿ in, out, 根据关键词 保留/禁用 相关的规则、重写; ⦿ inhn, outhn, 根据关键词 保留/删除 相关的主机名(hostname); ⦿ 分流规则另有 "policy=xxx" 参数, 可用于直接指定策略组,或为 Surge 类型 rule-set 生成策略组(默认"Shawn"策略组); ⦿ 示范: 禁用某重写引用中的 "淘宝比价 js" 以及 "weibo 的 js" ⚠️ ☞ https://myrewrite.list#out=tb_price.js+wb_ad.js 3⃣️ 通知参数 ntf=0/1, 用于 关闭/打开 资源解析器的提示通知 ⦿ rewrite/filter 默认“开启”通知提示, 以防规则误删除 ⦿ server 资源解析则默认”关闭“通知提示 ---------------------------------------------------------- */ /** * 使用说明, 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️⃣ 填入上述链接, 并打开的资源解析器开关 PS. 隐藏参数 ntf=0/1, 用于关闭/打开资源解析器的提示通知 ⦿ rewrite/filter 资源在有 in/out 参数时会默认开启通知提示, 以防规则误删除 ------------------------------ */ //$notify("test",$resource.link) var content0=$resource.content; var link0=$resource.link; var para=(link0.indexOf("http")!=-1 && link0.indexOf("://")!=-1)? link0:link0+content0.split("\n")[0]; var mark0=para.indexOf("#")!=-1? true:false; var type0=Type_Check(content0); para1=para.slice(para.indexOf("#")+1) //防止参数中其它位置也存在"#" var Pin0=mark0 && para1.indexOf("in=")!=-1? (para1.split("in=")[1].split("&")[0].split("+")).map(decodeURIComponent):null; var Pout0=mark0 && para1.indexOf("out=")!=-1? (para1.split("out=")[1].split("&")[0].split("+")).map(decodeURIComponent):null; //$notify(link0,"type0",para) var Phin0=mark0 && para1.indexOf("inhn=")!=-1? (para1.split("inhn=")[1].split("&")[0].split("+")).map(decodeURIComponent):null; //hostname var Phout0=mark0 && para1.indexOf("outhn=")!=-1? (para1.split("outhn=")[1].split("&")[0].split("+")).map(decodeURIComponent):null; //hostname //$notify(link0,"type1",para) var Pemoji=mark0 && para1.indexOf("emoji=")!=-1? para1.split("emoji=")[1].split("&")[0]:null; var Pudp0=mark0 && para1.indexOf("udp=")!=-1? para1.split("udp=")[1].split("&")[0]:0; var Ptfo0=mark0 && para1.indexOf("tfo=")!=-1? para1.split("tfo=")[1].split("&")[0]:0; var Pinfo=mark0 && para1.indexOf("info=")!=-1? para1.split("info=")[1].split("&")[0]:0; var Prname=mark0 && para1.indexOf("rename=")!=-1? para1.split("rename=")[1].split("&")[0].split("+"):null; var Prrname=mark0 && para1.indexOf("rrname=")!=-1? para1.split("rrname=")[1].split("&")[0].split("+"):null; var Ppolicy=mark0 && para1.indexOf("policy=")!=-1? decodeURIComponent(para1.split("policy=")[1].split("&")[0]):"Shawn"; var Pcert0=mark0 && para1.indexOf("cert=")!=-1? para1.split("cert=")[1].split("&")[0]:1; var Psort0=mark0 && para1.indexOf("sort=")!=-1? para1.split("sort=")[1].split("&")[0]:0; var PTls13=mark0 && para1.indexOf("tls13=")!=-1? para1.split("tls13=")[1].split("&")[0]:0; var Pntf0= mark0 && para1.indexOf("ntf=")!=-1? para1.split("ntf=")[1].split("&")[0]:2; var Pb64= mark0 && para1.indexOf("b64=")!=-1? para1.split("b64=")[1].split("&")[0]:0; var emojino=[" 0️⃣ "," 1⃣️ "," 2⃣️ "," 3⃣️ "," 4⃣️ "," 5⃣️ "," 6⃣️ "," 7⃣️ "," 8⃣️ "," 9⃣️ "," 🔟 "] var pfi=Pin0? "in="+Pin0+", ":"" var pfo=Pout0? "out="+Pout0:"" var pfihn=Phin0? "inhn="+Phin0+", ":"" var pfohn=Phout0? "outhn="+Phout0:"" const subinfo=$resource.info; const subtag=$resource.tag!=undefined? $resource.tag:""; const Base64=new Base64Code(); const escapeRegExp = str => str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); //处理特殊符号以便正则匹配使用 const qxpng="https://raw.githubusercontent.com/crossutility/Quantumult-X/master/quantumult-x.png" var subinfo_link = {"open-url": "https://t.me/QuanX_API", "media-url" :"https://shrtm.nu/ebAr"}; var rwrite_link = {"open-url":link0.split("#")[0], "media-url": "https://shrtm.nu/G2Xn"} var rule_link={"open-url":link0.split("#")[0], "media-url": "https://shrtm.nu/7eiK"} var nan_link={"open-url":link0.split("#")[0], "media-url": qxpng} var sub_link={"open-url":link0.split("#")[0], "media-url": "https://shrtm.nu/ebAr"} //$notify(link0,type0,content0) //响应头流量处理部分 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,subinfo_link) } 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],nan_link); flag=0; $done({content : ""}) }else if(type0=="unknown"){ $notify("😭 太难写了, 可能是 bug ⁉️ "+"⟦"+subtag+"⟧", "👻 本解析器 暂未支持/未能识别 该订阅格式", "⚠️ 已尝试直接导入Quantumult X",nan_link); $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){ total=Filter(total,Pin0,Pout0) } // else { // if(Pntf0!=0){ // $notify("👥 引用"+"⟦"+subtag+"⟧"+" 开始转换节点订阅","🐼️ 如需筛选节点请使用in/out及其他参数,可参考此示范:","👉 https://t.me/QuanXNews/110",sub_link);} // } if(Prrname){ // if(Pntf0!=0){ // $notify("👥 引用"+"⟦"+subtag+"⟧"+" 开始节点重命名","⚠️ 格式为 \"旧名@新名\",\"删除字段☠️\",及 \"前缀@\",\"@后缀\"","👉 当前添加参数为: "+Prrname, sub_link);} var Prn=Prrname; total=total.map(Rename); } if(Pemoji){ // if(Pntf0!=0){ // $notify("👥 引用"+"⟦"+subtag+"⟧"+" 开始更改旗帜 emoji","⚠️ 清除emoji请用参数 -1, 国行设备添加emoji请使用参数 2","👉 当前添加参数为: emoji="+Pemoji,sub_link)}; total=emoji_handle(total,Pemoji); } if(Prname){ // if(Pntf0!=0){ // $notify("👥 引用"+"⟦"+subtag+"⟧"+" 开始节点重命名","⚠️ 格式为 \"旧名@新名\",\"删除字段☠️\",及 \"前缀@\",\"@后缀\"","👉 当前添加参数为: "+Prname,sub_link);} 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,nan_link) // }; //$notify("Final","List",total) total=total.join("\n"); if(flag==1){ total=Base64.encode(total)} //强制 base64 $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=["dm1lc3M","c3NyOi8v","dHJvamFu","c3M6Ly","c3NkOi8v"]; var RewriteK=[" url "] var SubK2=["ss://","vmess://","ssr://","trojan://","ssd://"]; 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,nan_link); 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=(Phin0||Phout0)? HostNamecheck(subi,Phin0,Phout0):subi;//hostname 部分 }else if(subii.indexOf("hostname=")!=0){ //rewrite 部分 var inflag=Rcheck(subi,Pin); //if(inflag==1){$notify(inflag)} var outflag=Rcheck(subi,Pout); if(outflag==1 || inflag==0){ //$notify("a","b",subi) 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 && Pntf0!=0){ nowrite=dwrite.length<=10?emojino[dwrite.length]:dwrite.length $notify("🤖 "+"重写引用 ➟ "+"⟦"+subtag+"⟧","⛔️ 筛选参数: "+pfi+pfo,"☠️ 重写 rewrite 中已禁用以下"+nowrite+"个匹配项:"+"\n ⨷ "+dwrite.join("\n ⨷ "),rwrite_link ) } if(Nlist.length==0){$notify("🤖 "+"重写引用 ➟ "+"⟦"+subtag+"⟧","⛔️ 筛选参数: "+pfi+pfo,"⚠️ 筛选后剩余rewrite规则数为 0️⃣ 条, 请检查参数及原始链接",nan_link)} 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!=""){ //存在 out 参数时 if(!paraout.some(excludehn)){ //out 未命中🎯️ if(parain && parain!=""){ if(parain.some(excludehn)){ //Pin 命中🎯️ nname.push(hname[i]) } else{$notify("..xx") dname.push(hname[i])} //Pin 未命中🎯️的记录 }else{nname.push(hname[i])} //无in 参数 }else{dname.push(hname[i])} //out 参数命中 }else if(parain && parain!=""){ //不存在 out,但有 in 参数时 if(parain.some(excludehn)){ //Pin 命中🎯️ nname.push(hname[i]) }else{dname.push(hname[i])} }else { nname.push(hname[i]) } } //for j hname="hostname="+nname.join(", "); // $notify(hname,dname) if(dname.length>0 && Pntf0!=0){ if(paraout || parain && Pntf0!=0){ var noname=dname.length<=10?emojino[dname.length]:dname.length $notify("🤖 "+"重写引用 ➟ "+"⟦"+subtag+"⟧","⛔️ 筛选参数: "+pfihn+pfohn,"☠️ 主机名 hostname 中已删除以下"+noname+"个匹配项:"+"\n ⨷ "+dname.join(","),rwrite_link ) } } if(nname.length==0){ $notify("🤖 "+"重写引用 ➟ "+"⟦"+subtag+"⟧","⛔️ 筛选参数: "+pfihn+pfohn,"⚠️ 主机名 hostname 中剩余 0️⃣ 项, 请检查参数及原始链接",nan_link ) } 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){ // 有 out 参数时 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(Rule_Policy("-"+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<=10?emojino[dlist.length]:dlist.length if(dlist.length>0 ){ if(Pntf0!=0){$notify("🤖 "+"分流引用 ➟ "+"⟦"+subtag+"⟧","⛔️ 禁用: "+ Tout,"☠️ 已禁用以下"+no+"条匹配规则:"+"\n ⨷ "+dlist.join("\n ⨷ "),rule_link)} }else{$notify("🤖 "+"分流引用 ➟ "+"⟦"+subtag+"⟧","⛔️ 禁用: "+Tout,"⚠️ 未发现任何匹配项, 请检查参数或原始链接",nan_link)} if(Tin!="" && Tin!=null){ //有 in 跟 out 参数时 if(nlist.length>0 ){ var noin0=nlist.length<=10?emojino[nlist.length]:nlist.length if(Pntf0!=0){ $notify("🤖 "+"分流引用 ➟ "+"⟦"+subtag+"⟧","✅ 保留:"+Tin,"🎯 已保留以下 "+noin0+"条匹配规则:"+"\n ⨁ "+nlist.join("\n ⨁ "),rule_link)} } else{$notify("🤖 "+"分流引用 ➟ "+"⟦"+subtag+"⟧","✅ 保留:"+Tin+",⛔️ 禁用: "+Tout,"⚠️ 筛选后剩余规则数为 0️⃣ 条, 请检查参数及原始链接",nan_link) } } else {// if Tin (No Tin) if(nlist.length==0 ){ $notify("🤖 "+"分流引用 ➟ "+"⟦"+subtag+"⟧","⛔️ 禁用: "+Tout,"⚠️ 筛选后剩余规则数为 0️⃣ 条, 请检查参数及原始链接",nan_link) } } return [...dlist,...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); }else{dlist.push("-"+dd)} } } // for cnt if(nlist.length>0){ var noin=nlist.length<=10?emojino[nlist.length]:nlist.length if(Pntf0!=0){ $notify("🤖 "+"分流引用 ➟ "+"⟦"+subtag+"⟧","✅ 保留:"+Tin,"🎯 已保留以下 "+noin+"条匹配规则:"+"\n ⨁ "+nlist.join("\n ⨁ "),rule_link)} } else{$notify("🤖 "+"分流引用 ➟ "+"⟦"+subtag+"⟧","✅ 保留:"+Tin,"⚠️ 筛选后剩余规则数为 0️⃣ 条, 请检查参数及原始链接",nan_link)} return [...dlist,...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,nan_link); 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=="ssd"){ node = SSD2QX(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 instanceof Array){ for (var j in node) { QXlist.push(node[j]) } }else 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 ⨁ "),nan_link)} if(duplist.length>=1){ no= duplist.length<=10? emojino[duplist.length]:duplist.length ; $notify("⚠️ 引用"+"⟦"+subtag+"⟧"+" 内有"+no+"个重复节点名 ", "✅ 已添加⌘符号作为区分:", " ⨁ "+duplist.join("\n ⨁ "),nan_link)} 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(", ") } } // 判断节点过滤的函数 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=[]; var Delist=[]; var Nname=[] for(var i=0;i0){ $notify("👥 引用"+"⟦"+subtag+"⟧"+" 开始节点筛选","🕹 筛选关键字: "+pfi+pfo, "☠️ 已删除以下 "+no+"个节点\n"+Delist.join(", "),sub_link); } }else if(no1==0){ //无剩余节点时强制通知 $notify("‼️ ⟦"+subtag+"⟧"+"筛选后节点数为0⃣️","⚠️ 请自行检查原始链接以及筛选参数", link0, sub_link);} return Nlist } //SSR 类型 URI 转换 quanx 格式 function SSR2QX(subs,Pudp,Ptfo){ var nssr=[] var cnt=Base64.decode(subs.split("ssr://")[1].replace(/-/g,"+").replace(/_/g,"/")).split("\u0000")[0] var obfshost = ''; var oparam = ''; if(cnt.split(":").length<=6) { //排除难搞的 ipv6 节点 type="shadowsocks="; ip=cnt.split(":")[0]+":"+cnt.split(":")[1]; pwd="password="+Base64.decode(cnt.split("/?")[0].split(":")[5].replace(/-/g,"+").replace(/_/g,"/")).split("\u0000")[0]; mtd="method="+cnt.split(":")[3]; obfs="obfs="+cnt.split(":")[4]+", "; ssrp="ssr-protocol="+cnt.split(":")[2]; if(cnt.indexOf("obfsparam=")!=-1){ obfshost=cnt.split("obfsparam=")[1].split("&")[0]!=""? "obfs-host="+Base64.decode(cnt.split("obfsparam=")[1].split("&")[0].replace(/-/g,"+").replace(/_/g,"/")).split(",")[0].split("\u0000")[0]+", ":"" } if(cnt.indexOf("protoparam=")!=-1){ oparam=cnt.split("protoparam=")[1].split("&")[0]!=""? "ssr-protocol-param="+Base64.decode(cnt.split("protoparam=")[1].split("&")[0].replace(/-/g,"+").replace(/_/g,"/")).split(",")[0].split("\u0000")[0]+", ":"" } tag="tag="+(Base64.decode(cnt.split("remarks=")[1].split("&")[0].replace(/-/g,"+").replace(/_/g,"/"))).split("\u0000")[0] //console.log(Base64.decode(cnt.split("remarks=")[1].split("&")[0].replace(/-/g,"+").replace(/_/g,"/"))) pudp= Pudp==1? "udp-relay=true":"udp-relay=false"; ptfo= Ptfo==1? "fast-open=true":"fast-open=false"; nssr.push(type+ip,pwd,mtd,obfs+obfshost+oparam+ssrp,pudp,ptfo,tag) QX=nssr.join(", ") } return QX; } //Trojan 类型 URI 转换成 QX function TJ2QX(subs,Pudp,Ptfo,Pcert,Ptls13){ var ntrojan=[] var cnt=subs.split("trojan://")[1] type="trojan="; if(cnt.indexOf(":443")!=-1){ ip=cnt.split("@")[1].split(":443")[0]+":443"; }else{ ip=cnt.split("@")[1].split("?")[0].split("\n")[0].trim(); //非 443 端口的奇葩机场? } pwd="password="+cnt.split("@")[0]; obfs="over-tls=true"; pcert= cnt.indexOf("allowInsecure=0")!= -1? "tls-verification=true":"tls-verification=false"; ptls13= Ptls13==1?"tls13=true":"tls13=false" if(Pcert==0){pcert="tls-verification=false"} pudp= Pudp==1? "udp-relay=true":"udp-relay=false"; ptfo= Ptfo==1? "fast-open=true":"fast-open=false"; tag=cnt.indexOf("#")!=-1? "tag="+decodeURIComponent(cnt.split("#")[1]):"tag= [trojan]"+ip ntrojan.push(type+ip,pwd,obfs,pcert,ptls13,pudp,ptfo,tag) QX=ntrojan.join(", "); return QX; } //SS 类型 URI 转换 quanx 格式 function SS2QX(subs,Pudp,Ptfo){ var nssr=[] var cnt=subs.split("ss://")[1] //$notify("SS转换 ing","SS",cnt) if(cnt.split(":").length<=6) { //排除难搞的 ipv6 节点 type="shadowsocks="; if(cnt.indexOf("@")!=-1){ ip=cnt.split("@")[1].split("#")[0].split("/")[0]; pwdmtd=Base64.decode(cnt.split("@")[0].replace(/-/g,"+").replace(/_/g,"/")).split("\u0000")[0].split(":") }else{ var cnt0=Base64.decode(cnt.split("#")[0].replace(/-/g,"+").replace(/_/g,"/").split("\u0000")[0]); ip=cnt0.split("@")[1].split("#")[0].split("/")[0]; pwdmtd=cnt0.split("@")[0].split(":") } pwd="password="+pwdmtd[1]; mtd="method="+pwdmtd[0]; obfs= cnt.split("obfs%3D")[1]!=null ? ", obfs="+cnt.split("obfs%3D")[1].split("%3B")[0]: ""; obfshost=cnt.split("obfs-host%3D")[1]!=null ? ", obfs-host="+cnt.split("obfs-host%3D")[1].split("&")[0].split("#")[0]:""; tag="tag="+decodeURIComponent(cnt.split("#")[1]) pudp= Pudp==1? "udp-relay=true":"udp-relay=false"; ptfo= Ptfo==1? "fast-open=true":"fast-open=false"; nssr.push(type+ip,pwd,mtd+obfs+obfshost,pudp,ptfo,tag) QX=nssr.join(", ") return QX; //console.log(QX) } } //SSD 类型 URI 转换 quanx 格式 function SSD2QX(subs,Pudp,Ptfo){ var j=0 var QX=[] var cnt=JSON.parse(Base64.decode(subs.split("ssd://")[1])) var type="shadowsocks="; var pwd="password="+cnt.password; var mtd="method="+cnt.encryption; var obfs="" var obfshost="" var port=cnt.port? ":"+cnt.port:"" if(cnt.plugin_options){ obfs=cnt.plugin_options.split(";")[0]!=null ? ", "+cnt.plugin_options.split(";")[0]: ""; obfshost=cnt.plugin_options.split(";")[1]!=null ? ", "+cnt.plugin_options.split(";")[1]: ""; } pudp= Pudp==1? "udp-relay=true":"udp-relay=false"; ptfo= Ptfo==1? "fast-open=true":"fast-open=false"; for (var i in cnt.servers) { ip=cnt.servers[i].server; if(obfs==""){ obfs=cnt.servers[i].plugin_options.split(";")[0]!=null ? ", "+cnt.plugin_options.split(";")[0]: ""; obfshost=cnt.servers[i].plugin_options.split(";")[1]!=null ? ", "+cnt.plugin_options.split(";")[1]: ""; } if(ip.indexOf(".")>0){ //排除难搞的 ipv6 节点 port=cnt.servers[i].port?":"+cnt.servers[i].port:port; tag="tag="+cnt.servers[i].remarks; QX[j]=type+ip+port+", "+pwd+", "+mtd+obfs+obfshost+", "+pudp+", "+ptfo+", "+tag; var j=j+1; } } //$notify("QX","tst",QX) return QX; //console.log(QX) } // 用于过滤非节点部分(比如整份配置中其它内容) function isQuanX(content){ var cnts=content.split("\n"); 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, '') ); }; }