// ==UserScript== // @name 二维码识别 // @version 1.0 // @description 1.点击菜单栏里的"识别二维码"就可以识别当前页面中的二维码(快捷键:ctrl+q); 2.按住ctrl键,右键点击图片,即可识别该图片中的二维码; 3.支持上传二维码图片识别 // @author IIIStudio // @license MIT // @homepageURL https://cnb.cool/IIIStudio/Greasemonkey/QRCODE/ // @include * // @require https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js // @require https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.js // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @connect * // ==/UserScript== (function () { "use strict"; var css = ` #QRCODE_DECODER { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 999999; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 12px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); min-width: 320px; max-width: 400px; overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } #QRCODE_DECODER::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 4px; background: linear-gradient(90deg, #ff6b6b, #ffd93d, #6bcf7f, #4d96ff); } #QRCODE_DECODER .close { position: absolute; right: 12px; top: 12px; width: 24px; height: 24px; border-radius: 50%; color: #353535; text-decoration: none; font-size: 18px; font-weight: bold; display: flex; align-items: center; justify-content: center; transition: all 0.3s ease; z-index: 1; } #QRCODE_DECODER .close:hover { background: rgba(255, 255, 255, 0.3); transform: rotate(90deg); } #QRCODE_DECODER .result { padding: 30px 25px 25px; background: white; margin: 4px; border-radius: 8px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); } #QRCODE_DECODER .result-header { font-size: 16px; font-weight: 600; color: #2d3748; text-align: center; margin-bottom: 20px; display: flex; align-items: center; justify-content: center; gap: 8px; } #QRCODE_DECODER .result-header::before { content: '🔍'; font-size: 18px; } #QRCODE_DECODER .content-text { background: #f7fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 15px; margin-bottom: 20px; word-break: break-all; font-size: 14px; line-height: 1.5; color: #4a5568; max-height: 150px; overflow-y: auto; transition: all 0.3s ease; } #QRCODE_DECODER .content-text:hover { border-color: #667eea; box-shadow: 0 2px 8px rgba(102, 126, 234, 0.1); } #QRCODE_DECODER .action-buttons { display: flex; gap: 12px; justify-content: center; } #QRCODE_DECODER .action-buttons button { padding: 10px 20px; border: none; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.3s ease; flex: 1; display: flex; align-items: center; justify-content: center; gap: 6px; min-width: 80px; } #QRCODE_DECODER #copyQRContent { background: linear-gradient(135deg, #667eea, #764ba2); color: white; } #QRCODE_DECODER #copyQRContent:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); } #QRCODE_DECODER #copyQRContent:active { transform: translateY(0); } #QRCODE_DECODER #copyQRContent.copied { background: linear-gradient(135deg, #48bb78, #38a169); } #QRCODE_DECODER #openQRContent { background: linear-gradient(135deg, #ff6b6b, #ee5a52); color: white; } #QRCODE_DECODER #openQRContent:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(255, 107, 107, 0.4); } #QRCODE_DECODER #openQRContent:active { transform: translateY(0); } #QRCODE_DECODER .loading { padding: 40px 20px; text-align: center; color: #4a5568; font-size: 16px; display: flex; flex-direction: column; align-items: center; gap: 15px; } #QRCODE_DECODER .loading::before { content: '⏳'; font-size: 24px; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } #QRCODE_DECODER .error { color: #e53e3e; text-align: center; padding: 30px 20px; font-size: 14px; display: flex; flex-direction: column; align-items: center; gap: 10px; } #QRCODE_DECODER .error::before { content: '❌'; font-size: 24px; } #QRCODE_DECODER .upload-section { padding: 20px; background: white; margin: 4px; border-radius: 8px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); text-align: center; } #QRCODE_DECODER .upload-area { border: 2px dashed #cbd5e0; border-radius: 8px; padding: 30px 20px; cursor: pointer; transition: all 0.3s ease; background: #f7fafc; } #QRCODE_DECODER .upload-area:hover { border-color: #667eea; background: #edf2f7; } #QRCODE_DECODER .upload-icon { font-size: 48px; margin-bottom: 15px; color: #a0aec0; } #QRCODE_DECODER .upload-text { color: #4a5568; font-size: 14px; margin-bottom: 10px; } #QRCODE_DECODER .upload-hint { color: #718096; font-size: 12px; } #QRCODE_DECODER #fileInput { display: none; } #QRCODE_DECODER .upload-button { background: linear-gradient(135deg, #4299e1, #3182ce); color: white; border: none; border-radius: 6px; padding: 10px 20px; font-size: 14px; cursor: pointer; transition: all 0.3s ease; margin-top: 15px; } #QRCODE_DECODER .upload-button:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(66, 153, 225, 0.4); } `; var $ = jQuery; var currentQRContent = ''; function getDecoderLayer() { if ($("#QRCODE_DECODER").length) { return $("#QRCODE_DECODER"); } var layer = $('
').appendTo($("body")); var resultDiv = $('
').appendTo(layer); var closeBtn = $('×').appendTo(layer); closeBtn.on("click", function() { layer.remove(); }); // 点击背景关闭 layer.on("click", function(e) { if (e.target === this) { layer.remove(); } }); return layer; } function decodeQRCodeFromImage(src, callback) { // 检查是否是跨域图片 const isCrossOrigin = () => { try { const imgUrl = new URL(src, window.location.href); const currentUrl = new URL(window.location.href); return imgUrl.origin !== currentUrl.origin; } catch (e) { return true; } }; if (isCrossOrigin()) { // 使用GM_xmlhttpRequest处理跨域图片 GM_xmlhttpRequest({ method: "GET", url: src, responseType: "blob", onload: function(response) { const blob = response.response; const url = URL.createObjectURL(blob); const img = new Image(); img.onload = function() { decodeImageToQRCode(img); URL.revokeObjectURL(url); // 清理内存 }; img.onerror = function() { callback(new Error("图片加载失败")); }; img.src = url; }, onerror: function() { callback(new Error("无法加载跨域图片")); } }); } else { // 同源图片直接处理 const img = new Image(); img.crossOrigin = "Anonymous"; img.onload = function() { decodeImageToQRCode(img); }; img.onerror = function() { callback(new Error("图片加载失败")); }; img.src = src; } function decodeImageToQRCode(img) { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.width; canvas.height = img.height; ctx.drawImage(img, 0, 0); try { const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const code = jsQR(imageData.data, imageData.width, imageData.height); if (code) { callback(null, code.data); } else { callback(new Error("未检测到二维码")); } } catch (e) { callback(e); } } } function decodeQRCodeFromFile(file, callback) { const reader = new FileReader(); reader.onload = function(e) { const img = new Image(); img.onload = function() { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.width; canvas.height = img.height; ctx.drawImage(img, 0, 0); try { const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const code = jsQR(imageData.data, imageData.width, imageData.height); if (code) { callback(null, code.data); } else { callback(new Error("未检测到二维码")); } } catch (e) { callback(e); } }; img.onerror = function() { callback(new Error("图片加载失败")); }; img.src = e.target.result; }; reader.onerror = function() { callback(new Error("文件读取失败")); }; reader.readAsDataURL(file); } function copyToClipboard(text) { navigator.clipboard.writeText(text).then(function() { // 复制成功反馈 const copyBtn = $('#copyQRContent'); const originalText = copyBtn.html(); copyBtn.addClass('copied').html('✅ 已复制'); setTimeout(() => { copyBtn.removeClass('copied').html(originalText); }, 2000); }).catch(function() { // 降级方案 const textArea = document.createElement("textarea"); textArea.value = text; document.body.appendChild(textArea); textArea.select(); const success = document.execCommand("copy"); document.body.removeChild(textArea); if (success) { const copyBtn = $('#copyQRContent'); const originalText = copyBtn.html(); copyBtn.addClass('copied').html('✅ 已复制'); setTimeout(() => { copyBtn.removeClass('copied').html(originalText); }, 2000); } }); } function openURL(url) { if (/^https?:\/\//.test(url)) { window.open(url, '_blank'); } else { alert('这不是一个有效的网址'); } } function showQRResult(content) { currentQRContent = content; const layer = getDecoderLayer(); layer.find(".result") .html('
识别结果
' + '
' + content + '
' + '
' + '' + '' + '
'); layer.find('#copyQRContent').off('click').on('click', function(e) { e.stopPropagation(); copyToClipboard(currentQRContent); }); layer.find('#openQRContent').off('click').on('click', function(e) { e.stopPropagation(); openURL(currentQRContent); }); } function showLoading() { const layer = getDecoderLayer(); layer.find(".result").html('
识别中...
'); } function showError(message) { const layer = getDecoderLayer(); layer.find(".result").html('
' + message + '
'); } function showUploadSection() { const layer = getDecoderLayer(); layer.find(".result").html(`
📁
点击选择二维码图片或拖拽到此处
支持 JPG、PNG 等常见图片格式
`); // 点击上传区域触发文件选择 layer.find('#uploadArea').on('click', function() { layer.find('#fileInput').click(); }); // 浏览按钮点击 layer.find('#browseButton').on('click', function() { layer.find('#fileInput').click(); }); // 文件选择变化 layer.find('#fileInput').on('change', function(e) { const file = e.target.files[0]; if (file) { handleFileUpload(file); } }); // 拖拽功能 layer.find('#uploadArea').on('dragover', function(e) { e.preventDefault(); $(this).css({ 'border-color': '#667eea', 'background': '#e2e8f0' }); }); layer.find('#uploadArea').on('dragleave', function(e) { e.preventDefault(); $(this).css({ 'border-color': '#cbd5e0', 'background': '#f7fafc' }); }); layer.find('#uploadArea').on('drop', function(e) { e.preventDefault(); $(this).css({ 'border-color': '#cbd5e0', 'background': '#f7fafc' }); const files = e.originalEvent.dataTransfer.files; if (files.length > 0) { handleFileUpload(files[0]); } }); } function handleFileUpload(file) { // 检查文件类型 if (!file.type.startsWith('image/')) { showError('请选择图片文件'); return; } // 检查文件大小(限制为5MB) if (file.size > 5 * 1024 * 1024) { showError('文件大小不能超过5MB'); return; } showLoading(); decodeQRCodeFromFile(file, function(err, result) { if (err) { showError("识别失败: " + err.message); } else if (result) { showQRResult(result); } else { showError("未检测到二维码"); } }); } function scanPageForQRCode() { showLoading(); const images = document.getElementsByTagName('img'); let processed = 0; let foundQRCode = false; for (let i = 0; i < images.length; i++) { const img = images[i]; if (img.naturalWidth < 50 || img.naturalHeight < 50) { processed++; continue; } decodeQRCodeFromImage(img.src, function(err, result) { processed++; if (!foundQRCode && !err && result) { foundQRCode = true; showQRResult(result); } if (processed === images.length && !foundQRCode) { showError("未在页面中发现二维码"); } }); } if (images.length === 0) { showError("页面中没有图片"); } } // 添加CSS样式 var styleNode = document.createElement("style"); styleNode.type = "text/css"; styleNode.appendChild(document.createTextNode(css)); document.head.appendChild(styleNode); // 注册菜单命令 GM_registerMenuCommand("识别二维码", scanPageForQRCode, ""); GM_registerMenuCommand("上传二维码识别", function() { showUploadSection(); }, ""); $(document).contextmenu(function(e) { if (e.ctrlKey && e.target.tagName === "IMG") { showLoading(); decodeQRCodeFromImage(e.target.src, function(err, result) { if (err) { showError("识别失败: " + err.message); } else if (result) { showQRResult(result); } else { showError("未检测到二维码"); } }); e.preventDefault(); } }); $(document).keyup(function(e) { if (e.ctrlKey && e.keyCode === 81) { if ($("#QRCODE_DECODER").length) { $("#QRCODE_DECODER").remove(); } else { scanPageForQRCode(); } } }); })();