// ==UserScript==
// @name 磁力链接预览图片
// @namespace https://bbs.tampermonkey.net.cn/
// @match *://anybt.eth.link/*
// @match *://skrbtiu.top/*
// @match *://*.btmulu.work/hash/*
// @match *://cilian.site/*
// @version 1.0
// @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;
}
`;
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)) {
results.push(root);
}
}
}
for (let i = 0; i < root.childNodes.length; i++) {
results.push(...findNodesWithMatch(root.childNodes[i], regex));
}
return results;
}
function ajax(magnetUrl) {
if (!isBox) {
// 插入悬浮窗口
insertBox()
}
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)
}
});
}
// 储存已经添加过的节点
const processedNodes = new Set();
function exec(node) {
// 跳过已经添加上的
if (!processedNodes.has(node)) { // 检查是否已处理
const button = document.createElement('button');
button.textContent = '预览图片';
button.classList.add('preview-img'); // 添加样式类
let hash = node.nodeValue
let magnetRegex = /magnet:\?xt=urn:btih:/i;
if (!magnetRegex.test(hash)) {
hash = `magnet:?xt=urn:btih:${node.nodeValue}`;
}
button.dataset.magnetUrl = hash
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
]
let nodes = findNodesWithMatch(document.body, regexArr);
nodes.forEach(node => {
exec(node)
});
}, scanMs);
})();