// ==UserScript== // @name 磁力链接预览图片 // @namespace https://bbs.tampermonkey.net.cn/ // @match *://*.anybt.eth.link/* // @match *://*.skrbtiu.top/* // @match *://*.btmulu.work/* // @match *://*.cilian.site/* // @match *://*.javbus.com/* // @match *://*.north-plus.net/* // @match *://*.nyaa.si/* // @match *://*.mnmnmnmnmn.com/* // @match *://*.sehuatang.net/* // @match *://*.pianyuan.org/* // @match *://*.pwww.gying.in/* // @match *://*.magnet-search.com/* // @match *://*.mnplay.xyz/* // @match *://*.btstate.com/* // @match *://*.btsearch.net/* // @match *://*.magnetlink.net/* // @match *://*.cursor.vip/* // @match *://*.btmulu.cyou/* // @match *://*.filemood.com/* // @match *://*.btdig.com/* // @match *://*.xccl98.xyz/* // @match *://*.torrentq.co/* // @match *://*.cililianjie.net/* // @match *://*.vwcat.com/* // @match *://*.u6a6.link/* // @match *://*.0mag.net/* // @match *://*.cilian.site/* // @match *://*.3d48.com/* // @match *://*.bitsearch.to/* // @match *://*.bt4gprx.com/* // @match *://*.ntorrents.net/* // @match *://*.torrentdownload.info/* // @match *://*.similarweb.com/* // @version 1.2 // @description 磁力链接预览图片 对指定网站进行磁力链接预览 // @author Javaow 纸鸢花的花语 // @grant GM_xmlhttpRequest // ==/UserScript== (function () { 'use strict'; // 是否默认显示悬浮窗口 let isBox = false; // 默认窗口宽度 单位:px let boxWidth = "400"; // 最大窗口宽度 let boxMaxWidth = "50%"; // 最小窗口宽度 let boxMinWidth = "10%"; // 最小窗口高度 let boxMinHeight = "50px"; // 最大窗口高度 let boxMaxHeight = "500px"; // 扫描间隔 毫秒 太快会导致页面卡顿 太慢会导致预览按钮延迟很长时间出现 let scanMs = 1000; // 导入菜单栏样式 function initStyle() { const qStyle = document.createElement("style"); qStyle.setAttribute("type", "text/css"); qStyle.innerHTML = ` ul { list-style: none; } #qBox { max-width: ${boxMaxWidth}; min-width: ${boxMinWidth}; z-index: 9999; position: fixed; right: 30px; top: 20px; border-radius: 7px; border: 2.5px solid black; background-color: white; } #qTop{ background-color: #6192e4; color: #fff; user-select: none; cursor: move; position: relative; } #qBox>div { font-size: 14px; display: flex; align-items: center; justify-content: center; padding: 5px 5px; font-weight: bold; border-radius: 4px 4px 0px 0px; } #jxinput { border-radius: 2px; flex: 1; box-shadow: rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 1px 3px 1px; height: 20px; } #qBox span { font-weight: bold; padding: 5px; } #qBox span button { border: 0; display: inline-block; height: 30px; border-radius: 2px; min-width: 30px; } #qList { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; min-height: ${boxMinHeight}; max-height: ${boxMaxHeight}; overflow: hidden; overflow-y: scroll; overscroll-behavior: none; color: #000; } #qList::-webkit-scrollbar { width: 5px; } #qList::-webkit-scrollbar-track { background: #d7d7d7; border-radius: 6px; } #qList::-webkit-scrollbar-thumb { background: #888; border-radius: 6px; } #qList::-webkit-scrollbar-thumb:hover { background: #6192e4; } .preview-img{ margin-left: .7rem; background-color: #34bf9a; border-radius: 5px; color: #fff; border: none; padding: .5rem; cursor: pointer; } .close-btn{ margin-right: 5px; cursor: pointer; position: absolute; top: 50%; transform: translateY(-50%); right: 0; display: flex; justify-content: center; align-items: center; } #img-loading{ width: 50px; height: 50px; border: 5px solid #f3f3f3; border-top: 5px solid #3498db; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } `; let headNode = document.querySelector('head'); headNode.appendChild(qStyle); } let boxLeft, boxTop; function insertBox() { // 创建小菜单盒子 let qBox = document.createElement("div"); // 判断显示位置,显示默认位置还是根据上次位置显示 if (boxLeft == undefined || boxTop == undefined) { qBox.style.left = window.innerWidth - boxWidth + "px"; } else { qBox.style.left = boxLeft; qBox.style.top = boxTop; } qBox.id = "qBox"; qBox.innerHTML = `
磁力预览
`; // 添加到页面 document.body.insertBefore(qBox, document.body.firstElementChild); // =============== 拖拽实现 ================ const tag = document.querySelector('#qBox'); const qTop = document.querySelector('#qTop'); let isDragging = false; let startX, startY; let offsetX, offsetY; qTop.addEventListener('mousedown', e => { startX = e.clientX; startY = e.clientY; offsetX = startX - tag.offsetLeft; offsetY = startY - tag.offsetTop; isDragging = true; }); document.addEventListener('mousemove', e => { if (isDragging) { let x = e.clientX - offsetX; let y = e.clientY - offsetY; // 边界检测 防止超出边界 // 减去滚动条宽度 const maxX = window.innerWidth - tag.offsetWidth - (window.innerWidth - document.documentElement.clientWidth); // 可拖拽的最大 X 坐标 const maxY = window.innerHeight - tag.offsetHeight; x = Math.max(0, Math.min(x, maxX)); y = Math.max(0, Math.min(y, maxY)); tag.style.left = `${x}px`; tag.style.top = `${y}px`; } }); document.addEventListener('mouseup', () => { isDragging = false; }); // =============== 阻止滚动条穿透 ================ const scrollBox = document.getElementById("qList"); let initialPageY = 0 scrollBox.addEventListener('touchstart', (e) => { initialPageY = e.changedTouches[0].pageY }) scrollBox.addEventListener('touchmove', (e) => { // 滚动到边缘时阻止滚动 const deltaY = e.changedTouches[0].pageY - initialPageY // 禁止向上滚动溢出 if (e.cancelable && deltaY > 0 && scrollBox.scrollTop <= 0) { e.preventDefault() } // 禁止向下滚动溢出 if ( e.cancelable && deltaY < 0 && scrollBox.scrollTop + scrollBox.clientHeight >= scrollBox.scrollHeight ) { e.preventDefault() } }) // =============== 绑定关闭按钮 ================ document.getElementById("boxCloseBtn").addEventListener('click', (e) => { // 记录关闭前的位置 boxLeft = qBox.style.left boxTop = qBox.style.top qBox.remove(); isBox = false }) isBox = true } // 加载样式 加载窗口 initStyle() // 全文查找磁力链接 获取磁力链接文本所在的节点 function findNodesWithMatch(root, regex) { const results = []; if (!root) return results; // 文本节点 if (root.nodeType === 3) { for (let i = 0; i < regex.length; i++) { if (regex[i].test(root.nodeValue)) { // 跳过script标签中的磁力链接 if (root.parentNode.tagName.toLowerCase() != 'script') { results.push(root); } } } } // 元素节点 if (root.nodeType === 1) { // 匹配a标签的href if (root.tagName.toLowerCase() === 'a') { const href = root.getAttribute('href'); // 确保 href 属性存在 if (href) { for (let i = 0; i < regex.length; i++) { if (regex[i].test(href)) { results.push(root); } } } } } // fragment节点 if (root.nodeType === 11) { for (let i = 0; i < root.childNodes.length; i++) { results.push(...findNodesWithMatch(root.childNodes[i], regex)); } } // 递归遍历子节点 for (let i = 0; i < root.childNodes.length; i++) { results.push(...findNodesWithMatch(root.childNodes[i], regex)); } return results; } let Loading; function openLoading() { let loading = document.createElement("div"); loading.id = "img-loading" Loading = loading let qList = document.getElementById("qList") qList.innerHTML = ''; qList.append(Loading) } function closeLoading() { Loading.remove() Loading = undefined } function ajax(magnetUrl) { // 悬浮窗不存在就创建 if (!isBox) { insertBox() openLoading() }else{ openLoading() } const qList = document.getElementById("qList"); // Api有请求限制,一分钟内只能访问5次 GM_xmlhttpRequest({ url: "https://whatslink.info/api/v1/link?url=" + magnetUrl, method: "GET", headers: { "Referer": "https://whatslink.info/" }, onload: function (xhr) { if (xhr.status != 200) { qList.innerHTML = "请求接口错误"; return; } var obj = JSON.parse(xhr.responseText); console.log(xhr.responseText); if (obj.error != "") { qList.innerHTML = obj.name; return; } let div = document.createElement("div"); div.style.display = "flex"; div.style.flexWrap = "wrap"; div.style.padding = "0 .3rem"; div.className = "img-box" if (obj.screenshots == null) { qList.innerHTML = "无预览图"; } else { qList.innerHTML = ""; for (let j = 0; j < obj.screenshots.length; j++) { let a = document.createElement("a"); a.style.width = "100%"; a.style.height = "auto"; a.style.padding = ".3rem 0"; a.href = obj.screenshots[j].screenshot; a.target = "_blank"; let img = document.createElement("img"); img.src = obj.screenshots[j].screenshot img.style.width = "100%"; img.style.height = "auto"; a.append(img) div.append(a) qList.append(div) } closeLoading() } } }); } // 储存已经添加过的节点 const processedNodes = new Set(); // 处理节点 function exec(magnetArr) { for (let magnet in magnetArr) { let nodeArr = magnetArr[magnet]; // 有文本节点优先使用文本节点(a标签可能是图标等其他东西) let txtIndex = 0 for (let i = 0; i < nodeArr.length; i++) { if (nodeArr[i].nodeType === 3) { txtIndex = i; } } let node = magnetArr[magnet][txtIndex]; // 跳过已经添加过按钮的节点 if (!processedNodes.has(node)) { const button = document.createElement('button'); button.textContent = '预览图片'; button.classList.add('preview-img'); // 添加样式类 button.dataset.magnetUrl = magnet button.onclick = function (event) { // 阻止事件穿透到父级 event.stopPropagation(); event.preventDefault(); // 请求接口获取图片 ajax(event.target.dataset.magnetUrl) } // 将按钮插入到文本节点的父元素后 node.parentNode.insertBefore(button, node.nextSibling); // 将节点标记为已处理 processedNodes.add(node); } } } // 定时扫描 setInterval(function () { // 匹配磁力链接 let regexArr = [ /magnet:\?xt=urn:btih:[a-z0-9]{40}/i, /^[a-z0-9]{40}$/i, /[-._\/]{1}[a-z0-9]{40}[-._\/]{1}/i ] // 全文匹配磁力链接 let nodes = findNodesWithMatch(document.body, regexArr); let magnetArr = {} nodes.forEach(node => { let hash // 解析出hash if (node.nodeType === 3) { // 文本节点 hash = node.nodeValue.match(/[a-z0-9]{40}/i); } else if (node.nodeType === 1) { // 匹配a if (node.tagName.toLowerCase() === 'a') { hash = node.getAttribute('href').match(/[a-z0-9]{40}/i); } } if (hash == undefined || hash == node || hash == ""){ return; } // 同一磁力链接节点归为一组 let magnet = `magnet:?xt=urn:btih:${hash}` if (magnetArr[magnet]) { magnetArr[magnet].push(node); } else { magnetArr[magnet] = [node]; } }); // 处理节点 exec(magnetArr) }, scanMs); })();