Real-Debrid Enhancer

Enhance Real-Debrid with clickable rows, copy and debrid buttons, grid layout, and improved layout management on torrents and downloader pages.

当前为 2024-07-21 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Real-Debrid Enhancer
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  Enhance Real-Debrid with clickable rows, copy and debrid buttons, grid layout, and improved layout management on torrents and downloader pages.
// @author       UnderPL
// @match        https://real-debrid.com/torrents*
// @match        https://real-debrid.com/downloader*
// @grant        GM_setClipboard
// @grant        GM_addStyle
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    let copyButton, debridButton;

    GM_addStyle(`

    .tr.g1:not(.warning), .tr.g2:not(.warning), .tr.g1:not(.warning) + tr, .tr.g2:not(.warning) + tr {
        cursor: pointer;
        position: relative;
    }
    .tr.g1.selected, .tr.g2.selected, .tr.g1.selected + tr, .tr.g2.selected + tr {
        background-color: rgba(0, 255, 0, 0.3);
    }

    .tr.g1, .tr.g2 {
        border-top: 2px solid black/* Green border on top */

    }

    .tr.g1 + tr, .tr.g2 + tr {
        border-bottom: 2px solid black; /* Green border on bottom */

    }
    #buttonContainer {
        position: fixed;
        bottom: 10px;
        right: 10px;
        display: flex;
        flex-direction: column;
        gap: 10px;
        z-index: 9999;
    }
    #buttonContainer button {
        padding: 10px;
        background-color: #4CAF50;
        color: white;
        border: none;
        cursor: pointer;
        font-size: 16px;
    }
    #facebox .content {
        width: 90vw !important;
        max-width: 1200px !important;
        display: flex !important;
        flex-wrap: wrap !important;
        justify-content: space-between !important;
    }
    .torrent-info {
        width: calc(33.33% - 20px);
        margin-bottom: 20px;
        border: 1px solid #ccc;
        padding: 10px;
        box-sizing: border-box;
    }
    `);

    function initializeApplication() {
        if (window.location.href.includes('/torrents')) {
            cleanupTorrentPageLayout();
            createFloatingButtons();
            makeItemsSelectable();
            updateFloatingButtonsVisibility();
            setupTorrentInfoWindowObserver();
            checkForTorrentInfoWindow();
            setupItemHoverEffects();
            movePaginationToBottomRight();
            addSwitchToGridLayoutButton();
            //switchToGridLayout
        } else if (window.location.href.includes('/downloader')) {
            addExtractUrlsButtonToDownloader();
            addCopyLinksButton();
        }
    }

    function movePaginationToBottomRight() {
        const parentElement = document.querySelector('div.full_width_wrapper');
        const formElement = parentElement.querySelector('form:nth-child(1)');
        const pageElements = parentElement.querySelectorAll('div.full_width_wrapper > strong, div.full_width_wrapper > a[href^="./torrents?p="]');
        const containerDiv = document.createElement('div');
        const marginSize = '5px';
        const fontSize = '16px';

        containerDiv.style.position = 'absolute';
        containerDiv.style.right = '0';
        containerDiv.style.bottom = '0';
        containerDiv.style.display = 'flex';
        containerDiv.style.gap = marginSize;
        containerDiv.style.fontSize = fontSize;

        pageElements.forEach(page => {
            containerDiv.appendChild(page);
        });

        formElement.style.position = 'relative';
        formElement.appendChild(containerDiv);
    }

    function createFloatingButtons() {
        const container = document.createElement('div');
        container.id = 'buttonContainer';

        debridButton = document.createElement('button');
        debridButton.addEventListener('click', sendSelectedLinksToDebrid);

        copyButton = document.createElement('button');
        copyButton.addEventListener('click', copySelectedLinksToClipboard);

        container.appendChild(debridButton);
        container.appendChild(copyButton);
        document.body.appendChild(container);

        return container;
    }

    function updateFloatingButtonsVisibility() {
        const selectedLinks = getSelectedItemLinks();
        const count = selectedLinks.length;

        if (count > 0) {
            debridButton.textContent = `Debrid (${count})`;
            copyButton.textContent = `Copy Selected to Clipboard (${count})`;
            debridButton.style.display = 'block';
            copyButton.style.display = 'block';
        } else {
            debridButton.style.display = 'none';
            copyButton.style.display = 'none';
        }
    }

    function makeItemsSelectable() {
        const rows = document.querySelectorAll('.tr.g1, .tr.g2');
        rows.forEach(row => {
            const warningSpan = row.querySelector('span.px10 strong');
            if (!warningSpan || warningSpan.textContent !== 'Warning:') {
                const nextRow = row.nextElementSibling;
                const clickHandler = () => {
                    row.classList.toggle('selected');
                    if (nextRow) {
                        nextRow.classList.toggle('selected');
                    }
                    if (row.classList.contains('selected')) {
                        row.style.backgroundColor = 'rgba(0, 255, 0, 0.3)';
                        if (nextRow) nextRow.style.backgroundColor = 'rgba(0, 255, 0, 0.3)';
                    } else {
                        row.style.backgroundColor = '';
                        if (nextRow) nextRow.style.backgroundColor = '';
                    }
                    updateFloatingButtonsVisibility();
                };
                row.addEventListener('click', clickHandler);
                if (nextRow) {
                    nextRow.addEventListener('click', clickHandler);
                }
            } else {
                row.classList.add('warning');
                if (row.nextElementSibling) {
                    row.nextElementSibling.classList.add('warning');
                }
            }
        });

        const entries = document.querySelectorAll('.torrent-entry');
        entries.forEach(entry => {
            entry.addEventListener('click', () => {
                entry.classList.toggle('selected');
                if (entry.classList.contains('selected')) {
                    entry.style.backgroundColor = 'rgba(0, 255, 0, 0.3)';
                } else {
                    entry.style.backgroundColor = '';
                }
                updateFloatingButtonsVisibility();
            });
        });
    }

    function setupItemHoverEffects() {
        const rows = document.querySelectorAll('.tr.g1, .tr.g2');
        rows.forEach(row => {
            const nextRow = row.nextElementSibling;
            if (nextRow && !nextRow.classList.contains('g1') && !nextRow.classList.contains('g2')) {
                row.addEventListener('mouseenter', () => {
                    if (!row.classList.contains('selected')) {
                        row.style.backgroundColor = 'rgba(0, 255, 0, 0.1)';
                        nextRow.style.backgroundColor = 'rgba(0, 255, 0, 0.1)';
                    }
                });
                row.addEventListener('mouseleave', () => {
                    if (!row.classList.contains('selected')) {
                        row.style.backgroundColor = '';
                        nextRow.style.backgroundColor = '';
                    }
                });
                nextRow.addEventListener('mouseenter', () => {
                    if (!row.classList.contains('selected')) {
                        row.style.backgroundColor = 'rgba(0, 255, 0, 0.1)';
                        nextRow.style.backgroundColor = 'rgba(0, 255, 0, 0.1)';
                    }
                });
                nextRow.addEventListener('mouseleave', () => {
                    if (!row.classList.contains('selected')) {
                        row.style.backgroundColor = '';
                        nextRow.style.backgroundColor = '';
                    }
                });
            }
        });

        const entries = document.querySelectorAll('.torrent-entry');
        entries.forEach(entry => {
            entry.addEventListener('mouseenter', () => {
                if (!entry.classList.contains('selected')) {
                    entry.style.backgroundColor = 'rgba(0, 255, 0, 0.1)';
                }
            });
            entry.addEventListener('mouseleave', () => {
                if (!entry.classList.contains('selected')) {
                    entry.style.backgroundColor = '';
                }
            });
        });
    }

    function getSelectedItemLinks() {
        const selectedLinks = [];
        const selectedRows = document.querySelectorAll('.tr.g1.selected, .tr.g2.selected');
        const selectedEntries = document.querySelectorAll('.torrent-entry.selected');

        selectedRows.forEach(row => {
            const textarea = row.nextElementSibling.querySelector('textarea');
            if (textarea) {
                selectedLinks.push(textarea.value);
            }
        });

        selectedEntries.forEach(entry => {
            const textarea = entry.querySelector('textarea');
            if (textarea) {
                selectedLinks.push(textarea.value);
            }
        });

        return selectedLinks;
    }

    function copySelectedLinksToClipboard() {
        const selectedLinks = getSelectedItemLinks();
        if (selectedLinks.length > 0) {
            const clipboardText = selectedLinks.join('\n');
            GM_setClipboard(clipboardText);
        }
    }

    function sendSelectedLinksToDebrid(e) {
        e.preventDefault();
        const selectedLinks = getSelectedItemLinks();
        if (selectedLinks.length > 0) {
            const form = document.createElement('form');
            form.method = 'POST';
            form.action = './downloader';

            const input = document.createElement('textarea');
            input.name = 'links';
            input.value = selectedLinks.join('\n');
            form.appendChild(input);

            document.body.appendChild(form);
            form.submit();
            document.body.removeChild(form);
        }
    }

    function extractUrlsFromText(text) {
        const urlRegex = /(?:(?:https?):\/\/|www\.)(?:\([-A-Z0-9+&@#\/%=~_|$?!:,.]*\)|[-A-Z0-9+&@#\/%=~_|$?!:,.])*(?:\([-A-Z0-9+&@#\/%=~_|$?!:,.]*\)|[A-Z0-9+&@#\/%=~_|$])/igm;
        return text.match(urlRegex) || [];
    }

    function addExtractUrlsButtonToDownloader() {
        const textarea = document.getElementById('links');
        if (textarea) {
            const button = document.createElement('button');
            button.id = 'extractUrlsButton';
            button.textContent = 'Extract URL(s)';
            button.style.position = 'absolute';
            button.style.right = '28px';
            button.style.top = '0';
            button.addEventListener('click', function(e) {
                e.preventDefault();
                const content = textarea.value;
                const urls = extractUrlsFromText(content);
                textarea.value = urls.join('\n');
            });

            textarea.parentNode.style.position = 'relative';
            textarea.parentNode.appendChild(button);
        }
    }

    function addCopyLinksButton() {
        const linksContainer = document.querySelector('#links-container');
        if (linksContainer && linksContainer.children.length > 0) {
            const originalButton = document.querySelector('#sub_links');
            if (originalButton) {
                const copyButton = originalButton.cloneNode(true);
                copyButton.id = 'copy_links';
                copyButton.value = 'Copy links';
                copyButton.type = 'button';
                copyButton.style.display = 'block';
                copyButton.style.margin = '0 auto';
                copyButton.style.float = 'none'
                copyButton.style.marginBottom = '10px'

                copyButton.addEventListener('click', function(e) {
                    e.preventDefault();
                    const links = Array.from(document.querySelectorAll('#links-container .link-generated a'))
                    .filter(a => a.textContent.includes('DOWNLOAD'))
                    .map(a => a.href)
                    .join('\n');

                    if (links) {
                        GM_setClipboard(links);
                        copyButton.value = 'Copy Links ✔️';
                        setTimeout(() => {
                            copyButton.value = 'Copy links';
                        }, 1500);
                    }
                });

                linksContainer.insertAdjacentElement('afterend', copyButton);
            }
        }
    }


    function cleanupTorrentPageLayout() {
        const textContainer = document.querySelector('html.cufon-active.cufon-ready body div#block div#contentblock div#wrapper_global div.main_content_wrapper div.full_width_wrapper');
        if (textContainer) {
            Array.from(textContainer.childNodes).forEach(node => {
                if (node.nodeType === Node.TEXT_NODE) {
                    node.remove();
                }
            });
        }

        const brElements = document.querySelectorAll('html.cufon-active.cufon-ready body div#block div#contentblock div#wrapper_global div.main_content_wrapper div.full_width_wrapper br');
        brElements.forEach(br => br.remove());

        const centerElements = document.querySelectorAll('html.cufon-active.cufon-ready body div#block div#contentblock div#wrapper_global div.main_content_wrapper div.full_width_wrapper center');
        centerElements.forEach(center => center.remove());

        const contentSeparatorMiniElements = document.querySelectorAll('html.cufon-active.cufon-ready body div#block div#contentblock div#wrapper_global div.main_content_wrapper div.full_width_wrapper div.content_separator_mini');
        contentSeparatorMiniElements.forEach(div => div.remove());

        const h2Elements = document.querySelectorAll('html.cufon-active.cufon-ready body div#block div#contentblock div#wrapper_global div.main_content_wrapper div.full_width_wrapper h2');
        h2Elements.forEach(h2 => h2.remove());

        const spanElements = document.querySelectorAll('html.cufon-active.cufon-ready body div#block div#contentblock div#wrapper_global div.main_content_wrapper div.full_width_wrapper span.px10');
        spanElements.forEach(span => span.remove());
    }

    function redesignTorrentInfoWindow() {
        const facebox = document.getElementById('facebox');
        if (facebox) {
            const content = facebox.querySelector('.content');
            if (content) {
                content.style.width = '90vw';
                content.style.maxWidth = '1200px';
                content.style.display = 'flex';
                content.style.flexWrap = 'wrap';
                content.style.justifyContent = 'space-between';

                // Store the original buttons with their event listeners
                const startButtons = Array.from(content.querySelectorAll('input[type="button"][value="Start my torrent"]'));

                const torrentInfos = content.innerHTML.split('<h2>Torrent Files</h2>').filter(info => info.trim() !== '');

                content.innerHTML = '';

                torrentInfos.forEach((info, index) => {
                    const div = document.createElement('div');
                    div.className = 'torrent-info';

                    // Create a temporary div to parse the HTML
                    const tempDiv = document.createElement('div');
                    tempDiv.innerHTML = '<h2>Torrent Files</h2>' + info;

                    // Move the content except the button
                    while (tempDiv.firstChild) {
                        if (tempDiv.firstChild.tagName !== 'INPUT' || tempDiv.firstChild.type !== 'button') {
                            div.appendChild(tempDiv.firstChild);
                        } else {
                            tempDiv.removeChild(tempDiv.firstChild);
                        }
                    }

                    // Append the original button with its event listeners
                    if (startButtons[index]) {
                        div.appendChild(startButtons[index]);
                    }

                    content.appendChild(div);
                });
            }
        }
    }

    function setupTorrentInfoWindowObserver() {
        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                if (mutation.addedNodes && mutation.addedNodes.length > 0) {
                    for (let node of mutation.addedNodes) {
                        if (node.id === 'facebox') {
                            redesignTorrentInfoWindow();
                        }
                    }
                }
            });
        });

        observer.observe(document.body, { childList: true, subtree: true });
    }

    function checkForTorrentInfoWindow() {
        const intervalId = setInterval(() => {
            const facebox = document.getElementById('facebox');
            if (facebox) {
                redesignTorrentInfoWindow();
                clearInterval(intervalId);
            }
        }, 1000);
    }

    function createGridLayout(columnCount) {
        const table = document.querySelector('table[width="100%"]');
        if (!table) return;

        const container = document.createElement('div');
        container.id = 'torrent-grid-container';
        container.style.display = 'flex';
        container.style.flexWrap = 'wrap';
        container.style.justifyContent = 'space-between';

        const rows = table.querySelectorAll('tr');
        for (let i = 1; i < rows.length; i += 2) {
            const torrentDiv = createGridItemFromTableRows(rows[i], rows[i + 1]);
            container.appendChild(torrentDiv);
        }

        table.parentNode.replaceChild(container, table);
        applyGridLayoutStyles(columnCount);
        adjustImageSizeInNewLayout();
        moveDeleteLinkToEnd();
        makeItemsSelectable();
        setupItemHoverEffects();
    }

    function applyGridLayoutStyles(columnCount) {
        const width = `calc(${100 / columnCount}% - 20px)`;
        GM_addStyle(`
            #torrent-grid-container {
                width: 100%;
                max-width: 1200px;
                margin: 0 auto;
            }
            .torrent-entry {
                width: ${width};
                margin-bottom: 20px;
                border: 1px solid #ccc;
                padding: 10px;
                box-sizing: border-box;
                cursor: pointer;
            }
            .torrent-entry.selected {
                background-color: rgba(0, 255, 0, 0.3) !important;
            }
            .torrent-entry:hover:not(.selected) {
                background-color: rgba(0, 255, 0, 0.1);
            }
            .torrent-entry td {
                display: block;
                width: 100%;
            }
            .torrent-entry tr {
                display: block;
            }
            .torrent-entry form {
                margin-top: 10px;
            }
            .torrent-entry textarea {
                min-height: 2.5em;
                max-height: 6em;
                overflow-y: auto;
                resize: vertical;
            }
        `);
    }

    function adjustImageSizeInNewLayout() {
        document.querySelectorAll('#torrent-grid-container .torrent-entry form input[type="image"]').forEach(function(img) {
            img.style.width = '10%';
            img.style.height = 'auto';
            img.style.display = 'inline-block';
            img.style.marginLeft = '10px';
        });

        document.querySelectorAll('#torrent-grid-container .torrent-entry form').forEach(function(form) {
            form.style.display = 'flex';
            form.style.alignItems = 'center';
        });
    }

    function moveDeleteLinkToEnd() {
        document.querySelectorAll('.torrent-entry').forEach(entry => {
            const deleteLink = entry.querySelector('a[href*="del"]');
            if (deleteLink) {
                // Create a container for the delete link
                const deleteContainer = document.createElement('div');
                deleteContainer.classList.add('delete-container');
                deleteContainer.style.position = 'absolute';
                deleteContainer.style.right = '0';
                deleteContainer.style.top = '0';
                deleteContainer.style.display = 'flex';
                deleteContainer.style.alignItems = 'center';
                deleteContainer.style.height = '100%';
                deleteContainer.style.paddingRight = '10px';

                // Move the delete link into the new container
                deleteContainer.appendChild(deleteLink);
                entry.appendChild(deleteContainer);

                // Ensure the parent .torrent-entry has relative positioning
                entry.style.position = 'relative';
            }
        });
    }

    function createGridItemFromTableRows(mainRow, detailRow) {
        const div = document.createElement('div');
        div.className = 'torrent-entry';
        div.innerHTML = mainRow.innerHTML + detailRow.innerHTML;

        div.addEventListener('click', () => {
            div.classList.toggle('selected');
            updateFloatingButtonsVisibility();
        });

        return div;
    }

    function addSwitchToGridLayoutButton() {
        const button = document.createElement('button');
        button.textContent = 'Switch Layout';
        button.id = 'switchLayoutButton';
        button.style.position = 'fixed';
        button.style.top = '10px';
        button.style.right = '20px';
        button.style.zIndex = '1000';
        button.addEventListener('click', switchToGridLayout);
        document.body.appendChild(button);
    }

    function switchToGridLayout() {
        const columnCount = 3; // You can adjust this number as needed
        createGridLayout(columnCount);
        setupItemHoverEffects();
        makeItemsSelectable();
        updateFloatingButtonsVisibility();

        const button = document.getElementById('switchLayoutButton');
        if (button) {
            button.remove();
        }
    }

    if (document.readyState === 'complete') {
        initializeApplication();
    } else {
        window.addEventListener('load', initializeApplication);
    }
})();