您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
获取PT站点所有种子连接并且全自动感谢发布者
// ==UserScript== // @name PT全自动感谢 // @namespace http://tampermonkey.net/ // @version 0.7 // @description 获取PT站点所有种子连接并且全自动感谢发布者 // @author 天凉好个秋 // @match *://*/torrents.php* // @grant GM_setClipboard // @grant GM_xmlhttpRequest // @license GPL-3.0 License // ==/UserScript== (function() { 'use strict'; const currentSite = { protocol: window.location.protocol, host: window.location.host, baseUrl: window.location.protocol + '//' + window.location.host }; const style = document.createElement('style'); style.textContent = ` .custom-dialog { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 25px; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.15); width: 420px; z-index: 10000; font-family: Arial, sans-serif; } .dialog-title { font-size: 18px; font-weight: bold; margin-bottom: 20px; color: #333; text-align: center; } .task-info { background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px; text-align: center; } .current-task { margin-top: 15px; padding: 10px; background: #f0f7ff; border-radius: 6px; border-left: 4px solid #1890ff; display: none; } .progress-container { margin: 20px 0; background: #f0f2f5; height: 8px; border-radius: 4px; overflow: hidden; } .progress-bar { height: 100%; background: #1890ff; width: 0%; transition: width 0.3s; } .task-completed { color: #52c41a; font-weight: bold; } .task-failed { color: #ff4d4f; font-weight: bold; } .task-skipped { color: #faad14; font-weight: bold; } .task-status { margin-top: 15px; text-align: center; font-size: 16px; } .status-chips { display: flex; justify-content: center; margin-top: 15px; gap: 10px; } .status-chip { padding: 4px 12px; border-radius: 16px; font-size: 13px; display: inline-flex; align-items: center; } .chip-success { background-color: #f6ffed; border: 1px solid #b7eb8f; color: #52c41a; } .chip-error { background-color: #fff2f0; border: 1px solid #ffccc7; color: #ff4d4f; } .chip-warning { background-color: #fffbe6; border: 1px solid #ffe58f; color: #faad14; } .close-button { position: absolute; right: 15px; top: 15px; cursor: pointer; font-size: 20px; color: #999; transition: color 0.3s; } .close-button:hover { color: #666; } .action-buttons { text-align: center; margin-top: 25px; } .action-button { padding: 8px 20px; border: none; border-radius: 6px; background: #1890ff; color: white; cursor: pointer; transition: background 0.3s; } .action-button:hover { background: #40a9ff; } .action-button:disabled { background: #bedaff; cursor: not-allowed; } .dialog-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 9999; } .settings-group { margin: 15px 0; } .settings-label { display: block; margin-bottom: 8px; font-weight: bold; color: #333; } .settings-input { width: 100%; padding: 8px; border: 1px solid #d9d9d9; border-radius: 4px; box-sizing: border-box; } .settings-input:focus { border-color: #40a9ff; outline: none; box-shadow: 0 0 0 2px rgba(24,144,255,0.2); } .settings-radio { margin-right: 10px; } .settings-radio-label { margin-right: 20px; font-weight: normal; } .page-settings { display: none; margin-top: 10px; padding: 10px; background: #f8f9fa; border-radius: 4px; } .current-page-info { margin-bottom: 10px; font-size: 13px; color: #666; } .floating-button { position: fixed; bottom: 30px; right: 30px; width: 50px; height: 50px; border-radius: 50%; background: #1890ff; color: white; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 12px rgba(0,0,0,0.15); cursor: pointer; z-index: 9998; transition: all 0.3s; border: none; font-size: 24px; } .floating-button:hover { background: #40a9ff; transform: translateY(-3px); box-shadow: 0 6px 16px rgba(0,0,0,0.2); } .page-status { margin-top: 10px; font-size: 14px; color: #666; text-align: center; } .site-info { margin-top: 5px; font-size: 12px; color: #999; text-align: center; } `; document.head.appendChild(style); let settings = { interval: 2000, pageMode: 'current', pageCount: 1 }; function getCurrentPage() { const urlParams = new URLSearchParams(window.location.search); return parseInt(urlParams.get('page')) || 1; } function generatePageUrl(page) { const url = new URL(window.location.href); url.searchParams.set('page', page); return url.toString(); } async function getLinksFromPage(url) { try { const response = await fetch(url); const html = await response.text(); const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const links = []; const rows = doc.querySelectorAll('.torrentname'); rows.forEach(row => { const linkElement = row.querySelector('a[href^="details.php?id="]'); if (linkElement) { const href = linkElement.getAttribute('href'); const title = linkElement.getAttribute('title') || linkElement.textContent; if (href && title) { const fullUrl = `${currentSite.baseUrl}/${href}`; const idMatch = href.match(/id=(\d+)/); if (idMatch) { links.push({ title: title.trim(), url: fullUrl, id: idMatch[1] }); } } } }); return links; } catch (error) { console.error('获取页面链接失败:', error); return []; } } async function checkIfThanked(url) { try { const response = await fetch(url); const html = await response.text(); return html.includes('你已說過謝謝') || html.includes('你已说过谢谢'); } catch (error) { console.error('检查是否已感谢失败:', error); return false; } } async function executeThanks(id, url) { const formData = new URLSearchParams(); formData.append('id', id); const response = await fetch(`${currentSite.baseUrl}/thanks.php`, { method: 'POST', headers: { 'accept': '*/*', 'accept-language': 'zh-CN,zh;q=0.9', 'cache-control': 'no-cache', 'content-type': 'application/x-www-form-urlencoded', 'origin': currentSite.baseUrl, 'pragma': 'no-cache', 'referer': url, 'sec-ch-ua': '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Windows"', 'sec-fetch-dest': 'empty', 'sec-fetch-mode': 'cors', 'sec-fetch-site': 'same-origin', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36' }, body: formData, credentials: 'include' }); if (!response.ok) { throw new Error(`感谢请求失败: ${response.status}`); } const text = await response.text(); if (text.includes('error') || text.includes('失败')) { throw new Error(text); } return true; } function createDialog(links) { const currentPage = getCurrentPage(); const overlay = document.createElement('div'); overlay.className = 'dialog-overlay'; const dialog = document.createElement('div'); dialog.className = 'custom-dialog'; const closeButton = document.createElement('span'); closeButton.className = 'close-button'; closeButton.textContent = '×'; closeButton.onclick = () => { overlay.remove(); dialog.remove(); }; const title = document.createElement('div'); title.className = 'dialog-title'; title.textContent = '自动感谢任务'; const siteInfo = document.createElement('div'); siteInfo.className = 'site-info'; siteInfo.textContent = `当前站点: ${currentSite.host}`; const pageModeGroup = document.createElement('div'); pageModeGroup.className = 'settings-group'; const pageModeLabel = document.createElement('label'); pageModeLabel.className = 'settings-label'; pageModeLabel.textContent = '执行范围'; const pageModeOptions = document.createElement('div'); const currentPageRadio = document.createElement('input'); currentPageRadio.type = 'radio'; currentPageRadio.name = 'pageMode'; currentPageRadio.id = 'pageMode-current'; currentPageRadio.className = 'settings-radio'; currentPageRadio.value = 'current'; currentPageRadio.checked = true; const currentPageLabel = document.createElement('label'); currentPageLabel.htmlFor = 'pageMode-current'; currentPageLabel.className = 'settings-radio-label'; currentPageLabel.textContent = '当前页面'; const multiPageRadio = document.createElement('input'); multiPageRadio.type = 'radio'; multiPageRadio.name = 'pageMode'; multiPageRadio.id = 'pageMode-multi'; multiPageRadio.className = 'settings-radio'; multiPageRadio.value = 'multi'; const multiPageLabel = document.createElement('label'); multiPageLabel.htmlFor = 'pageMode-multi'; multiPageLabel.className = 'settings-radio-label'; multiPageLabel.textContent = '自定义页数'; const pageSettings = document.createElement('div'); pageSettings.className = 'page-settings'; pageSettings.id = 'page-settings'; const currentPageInfo = document.createElement('div'); currentPageInfo.className = 'current-page-info'; currentPageInfo.textContent = `当前页码: ${currentPage}`; const pageCountLabel = document.createElement('label'); pageCountLabel.className = 'settings-label'; pageCountLabel.textContent = '需要处理的页数:'; pageCountLabel.style.marginBottom = '8px'; const pageCountInput = document.createElement('input'); pageCountInput.className = 'settings-input'; pageCountInput.type = 'number'; pageCountInput.min = '1'; pageCountInput.max = '10'; pageCountInput.value = '3'; pageCountInput.id = 'page-count'; pageSettings.appendChild(currentPageInfo); pageSettings.appendChild(pageCountLabel); pageSettings.appendChild(pageCountInput); pageModeOptions.appendChild(currentPageRadio); pageModeOptions.appendChild(currentPageLabel); pageModeOptions.appendChild(multiPageRadio); pageModeOptions.appendChild(multiPageLabel); pageModeGroup.appendChild(pageModeLabel); pageModeGroup.appendChild(pageModeOptions); pageModeGroup.appendChild(pageSettings); currentPageRadio.addEventListener('change', () => { if(currentPageRadio.checked) { pageSettings.style.display = 'none'; settings.pageMode = 'current'; } }); multiPageRadio.addEventListener('change', () => { if(multiPageRadio.checked) { pageSettings.style.display = 'block'; settings.pageMode = 'multi'; settings.pageCount = parseInt(pageCountInput.value); } }); pageCountInput.addEventListener('change', () => { settings.pageCount = parseInt(pageCountInput.value); }); const taskInfo = document.createElement('div'); taskInfo.className = 'task-info'; taskInfo.id = 'task-info'; taskInfo.textContent = `当前页找到 ${links.length} 个链接`; const currentTask = document.createElement('div'); currentTask.className = 'current-task'; currentTask.id = 'current-task'; currentTask.style.display = 'none'; const pageStatus = document.createElement('div'); pageStatus.className = 'page-status'; pageStatus.id = 'page-status'; pageStatus.style.display = 'none'; const statusChips = document.createElement('div'); statusChips.className = 'status-chips'; statusChips.id = 'status-chips'; statusChips.innerHTML = ` <div class="status-chip chip-success">成功: 0</div> <div class="status-chip chip-warning">跳过: 0</div> <div class="status-chip chip-error">失败: 0</div> `; statusChips.style.display = 'none'; const progressContainer = document.createElement('div'); progressContainer.className = 'progress-container'; const progressBar = document.createElement('div'); progressBar.className = 'progress-bar'; progressBar.id = 'progress-bar'; progressContainer.appendChild(progressBar); const taskStatus = document.createElement('div'); taskStatus.className = 'task-status'; taskStatus.id = 'task-status'; taskStatus.textContent = '准备就绪'; const settingsGroup = document.createElement('div'); settingsGroup.className = 'settings-group'; const settingsLabel = document.createElement('label'); settingsLabel.className = 'settings-label'; settingsLabel.textContent = '请求间隔时间 (毫秒)'; const settingsInput = document.createElement('input'); settingsInput.className = 'settings-input'; settingsInput.type = 'number'; settingsInput.min = '1000'; settingsInput.max = '10000'; settingsInput.step = '500'; settingsInput.value = settings.interval; settingsInput.onchange = (e) => { settings.interval = parseInt(e.target.value); }; settingsGroup.appendChild(settingsLabel); settingsGroup.appendChild(settingsInput); const actionButtons = document.createElement('div'); actionButtons.className = 'action-buttons'; const executeButton = document.createElement('button'); executeButton.className = 'action-button'; executeButton.textContent = '开始执行任务'; executeButton.onclick = async () => { executeButton.disabled = true; executeButton.textContent = '任务执行中...'; settingsInput.disabled = true; pageCountInput.disabled = true; currentPageRadio.disabled = true; multiPageRadio.disabled = true; statusChips.style.display = 'flex'; if (settings.pageMode === 'current') { await executeAllTasks(links); } else { await executeMultiPageTasks(currentPage, settings.pageCount); } executeButton.textContent = '任务已完成'; }; actionButtons.appendChild(executeButton); dialog.appendChild(closeButton); dialog.appendChild(title); dialog.appendChild(siteInfo); dialog.appendChild(pageModeGroup); dialog.appendChild(taskInfo); dialog.appendChild(pageStatus); dialog.appendChild(currentTask); dialog.appendChild(statusChips); dialog.appendChild(progressContainer); dialog.appendChild(taskStatus); dialog.appendChild(settingsGroup); dialog.appendChild(actionButtons); document.body.appendChild(overlay); document.body.appendChild(dialog); } function updateStatusChips(completed, skipped, failed) { const statusChips = document.getElementById('status-chips'); if (statusChips) { statusChips.innerHTML = ` <div class="status-chip chip-success">成功: ${completed}</div> <div class="status-chip chip-warning">跳过: ${skipped}</div> <div class="status-chip chip-error">失败: ${failed}</div> `; } } async function executeMultiPageTasks(startPage, pageCount) { let totalCompleted = 0; let totalSkipped = 0; let totalFailed = 0; let totalLinks = 0; const progressBar = document.getElementById('progress-bar'); const taskStatus = document.getElementById('task-status'); const pageStatus = document.getElementById('page-status'); const taskInfo = document.getElementById('task-info'); pageStatus.style.display = 'block'; for (let i = 0; i < pageCount; i++) { const pageNum = startPage + i; const pageUrl = generatePageUrl(pageNum); pageStatus.textContent = `正在处理第 ${i + 1}/${pageCount} 页 (页码: ${pageNum})`; progressBar.style.width = `${(i / pageCount) * 100}%`; const pageLinks = await getLinksFromPage(pageUrl); totalLinks += pageLinks.length; taskInfo.textContent = `共找到 ${totalLinks} 个链接`; const pageResult = await executeAllTasks(pageLinks, totalCompleted, totalSkipped, totalFailed); totalCompleted += pageResult.completed; totalSkipped += pageResult.skipped; totalFailed += pageResult.failed; } progressBar.style.width = '100%'; if (totalFailed === 0) { taskStatus.textContent = `✅ 全部任务执行完成!`; taskStatus.className = 'task-status task-completed'; } else { taskStatus.textContent = `⚠️ 任务执行完成,部分失败!`; taskStatus.className = 'task-status task-failed'; } updateStatusChips(totalCompleted, totalSkipped, totalFailed); } async function executeAllTasks(links, initialCompleted = 0, initialSkipped = 0, initialFailed = 0) { const totalTasks = links.length; let completed = initialCompleted; let failed = initialFailed; let skipped = initialSkipped; const progressBar = document.getElementById('progress-bar'); const taskStatus = document.getElementById('task-status'); const currentTask = document.getElementById('current-task'); if (initialCompleted === 0) { progressBar.style.width = '0%'; } for (let i = 0; i < links.length; i++) { const link = links[i]; if (settings.pageMode === 'current') { const progress = Math.round((i / totalTasks) * 100); progressBar.style.width = `${progress}%`; } currentTask.style.display = 'block'; currentTask.textContent = `正在处理: ${link.title.substring(0, 50)}${link.title.length > 50 ? '...' : ''}`; try { const alreadyThanked = await checkIfThanked(link.url); if (alreadyThanked) { skipped++; currentTask.textContent += ' (已跳过)'; currentTask.style.borderLeft = '4px solid #faad14'; } else { await executeThanks(link.id, link.url); completed++; currentTask.style.borderLeft = '4px solid #52c41a'; } } catch (error) { failed++; currentTask.style.borderLeft = '4px solid #ff4d4f'; console.error('执行任务失败:', error, link); } updateStatusChips(completed, skipped, failed); if (settings.pageMode === 'current') { taskStatus.textContent = `进度: ${i + 1}/${totalTasks}`; } else { taskStatus.textContent = `当前页进度: ${i + 1}/${totalTasks}`; } await new Promise(resolve => setTimeout(resolve, settings.interval)); } if (settings.pageMode === 'current') { progressBar.style.width = '100%'; currentTask.style.display = 'none'; if (failed === 0) { taskStatus.textContent = `✅ 全部任务执行完成!`; taskStatus.className = 'task-status task-completed'; } else { taskStatus.textContent = `⚠️ 任务执行完成,部分失败!`; taskStatus.className = 'task-status task-failed'; } } return { completed, skipped, failed }; } function getCurrentPageLinks() { const links = []; const rows = document.querySelectorAll('.torrentname'); rows.forEach(row => { const linkElement = row.querySelector('a[href^="details.php?id="]'); if (linkElement) { const href = linkElement.getAttribute('href'); const title = linkElement.getAttribute('title') || linkElement.textContent; if (href && title) { const fullUrl = `${currentSite.baseUrl}/${href}`; const idMatch = href.match(/id=(\d+)/); if (idMatch) { links.push({ title: title.trim(), url: fullUrl, id: idMatch[1] }); } } } }); return links; } const floatingButton = document.createElement('button'); floatingButton.className = 'floating-button'; floatingButton.textContent = '👍'; floatingButton.title = '获取链接并执行感谢'; floatingButton.onclick = function() { const links = getCurrentPageLinks(); if (links.length > 0) { createDialog(links); } else { alert('未找到任何链接!'); } }; document.body.appendChild(floatingButton); })();