您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
一键将磁力链接、种子资源推送到远程的qBittorrent或Aria2客户端下载
// ==UserScript== // @name qBittorrent & Aria2 推送助手 // @namespace http://tampermonkey.net/ // @version 1.1 // @description 一键将磁力链接、种子资源推送到远程的qBittorrent或Aria2客户端下载 // @author deepseek & 通义 // @match *://*/* // @grant GM_xmlhttpRequest // @grant GM_notification // @grant GM_addStyle // @license MIT // @icon https://p.sda1.dev/26/90d92c0ef2f191f7f467677361af144f/push.png // ==/UserScript== (function () { 'use strict'; // ================== 配置区域 ================== // qBittorrent 在此处配置 const QBITTORRENT_URL = 'http://192.168.1.23:8080/'; const QBITTORRENT_USER = 'admin'; const QBITTORRENT_PASS = 'adminadmin'; // Aria2 在此处配置配置 const ARIA2_URL = 'http://192.168.1.23:6800/jsonrpc'; const ARIA2_SECRET = '123456'; const SHOW_DELAY = 300; // 显示延迟(毫秒) const HIDE_DELAY = 275; // 隐藏延迟(毫秒) // 配置支持推送的资源类型(通过注释禁用不需要的类型,主要作用于Aria2) const SUPPORTED_TYPES = [ 'magnet', // 磁力链接 'torrent', // BT种子文件 //'http', // HTTP下载 //'https', // HTTPS下载 //'ftp', // FTP下载 'ed2k', // eDonkey链接 //'metalink' // Metalink文件 ].filter(Boolean); // 过滤掉被注释的行 // ================== 配置结束 ================== let currentMainButton = null; let currentSubButtons = null; let showTimeout = null; let hideTimeout = null; // 添加全局样式 GM_addStyle(` .qba-push-container { position: absolute; z-index: 99999; display: flex; flex-direction: column; gap: 6px; pointer-events: none; opacity: 0; transform: translateY(10px); transition: all 0.3s ease; } .qba-push-container.visible { opacity: 1; transform: translateY(0); pointer-events: all; } .qba-main-button { padding: 6px 12px; font-size: 12px; font-weight: 600; color: white; background: linear-gradient(135deg, #4facfe, #00f2fe); border: none; border-radius: 20px; cursor: pointer; display: flex; align-items: center; gap: 6px; box-shadow: 0 3px 12px rgba(0, 0, 0, 0.15); transition: all 0.3s ease; white-space: nowrap; pointer-events: all; z-index: 100000; } .qba-main-button:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.25); } .qba-sub-buttons { display: flex; gap: 6px; background: rgba(30, 41, 59, 0.95); border-radius: 10px; padding: 8px; box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25); border: 1px solid rgba(255, 255, 255, 0.1); pointer-events: all; } .qba-sub-button { padding: 6px 10px; font-size: 11px; font-weight: 500; color: #e2e8f0; background: rgba(15, 23, 42, 0.8); border: none; border-radius: 6px; cursor: pointer; display: flex; align-items: center; gap: 5px; transition: all 0.3s ease; white-space: nowrap; } .qba-sub-button:hover { background: rgba(79, 172, 254, 0.3); transform: translateY(-2px); } .qba-qb-button { border-left: 2px solid #4facfe; } .qba-aria2-button { border-left: 2px solid #00f2fe; } .qba-working { background: #ffc107 !important; cursor: wait !important; } .qba-success { background: #28a745 !important; } .qba-error { background: #dc3545 !important; } `); // 资源类型图标映射 const resourceIcons = { 'magnet': '🧲', 'torrent': '📁', 'http': '🌐', 'https': '🔒', 'ftp': '📡', 'ed2k': '🔗', 'metalink': '⚙️', 'default': '⬇️' }; // 下载器图标 const downloaderIcons = { 'qb': '🔵', // qBittorrent图标 'aria2': '🟢' // Aria2图标 }; // 获取背景亮度(感知亮度公式) function getBackgroundBrightness(element) { let el = element; while (el && el !== document.body) { const style = window.getComputedStyle(el); const bg = style.backgroundColor; if (bg && bg !== 'rgba(0, 0, 0, 0)' && bg !== 'transparent') { const rgb = bg.match(/(\d+),\s*(\d+),\s*(\d+)/); if (rgb) { const r = parseInt(rgb[1]); const g = parseInt(rgb[2]); const b = parseInt(rgb[3]); return (r * 299 + g * 587 + b * 114) / 1000; } } el = el.parentElement; } return 255; // 默认亮色背景 } // 获取链接类型 function getLinkType(url) { if (url.startsWith('magnet:')) return 'magnet'; if (url.endsWith('.torrent') || url.includes('.torrent?')) return 'torrent'; if (url.startsWith('ed2k:')) return 'ed2k'; if (url.startsWith('metalink:')) return 'metalink'; if (url.startsWith('http:')) return 'http'; if (url.startsWith('https:')) return 'https'; if (url.startsWith('ftp:')) return 'ftp'; return 'default'; } // 创建主按钮 function createMainButton(url, text, link) { const linkType = getLinkType(url); // 检查资源类型是否在支持列表中 if (!SUPPORTED_TYPES.includes(linkType)) return null; const icon = resourceIcons[linkType] || resourceIcons.default; const button = document.createElement('button'); button.className = 'qba-main-button'; button.innerHTML = `${icon} ${linkType.toUpperCase()}`; button.setAttribute('aria-label', '推送下载资源'); button.dataset.linkType = linkType; // 判断背景明暗 const brightness = getBackgroundBrightness(link); const isDarkBg = brightness < 150; // 根据背景调整按钮文字颜色 if (isDarkBg) { button.style.color = '#fff'; } else { button.style.color = '#fff'; } return button; } // 创建子按钮 function createSubButtons(url, text, link) { const container = document.createElement('div'); container.className = 'qba-sub-buttons'; const linkType = getLinkType(url); // 只对磁力链接显示qBittorrent按钮 if (linkType === 'magnet') { // 创建推送到qBittorrent的按钮 const qbButton = document.createElement('button'); qbButton.className = 'qba-sub-button qba-qb-button'; qbButton.innerHTML = `<span>${downloaderIcons.qb}</span> <span>推送到 qB</span>`; qbButton.onclick = (e) => { e.stopPropagation(); pushToDownloader('qb', url, text, link, qbButton); }; container.appendChild(qbButton); } // 对于所有支持的类型,显示Aria2按钮 // 创建推送到Aria2的按钮 const aria2Button = document.createElement('button'); aria2Button.className = 'qba-sub-button qba-aria2-button'; aria2Button.innerHTML = `<span>${downloaderIcons.aria2}</span> <span>推送到 Aria2</span>`; aria2Button.onclick = (e) => { e.stopPropagation(); pushToDownloader('aria2', url, text, link, aria2Button); }; container.appendChild(aria2Button); return container; } // 创建按钮容器 function createButtonContainer(url, text, link) { // 移除现有的按钮 if (currentMainButton) { currentMainButton.remove(); currentMainButton = null; } if (currentSubButtons) { currentSubButtons.remove(); currentSubButtons = null; } const mainButton = createMainButton(url, text, link); if (!mainButton) return null; // 如果资源类型不在支持列表中,不创建按钮 const container = document.createElement('div'); container.className = 'qba-push-container'; const subButtons = createSubButtons(url, text, link); container.appendChild(mainButton); container.appendChild(subButtons); // 主按钮悬停事件 mainButton.addEventListener('mouseenter', () => { clearTimeout(hideTimeout); }); // 主按钮离开事件 mainButton.addEventListener('mouseleave', () => { hideTimeout = setTimeout(() => { container.classList.remove('visible'); setTimeout(() => { if (container.parentNode) { container.remove(); } }, 300); }, HIDE_DELAY); }); // 子按钮区域事件 subButtons.addEventListener('mouseenter', () => { clearTimeout(hideTimeout); }); subButtons.addEventListener('mouseleave', () => { hideTimeout = setTimeout(() => { container.classList.remove('visible'); setTimeout(() => { if (container.parentNode) { container.remove(); } }, 300); }, HIDE_DELAY); }); currentMainButton = mainButton; currentSubButtons = subButtons; return container; } // 显示按钮容器 function showButtonContainer(link) { clearTimeout(showTimeout); clearTimeout(hideTimeout); showTimeout = setTimeout(() => { const rect = link.getBoundingClientRect(); const scrollTop = window.scrollY || window.pageYOffset; const scrollLeft = window.scrollX || window.pageXOffset; const url = link.href; const text = link.textContent || '下载资源'; // 创建按钮容器 const container = createButtonContainer(url, text, link); if (!container) return; // 如果资源类型不在支持列表中,不显示按钮 document.body.appendChild(container); // 获取容器尺寸 container.style.visibility = 'hidden'; container.style.display = 'block'; const containerRect = container.getBoundingClientRect(); container.style.visibility = 'visible'; container.style.display = ''; // 计算位置 - 在链接上方居中显示 let top = rect.top + scrollTop - containerRect.height - 8; let left = rect.left + scrollLeft + (rect.width - containerRect.width) / 2; // 如果上方空间不足,显示在下方 if (top < 0) { top = rect.bottom + scrollTop + 8; } // 调整左右位置防止溢出 if (left < 0) left = 8; if (left + containerRect.width > window.innerWidth) { left = window.innerWidth - containerRect.width - 8; } container.style.top = `${top}px`; container.style.left = `${left}px`; // 显示容器 setTimeout(() => { container.classList.add('visible'); }, 50); }, SHOW_DELAY); } // 设置链接监听 function setupLinkListeners() { const selectors = [ 'a[href^="magnet:?xt=urn:btih:"]', 'a[href*=".torrent"]', 'a[href^="ed2k:"]', 'a[href^="http://"]', 'a[href^="https://"]', 'a[href^="ftp://"]' ]; const links = document.querySelectorAll(selectors.join(',')); links.forEach(link => { if (link.dataset.qbaAdded) return; link.dataset.qbaAdded = 'true'; // 鼠标进入链接 link.addEventListener('mouseenter', () => { showButtonContainer(link); }); // 鼠标离开链接 link.addEventListener('mouseleave', () => { clearTimeout(showTimeout); hideTimeout = setTimeout(() => { const container = document.querySelector('.qba-push-container'); if (container) { container.classList.remove('visible'); setTimeout(() => { if (container.parentNode) { container.remove(); } }, 300); } }, HIDE_DELAY); }); }); } // 推送到下载器 function pushToDownloader(downloader, url, text, link, clickedButton) { // 保存原始状态以便恢复 const originalHTML = clickedButton.innerHTML; const originalClass = clickedButton.className; // 更新按钮状态为"正在推送" clickedButton.innerHTML = '⏳ 推送中...'; clickedButton.classList.add('qba-working'); clickedButton.disabled = true; if (downloader === 'qb') { pushToQbittorrent(url, text, (success, message) => { handlePushResult(success, message, 'qBittorrent', clickedButton, originalHTML, originalClass); }); } else { pushToAria2(url, (success, message) => { handlePushResult(success, message, 'Aria2', clickedButton, originalHTML, originalClass); }); } } // 处理推送结果 function handlePushResult(success, message, downloaderName, clickedButton, originalHTML, originalClass) { // 移除工作状态 clickedButton.classList.remove('qba-working'); clickedButton.disabled = false; if (success) { // 推送成功 clickedButton.innerHTML = '✅ 已推送'; clickedButton.classList.add('qba-success'); // 显示成功通知(如果想要油猴脚本在浏览器报告推送通知请去掉注释) // GM_notification({ // title: '推送成功', // text: `资源已添加到${downloaderName}下载队列`, // timeout: 3000 // }); } else { // 推送失败 clickedButton.innerHTML = '❌ 失败'; clickedButton.classList.add('qba-error'); // 延迟显示错误信息 setTimeout(() => { alert(`推送到${downloaderName}失败: ${message}`); // 恢复原始状态(在失败时) clickedButton.innerHTML = originalHTML; clickedButton.className = originalClass; }, 100); } // 2秒后移除整个容器 setTimeout(() => { const container = document.querySelector('.qba-push-container'); if (container) { container.remove(); } }, 2000); } // 推送到 qBittorrent function pushToQbittorrent(url, description = '', callback) { console.log('🔗 正在推送到 qBittorrent:', url); const loginUrl = QBITTORRENT_URL + 'api/v2/auth/login'; const addUrl = QBITTORRENT_URL + 'api/v2/torrents/add'; // 第一步:登录获取 SID GM_xmlhttpRequest({ method: 'POST', url: loginUrl, headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Referer': QBITTORRENT_URL }, data: `username=${encodeURIComponent(QBITTORRENT_USER)}&password=${encodeURIComponent(QBITTORRENT_PASS)}`, withCredentials: true, onload: function (response) { if (response.status === 200 && response.responseText === 'Ok.') { console.log('✅ qBittorrent 登录成功'); // 第二步:推送任务 GM_xmlhttpRequest({ method: 'POST', url: addUrl, headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Referer': QBITTORRENT_URL }, data: `urls=${encodeURIComponent(url)}`, withCredentials: true, onload: function (res) { if (res.status === 200) { console.log('✅ qBittorrent 添加成功'); callback(true, '添加成功'); } else { console.error('❌ qBittorrent 添加失败:', res.status, res.statusText); callback(false, `服务器错误: ${res.status} ${res.statusText}`); } }, onerror: function (err) { console.error('❌ qBittorrent 网络错误:', err); callback(false, `网络错误: ${err.status} ${err.statusText}`); } }); } else { console.error('❌ qBittorrent 登录失败:', response.status, response.responseText); callback(false, '登录失败,请检查用户名密码或 Web UI 设置'); } }, onerror: function (err) { console.error('❌ qBittorrent 登录请求失败:', err); callback(false, `登录请求失败: ${err.status} ${err.statusText}`); } }); } // 推送到 Aria2 function pushToAria2(url, callback) { console.log('🔗 正在推送到 Aria2:', url); // 特殊处理:.torrent文件需要下载后推送 if (url.endsWith('.torrent') || url.includes('.torrent?')) { console.log('🔗 处理.torrent文件'); return handleTorrentFile(url, callback); } const payload = { jsonrpc: "2.0", id: "aria2_push_" + Date.now(), method: "aria2.addUri", params: [ `token:${ARIA2_SECRET}`, [url], {} ] }; console.log('📦 Aria2 请求负载:', payload); sendAria2Request(payload, callback); } // 处理.torrent文件 function handleTorrentFile(url, callback) { console.log('⬇️ 下载.torrent文件:', url); GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'arraybuffer', onload: function(response) { if (response.status === 200) { console.log('✅ .torrent文件下载成功'); const base64Torrent = arrayBufferToBase64(response.response); const payload = { jsonrpc: "2.0", id: "aria2_push_" + Date.now(), method: "aria2.addTorrent", params: [ `token:${ARIA2_SECRET}`, base64Torrent, [] ] }; sendAria2Request(payload, callback); } else { console.error('❌ 无法获取.torrent文件:', response.status, response.statusText); callback(false, `无法获取.torrent文件: ${response.status} ${response.statusText}`); } }, onerror: function(err) { console.error('❌ 下载.torrent文件失败:', err); callback(false, `下载.torrent文件失败: ${err.status} ${err.statusText}`); } }); } // 发送Aria2请求 function sendAria2Request(payload, callback) { GM_xmlhttpRequest({ method: 'POST', url: ARIA2_URL, headers: { 'Content-Type': 'application/json', }, data: JSON.stringify(payload), onload: function (response) { try { console.log('📨 Aria2 响应:', response.status, response.responseText); const res = JSON.parse(response.responseText); if (res.error) { console.error('❌ Aria2 返回错误:', res.error); callback(false, `Aria2 错误: ${res.error.message || res.error.code}`); } else { console.log('✅ 成功添加到 Aria2'); callback(true, '添加成功'); } } catch (e) { console.error('❌ 解析响应失败:', e); callback(false, '解析响应失败'); } }, onerror: function (err) { console.error('❌ 请求失败:', err); callback(false, `网络错误: ${err.status} ${err.statusText}`); }, ontimeout: function () { console.error('❌ 请求超时'); callback(false, '请求超时,请检查Aria2服务状态'); } }); } // ArrayBuffer 转 Base64 function arrayBufferToBase64(buffer) { let binary = ''; const bytes = new Uint8Array(buffer); const len = bytes.byteLength; for (let i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary); } // 主函数 function main() { console.log('🚀 qBittorrent & Aria2 推送助手已启动'); setupLinkListeners(); // 动态监听 DOM 变化 const observer = new MutationObserver(mutations => { mutations.forEach(() => setupLinkListeners()); }); observer.observe(document.body, { childList: true, subtree: true }); } // 页面加载后执行 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', main); } else { main(); } })();