Greasy Fork is available in English.

Real-Debrid Enhancer

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

// ==UserScript==
// @name         Real-Debrid Enhancer
// @namespace    http://tampermonkey.net/
// @version      2.1
// @description  Enhance Real-Debrid with clickable rows, copy and debrid buttons, grid layout, and improved layout management on torrents and downloader pages.
// @author       UnderPL
// @license      MIT
// @match        https://real-debrid.com/torrents*
// @match        https://real-debrid.com/
// @match        https://real-debrid.com/downloader*
// @grant        GM_setClipboard
// @grant        GM_addStyle
// ==/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(); //comment this and uncomment line below to automatically switch to the more compact version of the torrent page
            //switchToGridLayout()
        }

        if (window.location.href === 'https://real-debrid.com/' || 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);
    }
})();