diff --git a/Scripts/resource-parser.js b/Scripts/resource-parser.js new file mode 100644 index 0000000..17c13be --- /dev/null +++ b/Scripts/resource-parser.js @@ -0,0 +1,1111 @@ +/** +# Quantumult X 资源解析器 (2020-05-22: 19:59 ) + +本资源解析器作者: Shawn(请勿私聊问怎么用),有bug请反馈: @Shawn_KOP_bot +更新请关注tg频道: https://t.me/QuanX_API + +主要功能: 将各类服务器订阅解析成 QuantumultX 格式引用(支持 V2RayN/SSR/SS/Trojan/QuanX(conf&list)/Surge(conf&list)格式),并提供 1⃣️ 中的可选参数; + +附加功能: rewrite(重写) /filter(分流) 过滤, 可用于解决无法单独禁用远程引用中某(几)条 rewrite/hostname/filter, 以及直接导入 Surge 类型规则 list 的问题 + +0️⃣ 请在“订阅链接”后加入 "#" 后再加参数, 不同参数间请使用 "&" 来连接, 如: +"https://mysub.com#in=香港+台湾&emoji=1&tfo=1" + +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, 开启通知提示流量信息(前提:原订阅链接有返回该信息),默认关闭 + +2⃣️ "rewrite(重写)/filter(分流)" 引用--参数说明: +- 参数为 "out=xxx", 多个参数用 "+" 连接; +- 分流规则额外支持 "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 类型则会强制在有 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 + */ +var content0=$resource.content; +var para=decodeURIComponent($resource.link); +var type0=Type_Check(content0); +var Pin0=para.indexOf("in=")!=-1? para.split("#")[1].split("in=")[1].split("&")[0].split("+"):null; +var Pout0=para.indexOf("out=")!=-1? para.split("#")[1].split("out=")[1].split("&")[0].split("+"):null; +var Pemoji=para.indexOf("emoji=")!=-1? para.split("#")[1].split("emoji=")[1].split("&")[0].split("+"):null; +var Pudp0=para.indexOf("udp=")!=-1? para.split("#")[1].split("udp=")[1].split("&")[0].split("+"):0; +var Ptfo0=para.indexOf("tfo=")!=-1? para.split("#")[1].split("tfo=")[1].split("&")[0].split("+"):0; +var Pinfo=para.indexOf("info=")!=-1? para.split("#")[1].split("info=")[1].split("&")[0].split("+"):0; +var Prname=para.indexOf("rename=")!=-1? para.split("#")[1].split("rename=")[1].split("&")[0].split("+"):null; +var Ppolicy=para.indexOf("policy=")!=-1? para.split("#")[1].split("policy=")[1].split("&")[0].split("+"):"Shawn"; +var Pcert0=para.indexOf("cert=")!=-1? para.split("#")[1].split("cert=")[1].split("&")[0].split("+"):1; +var Psort0=para.indexOf("sort=")!=-1? para.split("#")[1].split("sort=")[1].split("&")[0].split("+"):0; +var PTls13=para.indexOf("tls13=")!=-1? para.split("#")[1].split("tls13=")[1].split("&")[0].split("+"):0; +var Pntf0= para.indexOf("ntf=")!=-1? para.split("#")[1].split("ntf=")[1].split("&")[0].split("+"):0; +//$notify(type0) + +//响应头流量处理部分 +var subinfo=$resource.info; +var subtag=$resource.tag; +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="订阅链接: "+para.split("#")[0] //没过期时间的显示订阅链接 + } + 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,Pout0); +}else if(type0=="Rule"){ + flag=3; + total=content0.split("\n"); + total=Rule_Handle(total,Pout0); +}else if(content0.trim()==""){ + $notify("‼️链接內容为空","⁉️请自行检查原始链接以及过滤参数",para); + flag=0; + $done({content : ""}) +}else { + $notify("😭 太难写了", "👻 本解析器 暂未支持/未能识别 该订阅格式", "☠️ 已尝试直接导入Quantumult X"); + $done({content : content0}); + flag=-1; +} + +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("👥 开始转换节点订阅:","🐶 您已添加节点筛选参数,如下","👍️ 保留的关键字:"+Pin0+"\n👎️ 排除的关键字:"+Pout0);} + total=filter(total,Pin0,Pout0) + } else { + if(Pntf0!=0){ + $notify("🐷 开始转换节点订阅","🐼️ 如需筛选节点请使用in/out及其他参数,可参考此示范:","👉 https://t.me/QuanXNews/110");} + } + if(Pemoji){ + if(Pntf0!=0){ + $notify("🏳️‍🌈 开始更改旗帜 emoji","清除emoji请用参数 -1, 国行设备添加emoji请使用参数 2","你当前所用的参数为 emoji="+Pemoji)}; + total=emoji_handle(total,Pemoji); + } + if(Prname){ + if(Pntf0!=0){ + $notify("🏳️‍🌈 开始节点重命名","格式为 \"旧名字@新名字\"","你当前所用的参数为"+Prname);} + var Prn=Prname; + total=total.map(Rename); + } + if(Psort0==1 || Psort0==-1){ + total=QXSort(total,Psort0); + } +<<<<<<< HEAD +<<<<<<< HEAD + total=TagCheck_QX(total) + if(total.length==0){ +======= + total=TagCheck_QX(total); + if(total.length==0){ +>>>>>>> 2fc50cc2f6ffcbbbf80aaff57172fb8feecfe39a +======= + total=TagCheck_QX(total); + if(total.length==0){ +>>>>>>> 2fc50cc2f6ffcbbbf80aaff57172fb8feecfe39a + $notify("‼️无有效节点","⁉️请自行检查原始链接以及过滤参数",para) + }; + $done({content : total.join("\n")}); +} + + +//判断订阅类型 +function Type_Check(subs){ + var type="" + var RuleK=["host","domain","ip-cidr","geoip","user-agent","ip6-cidr"]; + var QuanXK=["shadowsocks=","trojan=","vmess=","http="]; + var SurgeK=["=ss","=vmess","=trojan","=http","=custom"]; + var SubK=["dm1lc3M6Ly","c3NyOi8v","dHJvamFu","c3M6Ly"]; + var SubK2=["ss://","vmess://","ssr://","trojan://"]; + 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; + var subsn=subs.split("\n") + 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(subs.indexOf("hostname")!=-1){ + type="rewrite" + } else if(RuleK.some(RuleCheck)){ + type="Rule"; + } + return type +} + +function Trim(item){ + return item.trim() + } +//删除 rewrite 引用中的某部分 +function Rewrite_Filter(subs,Pout){ + cnt=subs; + nlist=[]; + drewrite=[]; + if(Pout!="" && Pout){ + Pout=Pout.map(Trim); + for(var i=0;i cc.indexOf(item)!=-1; + if(Pout.some(exclude)){ + if(cc.indexOf("hostname")!=-1 && cc.indexOf("=")!=-1){ //hostname 部分 + nname=[];//保留项 + dname=[];//删除项目 + hname=cc.split("=")[1].split(","); + for(var j=0;j dd.indexOf(item)!=-1; + if(!Pout.some(excludehn)){ + nname.push(hname[j]) + }else{dname.push(hname[j])} + } //for j + hname="hostname="+nname.join(", "); + //console.log(hname) + nlist.push(hname) + if(dname.length>0){$notify("🤖 您添加的[rewrite]过滤关键词为:"+Pout0.join(", "),"☠️ 主机名 hostname 中已为您删除以下"+dname.length+"个匹配项",dname.join(",") )} + } // if cc -hostname + else{ + drewrite.push(cc); + nlist.push(cc.replace(/ url /g," - ")); + } + }else{ //if Pout.some + nlist.push(cc) + } //else + } + }//cnt for + if(drewrite.length>0){$notify("🤖 您添加的[rewrite]过滤关键词为:"+Pout0.join(", "),"☠️ 复写 rewrite 中已为您禁用以下"+drewrite.length+"个匹配项",drewrite.join("\n") )}; + return nlist + }else { // Pout if +//$notify("no filter at all") + return cnt;} +} + +//分流规则转换及过滤,可用于 surge 及 quanx 的 rule-list +function Rule_Handle(subs,Pout){ + cnt=subs //.split("\n"); + out=Pout; //过滤参数 + ply=Ppolicy; //策略组 + var nlist=[] + var RuleK=["//","#",";"]; + if(Pout!="" && Pout!=null){ + var dlist=[]; + for(var i=0;icc.indexOf(item)!=-1; + const RuleCheck = (item) => cc.indexOf(item)!=-1; //无视注释行 + if(Pout.some(exclude) && !RuleK.some(RuleCheck)){ + dlist.push(cnt[i]) + } else if(!RuleK.some(RuleCheck) && cc){ //if Pout.some, 不操作注释项 + dd=Rule_Policy(cc); + nlist.push(dd); + } + }//for cnt + var no=dlist.length + if(dlist.length>0){$notify("🤖 您添加的分流 [filter] 过滤关键词为:"+out,"☠️ 已为您删除以下 "+no+"条匹配规则", dlist.join("\n")) + }else{$notify("🤖 您添加的[filter]过滤关键词为:"+out,"☠️ 没有发现任何匹配项",dlist)} + return nlist + } else{return cnt.map(Rule_Policy)}//if Pout +} + +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("未能解析其中部分规则",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){ + const $base64 = new Base64() + var list0=$base64.decode(subs).split("\n"); + var QuanXK=["shadowsocks=","trojan=","vmess=","http="]; + var SurgeK=["=ss","=vmess","=trojan","=http","=custom"]; + var QXlist=[]; + var node="" + 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(QuanXK.some(QuanXCheck)){ + node = list0[i] + }else if(SurgeK.some(SurgeCheck)){ + node = Surge2QX(list0[i]) + } + //$notify("Final","results",node) + QXlist.push(node) + } + } + return QXlist +} + +//混合订阅类型,用于未整体进行 base64 encode 的类型 +function Subs2QX(subs,Pudp,Ptfo,Pcert,Ptls13){ + //const $base64 = new Base64() + var list0=subs.split("\n"); + var QuanXK=["shadowsocks=","trojan=","vmess=","http="]; + var SurgeK=["=ss","=vmess","=trojan","=http"]; + var QXlist=[]; + var node="" + for(i=0;i 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(QuanXK.some(QuanXCheck)){ + node = list0[i] + }else if(SurgeK.some(SurgeCheck)){ + node = Surge2QX(list0[i]) + } + QXlist.push(node) + } + return QXlist +} + +// 检查节点名字(重复以及空名)等QuanX 不允许的情形 +function TagCheck_QX(content){ + var Olist=content + var Nlist=[] + var nmlist=[] + for(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] + for(i=0;i> 2); + out += base64EncodeChars.charAt((c1 & 0x3) << 4); + out += "=="; + break; + } + + + c2 = str.charCodeAt(i++); + + // 当最后剩余两个字节时 + if(i == len){ + out += base64EncodeChars.charAt(c1 >> 2); + out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)); + out += base64EncodeChars.charAt((c2 & 0xF) << 2); + out += "="; + break; + } + + //当剩余字节数大于等于3时 + c3 = str.charCodeAt(i++); + out += base64EncodeChars.charAt(c1 >> 2); + out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)); + out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6)); + out += base64EncodeChars.charAt(c3 & 0x3F); + } + return out; + } + + /** + * Base64解码函数 + * @param str + * @returns {*} + */ + function base64decode(str){ + var c1, c2, c3, c4; + var i, len, out; + + len = str.length; + i = 0; + out = ""; + while(i < len){ + /* 得到第一个字符 c1 + * 并过虑掉前后所有与Base64编码无关的字符 + * */ + do{ + c1 = base64DecodeChars[str.charCodeAt(i++) & 0xff]; + }while(i < len && c1 == -1); + + // 如果已经到达字符串结尾,并最后还未得到有效的Base64编码字符就结尾循环 + if(c1 == -1) + break; + + /* 得到字符 c2 + * 并过滤掉所有与Base64编码无关的字符 + */ + do{ + c2 = base64DecodeChars[str.charCodeAt(i++) & 0xff]; + }while(i < len && c2 == -1); + + // 如果已经到达字符串结尾,并最后还未得到有效的Base64编码字符就结尾循环 + if(c2 == -1) + break; + + // 根据Base64编码的 c1 和 c2 解码得到一个编码前的字符 + out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4)); + + /* 得到字符 c3 + * 并过滤掉所有与Base64编码无关的字符 + * 如果获取的 c3 是 '=' 字符则说明已经解码完成,返回解码得到的字符串 + */ + do{ + c3 = str.charCodeAt(i++) & 0xff; + if(c3 == 61) + return out; + c3 = base64DecodeChars[c3]; + }while(i < len && c3 == -1); + + // 如果已经到达字符串结尾,并最后还未得到有效的Base64编码字符就结尾循环 + if(c3 == -1) + break; + + // 根据Base64编码的 c2 和 c3 解码得到一个编码前的字符 + out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2)); + + /* 这一步就比较复杂了 + * 先是尝试获取第四个Base64 编码的字符 c4 + * 如果获取的 c4 是 '=' 字符则说明已经解码完成,返回解码得到的字符串 + * */ + do{ + c4 = str.charCodeAt(i++) & 0xff; + if(c4 == 61) + return out; + c4 = base64DecodeChars[c4]; + }while(i < len && c4 == -1); + + // 如果已经到达字符串结尾,并最后还未得到有效的Base64编码字符就结尾循环 + if(c4 == -1) + break; + + // 根据Base64编码的 c3 和 c4 解码得到一个编码前的字符 + out += String.fromCharCode(((c3 & 0x03) << 6) | c4); + } + return out; + } + + /** + * 把 unicode 码转换成 utf8 编码 + * @param str + * @returns {string} + */ + function unicodeToUtf8(str){ + var out, i, len, c; + + out = ""; + len = str.length; + for(i = 0; i < len; i++){ + c = str.charCodeAt(i); + + // 兼容 ASCII + if((c >= 0x0001) && (c <= 0x007F)){ + out += str.charAt(i); + }else if(c > 0x07FF){ + // 占三个字节的 utf8 + out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F)); + out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F)); + out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F)); + }else{ + // 占两个字节的 utf8 + out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F)); + out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F)); + } + } + return out; + } + + /** + * 把 utf8 编码转换成 unicode 码 + * @param str + * @returns {string} + */ + function utf8ToUnicode(str){ + var out, i, len, c; + var char2, char3; + + out = ""; + len = str.length; + i = 0; + while(i < len){ + c = str.charCodeAt(i++); + switch(c >> 4){ + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + // 0xxxxxxx ASCII 编码 + out += str.charAt(i - 1); + break; + case 12: + case 13: + // 110x xxxx 10xx xxxx + // 占两个字节的 utf8 + char2 = str.charCodeAt(i++); + out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F)); + break; + case 14: + // 1110 xxxx 10xx xxxx 10xx xxxx + // 占三个字节的 utf8 + char2 = str.charCodeAt(i++); + char3 = str.charCodeAt(i++); + out += String.fromCharCode(((c & 0x0F) << 12) | + ((char2 & 0x3F) << 6) | + ((char3 & 0x3F) << 0)); + break; + } + } + + return out; + } + + /** + * 转成 十六 进制编码 + * @param str + * @returns {string} + * @constructor + */ + function CharToHex(str){ + var out, i, len, c, h; + out = ""; + len = str.length; + i = 0; + while(i < len){ + c = str.charCodeAt(i++); + + // 把数据转换成十六进制的字符串 + h = c.toString(16); + if(h.length < 2) + h = "0" + h; + + out += "\\x" + h + " "; + if(i > 0 && i % 8 == 0) + out += "\r\n"; + } + + return out; + } + + this.encode=function(str){ + // 普通 Base64 编码 + return base64encode(unicodeToUtf8(str)); + }; + this.decode=function(str){ + // 普通 Base64 编码 + return utf8ToUnicode(base64decode(str)); + }; +// base64={ +// encode:function(str){ +// // 普通 Base64 编码 +// return base64encode(unicodeToUtf8(str)); +// }, +// encodeUrl:function(str){ +// // 使用 Base64 编码字符串 +// return base64encode(unicodeToUtf8(str),1) +// }, +// decode:function(str){ +// // 兼容的 Base64 解码 +// return utf8ToUnicode(base64decode(str)); +// }, +// encodeToHex:function(str){ +// // 普通 Base64 编码 以十六进制显示 +// return CharToHex(base64encode(unicodeToUtf8(str))); +// }, +// encodeUrlToHex:function(str){ +// // 使用 Base64 编码 url 以十六进制显示 +// return CharToHex(base64encode(unicodeToUtf8(str),1)); +// } +// } +}; \ No newline at end of file