// ==UserScript==
// @name 自动转换 ed2k 和磁力链接并一键复制
// @namespace http://tampermonkey.net/
// @version 1.5
// @description 自动检测网页中的所有 ed2k 和磁力链接并将其转换为可点击的超链接,同时提供一键复制所有链接的功能,并支持排除指定网址。
// @author 98-liu**
// @match *://*/*
// @grant GM_setClipboard
// @grant GM_getValue
// @grant GM_setValue
// @downloadURL none
// ==/UserScript==
(function() {
'use strict';
const EXCLUDE_KEY = 'excludedUrls';
// 检查当前页面是否在排除列表中
function isExcluded(url) {
const excludedUrls = GM_getValue(EXCLUDE_KEY, []);
for (const pattern of excludedUrls) {
if (new RegExp(pattern).test(url)) {
return true;
}
}
return false;
}
// 自动检测并转换 ed2k 和磁力链接为超链接
function convertLinks() {
if (isExcluded(window.location.href)) return;
// 正则表达式匹配 ed2k 链接
const ed2kRegex = /ed2k:\/\/\|file\|[^\|]+\|\d+\|[a-fA-F0-9]+\|\//g;
// 正则表达式匹配磁力链接
const magnetRegex = /magnet:\?xt=urn:[a-zA-Z0-9:.&=]+/g;
// 遍历网页中的所有文本节点
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
let node;
while (node = walker.nextNode()) {
if (node.nodeValue.match(ed2kRegex) || node.nodeValue.match(magnetRegex)) {
const parent = node.parentNode;
// 避免重复处理(如果已经是超链接则跳过)
if (parent.tagName !== 'A') {
let newHTML = node.nodeValue;
newHTML = newHTML.replace(ed2kRegex, '$&');
newHTML = newHTML.replace(magnetRegex, '$&');
const temp = document.createElement('div');
temp.innerHTML = newHTML;
// 将新内容插入到 DOM 中
while (temp.firstChild) {
parent.insertBefore(temp.firstChild, node);
}
// 移除原始文本节点
parent.removeChild(node);
}
}
}
}
// 获取网页中的所有 ed2k 和磁力链接,包括现有的超链接
function getAllLinks() {
const ed2kRegex = /ed2k:\/\/\|file\|[^\|]+\|\d+\|[a-fA-F0-9]+\|\//g;
const magnetRegex = /magnet:\?xt=urn:[a-zA-Z0-9:.&=]+/g;
const links = [];
// 查找所有现有的 ed2k 和磁力超链接
document.querySelectorAll('a').forEach(anchor => {
if (anchor.href.match(ed2kRegex) || anchor.href.match(magnetRegex)) {
links.push(anchor.href);
}
});
// 查找所有文本节点中的 ed2k 和磁力链接
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
let node;
while (node = walker.nextNode()) {
const matches = node.nodeValue.match(ed2kRegex) || [];
links.push(...matches);
const magnetMatches = node.nodeValue.match(magnetRegex) || [];
links.push(...magnetMatches);
}
return links.join('\n');
}
// 创建一键复制按钮
function createCopyButton() {
const button = document.createElement('button');
button.id = 'copyAllLinksButton';
button.textContent = '复制所有链接';
button.style.position = 'fixed';
button.style.top = '10px';
button.style.right = '10px';
button.style.zIndex = '10000';
button.style.padding = '10px';
button.style.backgroundColor = '#007bff';
button.style.color = '#fff';
button.style.border = 'none';
button.style.borderRadius = '5px';
button.style.cursor = 'pointer';
// 点击按钮时复制所有链接
button.addEventListener('click', () => {
const links = getAllLinks();
if (links) {
GM_setClipboard(links);
alert('已复制所有链接到剪贴板!');
} else {
alert('未找到链接!');
}
});
document.body.appendChild(button);
}
// 保存排除网址列表
function saveExcludedUrls(urls) {
GM_setValue(EXCLUDE_KEY, urls);
}
// 打开配置面板
function openConfigPanel() {
const panel = document.createElement('div');
panel.style.position = 'fixed';
panel.style.top = '10px';
panel.style.left = '10px';
panel.style.width = '300px';
panel.style.height = '200px';
panel.style.backgroundColor = '#fff';
panel.style.border = '1px solid #ccc';
panel.style.padding = '10px';
panel.style.zIndex = '1001';
panel.style.boxShadow = '2px 2px 10px rgba(0, 0, 0, 0.1)';
document.body.appendChild(panel);
const title = document.createElement('h3');
title.textContent = '排除网址配置';
panel.appendChild(title);
const list = document.createElement('ul');
list.style.maxHeight = '100px';
list.style.overflowY = 'auto';
panel.appendChild(list);
const input = document.createElement('input');
input.type = 'text';
input.placeholder = '输入网址模式(正则表达式)';
input.style.width = 'calc(100% - 22px)';
input.style.marginBottom = '5px';
panel.appendChild(input);
const addButton = document.createElement('button');
addButton.textContent = '添加';
addButton.addEventListener('click', () => {
const value = input.value.trim();
if (value) {
const excludedUrls = GM_getValue(EXCLUDE_KEY, []);
excludedUrls.push(value);
saveExcludedUrls(excludedUrls);
updateList();
input.value = '';
}
});
panel.appendChild(addButton);
const closeButton = document.createElement('button');
closeButton.textContent = '关闭';
closeButton.style.float = 'right';
closeButton.addEventListener('click', () => {
document.body.removeChild(panel);
});
panel.appendChild(closeButton);
updateList();
function updateList() {
list.innerHTML = '';
const excludedUrls = GM_getValue(EXCLUDE_KEY, []);
excludedUrls.forEach((pattern, index) => {
const li = document.createElement('li');
li.textContent = pattern;
const removeButton = document.createElement('button');
removeButton.textContent = '删除';
removeButton.style.float = 'right';
removeButton.addEventListener('click', () => {
excludedUrls.splice(index, 1);
saveExcludedUrls(excludedUrls);
updateList();
});
li.appendChild(removeButton);
list.appendChild(li);
});
}
}
// 创建配置按钮
function createConfigButton() {
const button = document.createElement('button');
button.id = 'configExcludeButton';
button.textContent = '配置排除网址';
button.style.position = 'fixed';
button.style.top = '40px';
button.style.right = '10px';
button.style.zIndex = '10000';
button.style.padding = '10px';
button.style.backgroundColor = '#ffc107';
button.style.color = '#fff';
button.style.border = 'none';
button.style.borderRadius = '5px';
button.style.cursor = 'pointer';
// 点击按钮时打开配置面板
button.addEventListener('click', openConfigPanel);
document.body.appendChild(button);
}
// 在页面加载完成后执行
window.addEventListener('load', () => {
if (!isExcluded(window.location.href)) {
convertLinks();
createCopyButton();
createConfigButton();
} else {
// 如果页面在排除列表中,移除可能存在的按钮
const copyButton = document.getElementById('copyAllLinksButton');
if (copyButton) {
document.body.removeChild(copyButton);
}
const configButton = document.getElementById('configExcludeButton');
if (configButton) {
document.body.removeChild(configButton);
}
}
});
// 监听动态内容加载(例如 AJAX)
const observer = new MutationObserver(() => {
if (!isExcluded(window.location.href)) {
convertLinks();
}
});
observer.observe(document.body, { childList: true, subtree: true });
})();