您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在磁力链接后添加标识符号,通过点击或悬停显示完整链接信息
当前为
// ==UserScript== // @name 磁力链接悬浮预览 // @namespace http://whatslink.info/ // @version 2.5 // @description 在磁力链接后添加标识符号,通过点击或悬停显示完整链接信息 // @author sexjpg // @grant GM_xmlhttpRequest // @grant GM_notification // @grant GM_setValue // @grant GM_getValue // @connect whatslink.info // @match *://*6v520.com*/* // @match *://*javdb*.*/* // @match *://*javbus*.*/* // @license MIT // ==/UserScript== (function () { 'use strict'; // 配置参数 const CONFIG = { delay: 500, // 悬浮延迟时间(毫秒) cacheTTL: 10800 * 60 * 1000, // 缓存有效期(10800分钟) indicator_innerhtml: '🧲' }; // 缓存对象,使用{} const magnetCache = GM_getValue('magnetCache', {}); // 创建悬浮框容器 const tooltip = document.createElement('div'); tooltip.style.cssText = ` position: fixed; max-width: 400px; min-width: 300px; padding: 15px; background: rgba(0, 0, 0, 0.95); color: #fff; border-radius: 8px; font-size: 14px; font-family: Arial, sans-serif; z-index: 9999; pointer-events: auto; /* 修改为 auto,允许鼠标事件 */ word-break: break-all; opacity: 0; transition: opacity 0.3s ease, transform 0.3s ease; transform: scale(0.95); box-shadow: 0 4px 12px rgba(0,0,0,0.4); display: none; `; // 新增变量用于控制 tooltip 状态 let tooltipHideTimer = null; let isTooltipHovered = false; document.body.appendChild(tooltip); // 磁力链接检测正则 const magnetRegex = /^magnet:\?xt=urn:btih:([a-fA-F0-9]{40})(?:&|$)/i; // 标识符号样式 const indicatorStyle = ` display: inline-block; width: 16px; height: 16px; background: #007bff; border-radius: 50%; color: white; text-align: center; font-size: 12px; margin-left: 4px; cursor: help; user-select: none; vertical-align: middle; transition: all 0.2s ease; `; // 获取磁力链接特征码 function getMagnetHash(magnetLink) { const match = magnetLink.match(magnetRegex); return match ? match[1].toLowerCase() : null; } // API请求函数(修正GET请求方式) function fetchMagnetInfo(magnetLink, callback) { try { GM_xmlhttpRequest({ method: 'GET', url: `https://whatslink.info/api/v1/link?url=${magnetLink}`, headers: { 'Content-Type': "text/plain", }, onload: function (response) { try { const data = JSON.parse(response.responseText); console.debug('网络请求数据', data); // 只缓存有效数据,有数据,且数据无错误,且文件类型不为空 if (data && !data.error && data.file_type) { const hash = getMagnetHash(magnetLink); if (hash) { magnetCache[hash] = { data: data, expiresAt: Date.now() + CONFIG.cacheTTL }; // 保存缓存 console.debug('更新缓存', magnetCache[hash]); GM_setValue('magnetCache', magnetCache); console.debug('更新缓存完成,总缓存数量:', Object.keys(magnetCache).length); } } callback(null, data); } catch (error) { callback(new Error('解析响应数据失败: ' + error.message)); } }, onerror: function (error) { callback(new Error('API请求失败: ' + error.statusText)); } }); } catch (error) { callback(new Error('请求异常: ' + error.message)); } } // 检查缓存 function checkCache(magnetLink) { const hash = getMagnetHash(magnetLink); console.debug('开始检索缓存,缓存总量', Object.keys(magnetCache).length, magnetCache); console.debug('检索特征码', hash); if (!hash || !magnetCache[hash]) { console.debug('缓存中未检索到特征码:', hash); return null }; // 检查缓存是否过期 if (Date.now() > magnetCache[hash].expiresAt) { delete magnetCache[hash]; console.debug('缓存特征码过期', hash); return null; } console.debug('获取缓存数据', magnetCache[hash]); return magnetCache[hash].data; } // 数据展示函数 function renderMagnetInfo(data) { let html = ` <div style="margin-bottom: 10px;"> <strong style="font-size: 16px; word-break: break-word;">${data.name || '未知名称'}</strong> </div> <div style="margin-bottom: 8px;"> <span>类型:</span> <span style="color: #17a2b8;">${data.type || '未知类型'}</span> </div> <div style="margin-bottom: 8px;"> <span>文件类型:</span> <span style="color: #ffc107;">${data.file_type || '未知文件类型'}</span> </div> <div style="margin-bottom: 8px;"> <span>大小:</span> <span style="color: #28a745;">${formatFileSize(data.size) || '未知大小'}</span> </div> <div style="margin-bottom: 8px;"> <span>文件数:</span> <span style="color: #dc3545;">${data.count || 0}</span> </div> `; if (data.screenshots && data.screenshots.length > 0) { html += `<div style="margin-top: 15px; display: flex; flex-wrap: wrap; gap: 5px;">`; data.screenshots.slice(0, 5).forEach(screenshot => { html += ` <div style="flex: 1 1 45%; min-width: 100px;"> <img src="${screenshot.screenshot}" style="width: 100%; border-radius: 4px; box-shadow: 0 2px 6px rgba(0,0,0,0.3);"> </div> `; }); html += `</div>`; } return html; } // 格式化文件大小 function formatFileSize(bytes) { if (bytes === undefined || bytes === null) return '未知大小'; if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } // 显示悬浮框的核心逻辑 function showTooltip(magnetLink, event) { // 检查缓存 const cachedData = checkCache(magnetLink); if (cachedData) { // 使用缓存数据 tooltip.innerHTML = renderMagnetInfo(cachedData); updateTooltipPosition(event); tooltip.style.display = 'block'; tooltip.style.opacity = '1'; tooltip.style.transform = 'scale(1)'; return; } // 显示加载状态 tooltip.innerHTML = '<div style="text-align: center; padding: 10px;">加载中...</div>'; tooltip.style.display = 'block'; tooltip.style.opacity = '1'; tooltip.style.transform = 'scale(1)'; updateTooltipPosition(event); // 请求API数据 fetchMagnetInfo(magnetLink, (error, data) => { if (error) { tooltip.innerHTML = `<div style="color: #dc3545; text-align: center; padding: 10px;">${error.message}</div>`; } else { tooltip.innerHTML = renderMagnetInfo(data); } updateTooltipPosition(event); }); } // 更新悬浮框位置 function updateTooltipPosition(e) { const tooltipRect = tooltip.getBoundingClientRect(); const viewportWidth = window.innerWidth; let x = e.clientX + 15; //本身是y = e.clientY + 15,改为往上调整15个像素 let y = e.clientY - 15; // 防止超出右侧视口 if (x + tooltipRect.width > viewportWidth - 20) { x = e.clientX - tooltipRect.width - 15; } tooltip.style.left = `${x}px`; tooltip.style.top = `${y}px`; } // 处理单个链接元素 function processLink(link) { // 检查是否是磁力链接且未被处理过 if (link.dataset.magnetProcessed || !magnetRegex.test(link.href)) { return; } link.dataset.magnetProcessed = 'true'; // 标记为已处理 let timer = null; let isHovered = false; // 新增悬停状态 const indicator = document.createElement('span'); indicator.innerHTML = CONFIG.indicator_innerhtml; indicator.style.cssText = indicatorStyle; link.appendChild(indicator); // 鼠标进入事件 indicator.addEventListener('mouseenter', (e) => { clearTimeout(tooltipHideTimer); // 清除之前的隐藏计时器 timer = setTimeout(() => { showTooltip(link.href, e); }, CONFIG.delay); }); indicator.addEventListener('mouseleave', () => { clearTimeout(timer); // 取消未触发的显示 // 不再立即隐藏 tooltip,交给 tooltip 自己控制 }); indicator.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); clearTimeout(timer); showTooltip(link.href, e); }); tooltip.addEventListener('mouseenter', () => { isTooltipHovered = true; clearTimeout(tooltipHideTimer); }); tooltip.addEventListener('mouseleave', () => { isTooltipHovered = false; tooltipHideTimer = setTimeout(() => { tooltip.style.opacity = '0'; tooltip.style.transform = 'scale(0.95)'; setTimeout(() => { tooltip.style.display = 'none'; }, 300); // 与 transition 时间匹配 }, CONFIG.delay); }); } // 使用 MutationObserver 监听动态内容 function observeDOMChanges() { const observer = new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { // 只处理元素节点 if (node.nodeType === 1) { // 检查节点本身是否是链接 if (node.tagName === 'A') { processLink(node); } // 检查节点下的所有链接 node.querySelectorAll('a').forEach(processLink); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); } // 初始执行 + 启动监听 document.querySelectorAll('a').forEach(processLink); // 处理页面已有的链接 observeDOMChanges(); // 监听后续动态添加的链接 })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址