MutliQRCode

PC端和移动端都可用的二维码识别

目前为 2023-05-28 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name MutliQRCode
  3. // @namespace https://greasyfork.org/zh-CN/users/1073349
  4. // @version 0.3.1
  5. // @description PC端和移动端都可用的二维码识别
  6. // @author 4ehex
  7. // @match *://*/*
  8. // @grant GM_addElement
  9. // @icon data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgc3R5bGU9IndpZHRoOiAxZW07aGVpZ2h0OiAxZW07dmVydGljYWwtYWxpZ246IG1pZGRsZTtmaWxsOiBjdXJyZW50Q29sb3I7b3ZlcmZsb3c6IGhpZGRlbjsiIHZpZXdCb3g9IjAgMCAxMDI0IDEwMjQiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwLWlkPSI3NjA1Ij48cGF0aCBkPSJNMTg2LjAyNjY2NyAwaDY1MS45NDY2NjZRMTAyNCAwIDEwMjQgMTg2LjAyNjY2N3Y2NTEuOTQ2NjY2UTEwMjQgMTAyNCA4MzcuOTczMzMzIDEwMjRIMTg2LjAyNjY2N1EwIDEwMjQgMCA4MzcuOTczMzMzVjE4Ni4wMjY2NjdRMCAwIDE4Ni4wMjY2NjcgMHoiIGZpbGw9IiMzNjg5RjUiIHAtaWQ9Ijc2MDYiPjwvcGF0aD48cGF0aCBkPSJNMjEzLjMzMzMzMyAzMDQuNjRBOTAuODggOTAuODggMCAwIDEgMzA0LjIxMzMzMyAyMTMuMzMzMzMzaDkwLjg4djQyLjY2NjY2N0gzMDYuMzQ2NjY3YTQ1LjY1MzMzMyA0NS42NTMzMzMgMCAwIDAtNDYuNTA2NjY3IDQ4LjY0djkxLjczMzMzM0gyMTMuMzMzMzMzek0zOTYuOCA4MTAuNjY2NjY3SDMwNi4zNDY2NjdhOTAuNDUzMzMzIDkwLjQ1MzMzMyAwIDAgMS05MC44OC05MS4zMDY2Njd2LTkxLjczMzMzM2g0Ni41MDY2NjZ2OTEuNzMzMzMzQTQ1LjY1MzMzMyA0NS42NTMzMzMgMCAwIDAgMzA4LjA1MzMzMyA3NjhoOTAuODhhMjk4LjY2NjY2NyAyOTguNjY2NjY3IDAgMCAwLTIuMTMzMzMzIDQyLjY2NjY2N3pNODEwLjY2NjY2NyA3MTkuMzZBOTAuNDUzMzMzIDkwLjQ1MzMzMyAwIDAgMSA3MTcuNjUzMzMzIDgxMC42NjY2NjdINjI3LjJ2LTQ2LjkzMzMzNGg5MC40NTMzMzNhNDUuNjUzMzMzIDQ1LjY1MzMzMyAwIDAgMCA0Ni41MDY2NjctNDYuNTA2NjY2di05MS4zMDY2NjdIODEwLjY2NjY2N3pNMjEzLjMzMzMzMyA0ODcuNjhoNTk1LjJ2NDYuOTMzMzMzSDIxMy4zMzMzMzN6TTgxMC42NjY2NjcgMzk2LjM3MzMzM2gtNDguNjRWMzA0LjY0YTQ1LjY1MzMzMyA0NS42NTMzMzMgMCAwIDAtNDYuMDgtNDYuNTA2NjY3aC05MC44OFYyMTMuMzMzMzMzaDkwLjg4YTkwLjg4IDkwLjg4IDAgMCAxIDkwLjg4IDkxLjMwNjY2N3oiIGZpbGw9IiNGRkZGRkYiIHAtaWQ9Ijc2MDciPjwvcGF0aD48L3N2Zz4=
  10. // @require https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js
  11. // @require https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js
  12. // @grant unsafeWindow
  13. // @license MIT
  14. // ==/UserScript==
  15.  
  16. (function() {
  17. 'use strict';
  18.  
  19. //全局变量 被选中的图片元素
  20. var g_img_ele = null;
  21. var g_is_moblie_env = isPhone();
  22.  
  23. //适配移动端 很多移动端浏览器并没有实现油猴接口
  24. //添加'识别二维码'按钮样式
  25. let btn_style = document.createElement('style');
  26. btn_style.type = 'text/css';
  27. if (!g_is_moblie_env){
  28. btn_style.innerText = `.idtfy_div{width:5.5vw;height:2.1vh;font-size:1vh;color:#000000;background:#F8F8FF;border-radius:1.5vh;border:0.12vh solid #f2cac9;line-height:2vh;text-align:center;vertical-align:middle;z-index:99999999;display:none;position:absolute;top:20;left:20;cursor:pointer;box-shadow:0.13vh 0.13vh 0.1vh #888888;}`;
  29. //GM_addStyle(`.idtfy_div{width:5.5vw;height:2.1vh;font-size:1vh;color:#000000;background:#F8F8FF;border-radius:1.5vh;border:0.12vh solid #f2cac9;line-height:2vh;text-align:center;vertical-align:middle;z-index:99999999;display:none;position:absolute;top:20;left:20;cursor:pointer;box-shadow:0.13vh 0.13vh 0.1vh #888888;}`);
  30. }
  31. else{
  32. btn_style.innerText = `.idtfy_div{width:25vw;height:3vh;font-size:1.6vh;color:#000000;background:#F8F8FF;border-radius:1.5vh;border:0.14vw solid #f2cac9;line-height:3vh;text-align:center;vertical-align:middle;z-index:99999999;display:none;position:absolute;top:20;left:20;cursor:pointer;box-shadow:0.15vw 0.15vw 0.13vw #888888;}`;
  33. //GM_addStyle(`.idtfy_div{width:25vw;height:3vh;font-size:1.6vh;color:#000000;background:#F8F8FF;border-radius:1.5vh;border:0.14vw solid #f2cac9;line-height:3vh;text-align:center;vertical-align:middle;z-index:99999999;display:none;position:absolute;top:20;left:20;cursor:pointer;box-shadow:0.15vw 0.15vw 0.13vw #888888;}`);
  34. }
  35. document.head.appendChild(btn_style);
  36.  
  37. //添加'识别二维码'按钮
  38. var identify_div = document.createElement('div');
  39. identify_div.id = 'identify_div';
  40. identify_div.className = 'idtfy_div';//👇添加一个图标
  41. identify_div.innerHTML = `<svg class="icon" style="width:1.3vh;height:1.3vh;vertical-align:middle;fill:currentColor;overflow:hidden" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3325"><path d="M613.888 715.264h-41.984C448.512 715.264 348.16 614.912 348.16 491.52s99.84-223.232 223.232-223.232h41.984c123.392 0 223.232 99.84 223.232 223.232 0.512 123.392-99.328 223.744-222.72 223.744z" fill="#CAD3FF" opacity=".2" p-id="3326"></path><path d="M873.984 940.032h-152.576c-14.336 0-25.6-11.264-25.6-25.6s11.264-25.6 25.6-25.6h147.456v-147.456c0-14.336 11.264-25.6 25.6-25.6s25.6 11.264 25.6 25.6v152.576c0 25.088-20.48 46.08-46.08 46.08zM894.464 311.296c-14.336 0-25.6-11.264-25.6-25.6v-148.48h-147.456c-14.336 0-25.6-11.264-25.6-25.6s11.264-25.6 25.6-25.6h152.576c25.6 0 46.08 20.48 46.08 46.08v153.6c0 13.824-11.264 25.6-25.6 25.6z m-20.48-174.08zM131.072 311.296c-14.336 0-25.6-11.264-25.6-25.6v-153.6c0-25.6 20.48-46.08 46.08-46.08h152.576c14.336 0 25.6 11.264 25.6 25.6s-11.264 25.6-25.6 25.6H156.672v148.48c0 13.824-11.776 25.6-25.6 25.6zM304.128 940.032H151.552c-25.6 0-46.08-20.48-46.08-46.08v-152.576c0-14.336 11.264-25.6 25.6-25.6s25.6 11.264 25.6 25.6v147.456h147.456c14.336 0 25.6 11.264 25.6 25.6s-11.264 25.6-25.6 25.6zM726.528 537.6H297.472c-14.336 0-25.6-11.264-25.6-25.6s11.264-25.6 25.6-25.6h429.056c14.336 0 25.6 11.264 25.6 25.6s-11.776 25.6-25.6 25.6z" fill="#4E63DD" p-id="3327"></path></svg> 识别二维码`;
  42. identify_div.onclick = OnClickedIdentifyBtn;
  43. document.body.appendChild(identify_div);
  44.  
  45. //添加精简后的'notie.js' 一款纯js实现的消息弹窗 github: https://github.com/jaredreich/notie
  46. if (typeof GM_addElement == 'function'){//好像不用GM_addElement没法添加有CSP的网站
  47. GM_addElement('script', {
  48. textContent: `var notie=function(){function D(a,b,c){document.activeElement.blur(),C++,setTimeout(function(){C--},1e3*e+10),1==C&&(z?(clearTimeout(A),clearTimeout(B),F(function(){E(a,b,c)})):E(a,b,c))}function E(b,c,d){var f;switch(z=!0,f=0,f="undefined"==typeof d?3e3:1>d?1e3:1e3*d,b){case 1:v.style.backgroundColor=g,v.onclick=function(){};break;case 2:v.style.backgroundColor=h,v.onclick=alert_outer_onclick;break;case 3:v.style.backgroundColor=i,v.onclick=alert_outer_onclick;break;case 4:v.style.backgroundColor=j,v.onclick=alert_outer_onclick}x.innerHTML=c,v.style.top="-10000px",v.style.display="table",v.style.top="-"+v.offsetHeight-5+"px",A=setTimeout(function(){a&&(v.style.boxShadow="0px 0px 10px 0px rgba(0,0,0,0.5)"),v.style.MozTransition="all "+e+"s ease",v.style.WebkitTransition="all "+e+"s ease",v.style.transition="all "+e+"s ease",v.style.top=0,B=setTimeout(function(){F(function(){})},f)},20)}function F(b){v.style.top="-"+v.offsetHeight-5+"px",setTimeout(function(){a&&(v.style.boxShadow=""),v.style.MozTransition="",v.style.WebkitTransition="",v.style.transition="",v.style.top="-10000px",z=!1,b&&b()},1e3*e+10)}var v,w,x,z,A,B,C,a=!0,b="18px",c="24px",d=600,e=.3,g="#57BF57",h="#E3B771",i="#E1715B",j="#4D82D6",k="#FFF",l="notie-alert-outer",m="notie-alert-inner",n="notie-alert-text",o=function(a){a.style.fontSize=window.innerWidth<=d?b:c},p=500,q=function(a,b,c){var d;return function(){var e=this,f=arguments,g=function(){d=null,c||a.apply(e,f)},h=c&&!d;clearTimeout(d),d=setTimeout(g,b),h&&a.apply(e,f)}};return window.addEventListener("keydown",function(a){var b=13==a.which||13==a.keyCode,c=27==a.which||27==a.keyCode;z&&(b||c)&&(clearTimeout(A),clearTimeout(B),F())}),"undefined"==typeof Element.prototype.addEventListener&&(Element.prototype.addEventListener=Window.prototype.addEventListener=function(a,b){return a="on"+a,this.attachEvent(a,b)}),v=document.createElement("div"),v.id=l,v.style.position="fixed",v.style.top="0",v.style.left="0",v.style.zIndex="999999999",v.style.height="auto",v.style.width="100%",v.style.display="none",v.style.textAlign="center",v.style.cursor="default",v.style.MozTransition="",v.style.WebkitTransition="",v.style.transition="",v.style.cursor="pointer",alert_outer_onclick=function(){clearTimeout(A),clearTimeout(B),F()},w=document.createElement("div"),w.id=m,w.style.padding="20px",w.style.display="table-cell",w.style.verticalAlign="middle",v.appendChild(w),x=document.createElement("span"),x.id=n,x.style.color=k,x.style.fontSize=window.innerWidth<=d?b:c,window.addEventListener("resize",q(o.bind(null,x),p),!0),w.appendChild(x),document.body.appendChild(v),z=!1,C=0,{alert:D,alert_hide:F}}();`
  49. });
  50. }
  51. else{
  52. const notie_script = document.createElement('script')
  53. notie_script.innerText = `var notie=function(){function D(a,b,c){document.activeElement.blur(),C++,setTimeout(function(){C--},1e3*e+10),1==C&&(z?(clearTimeout(A),clearTimeout(B),F(function(){E(a,b,c)})):E(a,b,c))}function E(b,c,d){var f;switch(z=!0,f=0,f="undefined"==typeof d?3e3:1>d?1e3:1e3*d,b){case 1:v.style.backgroundColor=g,v.onclick=function(){};break;case 2:v.style.backgroundColor=h,v.onclick=alert_outer_onclick;break;case 3:v.style.backgroundColor=i,v.onclick=alert_outer_onclick;break;case 4:v.style.backgroundColor=j,v.onclick=alert_outer_onclick}x.innerHTML=c,v.style.top="-10000px",v.style.display="table",v.style.top="-"+v.offsetHeight-5+"px",A=setTimeout(function(){a&&(v.style.boxShadow="0px 0px 10px 0px rgba(0,0,0,0.5)"),v.style.MozTransition="all "+e+"s ease",v.style.WebkitTransition="all "+e+"s ease",v.style.transition="all "+e+"s ease",v.style.top=0,B=setTimeout(function(){F(function(){})},f)},20)}function F(b){v.style.top="-"+v.offsetHeight-5+"px",setTimeout(function(){a&&(v.style.boxShadow=""),v.style.MozTransition="",v.style.WebkitTransition="",v.style.transition="",v.style.top="-10000px",z=!1,b&&b()},1e3*e+10)}var v,w,x,z,A,B,C,a=!0,b="18px",c="24px",d=600,e=.3,g="#57BF57",h="#E3B771",i="#E1715B",j="#4D82D6",k="#FFF",l="notie-alert-outer",m="notie-alert-inner",n="notie-alert-text",o=function(a){a.style.fontSize=window.innerWidth<=d?b:c},p=500,q=function(a,b,c){var d;return function(){var e=this,f=arguments,g=function(){d=null,c||a.apply(e,f)},h=c&&!d;clearTimeout(d),d=setTimeout(g,b),h&&a.apply(e,f)}};return window.addEventListener("keydown",function(a){var b=13==a.which||13==a.keyCode,c=27==a.which||27==a.keyCode;z&&(b||c)&&(clearTimeout(A),clearTimeout(B),F())}),"undefined"==typeof Element.prototype.addEventListener&&(Element.prototype.addEventListener=Window.prototype.addEventListener=function(a,b){return a="on"+a,this.attachEvent(a,b)}),v=document.createElement("div"),v.id=l,v.style.position="fixed",v.style.top="0",v.style.left="0",v.style.zIndex="999999999",v.style.height="auto",v.style.width="100%",v.style.display="none",v.style.textAlign="center",v.style.cursor="default",v.style.MozTransition="",v.style.WebkitTransition="",v.style.transition="",v.style.cursor="pointer",alert_outer_onclick=function(){clearTimeout(A),clearTimeout(B),F()},w=document.createElement("div"),w.id=m,w.style.padding="20px",w.style.display="table-cell",w.style.verticalAlign="middle",v.appendChild(w),x=document.createElement("span"),x.id=n,x.style.color=k,x.style.fontSize=window.innerWidth<=d?b:c,window.addEventListener("resize",q(o.bind(null,x),p),!0),w.appendChild(x),document.body.appendChild(v),z=!1,C=0,{alert:D,alert_hide:F}}();`;
  54. document.head.appendChild(notie_script);
  55. }
  56.  
  57. if (!g_is_moblie_env){
  58. //电脑端 右键弹出 双击隐藏
  59. window.onmousedown = function(e) {
  60. if (e.button == 2) {//右键
  61. var clickedElement = e.target;
  62. if ((g_img_ele = GetQREle(clickedElement)) != null){
  63. document.getElementById("identify_div").style.top = (e.pageY + 10) + "px";
  64. document.getElementById("identify_div").style.left = (e.pageX) + "px";
  65. document.getElementById("identify_div").style.display = 'block';
  66. }
  67. }
  68. };
  69.  
  70. window.ondblclick = function(e) {//双击隐藏按钮
  71. document.getElementById("identify_div").style.display = 'none';
  72. }
  73. }
  74. else{
  75. //移动端 监听长按事件
  76. let time_out = 0, touch_time = 800;
  77. window.addEventListener("touchstart", function(e){
  78. time_out = setTimeout(function(){
  79. var touchedElement = e.target;
  80. if ((g_img_ele = GetQREle(touchedElement)) != null) {
  81. document.getElementById("identify_div").style.top = (e.touches[0].pageY - 50) + "px";
  82. document.getElementById("identify_div").style.left = (e.touches[0].pageX + 20) + "px";
  83. document.getElementById("identify_div").style.display = 'block';
  84. }
  85. }, touch_time);//touch_time毫秒后弹出识别按钮
  86.  
  87. //触碰其他地方则隐藏按钮
  88. if (e.target.id != 'identify_div'){
  89. g_img_ele = null;
  90. document.getElementById("identify_div").style.display = 'none';
  91. }
  92. });
  93. window.addEventListener("touchmove", function(e){
  94. // 如果触摸未达到 touch_time ms且开始移动,则清除计时器
  95. clearTimeout(time_out);
  96. time_out = 0;
  97. });
  98. window.addEventListener("touchend", function(e){
  99. // 如果触摸未达到 touch_time ms且离开屏幕,则清除计时器
  100. clearTimeout(time_out);
  101. time_out = 0;
  102. });
  103. }
  104.  
  105. //通过UA判断是否是移动端
  106. function isPhone() {
  107. var info = navigator.userAgent;
  108. var isPhone = /mobile/i.test(info);
  109. return isPhone;
  110. }
  111.  
  112. //从传入的元素中获取存有图片的元素 若没有则返回null
  113. function GetQREle(ele){
  114. let ret_ele = null;
  115.  
  116. if (ele.tagName == 'IMG'){
  117. if (ele.src != null) ret_ele = ele;
  118. }
  119. else{
  120. //遍历子元素看看是否有图片
  121. var childs = ele.childNodes;
  122. if (childs.length >= 6) return ret_ele;
  123. for(var i = childs.length - 1; i >= 0; i--) {
  124. if (childs[i].tagName == 'IMG' && childs[i].src != null){
  125. ret_ele = childs[i];
  126. break;
  127. }
  128. }
  129. }
  130.  
  131. return ret_ele;
  132. }
  133.  
  134. //通过html2canvs截图并识别
  135. function Html2CanvasAndIndentify(ele){
  136. html2canvas(ele,{
  137. useCORS: true, //开启跨域配置
  138. allowTaint: false, //允许跨域图片
  139. }).then(function(canvas) {
  140. var imgData = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height);
  141.  
  142. const code = jsQR(imgData.data, imgData.width, imgData.height, {
  143. inversionAttempts: "dontInvert",
  144. });
  145. if (code == null) {
  146. notie.alert(3, '识别失败!', 3);
  147. }else {
  148. let display_text = code.data, ope_html = '<div><a id="copy_btn" href="javascript:void(0);">复制</a>[placeholder_]<a style="margin-left: 20px" href="javascript:void(0);" onclick="notie.alert_hide()">关闭</a></div>';
  149. if (g_is_moblie_env && (code.data.length >= 40)){//如果是移动端 且识别内容过长 则隐藏一部分内容
  150. display_text = code.data.substr(0, 35) + '...';
  151. }
  152. if (isMaybeURL(code.data)){
  153. let jump_url = code.data;
  154. if (jump_url.substr(0, 4) != 'http') jump_url = 'https://' + jump_url;
  155. ope_html = ope_html.replace("[placeholder_]", '<a id="goto_btn" href="' + jump_url + '" target="_blank" style="margin-left: 20px">转到</a>');
  156. }
  157. else{
  158. ope_html = ope_html.replace("[placeholder_]", '<a id="search_btn" href="https://www.baidu.com/s?wd=' + encodeURIComponent(code.data) + '" target="_blank" style="margin-left: 20px">搜索</a>');
  159. }
  160. notie.alert(1, '<a id="all_text" style="display: none;">'+ code.data +'</a>识别到以下文本:<br><div style="word-wrap:break-word;">' + display_text + '</div>' + ope_html, 20);
  161. document.getElementById("copy_btn").onclick = OnClickedCopyBtn;
  162. }
  163.  
  164. }).catch(function(err){
  165. notie.alert(3, 'html2canvas出错:<br>' + err, 5);
  166. });
  167. }
  168.  
  169.  
  170. //'识别二维码'按钮事件
  171. function OnClickedIdentifyBtn(){
  172. if ((typeof jsQR != 'function') || (typeof html2canvas != 'function')){
  173. notie.alert(3, '外部JS脚本未加载成功!', 3);
  174. return;
  175. }
  176.  
  177. if (g_img_ele == null){
  178. notie.alert(2, '未选中任何图片', 2);
  179. }
  180. else {
  181. Html2CanvasAndIndentify(g_img_ele);
  182. }
  183. document.getElementById("identify_div").style.display = 'none';
  184. }
  185.  
  186. //'复制'按钮单击事件
  187. function OnClickedCopyBtn(){
  188. //GM_setClipboard(document.getElementById("all_text").innerText);
  189. navigator.clipboard.writeText(document.getElementById("all_text").innerText)
  190. notie.alert_hide();
  191. notie.alert(1, '复制成功!', 2);
  192. }
  193.  
  194. //判字符串是否可能为URL
  195. function isMaybeURL(str) {
  196. let is_maybe = false;
  197. if ((str.substr(0, 4) == 'http') ||
  198. (str.substr(0, 3) == 'www') ||
  199. (str.indexOf('.com') != -1 || str.indexOf('.cn') != -1 || str.indexOf('.org') != -1 || str.indexOf('.net') != -1) ||
  200. (str.indexOf('.') != -1 && str.indexOf('/') != -1)) is_maybe = true;
  201. return is_maybe;
  202. }
  203. })();