您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
目前支持BT4G/BTDig/SOBT/BTMulu/Nyaa/DMHY/CLM(磁力链搜索引擎)添加一键复制磁力链和推送到115网盘进行离线(推送离线任务需当前浏览器已登录115会员账号)
// ==UserScript== // @name 一键复制磁力链和推送到115离线 // @author [email protected] // @description 目前支持BT4G/BTDig/SOBT/BTMulu/Nyaa/DMHY/CLM(磁力链搜索引擎)添加一键复制磁力链和推送到115网盘进行离线(推送离线任务需当前浏览器已登录115会员账号) // @version 1.1.1.20250727 // @icon https://github.githubassets.com/assets/mona-loading-default-c3c7aad1282f.gif // @include *://bt4gprx.com/* // @include *://btdig.com/* // @include *://*.btdig.com/* // @include *://sobt*.*/* // @include *://nyaa.si/* // @include *://cl*.cl*/* // @include *://*.btmulu.*/* // @include *://btmulu.*/* // @include *://idope.se/* // @include *://*.dmhy.org/* // @include *://dmhy.org/* // @grant GM_setClipboard // @grant GM_notification // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @connect 115.com // @connect login.115.com // @connect * // @run-at document-end // @namespace https://greasyfork.org/users/1453515 // @license MIT // ==/UserScript== (function() { 'use strict'; // 移动设备检测 const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); // 配置参数 const CONFIG = { notificationTimeout: isMobile ? 5000 : 3000, retryCount: 3, retryDelay: 1000, cookieRefreshInterval: 30 * 60 * 1000 // 30分钟刷新一次Cookie }; // 错误码映射 const ERROR_CODES = { 10008: '任务已存在,无需重复添加', 911: '需要账号验证,请确保已登录115会员账号', 990: '任务包含违规内容,无法添加', 991: '服务器繁忙,请稍后再试', 992: '离线下载配额已用完', 993: '当前账号无权使用离线下载功能', 994: '文件大小超过限制', 995: '不支持的链接类型', 996: '网络错误,请检查连接', 997: '服务器内部错误', 998: '请求超时', 999: '未知错误' }; // 按钮样式常量 const BUTTON_STYLES = { common: { cursor: 'pointer', borderRadius: '4px', padding: isMobile ? '8px 12px' : '4px 8px', fontSize: isMobile ? '14px' : '12px', transition: 'all 0.15s ease-in-out', fontWeight: '400', lineHeight: '1.5', verticalAlign: 'middle', touchAction: 'manipulation', minWidth: isMobile ? '120px' : 'auto', minHeight: isMobile ? '40px' : 'auto' }, copy: { backgroundColor: '#000000', color: '#ffffff', border: '1px solid #000000', marginRight: '5px' }, offline: { backgroundColor: '#1E50A2', color: '#fff', border: '1px solid #1a4580' }, hoverCopy: { backgroundColor: '#333333', borderColor: '#333333' }, hoverOffline: { backgroundColor: '#1a4580', borderColor: '#163c70' } }; // 主初始化函数 function initializeScript() { // 添加Tampermonkey菜单命令 addMenuCommands(); // 设置定时刷新Cookie setInterval(checkCookieRefresh, 5 * 60 * 1000); // 监听页面变化 setupMutationObserver(); // 初始添加按钮 addActionButtons(); injectBtmuluTitleWidthCSS(); insertIdopeDetailButtons(); } // 添加Tampermonkey菜单命令 function addMenuCommands() { GM_registerMenuCommand("检查115登录状态", async () => { const isLoggedIn = await check115Login(true); showNotification('115状态', isLoggedIn ? '已登录' : '未登录'); if (!isLoggedIn) { setTimeout(() => { if (confirm('需要登录115网盘,是否进入115网盘登录页面?')) { window.open("https://115.com/?mode=login", "_blank"); } }, 500); } }); GM_registerMenuCommand("清除115登录状态", () => { GM_setValue('115_cookies', ''); GM_setValue('115_last_cookie_refresh', 0); showNotification('115状态', '已清除登录状态'); }); GM_registerMenuCommand("打开115网盘", () => window.open("https://115.com", "_blank")); } // 检查Cookie是否需要刷新 function checkCookieRefresh() { const lastRefresh = GM_getValue('115_last_cookie_refresh', 0); if (Date.now() - lastRefresh > CONFIG.cookieRefreshInterval) { check115Login(false); } } // 设置DOM变化观察器 function setupMutationObserver() { const observer = new MutationObserver(handleDomChanges); observer.observe(document, { childList: true, subtree: true }); } // DOM变化处理 function handleDomChanges(mutations) { for (const mutation of mutations) { if (mutation.addedNodes.length) { addActionButtons(); injectBtmuluTitleWidthCSS(); insertIdopeDetailButtons(); } } } // 注入btmulu标题宽度自适应CSS function injectBtmuluTitleWidthCSS() { if (!/([^.]+\.)?btmulu\.[^/]+$/.test(window.location.host)) return; if (document.getElementById('btmulu-title-width-style')) return; const style = document.createElement('style'); style.id = 'btmulu-title-width-style'; style.textContent = ` article.item > div > a { display: inline-block !important; width: auto !important; max-width: 100% !important; vertical-align: middle; } article.item > div > a h4 { display: inline !important; } `; document.head.appendChild(style); } // 处理iDope详情页按钮插入到标题左侧 function insertIdopeDetailButtons() { const nameDiv = document.getElementById('name'); const magnetA = document.getElementById('mangetinfo'); if (!nameDiv || !magnetA) return; if (nameDiv.dataset.buttonsAdded) return; nameDiv.dataset.buttonsAdded = 'true'; const magnetLink = magnetA.href; const btnContainer = document.createElement('span'); btnContainer.className = 'magnet-action-buttons'; btnContainer.style.display = 'inline-flex'; btnContainer.style.alignItems = 'center'; btnContainer.style.marginRight = '10px'; btnContainer.appendChild(createButton('copy', magnetLink, '🔗', 'idope')); btnContainer.appendChild(createButton('offline', magnetLink, '<img src="https://115.com/favicon.ico" style="width:12px;height:12px;vertical-align:middle;">', 'idope')); nameDiv.insertBefore(btnContainer, nameDiv.firstChild); } // 添加操作按钮 function addActionButtons() { // BT4G网站处理 if (window.location.host.includes('bt4gprx.com')) { handleBT4GSite(); } // CLM网站处理 else if (/cl[^.]+\.[^.]+\..+/.test(window.location.host)) { handleCLMSite(); } // iDope网站处理 else if (window.location.host.includes('idope.se')) { handleIdopeSite(); } // 其他网站处理 else { handleCommonSites(); } } // 处理CLM网站 function handleCLMSite() { const magnetWrapper = document.querySelector('.Information_magnet_wrapper'); if (!magnetWrapper || magnetWrapper.dataset.buttonsAdded) return; magnetWrapper.dataset.buttonsAdded = true; const magnetLink = document.querySelector('.Information_magnet'); if (!magnetLink || !magnetLink.href.startsWith('magnet:')) return; const btnContainer = document.createElement('div'); btnContainer.className = 'magnet-action-buttons'; btnContainer.style.margin = '10px 0'; btnContainer.style.display = 'flex'; btnContainer.style.gap = '10px'; btnContainer.appendChild(createButton('copy', magnetLink)); btnContainer.appendChild(createButton('offline', magnetLink)); magnetLink.parentNode.insertBefore(btnContainer, magnetLink.nextSibling); } // 处理iDope网站 function handleIdopeSite() { document.querySelectorAll('.resultdivtop').forEach(resultDiv => { if (resultDiv.dataset.buttonsAdded) return; resultDiv.dataset.buttonsAdded = true; const titleLink = resultDiv.querySelector('a[href^="/torrent/"]'); if (!titleLink) return; const hashMatch = titleLink.href.match(/\/torrent\/[^\/]+\/([a-f0-9]+)\//i); if (!hashMatch || !hashMatch[1]) return; const magnetLink = `magnet:?xt=urn:btih:${hashMatch[1]}`; const btnContainer = document.createElement('span'); btnContainer.className = 'magnet-action-buttons'; btnContainer.style.display = 'inline-flex'; btnContainer.style.alignItems = 'center'; btnContainer.style.marginRight = '8px'; btnContainer.appendChild(createButton('copy', magnetLink, '🔗', 'idope')); btnContainer.appendChild(createButton('offline', magnetLink, '<img src="https://115.com/favicon.ico" style="width:12px;height:12px;vertical-align:middle;">', 'idope')); const titleDiv = titleLink.querySelector('.resultdivtopname'); if (titleDiv) { titleDiv.insertBefore(btnContainer, titleDiv.firstChild); } else { titleLink.insertBefore(btnContainer, titleLink.firstChild); } }); } // 创建按钮的通用函数 function createButton(type, element, icon = null, styleType = 'common', noDefaultClick = false) { const btn = document.createElement('button'); btn.className = `${type}-magnet-btn`; // 设置按钮内容和样式 if (styleType === 'idope') { applyIdopeButtonStyle(btn, type, icon); } else if (styleType === 'btmulu') { applyBtmuluButtonStyle(btn, type, element); } else { applyCommonButtonStyle(btn, type, icon); } // 添加点击事件 if (!noDefaultClick) { setupButtonClickHandler(btn, type, element, styleType); } return btn; } // 应用iDope风格按钮样式 function applyIdopeButtonStyle(btn, type, icon) { Object.assign(btn.style, { cursor: 'pointer', backgroundColor: 'transparent', color: '#555', border: '1px solid #ddd', borderRadius: '4px', padding: '2px 6px', fontSize: '12px', marginRight: '5px', transition: 'all 0.15s ease-in-out', fontWeight: '400', lineHeight: '1.5', verticalAlign: 'middle', touchAction: 'manipulation', width: '30px', height: '26px', minWidth: '30px', minHeight: '26px', boxSizing: 'border-box', }); btn.innerHTML = icon || (type === 'copy' ? '🔗' : '<img src="https://115.com/favicon.ico" style="width:12px;height:12px;vertical-align:middle;">'); btn.addEventListener('mouseenter', () => { btn.style.backgroundColor = '#f0f0f0'; btn.style.borderColor = '#ccc'; }); btn.addEventListener('mouseleave', () => { btn.style.backgroundColor = 'transparent'; btn.style.borderColor = '#ddd'; }); btn.addEventListener('touchstart', () => { btn.style.backgroundColor = '#f0f0f0'; btn.style.borderColor = '#ccc'; }); btn.addEventListener('touchend', () => { btn.style.backgroundColor = 'transparent'; btn.style.borderColor = '#ddd'; }); } // 应用BTMulu风格按钮样式 function applyBtmuluButtonStyle(btn, type, typeLabel) { btn.textContent = type === 'copy' ? '复制' : '离线'; btn.style.height = typeLabel.offsetHeight ? typeLabel.offsetHeight + 'px' : '22px'; btn.style.lineHeight = typeLabel.offsetHeight ? typeLabel.offsetHeight + 'px' : '22px'; btn.style.padding = '0 10px'; btn.style.fontSize = '12px'; btn.style.background = type === 'copy' ? '#000' : '#1E50A2'; btn.style.color = '#fff'; btn.style.border = 'none'; btn.style.borderRadius = '4px'; btn.style.marginRight = '5px'; btn.style.marginTop = '1px'; btn.style.marginLeft = type === 'copy' ? '-2px' : '2px'; btn.style.verticalAlign = 'middle'; btn.style.cursor = 'pointer'; btn.style.transition = 'background 0.15s'; btn.addEventListener('mouseenter', () => { btn.style.background = type === 'copy' ? '#333' : '#163c70'; }); btn.addEventListener('mouseleave', () => { btn.style.background = type === 'copy' ? '#000' : '#1E50A2'; }); } // 应用通用按钮样式 function applyCommonButtonStyle(btn, type, icon) { Object.assign(btn.style, BUTTON_STYLES.common); Object.assign(btn.style, type === 'copy' ? BUTTON_STYLES.copy : BUTTON_STYLES.offline); btn.innerHTML = icon || (type === 'copy' ? '🔗 复制' : '<img src="https://115.com/favicon.ico" style="width:14px;height:14px;vertical-align:middle;"> 离线'); btn.addEventListener('mouseenter', () => { Object.assign(btn.style, type === 'copy' ? BUTTON_STYLES.hoverCopy : BUTTON_STYLES.hoverOffline); }); btn.addEventListener('mouseleave', () => { Object.assign(btn.style, type === 'copy' ? BUTTON_STYLES.copy : BUTTON_STYLES.offline); }); btn.addEventListener('touchstart', () => { Object.assign(btn.style, type === 'copy' ? BUTTON_STYLES.hoverCopy : BUTTON_STYLES.hoverOffline); }); btn.addEventListener('touchend', () => { Object.assign(btn.style, type === 'copy' ? BUTTON_STYLES.copy : BUTTON_STYLES.offline); }); } // 设置按钮点击事件处理 function setupButtonClickHandler(btn, type, element, styleType) { const handleClick = async (e) => { e.preventDefault(); e.stopPropagation(); const magnetLink = typeof element === 'string' ? element : await extractMagnetLink(element); if (!magnetLink) return; if (type === 'copy') { // 针对不同站点传递 styleType let feedbackType = styleType; // nyaa.si 识别 if (window.location.host.includes('nyaa.si')) feedbackType = 'nyaa'; await handleCopyAction(btn, magnetLink, feedbackType); } else { await handleOfflineAction(btn, magnetLink, styleType); } }; btn.addEventListener('click', handleClick); btn.addEventListener('touchend', handleClick); } // 处理复制操作 async function handleCopyAction(btn, magnetLink, styleType) { try { GM_setClipboard(magnetLink, 'text'); if (isMobile && navigator.clipboard && navigator.clipboard.writeText) { try { await navigator.clipboard.writeText(magnetLink); } catch (clipboardError) { console.log('使用navigator.clipboard失败:', clipboardError); } } showNotification('磁力链已复制', magnetLink); // 按钮反馈效果 const originalHTML = btn.innerHTML; let feedbackHTML = ''; if (["idope", "btmulu", "nyaa"].includes(styleType)) { // 绿色对勾SVG feedbackHTML = '<svg width="18" height="18" viewBox="0 0 18 18" style="display:inline-block;vertical-align:middle;"><circle cx="9" cy="9" r="9" fill="#4caf50"/><polyline points="5,10 8,13 13,6" fill="none" stroke="#fff" stroke-width="2"/></svg>'; } else { // "完成"或"成功" feedbackHTML = styleType === 'bt4g' ? '🔗 完成' : '🔗 完成'; } btn.innerHTML = feedbackHTML; btn.disabled = true; setTimeout(() => { btn.innerHTML = originalHTML; btn.disabled = false; }, 2000); } catch (error) { showNotification('复制失败', `请手动复制: ${magnetLink}`); } } // 处理离线操作 async function handleOfflineAction(btn, magnetLink, styleType) { await process115Offline(magnetLink); // 按钮反馈效果 const originalHTML = btn.innerHTML; btn.innerHTML = styleType === 'idope' ? '<svg width="18" height="18" viewBox="0 0 18 18" style="display:inline-block;vertical-align:middle;"><circle cx="9" cy="9" r="9" fill="#4caf50"/><polyline points="5,10 8,13 13,6" fill="none" stroke="#fff" stroke-width="2"/></svg>' : styleType === 'btmulu' ? '成功' : '<img src="https://115.com/favicon.ico" style="width:14px;height:14px;vertical-align:middle;"> 成功'; btn.disabled = true; setTimeout(() => { btn.innerHTML = originalHTML; btn.disabled = false; }, 2000); } // 处理BT4G网站 function handleBT4GSite() { // 在搜索结果标题前插入按钮 document.querySelectorAll('.result-item h5 > a[href^="/magnet/"]').forEach(titleA => { if (titleA.dataset.bt4gButtonsAdded) return; titleA.dataset.bt4gButtonsAdded = 'true'; const btnContainer = document.createElement('span'); btnContainer.className = 'magnet-action-buttons'; btnContainer.style.display = 'inline-block'; btnContainer.style.marginRight = '8px'; btnContainer.style.verticalAlign = 'middle'; // 复制按钮 const copyBtn = createButton('copy', titleA, null, 'common', true); copyBtn.addEventListener('click', async (e) => { e.preventDefault(); e.stopPropagation(); const magnet = await fetchBT4GMagnetFromDetail(titleA.href); if (!magnet) return showNotification('复制失败', '未获取到磁力链'); GM_setClipboard(magnet, 'text'); showNotification('磁力链已复制', magnet); copyBtn.innerHTML = '🔗 完成'; copyBtn.disabled = true; setTimeout(() => { copyBtn.innerHTML = '🔗 复制'; copyBtn.disabled = false; }, 2000); }); // 离线按钮 const offlineBtn = createButton('offline', titleA, null, 'common', true); offlineBtn.addEventListener('click', async (e) => { e.preventDefault(); e.stopPropagation(); const magnet = await fetchBT4GMagnetFromDetail(titleA.href); if (!magnet) return showNotification('推送失败', '未获取到磁力链'); await process115Offline(magnet); offlineBtn.innerHTML = '<img src="https://115.com/favicon.ico" style="width:14px;height:14px;vertical-align:text-bottom;margin-right:2px;"> 成功'; offlineBtn.disabled = true; setTimeout(() => { offlineBtn.innerHTML = '<img src="https://115.com/favicon.ico" style="width:14px;height:14px;vertical-align:text-bottom;margin-right:2px;"> 离线'; offlineBtn.disabled = false; }, 2000); }); btnContainer.appendChild(copyBtn); btnContainer.appendChild(offlineBtn); titleA.parentNode.insertBefore(btnContainer, titleA); }); // 详情页按钮逻辑 document.querySelectorAll('.card-body').forEach(cardBody => { if (cardBody.dataset.buttonsAdded) return; const magnetBtn = cardBody.querySelector('a[href*="downloadtorrentfile.com/hash/"]'); if (!magnetBtn) return; cardBody.dataset.buttonsAdded = true; const btnContainer = document.createElement('div'); btnContainer.className = 'magnet-action-buttons'; btnContainer.style.display = 'inline-block'; btnContainer.style.marginRight = '10px'; btnContainer.appendChild(createButton('copy', magnetBtn, '🔗 复制', 'bt4g')); btnContainer.appendChild(createButton('offline', magnetBtn, '<img src="https://115.com/favicon.ico" style="width:14px;height:14px;vertical-align:text-bottom;margin-right:2px;"> 离线', 'bt4g')); magnetBtn.parentNode.insertBefore(btnContainer, magnetBtn); }); } // 异步获取BT4G详情页磁力链 async function fetchBT4GMagnetFromDetail(detailHref) { try { let url = detailHref; if (!/^https?:/.test(url)) { url = location.origin + url; } const resp = await fetch(url, { credentials: 'omit' }); const html = await resp.text(); const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const magnetA = doc.querySelector('a.btn.btn-primary.me-2[href*="downloadtorrentfile.com/hash/"]'); if (!magnetA) return null; const hashMatch = magnetA.href.match(/hash\/([a-f0-9]{40})/i); if (!hashMatch) return null; return `magnet:?xt=urn:btih:${hashMatch[1]}`; } catch (e) { return null; } } // 处理其他通用网站 function handleCommonSites() { // SOBT网站处理 if (/sobt[^.]+\..+/.test(window.location.host)) { handleSOBTSite(); } // BTDig网站处理 else if (window.location.host.endsWith('btdig.com')) { handleBTDigSite(); } // BTMulu网站处理 else if (/([^.]+\.)?btmulu\.[^/]+$/.test(window.location.host)) { handleBTMuluSite(); } // Nyaa网站处理 else if (window.location.host.includes('nyaa.si')) { handleNyaaSite(); } // DMHY网站处理 else if (window.location.host.includes('dmhy.org')) { handleDMHYSite(); } } // 处理SOBT网站 function handleSOBTSite() { // 处理搜索结果页 document.querySelectorAll('h3 > a[href^="/torrent/"]').forEach(titleLink => { if (titleLink.dataset.buttonsAdded) return; titleLink.dataset.buttonsAdded = true; const btnContainer = document.createElement('span'); btnContainer.className = 'magnet-action-buttons'; btnContainer.style.display = 'inline-block'; btnContainer.style.marginLeft = '10px'; btnContainer.style.marginRight = '5px'; btnContainer.appendChild(createButton('copy', titleLink)); btnContainer.appendChild(createButton('offline', titleLink)); titleLink.parentNode.insertBefore(btnContainer, titleLink); }); // 处理详情页的磁力链接 document.querySelectorAll('.panel-body a[href^="magnet:"]').forEach(magnetLink => { if (magnetLink.dataset.buttonsAdded) return; magnetLink.dataset.buttonsAdded = true; const btnContainer = document.createElement('div'); btnContainer.className = 'magnet-action-buttons'; btnContainer.style.margin = '10px 0'; btnContainer.appendChild(createButton('copy', magnetLink)); btnContainer.appendChild(createButton('offline', magnetLink)); magnetLink.parentNode.insertBefore(btnContainer, magnetLink.nextSibling); }); // 处理详情页磁力链 input 框右侧按钮 const mLinkInput = document.getElementById('m_link'); if (mLinkInput && !mLinkInput.dataset.idopeButtonsAdded) { mLinkInput.dataset.idopeButtonsAdded = 'true'; const btnContainer = document.createElement('span'); btnContainer.className = 'magnet-action-buttons'; btnContainer.style.display = 'inline-flex'; btnContainer.style.alignItems = 'center'; btnContainer.style.marginLeft = '8px'; // 复制按钮 btnContainer.appendChild(createButton('copy', mLinkInput.value, '🔗', 'idope')); // 离线按钮 btnContainer.appendChild(createButton('offline', mLinkInput.value, '<img src="https://115.com/favicon.ico" style="width:12px;height:12px;vertical-align:middle;">', 'idope')); mLinkInput.parentNode.insertBefore(btnContainer, mLinkInput.nextSibling); } } // 处理BTDig网站 function handleBTDigSite() { document.querySelectorAll('.torrent_name > a').forEach(titleLink => { if (titleLink.dataset.buttonsAdded) return; titleLink.dataset.buttonsAdded = true; const btnContainer = document.createElement('span'); btnContainer.className = 'magnet-action-buttons'; btnContainer.style.display = 'inline-block'; btnContainer.style.marginRight = '10px'; let resultDiv = titleLink.closest('.one_result'); let magnetLink = resultDiv ? resultDiv.querySelector('.torrent_magnet a[href^="magnet:"]') : null; if (!magnetLink) return; btnContainer.appendChild(createButton('copy', magnetLink)); btnContainer.appendChild(createButton('offline', magnetLink)); titleLink.parentNode.insertBefore(btnContainer, titleLink); }); } // 处理BTMulu网站 function handleBTMuluSite() { document.querySelectorAll('article.item a[href^="/hash/"]').forEach(titleLink => { if (titleLink.dataset.buttonsAdded) return; titleLink.dataset.buttonsAdded = true; const h4 = titleLink.querySelector('h4'); if (!h4) return; const typeLabel = h4.querySelector('span.label'); if (!typeLabel) return; const btnContainer = document.createElement('span'); btnContainer.className = 'magnet-action-buttons'; btnContainer.style.display = 'inline-flex'; btnContainer.style.alignItems = 'center'; btnContainer.style.marginLeft = '10px'; btnContainer.style.gap = '5px'; const hashMatch = titleLink.href.match(/\/hash\/([a-f0-9]+)\.html$/i); if (!hashMatch || !hashMatch[1]) return; const magnetLink = `magnet:?xt=urn:btih:${hashMatch[1]}`; // 使用iDope风格的按钮 btnContainer.appendChild(createButton('copy', magnetLink, '🔗', 'idope')); btnContainer.appendChild(createButton('offline', magnetLink, '<img src="https://115.com/favicon.ico" style="width:12px;height:12px;vertical-align:middle;">', 'idope')); typeLabel.after(btnContainer); }); } // 处理Nyaa网站 function handleNyaaSite() { document.querySelectorAll('td.text-center a[href^="magnet:"]').forEach(magnetLink => { if (magnetLink.dataset.buttonsAdded) return; magnetLink.dataset.buttonsAdded = true; let tr = magnetLink.closest('tr'); let downloadBtn = tr ? tr.querySelector("a[href^='/download/']") : null; const btnContainer = document.createElement('span'); btnContainer.className = 'magnet-action-buttons'; btnContainer.style.display = 'inline-flex'; btnContainer.style.alignItems = 'center'; btnContainer.style.marginRight = '6px'; btnContainer.appendChild(createButton('copy', magnetLink, '🔗', 'idope')); btnContainer.appendChild(createButton('offline', magnetLink, '<img src="https://115.com/favicon.ico" style="width:12px;height:12px;vertical-align:middle;">', 'idope')); if (downloadBtn) { downloadBtn.parentNode.insertBefore(btnContainer, downloadBtn); } else { magnetLink.parentNode.insertBefore(btnContainer, magnetLink.nextSibling); } }); } // 处理DMHY网站 function handleDMHYSite() { const magnetHeader = document.querySelector('#topic_list th:nth-child(4)'); if (magnetHeader) { magnetHeader.style.width = '18%'; } document.querySelectorAll('a.download-arrow.arrow-magnet').forEach(magnetLink => { if (magnetLink.dataset.buttonsAdded) return; magnetLink.dataset.buttonsAdded = true; const btnContainer = document.createElement('span'); btnContainer.className = 'magnet-action-buttons'; btnContainer.style.display = 'inline-block'; btnContainer.style.marginLeft = '5px'; btnContainer.appendChild(createButton('copy', magnetLink)); btnContainer.appendChild(createButton('offline', magnetLink)); magnetLink.parentNode.insertBefore(btnContainer, magnetLink); }); document.querySelectorAll('#tabs-1 a.magnet, #tabs-1 a#magnet2').forEach(magnetLink => { if (magnetLink.dataset.buttonsAdded) return; magnetLink.dataset.buttonsAdded = true; const btnContainer = document.createElement('span'); btnContainer.className = 'magnet-action-buttons'; btnContainer.style.display = 'inline-block'; btnContainer.style.marginLeft = '5px'; btnContainer.appendChild(createButton('copy', magnetLink)); btnContainer.appendChild(createButton('offline', magnetLink)); magnetLink.parentNode.insertBefore(btnContainer, magnetLink.nextSibling); }); } // 从元素提取磁力链 async function extractMagnetLink(element) { try { if (element.href) { // SOBT网站的标题链 if (element.href.includes('/torrent/')) { const hashMatch = element.href.match(/\/torrent\/([a-f0-9]+)\.html$/i); if (hashMatch && hashMatch[1]) { return `magnet:?xt=urn:btih:${hashMatch[1]}`; } } // BT4G网站的磁力链 else if (element.href.includes('downloadtorrentfile.com/hash/')) { const hashMatch = element.href.match(/hash\/([a-f0-9]+)/i); if (hashMatch && hashMatch[1]) { return `magnet:?xt=urn:btih:${hashMatch[1]}`; } } // BTMulu网站的标题链 else if (element.href.includes('/hash/')) { const hashMatch = element.href.match(/\/hash\/([a-f0-9]+)\.html$/i); if (hashMatch && hashMatch[1]) { return `magnet:?xt=urn:btih:${hashMatch[1]}`; } } // iDope网站的标题链 else if (element.href.includes('/torrent/')) { const hashMatch = element.href.match(/\/torrent\/[^\/]+\/([a-f0-9]+)\//i); if (hashMatch && hashMatch[1]) { return `magnet:?xt=urn:btih:${hashMatch[1]}`; } } // Nyaa/DMHY网站或直接磁力链 else if (element.href.startsWith('magnet:')) { return element.href; } } // 如果是字符串直接返回 if (typeof element === 'string' && element.startsWith('magnet:')) { return element; } throw new Error('无法提取磁力链Hash'); } catch (error) { showNotification('错误', error.message); return null; } } // 检查115登录状态 async function check115Login(forceCheck = false) { try { const lastRefresh = GM_getValue('115_last_cookie_refresh', 0); const currentCookies = GM_getValue('115_cookies', ''); if (!forceCheck && currentCookies && Date.now() - lastRefresh < CONFIG.cookieRefreshInterval) { return true; } const cookies = await getCurrent115Cookies(); if (!cookies) { GM_setValue('115_cookies', ''); GM_setValue('115_last_cookie_refresh', 0); return false; } const isValid = await validate115Cookies(cookies); if (isValid) { GM_setValue('115_cookies', cookies); GM_setValue('115_last_cookie_refresh', Date.now()); return true; } GM_setValue('115_cookies', ''); GM_setValue('115_last_cookie_refresh', 0); return false; } catch (error) { console.error('检查登录状态失败:', error); return false; } } // 获取当前有效的115 Cookie function getCurrent115Cookies() { return new Promise((resolve) => { GM_xmlhttpRequest({ url: 'https://115.com/', method: 'GET', anonymous: true, onload: function(response) { const cookieHeader = response.responseHeaders .split('\n') .find(row => row.toLowerCase().startsWith('set-cookie:')); if (cookieHeader) { const cookies = cookieHeader.replace(/^set-cookie:\s*/i, '').split(';')[0]; resolve(cookies); } else { if (response.finalUrl.includes('login.115.com')) { resolve(''); } else { const savedCookies = GM_getValue('115_cookies', ''); resolve(savedCookies); } } }, onerror: () => resolve('') }); }); } // 验证Cookie是否有效 function validate115Cookies(cookies) { return new Promise((resolve) => { GM_xmlhttpRequest({ url: 'https://115.com/web/lixian/', method: 'GET', headers: { 'Cookie': cookies }, onload: function(response) { resolve(!response.finalUrl.includes('login.115.com')); }, onerror: () => resolve(false) }); }); } // 离线下载处理流程 async function process115Offline(magnetLink) { const notificationId = Date.now(); try { showNotification('115离线', '正在检查登录状态...', notificationId); const isLoggedIn = await check115Login(true); if (!isLoggedIn) { throw new Error('请先登录115网盘'); } showNotification('115离线', '正在提交离线任务...', notificationId); const result = await submit115OfflineTask(magnetLink); handleOfflineResult(result); } catch (error) { showNotification('115离线失败', error.message); if (error.message.includes('登录')) { setTimeout(() => { if (confirm('需要登录115网盘,是否进入115网盘登录页面?')) { window.open('https://115.com/?mode=login', '_blank'); } }, 500); } } finally { GM_notification({ id: notificationId, done: true }); } } // 提交离线任务 async function submit115OfflineTask(magnetLink) { const cookies = GM_getValue('115_cookies', ''); if (!cookies) { throw new Error('未检测到有效的登录状态'); } const response = await fetch115Api( `https://115.com/web/lixian/?ct=lixian&ac=add_task_url&url=${encodeURIComponent(magnetLink)}`, { headers: { 'Cookie': cookies } } ); return tryParseJson(response); } // 处理离线结果 function handleOfflineResult(result) { if (!result) { throw new Error('无效的响应'); } if (result.state) { showNotification('115离线成功', '任务已成功添加到离线下载列表'); return; } const errorMsg = ERROR_CODES[result.errcode] || result.error_msg || '未知错误'; throw new Error(errorMsg); } // 通用请求函数 function fetch115Api(url, options = {}) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ url: url, method: options.method || 'GET', headers: { 'User-Agent': navigator.userAgent, 'Origin': 'https://115.com', ...(options.headers || {}) }, data: options.body, onload: function(response) { if (response.status >= 200 && response.status < 300) { resolve(response.responseText); } else { reject(new Error(`请求失败: ${response.status}`)); } }, onerror: reject }); }); } // 尝试解析JSON function tryParseJson(text) { try { return JSON.parse(text); } catch (e) { return null; } } // 显示通知 function showNotification(title, text, id = null) { GM_notification({ title: title, text: text, timeout: CONFIG.notificationTimeout, ...(id ? { id } : {}) }); } // 启动脚本 initializeScript(); })();