// ==UserScript== // @name PTool // @namespace https://github.com/AboutCXJ // @version 2025-02-12 // @description PT站点自动批量下载种子 // @author AboutCXJ // @updateURL https://raw.githubusercontent.com/AboutCXJ/PTool/main/PTool.js // @downloadURL https://raw.githubusercontent.com/AboutCXJ/PTool/main/PTool.js // @include http://* // @include https://* // @icon https://www.google.com/s2/favicons?sz=64&domain=tampermonkey.net // @grant GM_notification // ==/UserScript== (function () { "use strict"; let DEBUG_MODE = false; let totalPages = 1; //要下载的种子页数(1000个/每天,不要大于10页) let singleSeedDelay = DEBUG_MODE ? 100 : 5000; //两种之间延时(ms) let multipleSeedDelay = DEBUG_MODE ? 5 : 60 * 60 * 1000; //每下载100种延时 let pageDelay = 10000; //翻页延时 let excludeSeeding = true; //排除正在做种的种子 let excludeZeroSeeding = true; //排除0做种的种子 let dryRun = true; //模拟运行 let multiplePage = false; //是否多页下载 let seedGap = 110; //间隔多少个种子触发一次大延时 let currentPage = 1; let downloadCount = 0; let logPanel, beginPanel; let selector; // NexusPHP站点 const nexusPHPSites = [ "hdfans.org", "hdvideo.one", "ubits.club", "pt.btschool.club", ]; // M-Team站点 const mteamSites = ["m-team"]; // 所有站点 const allSites = [...nexusPHPSites, ...mteamSites]; // 种子页面路径 const torrentsPagePaths = ["browse", "torrents.php"]; // 配置选择器 function loadSelector(currentURL) { if (mteamSites.some((site) => currentURL.includes(site))) { multiplePage = true; selector = { list: "tbody tr", title: "td:nth-child(3)", downloader: "td button", progressBar: "div[aria-valuenow='100']", size: "td div[class='mx-[-5px]']", seeders: "td span[aria-label*='arrow-up'] + span", leechers: "td span[aria-label*='arrow-down'] + span", nextPage: "li[title='下一頁'] button", }; } else if (nexusPHPSites.some((site) => currentURL.includes(site))) { selector = { list: "table[class='torrents'] > tbody > tr", title: "table[class='torrentname'] tr td", downloader: "table[class='torrentname'] tr td[width] a", progressBar: "div[title*=seeding]", size: "td:nth-child(5)", seeders: "td:nth-child(6)", leechers: "td:nth-child(7)", nextPage: "p[class='nexus-pagination'] a", }; } } // 监听URL变化 function observeURLChange() { let previousURL = location.href; // 创建监视器 const observer = new MutationObserver(() => { const currentURL = location.href; if (currentURL !== previousURL) { previousURL = currentURL; handleURLChange(currentURL); } }); observer.observe(document.body, { childList: true, subtree: true }); } // 处理URL变化 function handleURLChange(currentURL) { if (!allSites.some((site) => currentURL.includes(site))) { return; } if (torrentsPagePaths.some((path) => currentURL.includes(path))) { loadSelector(currentURL); loadBeginPanel(); } else { removeBeginPanel(); removeLogPanel(); } } //加载开始面板 function loadBeginPanel() { if (beginPanel) return; beginPanel = document.createElement("div"); beginPanel.style.position = "fixed"; beginPanel.style.bottom = "10px"; beginPanel.style.left = "10px"; beginPanel.style.zIndex = "10000"; beginPanel.style.padding = "10px"; beginPanel.style.backgroundColor = "#BDCAD6"; beginPanel.style.borderRadius = "5px"; beginPanel.style.fontSize = "12px"; document.body.appendChild(beginPanel); //页数输入框 const pageInput = document.createElement("input"); pageInput.type = "number"; pageInput.max = 10; pageInput.value = totalPages; if (multiplePage) { beginPanel.appendChild(createInputModule(pageInput, `下载几页:`)); } //种之间延时输入框 const singleSeedDelayInput = document.createElement("input"); singleSeedDelayInput.type = "number"; singleSeedDelayInput.step = 0.1; singleSeedDelayInput.value = singleSeedDelay / 1000; beginPanel.appendChild( createInputModule(singleSeedDelayInput, `单种延时:(秒)`) ); //百种延时 const multipleSeedDelayInput = document.createElement("input"); multipleSeedDelayInput.type = "number"; multipleSeedDelayInput.value = multipleSeedDelay / 1000 / 60; beginPanel.appendChild( createInputModule(multipleSeedDelayInput, `多种延时:(分)`) ); //翻页延时 const pageDelayInput = document.createElement("input"); pageDelayInput.placeholder = `翻页延时?${formatTime(pageDelay)}`; pageDelayInput.type = "number"; pageDelayInput.value = pageDelay / 1000; beginPanel.appendChild( createInputModule(pageDelayInput, `翻页延时:(秒)`) ); //排除做种中 const excludeSeedingCheck = document.createElement("input"); excludeSeedingCheck.type = "checkbox"; excludeSeedingCheck.checked = excludeSeeding; beginPanel.appendChild( createInputModule(excludeSeedingCheck, `排除正在做种:`) ); //排除0做种 const excludeZeroSeedingCheck = document.createElement("input"); excludeZeroSeedingCheck.type = "checkbox"; excludeZeroSeedingCheck.checked = excludeSeeding; beginPanel.appendChild( createInputModule(excludeZeroSeedingCheck, `排除零做种:`) ); //模拟运行 const dryRunCheck = document.createElement("input"); dryRunCheck.type = "checkbox"; dryRunCheck.checked = dryRun; beginPanel.appendChild(createInputModule(dryRunCheck, `模拟运行:`)); //开始按钮 const beginButton = document.createElement("button"); beginButton.innerText = "开始"; beginButton.style.width = "100%"; beginButton.style.padding = "5px"; beginButton.style.backgroundColor = "white"; beginButton.style.color = "black"; beginButton.style.border = "none"; beginButton.style.borderRadius = "5px"; beginButton.style.cursor = "pointer"; beginButton.style.margin = "5px 0"; beginPanel.appendChild(beginButton); // 按钮点击事件 beginButton.addEventListener("click", () => { loadLogPanel(); totalPages = pageInput.value || totalPages; singleSeedDelay = singleSeedDelayInput.value * 1000; multipleSeedDelay = multipleSeedDelayInput.value * 1000 * 60; pageDelay = pageDelayInput.value * 1000; excludeSeeding = excludeSeedingCheck.checked; excludeZeroSeeding = excludeZeroSeedingCheck.checked; dryRun = dryRunCheck.checked; if ( singleSeedDelay < 0 || multipleSeedDelay < 0 || pageDelay < 0 || totalPages < 1 || totalPages > 10 ) { panelMessage( "请输入正确的参数!馒头限制:100种/小时,1000种/天. " ); return; } beginPanel.style.display = "none"; clearLogPanel(); begin(); }); // function createInputModule(input, tip) { const div = document.createElement("div"); div.style.display = "flex"; input.style.borderRadius = "5px"; input.style.padding = "5px"; input.style.color = "black"; input.style.margin = "5px 0"; input.style.width = "60px"; const label = document.createElement("label"); label.innerText = tip; label.style.whiteSpace = "nowrap"; label.style.borderRadius = "5px"; label.style.width = "100%"; label.style.padding = "5px"; label.style.color = "black"; label.style.margin = "5px 0"; div.appendChild(label); div.appendChild(input); return div; } } // 移除开始面板 function removeBeginPanel() { if (beginPanel) { beginPanel.remove(); beginPanel = null; } } //加载日志面板 function loadLogPanel() { if (logPanel) return; logPanel = document.createElement("div"); logPanel.style.position = "fixed"; logPanel.style.bottom = "10px"; logPanel.style.right = "10px"; logPanel.style.width = "500px"; logPanel.style.height = "200px"; logPanel.style.zIndex = "10000"; logPanel.style.padding = "5px"; logPanel.style.backgroundColor = "rgba(0, 0, 0, 0.8)"; logPanel.style.borderRadius = "5px"; logPanel.style.color = "white"; logPanel.style.fontSize = "10px"; logPanel.style.overflowY = "auto"; document.body.appendChild(logPanel); } //清空日志面板 function clearLogPanel() { logPanel.innerHTML = ""; } //打印日志 function panelMessage(message) { const timestamp = new Date().toLocaleString(); logPanel.innerHTML += `<div>[${timestamp}] ${message}</div>`; logPanel.scrollTop = logPanel.scrollHeight; console.log(`[${timestamp}] ${message}`); } // 移除日志面板 function removeLogPanel() { if (logPanel) { logPanel.remove(); logPanel = null; } } //格式化时间 function formatTime(milliseconds) { let totalSeconds = Math.fround(milliseconds / 1000).toFixed(2); // 转换为秒 if (totalSeconds < 60) { return `${totalSeconds} 秒`; } else if (totalSeconds < 3600) { const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; return seconds === 0 ? `${minutes} 分钟` : `${minutes} 分钟 ${seconds} 秒`; } else { const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; return `${hours} 小时${minutes > 0 ? ` ${minutes} 分钟` : ""}${ seconds > 0 ? ` ${seconds} 秒` : "" }`; } } //预处理数据 function preprocessingDatas() { const list = document.querySelectorAll(selector.list); let datas = Array(); for (let index = 0; index < list.length; index++) { const element = list[index]; var data = new Object(); let title = element.querySelector(selector.title); if (title) { data.title = title.innerText; } else { continue; } data.downloader = element.querySelector(selector.downloader); let progress = element.querySelector(selector.progressBar); if (progress) { data.seeding = true; } else { data.seeding = false; } let size = element.querySelector(selector.size); if (size) { data.size = size.innerText; } let seeders = element.querySelector(selector.seeders); if (seeders) { data.seeders = seeders.innerText; } let leechers = element.querySelector(selector.leechers); if (leechers) { data.leechers = leechers.innerText; } // console.log(data); datas.push(data); } return datas; } //下载种子 async function downloadTorrents() { const datas = preprocessingDatas(); for (let i = 0; i < datas.length; i++) { const data = datas[i]; let shoudleSkip = false; let skipReason = ""; //排除正在做种的种子 if (data.seeding && excludeSeeding) { shoudleSkip = true; skipReason += "正在做种,"; } //排除0做种的种子 if (data.seeders === "0" && excludeZeroSeeding) { shoudleSkip = true; skipReason += "0做种。"; } panelMessage( `页:${currentPage} 种:${i + 1} 做种数:${ data.seeders } 下载数:${data.leechers} 大小:${data.size} 做种:${ data.seeding } 跳过:${shoudleSkip} 原因:${skipReason} <br /> ${ data.title } <hr />` ); if (shoudleSkip) { continue; } if (!dryRun) { data.downloader.click(); } downloadCount++; //每下载xxx个种子,暂停下载 if (downloadCount % seedGap === 0) { panelMessage( `已下载${downloadCount}个种子,等待${formatTime( multipleSeedDelay )}` ); await new Promise((resolve) => setTimeout(resolve, multipleSeedDelay) ); } await new Promise((resolve) => setTimeout(resolve, singleSeedDelay) ); } } //翻页 async function goToNextPage() { const nextPageButton = document.querySelector(selector.nextPage); if (nextPageButton) { nextPageButton.click(); panelMessage( `翻到第${currentPage + 1}页。等待${formatTime(pageDelay)}。` ); await new Promise((resolve) => setTimeout(resolve, pageDelay)); } else { panelMessage("未找到翻页按钮!"); return false; } return true; } //入口函数 async function begin() { panelMessage( `<br />页数:${totalPages} <br /> 单种延时:${formatTime( singleSeedDelay )} <br /> 多种延时:${formatTime( multipleSeedDelay )} <br /> 翻页延时:${formatTime( pageDelay )} <br /> 排除做种:${excludeSeeding} <br /> 排除零做种:${excludeZeroSeeding} <br /> 模拟运行:${dryRun} <hr />` ); while (currentPage <= totalPages) { panelMessage( `开始下载第${currentPage}页,共${totalPages}页。<hr />` ); await downloadTorrents(); if (currentPage < totalPages) { const hasNextPage = await goToNextPage(); if (!hasNextPage) break; } currentPage++; } let finishTip = `全部任务已完成,共下载${downloadCount}个种子!`; panelMessage(finishTip); GM_notification(finishTip); //恢复初始状态 currentPage = 1; downloadCount = 0; beginPanel.style.display = "block"; } // 初始化监听 observeURLChange(); // 初始检查 handleURLChange(location.href); })();