// ==UserScript== // @name Magnet Search Assistant // @namespace http://tampermonkey.net/ // @version 1.0.0 // @description 磁力搜索助手 - 悬浮搜索框、磁力链接高亮复制、站点导航 // @author 汉口铭哥 // @match *://cltt.me/* // @match *://*.cltt.me/* // @match *://skrbt.com/* // @match *://*.skrbt.com/* // @match *://nyaa.si/* // @match *://*.nyaa.si/* // @match *://archive.org/torrents* // @match *://*.archive.org/torrents* // @match *://ddcl.me/* // @match *://*.ddcl.me/* // @match *://dmhy.org/* // @match *://*.dmhy.org/* // @match *://mp4ba.vip/* // @match *://*.mp4ba.vip/* // @match *://mp4.biz/* // @match *://*.mp4.biz/* // @match *://dytt8899.com/* // @match *://*.dytt8899.com/* // @match *://dytt33.com/* // @match *://*.dytt33.com/* // @match *://dxyhnt.com/* // @match *://*.dxyhnt.com/* // @match *://www.dyttgov.com/* // @match *://*.dyttgov.com/* // @match *://xunlei8.me/* // @match *://*.xunlei8.me/* // @match *://web5.mukaku.com/* // @match *://*.mukaku.com/* // @match *://www.butailing.com/* // @match *://*.butailing.com/* // @match *://www.pianku6.com/* // @match *://*.pianku6.com/* // @grant GM_setClipboard // @grant GM_addStyle // @run-at document-end // @license MIT // ==/UserScript== (function() { 'use strict'; // ==================== 配置区域 ==================== const CONFIG = { // 支持的搜索引擎站点 searchEngines: [ { name: '当前站点', url: 'CURRENT', icon: '🔍' }, { name: 'cltt.me', url: 'https://cltt.me/search?keyword=', icon: '📎' }, { name: 'skrbt.com', url: 'https://skrbt.com/search?keyword=', icon: '🔎' }, { name: 'nyaa.si', url: 'https://nyaa.si/?f=0&c=0_0&q=', icon: '🐱' }, { name: 'archive.org', url: 'https://archive.org/torrents?query=', icon: '📚' }, { name: 'ddcl.me', url: 'https://ddcl.me/search?keyword=', icon: '📋' }, { name: 'dmhy.org', url: 'https://dmhy.org/topics/list?keyword=', icon: '🎌' }, { name: 'mp4ba.vip', url: 'https://mp4ba.vip/search?keyword=', icon: '🎬' }, { name: 'dytt8899', url: 'https://dytt8899.com/search?keyword=', icon: '🎞️' }, { name: 'xunlei8', url: 'https://xunlei8.me/search?keyword=', icon: '⚡' }, { name: 'mukaku', url: 'https://web5.mukaku.com/search?keyword=', icon: '🔥' }, { name: 'pianku6', url: 'https://www.pianku6.com/search?keyword=', icon: '📺' } ], // 站点快捷导航 navSites: [ { name: 'cltt', url: 'https://cltt.me', color: '#4a90d9' }, { name: 'skrbt', url: 'https://skrbt.com', color: '#e74c3c' }, { name: 'nyaa', url: 'https://nyaa.si', color: '#2ecc71' }, { name: 'archive', url: 'https://archive.org/torrents', color: '#f39c12' }, { name: 'ddcl', url: 'https://ddcl.me', color: '#9b59b6' }, { name: 'dmhy', url: 'https://dmhy.org', color: '#e91e63' }, { name: 'mp4ba', url: 'https://mp4ba.vip', color: '#00bcd4' }, { name: 'dytt', url: 'https://dytt8899.com', color: '#ff5722' }, { name: 'xunlei8', url: 'https://xunlei8.me', color: '#795548' }, { name: 'mukaku', url: 'https://web5.mukaku.com', color: '#607d8b' }, { name: 'pianku6', url: 'https://www.pianku6.com', color: '#8bc34a' } ] }; // ==================== 样式定义 ==================== const STYLES = ` /* 搜索框容器 */ #msa-search-container { position: fixed; top: 10px; left: 50%; transform: translateX(-50%); z-index: 2147483647; display: flex; align-items: center; gap: 8px; background: #fff; padding: 8px 12px; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.15); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 14px; transition: all 0.3s ease; } #msa-search-container:hover { box-shadow: 0 6px 25px rgba(0,0,0,0.2); } /* 下拉选择框 */ #msa-engine-select { padding: 6px 10px; border: 1px solid #ddd; border-radius: 6px; background: #f8f9fa; cursor: pointer; font-size: 13px; outline: none; min-width: 100px; } #msa-engine-select:hover { border-color: #4a90d9; } /* 搜索输入框 */ #msa-search-input { padding: 6px 12px; border: 1px solid #ddd; border-radius: 6px; width: 280px; font-size: 14px; outline: none; transition: border-color 0.2s; } #msa-search-input:focus { border-color: #4a90d9; } /* 搜索按钮 */ #msa-search-btn { padding: 6px 16px; background: #4a90d9; color: #fff; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; transition: background 0.2s; } #msa-search-btn:hover { background: #357abd; } /* 导航栏 */ #msa-nav-bar { position: fixed; top: 70px; right: 10px; z-index: 2147483646; display: flex; flex-direction: column; gap: 4px; background: #fff; padding: 8px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.1); max-height: calc(100vh - 100px); overflow-y: auto; } #msa-nav-bar::-webkit-scrollbar { width: 4px; } #msa-nav-bar::-webkit-scrollbar-thumb { background: #ddd; border-radius: 2px; } /* 导航项 */ .msa-nav-item { padding: 6px 10px; border-radius: 6px; color: #fff; text-decoration: none; font-size: 12px; font-weight: 500; text-align: center; transition: transform 0.2s, opacity 0.2s; white-space: nowrap; } .msa-nav-item:hover { transform: translateX(-4px); opacity: 0.9; } /* 磁力链接高亮 */ .msa-magnet-link { display: inline-flex; align-items: center; gap: 6px; padding: 4px 8px; background: #e3f2fd; border: 1px solid #90caf9; border-radius: 6px; color: #1565c0; font-size: 13px; word-break: break-all; margin: 2px 0; text-decoration: none; } .msa-magnet-link:hover { background: #bbdefb; } /* 复制按钮 */ .msa-copy-btn { padding: 2px 8px; background: #4a90d9; color: #fff; border: none; border-radius: 4px; cursor: pointer; font-size: 11px; white-space: nowrap; flex-shrink: 0; } .msa-copy-btn:hover { background: #357abd; } .msa-copy-btn.copied { background: #4caf50; } /* 提示信息 */ #msa-toast { position: fixed; top: 70px; left: 50%; transform: translateX(-50%) translateY(-20px); z-index: 2147483647; padding: 10px 24px; background: #333; color: #fff; border-radius: 6px; font-size: 14px; opacity: 0; pointer-events: none; transition: all 0.3s ease; } #msa-toast.show { opacity: 1; transform: translateX(-50%) translateY(0); } /* 隐藏类 */ .msa-hidden { display: none !important; } `; // ==================== 工具函数 ==================== const Utils = { // 显示提示 showToast(message, duration = 2000) { let toast = document.getElementById('msa-toast'); if (!toast) { toast = document.createElement('div'); toast.id = 'msa-toast'; document.body.appendChild(toast); } toast.textContent = message; toast.classList.add('show'); setTimeout(() => toast.classList.remove('show'), duration); }, // 复制到剪贴板 async copyToClipboard(text) { try { await navigator.clipboard.writeText(text); return true; } catch (err) { // 降级方案 const textarea = document.createElement('textarea'); textarea.value = text; textarea.style.position = 'fixed'; textarea.style.opacity = '0'; document.body.appendChild(textarea); textarea.select(); const success = document.execCommand('copy'); document.body.removeChild(textarea); return success; } }, // 获取当前搜索关键词 getCurrentKeyword() { const url = new URL(window.location.href); const params = ['q', 'query', 'keyword', 'search', 's', 'k', 'wd', 'word']; for (const p of params) { const v = url.searchParams.get(p); if (v) return decodeURIComponent(v); } // 尝试从路径获取 const match = url.pathname.match(/\/search\/(.+)/); if (match) return decodeURIComponent(match[1]); return ''; } }; // ==================== 搜索框模块 ==================== const SearchBox = { container: null, select: null, input: null, init() { this.createElements(); this.bindEvents(); }, createElements() { // 主容器 this.container = document.createElement('div'); this.container.id = 'msa-search-container'; // 搜索引擎选择 this.select = document.createElement('select'); this.select.id = 'msa-engine-select'; CONFIG.searchEngines.forEach((engine, index) => { const option = document.createElement('option'); option.value = index; option.textContent = engine.name; this.select.appendChild(option); }); // 搜索输入框 this.input = document.createElement('input'); this.input.id = 'msa-search-input'; this.input.type = 'text'; this.input.placeholder = '输入关键词搜索...'; this.input.value = Utils.getCurrentKeyword(); // 搜索按钮 const btn = document.createElement('button'); btn.id = 'msa-search-btn'; btn.textContent = '搜索'; this.container.appendChild(this.select); this.container.appendChild(this.input); this.container.appendChild(btn); // 插入页面 if (document.body) { document.body.appendChild(this.container); } else { document.addEventListener('DOMContentLoaded', () => { document.body.appendChild(this.container); }); } }, bindEvents() { // 搜索按钮点击 this.container.addEventListener('click', (e) => { if (e.target.id === 'msa-search-btn') { this.doSearch(); } }); // 回车搜索 this.input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { this.doSearch(); } }); // 快捷键聚焦 document.addEventListener('keydown', (e) => { if (e.ctrlKey && e.shiftKey && e.key === 'F') { e.preventDefault(); this.input.focus(); this.input.select(); } }); }, doSearch() { const keyword = this.input.value.trim(); if (!keyword) { Utils.showToast('请输入搜索关键词'); return; } const engineIndex = parseInt(this.select.value); const engine = CONFIG.searchEngines[engineIndex]; if (engine.url === 'CURRENT') { // 在当前站点搜索 const currentHost = window.location.hostname; let searchUrl = ''; if (currentHost.includes('nyaa.si')) { searchUrl = `https://nyaa.si/?f=0&c=0_0&q=${encodeURIComponent(keyword)}`; } else if (currentHost.includes('archive.org')) { searchUrl = `https://archive.org/torrents?query=${encodeURIComponent(keyword)}`; } else { // 通用搜索参数 searchUrl = `${window.location.protocol}//${currentHost}/search?keyword=${encodeURIComponent(keyword)}`; } window.location.href = searchUrl; } else { window.open(engine.url + encodeURIComponent(keyword), '_blank'); } } }; // ==================== 导航栏模块 ==================== const NavBar = { init() { this.createElements(); }, createElements() { const nav = document.createElement('div'); nav.id = 'msa-nav-bar'; CONFIG.navSites.forEach(site => { const link = document.createElement('a'); link.className = 'msa-nav-item'; link.href = site.url; link.target = '_blank'; link.textContent = site.name; link.style.background = site.color; nav.appendChild(link); }); if (document.body) { document.body.appendChild(nav); } else { document.addEventListener('DOMContentLoaded', () => { document.body.appendChild(nav); }); } } }; // ==================== 磁力链接处理模块 ==================== const MagnetHandler = { processedLinks: new WeakSet(), init() { this.processPage(); // 监听动态内容 this.observeMutations(); }, // 检查文本是否是磁力链接 isMagnet(text) { return typeof text === 'string' && text.trim().startsWith('magnet:?'); }, // 提取磁力链接 extractMagnet(text) { if (!text) return null; const match = text.match(/magnet:\?[^"\s<>]+/i); return match ? match[0] : null; }, // 创建高亮元素 createMagnetElement(magnetUrl, originalText) { const wrapper = document.createElement('span'); wrapper.className = 'msa-magnet-link'; // 链接文本(截断显示) const displayText = magnetUrl.length > 60 ? magnetUrl.substring(0, 57) + '...' : magnetUrl; wrapper.title = magnetUrl; const textSpan = document.createElement('span'); textSpan.textContent = displayText; // 复制按钮 const copyBtn = document.createElement('button'); copyBtn.className = 'msa-copy-btn'; copyBtn.textContent = '复制'; copyBtn.addEventListener('click', async (e) => { e.preventDefault(); e.stopPropagation(); const success = await Utils.copyToClipboard(magnetUrl); if (success) { copyBtn.textContent = '已复制'; copyBtn.classList.add('copied'); Utils.showToast('磁力链接已复制到剪贴板'); setTimeout(() => { copyBtn.textContent = '复制'; copyBtn.classList.remove('copied'); }, 2000); } else { Utils.showToast('复制失败,请手动复制'); } }); wrapper.appendChild(textSpan); wrapper.appendChild(copyBtn); return wrapper; }, // 处理文本节点 processTextNode(node) { if (this.processedLinks.has(node)) return; const text = node.textContent; const magnet = this.extractMagnet(text); if (magnet && node.parentElement) { // 检查父元素是否已经是我们的元素 if (node.parentElement.classList && node.parentElement.classList.contains('msa-magnet-link')) { return; } const parent = node.parentElement; const html = parent.innerHTML; // 创建替换用的HTML const magnetHtml = ` ${magnet.length > 60 ? magnet.substring(0, 57) + '...' : magnet} `; // 替换包含磁力链接的部分 const newHtml = html.replace(magnet, magnetHtml); if (newHtml !== html) { // 使用临时容器处理 const temp = document.createElement('div'); temp.innerHTML = newHtml; const fragment = document.createDocumentFragment(); while (temp.firstChild) { fragment.appendChild(temp.firstChild); } parent.innerHTML = ''; parent.appendChild(fragment); } this.processedLinks.add(node); } }, // 处理页面中的磁力链接 processPage() { // 查找所有可能包含磁力链接的元素 const walker = document.createTreeWalker( document.body, NodeFilter.SHOW_TEXT, null, false ); const nodesToProcess = []; let node; while (node = walker.nextNode()) { if (this.isMagnet(node.textContent)) { nodesToProcess.push(node); } } // 处理收集到的节点 nodesToProcess.forEach(n => this.processTextNode(n)); // 处理 href 属性中的磁力链接 const links = document.querySelectorAll('a[href^="magnet:?"]'); links.forEach(link => { if (link.classList.contains('msa-magnet-processed')) return; const magnet = link.getAttribute('href'); link.classList.add('msa-magnet-processed'); // 创建包装元素 const wrapper = this.createMagnetElement(magnet, magnet); link.parentNode.insertBefore(wrapper, link); link.style.display = 'none'; // 保留原始链接但隐藏 const hiddenLink = link; hiddenLink.style.display = 'none'; }); // 处理其他属性中的磁力链接 const allElements = document.querySelectorAll('*'); allElements.forEach(el => { // 检查 data 属性等 for (const attr of el.attributes || []) { if (attr.value && attr.value.startsWith('magnet:?')) { // 已经在处理href时覆盖 } } }); }, // 监听DOM变化 observeMutations() { const observer = new MutationObserver((mutations) => { let shouldProcess = false; mutations.forEach(mutation => { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { shouldProcess = true; } }); if (shouldProcess) { // 延迟处理,避免频繁触发 clearTimeout(this.processTimer); this.processTimer = setTimeout(() => this.processPage(), 500); } }); observer.observe(document.body, { childList: true, subtree: true }); } }; // ==================== 全局复制函数 ==================== window.__msa_copyMagnet = async function(btn) { const wrapper = btn.closest('.msa-magnet-link'); const magnet = wrapper.getAttribute('data-magnet') || wrapper.querySelector('span').textContent; const success = await Utils.copyToClipboard(magnet); if (success) { btn.textContent = '已复制'; btn.classList.add('copied'); Utils.showToast('磁力链接已复制到剪贴板'); setTimeout(() => { btn.textContent = '复制'; btn.classList.remove('copied'); }, 2000); } }; // ==================== 初始化 ==================== function init() { // 添加样式 GM_addStyle(STYLES); // 初始化各模块 SearchBox.init(); NavBar.init(); // 延迟处理磁力链接,确保页面加载完成 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { MagnetHandler.init(); }); } else { MagnetHandler.init(); } } // 启动 init(); })();