// ==UserScript== // @name 智能识别二维码 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 强大的二维码识别工具 - 支持悬停识别、快捷键、预识别等功能 // @author Spkyle // @license CC-BY-NC-SA-4.0 // @match *://*/* // @grant GM_setClipboard // @grant GM_notification // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @require https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js // @run-at document-end // @downloadURL https://update.greasyfork.icu/scripts/558376/%E6%99%BA%E8%83%BD%E8%AF%86%E5%88%AB%E4%BA%8C%E7%BB%B4%E7%A0%81.user.js // @updateURL https://update.greasyfork.icu/scripts/558376/%E6%99%BA%E8%83%BD%E8%AF%86%E5%88%AB%E4%BA%8C%E7%BB%B4%E7%A0%81.meta.js // ==/UserScript== (function() { 'use strict'; // 默认配置 const defaultConfig = { hoverEnabled: true, // 悬停显示按钮 preRecognition: false, // 预识别(只在二维码上显示按钮) squareOnly: false, // 只在正方形图片上显示 hoverDelay: 400, // 悬停延迟(ms) buttonSize: 50, // 按钮大小 shortcutKey: 'ctrlKey', // 快捷键 showNotification: true, // 显示通知 autoOpen: false // 识别后自动打开链接 }; // 加载配置 let config = { ...defaultConfig, ...GM_getValue('qrConfig', {}) }; let floatingButton = null; let currentImageElement = null; let showTimer = null; let hideTimer = null; let isEnabled = config.hoverEnabled; // 注册油猴菜单 GM_registerMenuCommand('⚙️ 设置选项', showSettingsPanel); GM_registerMenuCommand(isEnabled ? '✅ 悬停识别 (已启用)' : '❌ 悬停识别 (已禁用)', toggleHover); GM_registerMenuCommand(config.preRecognition ? '✅ 预识别 (已启用)' : '❌ 预识别 (已禁用)', togglePreRecognition); GM_registerMenuCommand(config.squareOnly ? '✅ 仅正方形 (已启用)' : '❌ 仅正方形 (已禁用)', toggleSquareOnly); GM_registerMenuCommand('🔄 恢复默认设置', resetSettings); console.log('🔍 二维码识别脚本已加载'); // 切换悬停识别 function toggleHover() { config.hoverEnabled = !config.hoverEnabled; isEnabled = config.hoverEnabled; GM_setValue('qrConfig', config); showNotification(config.hoverEnabled ? '✅ 悬停识别已启用' : '❌ 悬停识别已禁用', 'success'); location.reload(); } // 切换预识别 function togglePreRecognition() { config.preRecognition = !config.preRecognition; GM_setValue('qrConfig', config); showNotification(config.preRecognition ? '✅ 预识别已启用(会影响性能)' : '❌ 预识别已禁用', 'success'); location.reload(); } // 切换仅正方形 function toggleSquareOnly() { config.squareOnly = !config.squareOnly; GM_setValue('qrConfig', config); showNotification(config.squareOnly ? '✅ 仅识别正方形已启用' : '❌ 仅识别正方形已禁用', 'success'); location.reload(); } // 恢复默认设置 function resetSettings() { if (confirm('确定要恢复默认设置吗?')) { config = { ...defaultConfig }; GM_setValue('qrConfig', config); showNotification('✅ 已恢复默认设置', 'success'); location.reload(); } } // 创建悬浮识别按钮 function createFloatingButton() { if (floatingButton) return floatingButton; floatingButton = document.createElement('div'); floatingButton.innerHTML = '🔍'; floatingButton.title = '点击识别二维码'; floatingButton.style.cssText = ` position: fixed; width: ${config.buttonSize}px; height: ${config.buttonSize}px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 50%; display: none; align-items: center; justify-content: center; font-size: ${config.buttonSize * 0.48}px; cursor: pointer; z-index: 2147483647; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.5); transition: all 0.2s ease; user-select: none; `; floatingButton.onmouseenter = function() { clearTimeout(hideTimer); this.style.transform = 'scale(1.15)'; this.style.boxShadow = '0 6px 25px rgba(102, 126, 234, 0.7)'; }; floatingButton.onmouseleave = function() { this.style.transform = 'scale(1)'; this.style.boxShadow = '0 4px 15px rgba(102, 126, 234, 0.5)'; hideTimer = setTimeout(() => { if (floatingButton) floatingButton.style.display = 'none'; }, 400); }; floatingButton.onclick = function(e) { e.stopPropagation(); e.preventDefault(); if (currentImageElement && currentImageElement.tagName) { floatingButton.style.display = 'none'; decodeQRCode(currentImageElement); } else { showNotification('图片元素已失效,请重新悬停', 'error'); floatingButton.style.display = 'none'; } }; document.body.appendChild(floatingButton); return floatingButton; } // 显示按钮 async function showButton(imgElement) { if (!isEnabled || !imgElement || !imgElement.tagName) return; // 预识别模式 if (config.preRecognition) { const imageData = await getImageData(imgElement); if (imageData) { const code = jsQR(imageData.data, imageData.width, imageData.height); if (!code) return; // 不是二维码,不显示按钮 } } // 只识别正方形图片 if (config.squareOnly) { const rect = imgElement.getBoundingClientRect(); const ratio = rect.width / rect.height; if (ratio < 0.8 || ratio > 1.2) return; // 不是正方形,不显示 } currentImageElement = imgElement; clearTimeout(hideTimer); const btn = createFloatingButton(); const rect = imgElement.getBoundingClientRect(); let left = rect.right + window.scrollX - config.buttonSize - 10; let top = rect.top + window.scrollY + 10; if (left < 10) left = rect.left + window.scrollX + 10; if (left + config.buttonSize > window.innerWidth + window.scrollX) { left = rect.left + window.scrollX + 10; } if (top < window.scrollY + 10) top = window.scrollY + 10; if (top + config.buttonSize > window.innerHeight + window.scrollY) { top = rect.bottom + window.scrollY - config.buttonSize - 10; } btn.style.left = left + 'px'; btn.style.top = top + 'px'; btn.style.display = 'flex'; } // 检查是否是图片元素 function isImageElement(element) { if (!element || !element.tagName) return false; if (element.tagName === 'IMG' || element.tagName === 'CANVAS') { return true; } try { const bg = window.getComputedStyle(element).backgroundImage; return bg && bg !== 'none' && bg.includes('url'); } catch (e) { return false; } } // 监听鼠标悬停 document.addEventListener('mouseover', function(e) { if (!isEnabled) return; const target = e.target; if (target === floatingButton) return; if (isImageElement(target)) { clearTimeout(showTimer); clearTimeout(hideTimer); showTimer = setTimeout(() => { if (isImageElement(target)) { showButton(target); } }, config.hoverDelay); } }, true); // 监听鼠标移出 document.addEventListener('mouseout', function(e) { const target = e.target; if (isImageElement(target)) { clearTimeout(showTimer); if (floatingButton && !floatingButton.matches(':hover')) { clearTimeout(hideTimer); hideTimer = setTimeout(() => { if (floatingButton && floatingButton.style.display !== 'none') { floatingButton.style.display = 'none'; } }, 600); } } }, true); // 快捷键识别 document.addEventListener('contextmenu', function(e) { const target = e.target; if (isImageElement(target) && e[config.shortcutKey]) { e.preventDefault(); decodeQRCode(target); if (floatingButton) floatingButton.style.display = 'none'; } }, true); // 滚动时隐藏 document.addEventListener('scroll', () => { clearTimeout(showTimer); if (floatingButton) floatingButton.style.display = 'none'; }, true); // 解码二维码 async function decodeQRCode(element) { if (!element || !element.tagName) { showNotification('图片元素无效,请重试', 'error'); return; } try { if (config.showNotification) { GM_notification({ text: '正在识别二维码...', title: '二维码识别', timeout: 1000 }); } const imageData = await getImageData(element); if (!imageData) { showNotification('无法读取图片数据', 'error'); return; } const code = jsQR(imageData.data, imageData.width, imageData.height); if (code) { if (config.autoOpen && (code.data.startsWith('http://') || code.data.startsWith('https://'))) { window.open(code.data, '_blank'); showNotification('✅ 已自动打开链接', 'success'); } else { showQRResult(code.data); } } else { showNotification('未检测到二维码', 'error'); } } catch (error) { console.error('二维码识别错误:', error); showNotification('识别失败: ' + error.message, 'error'); } } // 获取图片数据 async function getImageData(element) { if (!element || !element.tagName) return null; const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); let img; if (element.tagName === 'IMG') { img = element; } else if (element.tagName === 'CANVAS') { return element.getContext('2d').getImageData(0, 0, element.width, element.height); } else { const bg = window.getComputedStyle(element).backgroundImage; const urlMatch = bg.match(/url\(['"]?([^'"]+)['"]?\)/); if (urlMatch) { img = new Image(); img.crossOrigin = 'anonymous'; img.src = urlMatch[1]; await new Promise((resolve, reject) => { img.onload = resolve; img.onerror = reject; }); } } if (!img) return null; canvas.width = img.naturalWidth || img.width; canvas.height = img.naturalHeight || img.height; ctx.drawImage(img, 0, 0); return ctx.getImageData(0, 0, canvas.width, canvas.height); } // 显示设置面板 function showSettingsPanel() { const modal = document.createElement('div'); modal.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.6); z-index: 2147483647; display: flex; align-items: center; justify-content: center; `; const panel = document.createElement('div'); panel.style.cssText = ` background: white; padding: 30px; border-radius: 12px; max-width: 500px; width: 90%; box-shadow: 0 8px 32px rgba(0,0,0,0.3); max-height: 80vh; overflow-y: auto; `; panel.innerHTML = `
鼠标悬停图片时自动显示识别按钮
只在检测到二维码的图片上显示按钮(会消耗性能)
过滤掉明显不是二维码的长方形图片
识别时显示浏览器通知
识别到链接后自动在新标签页打开