磁力链接预览图片
// ==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 = `
<div id="qTop">
<span style="margin: auto;">
磁力预览
</span>
<span class="close-btn" id="boxCloseBtn">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12L19 6.41Z" fill="currentColor"/>
</svg>
</span>
</div>
<div style="display: block;">
<div id="qList"></div>
</div>
`;
// 添加到页面
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);
})();