// ==UserScript== // @name Get Free Torrents // @namespace http://tampermonkey.net/ // @version 1.0.6 // @description 该插件主要用于抓取指定页面(即 "torrents.php")中的免费种子信息,并将其按照剩余时间从短到长排序后,以表格形式呈现给用户。用户可以一键复制所有展示种子的链接,同时具备筛选功能,允许用户设定自定义时间阈值,仅复制剩余时间超过该阈值的种子链接。此外,插件还支持添加自定义URL参数以扩展功能或满足个性化需求。 // @author 飞天小猪 // @match http*://*/*torrents*.php* // @match http*://kp.m-team.cc/* // @match http*://*/*special*.php* // @icon https://gongjux.com/files/3/4453uhm5937m/32/favicon.ico // @grant none // @require https://greasyfork.org/scripts/453166-jquery/code/jquery.js?version=1105525 // @require https://greasyfork.org/scripts/28502-jquery-ui-v1-11-4/code/jQuery%20UI%20-%20v1114.js?version=187735 // @license MIT // @downloadURL none // ==/UserScript== (function () { 'use strict'; const specialRules = [ { site: 'https://hhanclub.top', torrentMethod: (item) => $(item).closest('.torrent-table-sub-info'), titleMethod: ($tdElement) => $($tdElement.find('a[class*="torrent-info-text-name"]')[0]).text(), timeMethod: ($tdElement) => { const dateTimeRegex = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/ const $spansWithTitle = $tdElement.find('span[title]'); return $spansWithTitle.filter(function () { return dateTimeRegex.test($(this).attr('title')); }).get().length ? $($spansWithTitle.filter(function () { return dateTimeRegex.test($(this).attr('title')); }).get()[0]).attr('title') : 'infinite' }, urlMethod: ($tdElement) => normalizeUrl(location.origin + '/' + $($tdElement.find('a[href*="download.php"]')[0]).attr('href')), freeSelector: () => $('[class*="free"]') }, { site: 'https://kp.m-team.cc', torrentMethod: (item) => $(item).closest('tr'), titleMethod: ($tdElement) => $($tdElement.find('strong')[0]).text(), timeMethod: ($tdElement) => { const freeTagParent = $tdElement.find('span.ant-tag:contains("Free")').parent().first() if (freeTagParent.is('span') && freeTagParent.attr('title').includes('促銷')) { const title = freeTagParent.attr('title'); const deadlinePattern = /(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/; const match = title.match(deadlinePattern); if (match) { const deadline = match[1] return deadline } else { return 'infinite' } } else { return 'infinite' } }, urlMethod: ($tdElement) => { const url = $($tdElement.find('a[href*="download.php"]')[0]).attr('href') const regex = /\/detail\/(\d+)/; const match = url.match(regex); if (match) { // 如果匹配成功,match数组的第一个元素是整个匹配的子串, // 第二个元素(match[1])是第一个捕获组的内容,即所需的ID return match[1]; } else { return '' } }, freeSelector: () => $('span.ant-tag:contains("Free")') }, { site: 'https://amani.top', torrentMethod: (item) => $($(item)).parentsUntil('table', 'tr'), titleMethod: ($tdElement) => $($tdElement.find('div[class*="title"]')[0]).text(), timeMethod: ($tdElement) => { // const dateTimeRegex = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/ // const $spansWithTitle = $tdElement.find('span[title]'); // return $spansWithTitle.filter(function () { // return dateTimeRegex.test($(this).attr('title')); // }).get().length ? $($spansWithTitle.filter(function () { // return dateTimeRegex.test($(this).attr('title')); // }).get()[0]).attr('title') : 'infinite' return 'infinite' }, urlMethod: ($tdElement) => normalizeUrl(location.origin + '/' + $($tdElement.find('a[title*="下载"]')[0]).attr('href')) }, ] function selectTDsWithFreeClassAncestors() { var $matchingTds = $(); // 选择所有class包含"free"的后代元素 const siteInfo = specialRules.find(i => i.site === location.origin) var $freeElements = siteInfo && siteInfo.freeSelector ? siteInfo.freeSelector().toArray() : $('[class*="free"]').toArray(); // 遍历这些元素,找到它们的所有祖先td元素 $freeElements.forEach((item) => { let $ancestorsWithClass = null if (siteInfo) { $ancestorsWithClass = siteInfo.torrentMethod(item) } else { $ancestorsWithClass = $($(item)).parentsUntil('table', 'tr'); } // 把找到的td元素加入结果集 $matchingTds = $matchingTds.add($ancestorsWithClass); }); // 返回结果集 return $matchingTds; } // 调用函数并进行操作 function normalizeUrl(url) { const httpPattern = /^(https?|ftp):\/\/[^/]+/; // 匹配http、https或ftp开头的URL部分 const matchedUrl = url.match(httpPattern); if (matchedUrl) { // 获取URL部分之后的子串 const remainingStr = url.slice(matchedUrl[0].length); // 替换剩余部分中的双斜杠为单斜杠 const fixedRemainingStr = remainingStr.replace(/\/{2,}/g, '/'); // 将处理过的剩余部分与原始URL部分拼接 return matchedUrl[0] + fixedRemainingStr; } else { // 如果字符串不以http(s)://开头,直接替换整个字符串中的双斜杠为单斜杠 return url.replace(/\/{2,}/g, '/'); } } function timeSort(data) { return data.sort((a, b) => { if (a.time === 'infinite') { return 1; } else if (b.time === 'infinite') { return -1; } else { return new Date(a.time) - new Date(b.time); } }); } function calcRestTime(timeStr) { if (timeStr === 'infinite') { return { restTime: '无限', days: 9999, hours: 9999, minutes: 9999 }; } const date = new Date(timeStr); const now = new Date(); const sub = Math.abs(date.getTime() - now.getTime()) // 转换为天数、小时数、分钟数和秒数 const days = Math.floor(sub / (1000 * 60 * 60 * 24)); const hours = Math.floor((sub % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const minutes = Math.floor((sub % (1000 * 60 * 60)) / (1000 * 60)); return { restTime: `${days > 0 ? days + '天 ' : ''}${hours > 0 ? hours + '小时 ' : ''}${minutes}分`, days, hours, minutes } } function getInfo() { const siteInfo = specialRules.find(i => i.site === location.origin) const selectedTds = selectTDsWithFreeClassAncestors().toArray(); const result = selectedTds.map(i => { const $tdElement = $(i); const dateTimeRegex = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/; const item = { title: '', time: '', url: '' } if (siteInfo) { item.title = siteInfo.titleMethod($tdElement) item.time = siteInfo.timeMethod($tdElement) item.url = siteInfo.urlMethod($tdElement) } else { item.title = $($tdElement.find('a[href*="details.php"]')[0]).attr('title') const $spansWithTitle = $tdElement.find('span[title]'); const url = $($tdElement.find('a[href*="download.php"]')[0]).attr('href') item.time = $spansWithTitle.filter(function () { return dateTimeRegex.test($(this).attr('title')); }).get().length ? $($spansWithTitle.filter(function () { return dateTimeRegex.test($(this).attr('title')); }).get()[0]).attr('title') : 'infinite' item.url = normalizeUrl(location.origin + '/' + url) } return item }) result.forEach(i => { const { restTime, days, hours, minutes } = calcRestTime(i.time) i.restTime = restTime i.days = days i.hours = hours i.minutes = minutes }) return timeSort(result) } // 复制文字到剪贴板 async function copyToClipboard(text) { try { await navigator.clipboard.writeText(text); } catch (err) { console.error('Failed to copy to clipboard: ', err); } } function setButton() { console.log('setbtn') // 创建一个新的button元素 var button = document.createElement('button'); button.textContent = '获取信息'; // 设置按钮文字 // 设置按钮的基本样式(包括固定定位与默认透明度) button.style.cssText = ` position: fixed; /* 或者 absolute,取决于您的布局需求 */ top: 88px; /* 举例位置,您可以自定义 */ right: 10px; /* 举例位置,您可以自定义 */ z-index: 999; background-color: #007bff; color: white; padding: 6px 12px; border: none; border-radius: 5px; cursor: pointer; opacity: 0.3; transition: opacity 0.3s ease; `; // 添加鼠标悬浮时的透明度变化 button.addEventListener('mouseover', function () { this.style.opacity = 1; }); // 添加鼠标离开时的透明度变化 button.addEventListener('mouseout', function () { this.style.opacity = 0.3; }); button.addEventListener('click', setTable) // 将按钮添加到文档中 document.body.appendChild(button); } function setTable() { if (location.origin === 'https://kp.m-team.cc') return alert("该站点支持正在开发中。。。") const sortedData = getInfo() const mask = document.createElement('div'); mask.addEventListener('click', (event) => { // 判断点击的是mask本身还是其子元素 if (event.target === mask) { document.body.removeChild(mask); } else { event.stopPropagation(); // 阻止子元素点击事件向上冒泡到mask } }); mask.classList.add('mask'); mask.style.cssText = ` background-color: rgba(0, 0, 0, 0.2); position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 1000; display: flex; justify-content: center; align-items: center; box-sizing: border-box; ` document.body.appendChild(mask); const tableWrap = document.createElement('div') tableWrap.classList.add('table-wrap'); tableWrap.style.cssText = ` max-width: 1000px; max-height: 900px; min-height: 500px; min-width: 500px; width: 50vw; height: 70vh; background-color: #fff; overflow-y: auto; padding: 10px; ` // 创建操作区域 const btnArea = document.createElement('div') btnArea.style.cssText = ` height: 40px; margin-bottom: 8px; box-sizing: border-box; padding: 4px 0; ` // 创建关闭按钮 const closeBtn = document.createElement('button') closeBtn.textContent = '关闭' closeBtn.style.cssText = ` background-color: #F56C6C; color: white; padding: 6px 12px; border: none; border-radius: 5px; cursor: pointer; margin-right: 10px; ` closeBtn.addEventListener('click', () => { document.body.removeChild(mask); }) // 创建复制链接按钮 const copyLink = document.createElement('button') copyLink.textContent = '复制下载链接' copyLink.style.cssText = ` background-color: #007bff; color: white; padding: 6px 12px; border: none; border-radius: 5px; cursor: pointer; margin-right: 10px; ` const filterInput = document.createElement('input') filterInput.type = 'number' filterInput.placeholder = '剩余时间 > ?' filterInput.style.cssText = ` width: 90px; margin-right: 10px; padding: 6px; border-radius: 4px; border: 1px solid #2165f9; ` copyLink.addEventListener('click', () => { const siteInfo = specialRules.find(i => i.site === location.origin) const getTorrentType = siteInfo && siteInfo.getTorrentType if (getTorrentType && getTorrentType === 'remote') { // 发起网络请求获取链接 } else { let suffix = '' if (textArea.value) { suffix = `&${textArea.value.split('\n').join('&')}` } if (filterInput.value) { const limit = +filterInput.value * 60 console.log(limit) const filterData = sortedData.filter(i => (i.days * 24 + i.hours) * 60 + i.minutes > limit) const str = filterData.map(i => i.url + suffix).join('\n') copyToClipboard(str) alert(`复制成功 已复制 ${filterData.length} 个种子`) } else { const str = sortedData.map(i => i.url + suffix).join('\n') copyToClipboard(str) alert(`复制成功 已复制 ${sortedData.length} 个种子`) } } }) // 创建复制cookie按钮 const copyCookie = document.createElement('button') copyCookie.textContent = '复制Cookie' copyCookie.style.cssText = ` background-color: #007bff; color: white; padding: 6px 12px; border: none; border-radius: 5px; cursor: pointer; ` copyCookie.addEventListener('click', () => { const cookie = document.cookie if (cookie) { copyToClipboard(cookie) alert('复制成功') } else { alert('Cookie 为空') } }) btnArea.appendChild(closeBtn) btnArea.appendChild(filterInput) btnArea.appendChild(copyLink) btnArea.appendChild(copyCookie) const textAreaWrap = document.createElement('div') textAreaWrap.style.cssText = ` height: 160px; width: 100%; overflow-y: auto; padding: 10px; box-sizing: border-box; ` const textArea = document.createElement('textarea') textArea.placeholder = '请输入自定义参数 1行一条,格式为 key=value' textArea.style.cssText = ` width: 100%; height: 100%; border: 1px solid #2165f9; box-sizing: border-box; padding: 10px; border-radius: 4px; ` textAreaWrap.appendChild(textArea) // 创建表格元素 const table = document.createElement('table'); table.style.cssText = ` width: 100%; height: 100%; `; const lessOnHour = sortedData.filter(i => i.days <= 0 && i.hours <= 0).length const lessAHalfDay = sortedData.filter(i => i.days <= 0 && i.hours <= 12 && i.hours > 1).length const total = document.createElement('div') total.style.fontSize = '16px' total.style.fontWeight = 'bold' const num = sortedData.length const span1 = document.createElement('span') span1.textContent = '共有 ' const span2 = document.createElement('span') span2.textContent = num span2.style.color = '#2165f9' const span3 = document.createElement('span') span3.textContent = ' 个免费种子' const tip1 = document.createElement('span') const tip2 = document.createElement('span') const tip3 = document.createElement('span') const tip4 = document.createElement('span') const tip5 = document.createElement('span') tip1.textContent = '小于1小时:' tip2.textContent = lessOnHour tip2.style.color = 'red' tip3.textContent = ' 个,大于1小时小于12小时:' tip4.textContent = lessAHalfDay tip4.style.color = 'orange' tip5.textContent = ' 个' const tipSpan = document.createElement('span') tipSpan.style.fontSize = '12px' tipSpan.style.marginLeft = '20px' tipSpan.appendChild(tip1) tipSpan.appendChild(tip2) tipSpan.appendChild(tip3) tipSpan.appendChild(tip4) tipSpan.appendChild(tip5) total.appendChild(span1) total.appendChild(span2) total.appendChild(span3) total.appendChild(tipSpan) tableWrap.appendChild(btnArea) tableWrap.appendChild(total) tableWrap.appendChild(textAreaWrap) const tableBox = document.createElement('div'); tableBox.style.cssText = ` height: calc(100% - 232px); width: 100%; overflow-y: auto; ` tableWrap.appendChild(tableBox); tableBox.appendChild(table); mask.appendChild(tableWrap); // 创建表头 const thead = document.createElement('thead'); const headerRow = document.createElement('tr'); ['序号', '剩余时间', '标题', '下载链接'].forEach(header => { const th = document.createElement('th'); th.textContent = header; headerRow.appendChild(th); th.style.cssText = ` position: sticky; top: 0; background-color: white; /* 添加背景颜色以防止内容滚动时穿透 */ z-index: 2; ` }); thead.appendChild(headerRow); table.appendChild(thead); // 创建表体 const tbody = document.createElement('tbody'); sortedData.forEach((item, index) => { const row = document.createElement('tr'); const indexCell = document.createElement('td'); indexCell.textContent = index + 1; row.appendChild(indexCell); const timeCell = document.createElement('td'); timeCell.textContent = item.restTime; row.appendChild(timeCell); const titleCell = document.createElement('td'); titleCell.textContent = item.title row.appendChild(titleCell); const linkCell = document.createElement('td'); linkCell.textContent = item.url row.appendChild(linkCell); if (item.days <= 0 && item.hours <= 0) { indexCell.style.color = 'red'; timeCell.style.color = 'red'; titleCell.style.color = 'red'; linkCell.style.color = 'red'; } else if (item.days <= 0 && item.hours <= 12) { indexCell.style.color = 'orange'; timeCell.style.color = 'orange'; titleCell.style.color = 'orange'; linkCell.style.color = 'orange'; } tbody.appendChild(row); }); table.appendChild(tbody); } setButton() })();