Greasy Fork

Greasy Fork is available in English.

Get Free Torrents

该插件主要用于抓取指定页面(即 "torrents.php")中的免费种子信息,并将其按照剩余时间从短到长排序后,以表格形式呈现给用户。用户可以一键复制所有展示种子的链接,同时具备筛选功能,允许用户设定自定义时间阈值,仅复制剩余时间超过该阈值的种子链接。此外,插件还支持添加自定义URL参数以扩展功能或满足个性化需求。

当前为 2024-04-23 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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 http://greasyfork.icu/scripts/453166-jquery/code/jquery.js?version=1105525
// @require http://greasyfork.icu/scripts/28502-jquery-ui-v1-11-4/code/jQuery%20UI%20-%20v1114.js?version=187735
// @license MIT
// ==/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()
})();