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      3.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
// @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, deleteButton;

    GM_addStyle(`
    /* Selection styling */
    .tr.g1:not(.warning), .tr.g2:not(.warning), .tr.g1:not(.warning) + tr, .tr.g2:not(.warning) + tr {
        cursor: pointer;
        position: relative;
        transition: all 0.2s ease-in-out;
    }
    
    .tr.g1.selected, .tr.g2.selected, .tr.g1.selected + tr, .tr.g2.selected + tr {
        background-color: rgba(40, 167, 69, 0.15) !important;
        border-left: 4px solid #28a745 !important;
        box-shadow: 0 2px 4px rgba(40, 167, 69, 0.1);
    }

    .tr.g1:hover:not(.selected):not(.warning), 
    .tr.g2:hover:not(.selected):not(.warning), 
    .tr.g1:hover:not(.selected):not(.warning) + tr, 
    .tr.g2:hover:not(.selected):not(.warning) + tr {
        background-color: rgba(40, 167, 69, 0.05);
    }

    .torrent-entry {
        transition: all 0.2s ease-in-out;
        border: 1px solid transparent;
    }

    .torrent-entry.selected {
        background-color: rgba(40, 167, 69, 0.15) !important;
        border: 1px solid #28a745 !important;
        box-shadow: 0 2px 4px rgba(40, 167, 69, 0.1);
        transform: translateY(-1px);
    }

    .torrent-entry:hover:not(.selected) {
        background-color: rgba(40, 167, 69, 0.05);
        transform: translateY(-1px);
    }

    .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: 20px;
        right: 20px;
        display: flex;
        flex-direction: column;
        gap: 12px;
        z-index: 9999;
    }
    #buttonContainer button {
        padding: 12px 20px;
        background-color: #4CAF50;
        color: white;
        border: none;
        border-radius: 10px;
        cursor: pointer;
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
        font-size: 14px;
        font-weight: 500;
        letter-spacing: 0.3px;
        transition: all 0.2s ease;
        box-shadow: 0 3px 6px rgba(0,0,0,0.16);
        min-width: 200px;
        width: 250px; /* Fixed width for all buttons */
        text-align: center;
        text-transform: uppercase;
        white-space: nowrap; /* Prevent text wrapping */
        overflow: hidden; /* Hide overflow text */
        text-overflow: ellipsis; /* Show ellipsis for overflow */
    }
    #buttonContainer button:hover {
        transform: translateY(-2px);
        box-shadow: 0 4px 8px rgba(0,0,0,0.2);
        filter: brightness(1.05);
    }
    #buttonContainer button:active {
        transform: translateY(1px);
        box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    }
    /* Button click animation */
    .button-clicked {
        animation: button-click-animation 0.5s ease;
        background-color: #3a8a3e !important; /* Darker shade */
    }
    @keyframes button-click-animation {
        0% { transform: scale(1); }
        50% { transform: scale(0.95); }
        100% { transform: scale(1); }
    }
    /* Only apply grid layout when the class is present */
    #facebox .content.grid-layout {
        width: 90vw !important;
        max-width: 1200px !important;
        display: flex !important;
        flex-wrap: wrap !important;
        justify-content: space-between !important;
    }
    /* Center the facebox when grid layout is applied */
    #facebox.grid-layout {
        left: 50% !important;
        transform: translateX(-50%) !important;
    }
    .torrent-info {
        width: calc(33.33% - 20px);
        margin-bottom: 20px;
        border: 1px solid #ccc;
        padding: 10px;
        box-sizing: border-box;
    }
    #switchLayoutButton {
        padding: 12px 20px !important;
        background-color: #2196F3 !important;
        color: white !important;
        border: none !important;
        border-radius: 10px !important;
        cursor: pointer !important;
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important;
        font-size: 14px !important;
        font-weight: 500 !important;
        letter-spacing: 0.3px !important;
        transition: all 0.2s ease !important;
        box-shadow: 0 3px 6px rgba(0,0,0,0.16) !important;
        text-transform: uppercase !important;
    }
    #switchLayoutButton:hover {
        transform: translateY(-2px) !important;
        box-shadow: 0 4px 8px rgba(0,0,0,0.2) !important;
        filter: brightness(1.05) !important;
    }
    #switchLayoutButton:active {
        transform: translateY(1px) !important;
        box-shadow: 0 2px 4px rgba(0,0,0,0.1) !important;
    }
    #extractUrlsButton {
        padding: 8px 12px;
        background-color: #2196F3;
        color: white;
        border: none;
        border-radius: 6px;
        cursor: pointer;
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
        font-size: 13px;
        font-weight: 500;
        letter-spacing: 0.3px;
        transition: all 0.2s ease;
        box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        text-transform: uppercase;
        position: absolute;
        right: 10px;
        top: 10px;
    }
    #extractUrlsButton:hover {
        transform: translateY(-1px);
        box-shadow: 0 3px 6px rgba(0,0,0,0.15);
        filter: brightness(1.05);
    }
    #extractUrlsButton:active {
        transform: translateY(1px);
        box-shadow: 0 1px 2px rgba(0,0,0,0.1);
    }
    `);

    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);
        
        // Add selection buttons
        addSelectionButtons(formElement);
    }
    
    function addSelectionButtons(formElement) {
        // Create button container
        const buttonContainer = document.createElement('div');
        buttonContainer.id = 'selectionButtonsContainer';
        buttonContainer.style.display = 'inline-block';
        buttonContainer.style.marginLeft = '10px';
        buttonContainer.style.gap = '10px';
        
        // Create Select All button
        const selectAllButton = document.createElement('button');
        selectAllButton.id = 'selectAllButton';
        selectAllButton.textContent = 'Select All';
        selectAllButton.type = 'button'; // Prevent form submission
        selectAllButton.className = 'selection-control-button';
        selectAllButton.addEventListener('click', (e) => {
            // Add visual feedback without text change
            addButtonClickFeedback(selectAllButton);
            selectAllItems();
        });
        
        // Create Unselect All button
        const unselectAllButton = document.createElement('button');
        unselectAllButton.id = 'unselectAllButton';
        unselectAllButton.textContent = 'Unselect All';
        unselectAllButton.type = 'button'; // Prevent form submission
        unselectAllButton.className = 'selection-control-button';
        unselectAllButton.addEventListener('click', (e) => {
            // Add visual feedback without text change
            addButtonClickFeedback(unselectAllButton);
            unselectAllItems();
        });
        
        // Create Reverse Selection button (hidden initially using opacity instead of display:none)
        const reverseSelectionButton = document.createElement('button');
        reverseSelectionButton.id = 'reverseSelectionButton';
        reverseSelectionButton.textContent = 'Invert Selection';
        reverseSelectionButton.type = 'button'; // Prevent form submission
        reverseSelectionButton.className = 'selection-control-button';
        // Use opacity and pointer-events to hide rather than display:none
        reverseSelectionButton.style.opacity = '0';
        reverseSelectionButton.style.pointerEvents = 'none';
        reverseSelectionButton.style.transition = 'opacity 0.2s ease';
        reverseSelectionButton.addEventListener('click', (e) => {
            // Add visual feedback without text change
            addButtonClickFeedback(reverseSelectionButton);
            reverseSelection();
        });
        
        // Add buttons to container
        buttonContainer.appendChild(selectAllButton);
        buttonContainer.appendChild(unselectAllButton);
        buttonContainer.appendChild(reverseSelectionButton);
        
        // Find the Convert button and insert our buttons after it
        const convertButton = formElement.querySelector('input[value="Convert"]');
        if (convertButton) {
            // Insert after the Convert button
            convertButton.insertAdjacentElement('afterend', buttonContainer);
        } else {
            // Fallback - just append to the form
            formElement.appendChild(buttonContainer);
        }
        
        // Add CSS for buttons
        GM_addStyle(`
            .selection-control-button {
                padding: 8px 12px;
                background-color: #2196F3;
                color: white;
                border: none;
                border-radius: 6px;
                cursor: pointer;
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
                font-size: 13px;
                font-weight: 500;
                letter-spacing: 0.3px;
                transition: all 0.2s ease;
                box-shadow: 0 2px 4px rgba(0,0,0,0.1);
                text-transform: uppercase;
                margin-right: 5px;
                display: inline-block;
                min-width: 120px; /* Minimum width for selection buttons */
                text-align: center;
                white-space: nowrap; /* Prevent text wrapping */
            }
            
            .selection-control-button:hover {
                transform: translateY(-1px);
                box-shadow: 0 3px 6px rgba(0,0,0,0.15);
                filter: brightness(1.05);
            }
            
            .selection-control-button:active {
                transform: translateY(1px);
                box-shadow: 0 1px 2px rgba(0,0,0,0.1);
            }
            
            .selection-control-button.button-clicked {
                background-color: #1976D2 !important; /* Darker blue */
            }
            
            #selectionButtonsContainer {
                vertical-align: middle;
            }
        `);
    }
    
    function selectAllItems() {
        // Get all selectable items in current view
        const gridContainer = document.getElementById('torrent-grid-container');
        const isGridActive = gridContainer && gridContainer.style.display !== 'none';
        
        if (isGridActive) {
            // Select all grid items
            const entries = document.querySelectorAll('.torrent-entry:not(.warning)');
            entries.forEach(entry => {
                if (!entry.classList.contains('selected')) {
                    entry.classList.add('selected');
                    entry.style.backgroundColor = 'rgba(40, 167, 69, 0.15)';
                    
                    // Get ID and sync with table view
                    const id = getIdentifierFromElement(entry);
                    if (id) {
                        syncTableViewSelection(id, true);
                    }
                }
            });
        } else {
            // Select all table rows
            const rows = document.querySelectorAll('.tr.g1:not(.warning), .tr.g2:not(.warning)');
            rows.forEach(row => {
                if (!row.classList.contains('selected')) {
                    row.classList.add('selected');
                    const nextRow = row.nextElementSibling;
                    if (nextRow && !nextRow.classList.contains('g1') && !nextRow.classList.contains('g2')) {
                        nextRow.classList.add('selected');
                    }
                    
                    // Get ID and sync with grid view
                    const id = getIdentifierFromElement(row);
                    if (id) {
                        syncSelectionState(id, true);
                    }
                }
            });
        }
        
        updateFloatingButtonsVisibility();
        updateReverseSelectionButtonVisibility();
    }
    
    function unselectAllItems() {
        // Unselect all items in both views
        document.querySelectorAll('.tr.g1.selected, .tr.g2.selected, .torrent-entry.selected').forEach(item => {
            item.classList.remove('selected');
            item.style.backgroundColor = '';
            
            // For table rows, also unselect detail row
            if (item.classList.contains('g1') || item.classList.contains('g2')) {
                const nextRow = item.nextElementSibling;
                if (nextRow && !nextRow.classList.contains('g1') && !nextRow.classList.contains('g2')) {
                    nextRow.classList.remove('selected');
                    nextRow.style.backgroundColor = '';
                }
            }
        });
        
        updateFloatingButtonsVisibility();
        updateReverseSelectionButtonVisibility();
    }
    
    function reverseSelection() {
        // Get all selectable items in current view
        const gridContainer = document.getElementById('torrent-grid-container');
        const isGridActive = gridContainer && gridContainer.style.display !== 'none';
        
        if (isGridActive) {
            // Reverse selection in grid view
            const entries = document.querySelectorAll('.torrent-entry:not(.warning)');
            entries.forEach(entry => {
                const isSelected = entry.classList.contains('selected');
                
                if (isSelected) {
                    // Properly remove selection styles
                    entry.classList.remove('selected');
                    entry.style.backgroundColor = ''; 
                } else {
                    entry.classList.add('selected');
                    entry.style.backgroundColor = 'rgba(40, 167, 69, 0.15)';
                }
                
                // Get ID and sync with table view
                const id = getIdentifierFromElement(entry);
                if (id) {
                    syncTableViewSelection(id, !isSelected);
                }
            });
        } else {
            // Reverse selection in table view
            const rows = document.querySelectorAll('.tr.g1:not(.warning), .tr.g2:not(.warning)');
            rows.forEach(row => {
                const isSelected = row.classList.contains('selected');
                
                if (isSelected) {
                    // Properly remove selection styles
                    row.classList.remove('selected');
                    row.style.backgroundColor = '';
                    
                    const nextRow = row.nextElementSibling;
                    if (nextRow && !nextRow.classList.contains('g1') && !nextRow.classList.contains('g2')) {
                        nextRow.classList.remove('selected');
                        nextRow.style.backgroundColor = '';
                    }
                } else {
                    row.classList.add('selected');
                    
                    const nextRow = row.nextElementSibling;
                    if (nextRow && !nextRow.classList.contains('g1') && !nextRow.classList.contains('g2')) {
                        nextRow.classList.add('selected');
                    }
                }
                
                // Get ID and sync with grid view
                const id = getIdentifierFromElement(row);
                if (id) {
                    syncSelectionState(id, !isSelected);
                }
            });
        }
        
        updateFloatingButtonsVisibility();
        updateReverseSelectionButtonVisibility();
    }
    
    function updateReverseSelectionButtonVisibility() {
        const reverseButton = document.getElementById('reverseSelectionButton');
        if (!reverseButton) return;
        
        const hasSelectedItems = document.querySelectorAll('.tr.g1.selected, .tr.g2.selected, .torrent-entry.selected').length > 0;
        
        // Use opacity instead of display to show/hide
        if (hasSelectedItems) {
            reverseButton.style.opacity = '1';
            reverseButton.style.pointerEvents = 'auto';
        } else {
            reverseButton.style.opacity = '0';
            reverseButton.style.pointerEvents = 'none';
        }
    }

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

        debridButton = document.createElement('button');
        debridButton.addEventListener('click', (e) => {
            // Add visual feedback
            addButtonClickFeedback(debridButton, 'Sent to Debrid');
            sendSelectedLinksToDebrid(e);
        });

        copyButton = document.createElement('button');
        copyButton.addEventListener('click', (e) => {
            // Add visual feedback
            addButtonClickFeedback(copyButton, 'Copied!');
            copySelectedLinksToClipboard();
        });
        
        // Add delete button 
        deleteButton = document.createElement('button');
        deleteButton.style.backgroundColor = '#dc3545';
        deleteButton.addEventListener('click', (e) => {
            addButtonClickFeedback(deleteButton);
            deleteSelectedTorrents();
        });

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

        return container;
    }

    function updateFloatingButtonsVisibility() {
        const selectedLinks = getSelectedItemLinks();
        const count = selectedLinks.length;
        
        // Get unique selected items count
        const uniqueSelectedIds = getUniqueSelectedItemsCount();
        const itemCount = uniqueSelectedIds.length;

        if (count > 0) {
            debridButton.textContent = `Debrid (${count})`;
            copyButton.textContent = `Copy Selected (${count})`;
            deleteButton.textContent = `Delete (${itemCount})`;
            debridButton.style.display = 'block';
            copyButton.style.display = 'block';
            deleteButton.style.display = 'block';
        } else {
            debridButton.style.display = 'none';
            copyButton.style.display = 'none';
            deleteButton.style.display = 'none';
        }
        
        // Update visibility of Reverse Selection button
        updateReverseSelectionButtonVisibility();
    }

    function getUniqueSelectedItemsCount() {
        const uniqueIds = new Set();
        const gridContainer = document.getElementById('torrent-grid-container');
        const isGridActive = gridContainer && gridContainer.style.display !== 'none';
        
        if (isGridActive) {
            // Count only grid items if grid view is active
            const selectedEntries = document.querySelectorAll('.torrent-entry.selected');
            selectedEntries.forEach(entry => {
                const id = getIdentifierFromElement(entry);
                if (id) uniqueIds.add(id);
            });
        } else {
            // Count only table rows if table view is active
            const selectedRows = document.querySelectorAll('.tr.g1.selected, .tr.g2.selected');
            selectedRows.forEach(row => {
                const id = getIdentifierFromElement(row);
                if (id) uniqueIds.add(id);
            });
        }
        
        return Array.from(uniqueIds);
    }

    function makeItemsSelectable() {
        const rows = document.querySelectorAll('.tr.g1, .tr.g2');
        rows.forEach(row => {
            // Skip if already has a click handler
            if (row.hasAttribute('data-has-click-handler')) return;
            
            const warningSpan = row.querySelector('span.px10 strong');
            if (!warningSpan || warningSpan.textContent !== 'Warning:') {
                const nextRow = row.nextElementSibling;
                
                // Add event stopping for delete buttons and download images
                const deleteButton = row.querySelector('a[href*="del"]');
                if (deleteButton) {
                    deleteButton.addEventListener('click', (e) => {
                        e.stopPropagation();
                    });
                }
                
                // Add event stopping for file info buttons
                const fileInfoButton = row.querySelector('a[rel="facebox"]');
                if (fileInfoButton) {
                    fileInfoButton.addEventListener('click', (e) => {
                        e.stopPropagation();
                    });
                }
                
                const clickHandler = () => {
                    row.classList.toggle('selected');
                    if (nextRow) {
                        nextRow.classList.toggle('selected');
                    }
                    
                    // Get ID and sync with grid view
                    const id = getIdentifierFromElement(row);
                    if (id) {
                        syncSelectionState(id, row.classList.contains('selected'));
                    }
                    
                    updateFloatingButtonsVisibility();
                };
                
                row.addEventListener('click', clickHandler);
                row.setAttribute('data-has-click-handler', 'true');
                
                if (nextRow) {
                    // Add event stopping for download buttons in the details row
                    const downloadButtons = nextRow.querySelectorAll('input[type="image"]');
                    downloadButtons.forEach(button => {
                        button.addEventListener('click', (e) => {
                            e.stopPropagation();
                        });
                    });
                    
                    nextRow.addEventListener('click', clickHandler);
                    nextRow.setAttribute('data-has-click-handler', 'true');
                }
            } else {
                row.classList.add('warning');
                if (row.nextElementSibling) {
                    row.nextElementSibling.classList.add('warning');
                }
            }
        });

        const entries = document.querySelectorAll('.torrent-entry');
        entries.forEach(entry => {
            // Skip if already has a click handler
            if (entry.hasAttribute('data-has-click-handler')) return;
            
            // Add event stopping for buttons in grid view
            const deleteButton = entry.querySelector('a[href*="del"]');
            if (deleteButton) {
                deleteButton.addEventListener('click', (e) => {
                    e.stopPropagation();
                });
            }
            
            const downloadButtons = entry.querySelectorAll('input[type="image"]');
            downloadButtons.forEach(button => {
                button.addEventListener('click', (e) => {
                    e.stopPropagation();
                });
            });
            
            const fileInfoButtons = entry.querySelectorAll('a[rel="facebox"]');
            fileInfoButtons.forEach(button => {
                button.addEventListener('click', (e) => {
                    e.stopPropagation();
                });
            });
            
            entry.addEventListener('click', (e) => {
                // Prevent click propagation if this is a delete button
                if (e.target.closest('a[href*="del"]') || 
                    e.target.closest('input[type="image"]') ||
                    e.target.closest('a[rel="facebox"]')) {
                    return;
                }
                
                // Toggle selection state
                entry.classList.toggle('selected');
                
                // Get ID and sync with table view
                const id = getIdentifierFromElement(entry);
                if (id) {
                    syncSelectionState(id, entry.classList.contains('selected'));
                }
                
                updateFloatingButtonsVisibility();
            });
            
            entry.setAttribute('data-has-click-handler', 'true');
        });
    }

    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() {
        // Use a Set to store unique links and prevent duplication
        const uniqueLinks = new Set();
        const uniqueIds = new Set();
        
        // Process selected rows in table view
        const selectedRows = document.querySelectorAll('.tr.g1.selected, .tr.g2.selected');
        selectedRows.forEach(row => {
            // Extract torrent ID to prevent duplicates
            const id = getIdentifierFromElement(row);
            if (id && !uniqueIds.has(id)) {
                uniqueIds.add(id);
                const textarea = row.nextElementSibling.querySelector('textarea');
                if (textarea && textarea.value) {
                    uniqueLinks.add(textarea.value);
                }
            }
        });
        
        // Only process grid items if grid view is active
        const gridContainer = document.getElementById('torrent-grid-container');
        if (gridContainer && gridContainer.style.display !== 'none') {
            const selectedEntries = document.querySelectorAll('.torrent-entry.selected');
            selectedEntries.forEach(entry => {
                // Extract torrent ID to prevent duplicates
                const id = getIdentifierFromElement(entry);
                if (id && !uniqueIds.has(id)) {
                    uniqueIds.add(id);
                    const textarea = entry.querySelector('textarea');
                    if (textarea && textarea.value) {
                        uniqueLinks.add(textarea.value);
                    }
                }
            });
        }
        
        return Array.from(uniqueLinks);
    }

    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) {
        // Enhanced URL regex that better handles various URL formats
        const urlRegex = /(?:(?:https?|ftp):\/\/|www\.)(?:\([-A-Z0-9+&@#\/%=~_|$?!:,.]*\)|[-A-Z0-9+&@#\/%=~_|$?!:,.])*(?:\([-A-Z0-9+&@#\/%=~_|$?!:,.]*\)|[A-Z0-9+&@#\/%=~_|$])/ig;
        const urls = text.match(urlRegex) || [];
        // Filter out duplicates and ensure proper http prefix
        return [...new Set(urls)].map(url => {
            if (!url.startsWith('http')) {
                return 'http://' + url;
            }
            return url;
        });
    }

    function addExtractUrlsButtonToDownloader() {
        const textarea = document.getElementById('links');
        if (textarea) {
            const button = document.createElement('button');
            button.id = 'extractUrlsButton';
            button.textContent = 'Extract URLs';
            
            button.addEventListener('click', function(e) {
                e.preventDefault();
                const content = textarea.value;
                const urls = extractUrlsFromText(content);
                
                // Add visual feedback
                addButtonClickFeedback(button);
                
                if (urls.length > 0) {
                    textarea.value = urls.join('\n');
                    // Visual feedback
                    button.textContent = `${urls.length} URLs Found`;
                    setTimeout(() => {
                        button.textContent = 'Extract URLs';
                    }, 2000);
                } else {
                    button.textContent = 'No URLs Found';
                    setTimeout(() => {
                        button.textContent = 'Extract URLs';
                    }, 2000);
                }
            });

            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);
                        
                        // Add visual feedback (for input elements)
                        copyButton.classList.add('button-clicked');
                        const originalValue = copyButton.value;
                        copyButton.value = 'Copied!';
                        
                        setTimeout(() => {
                            copyButton.classList.remove('button-clicked');
                            copyButton.value = originalValue;
                        }, 500);
                    }
                });

                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) {
                // Count torrent sections by splitting on <h2> tags
                const torrentInfos = content.innerHTML.split('<h2>Torrent Files</h2>').filter(info => info.trim() !== '');
                
                // Only apply grid layout if 3+ torrents
                if (torrentInfos.length < 3) return;
                
                // Add class for CSS to apply instead of inline styles
                content.classList.add('grid-layout');
                // Add class to facebox itself for positioning
                facebox.classList.add('grid-layout');

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

                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;

        // First, check if grid already exists and remove it
        const existingGrid = document.getElementById('torrent-grid-container');
        if (existingGrid) {
            existingGrid.remove();
        }

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

        // Create grid items from table rows
        const rows = table.querySelectorAll('tr');
        for (let i = 1; i < rows.length; i += 2) {
            // Check if original row is selected
            const isSelected = rows[i].classList.contains('selected');
            const torrentDiv = createGridItemFromTableRows(rows[i], rows[i + 1], isSelected);
            container.appendChild(torrentDiv);
        }

        // Insert grid after the table
        table.parentNode.insertBefore(container, table.nextSibling);
        
        // Hide the table but keep it in the DOM
        table.style.display = 'none';
        
        // Mark the table for later reference
        table.id = 'original-torrent-table';
        
        applyGridLayoutStyles(columnCount);
        adjustImageSizeInNewLayout();
        moveDeleteLinkToEnd();
        
        // Apply enhanced selection handling
        setupGridItemsEventHandlers();
        
        updateFloatingButtonsVisibility(); // Update button visibility to reflect current selections
    }

    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;
                position: relative;
            }
            /* Fix for long filenames with dots */
            .torrent-entry span[id^="name_"] {
                display: block;
                word-break: break-all;
                overflow-wrap: break-word;
                white-space: normal;
                width: 100%;
                margin-bottom: 5px;
                font-weight: bold;
            }
            .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, isSelected = false) {
        const div = document.createElement('div');
        div.className = 'torrent-entry';
        div.innerHTML = mainRow.innerHTML + detailRow.innerHTML;

        // Set selected state if the original row was selected
        if (isSelected) {
            div.classList.add('selected');
            div.style.backgroundColor = 'rgba(40, 167, 69, 0.15)';
        }

        return div;
    }
    
    // Get a unique identifier from an element (row or grid item)
    function getIdentifierFromElement(element) {
        // Try to find a unique ID in the element (torrent ID, name ID, etc.)
        const idElement = element.querySelector('[id^="name_"], [id^="link_"], [id^="status_"]');
        if (idElement) {
            return idElement.id;
        }
        return null;
    }
    
    // Sync selection state between table and grid views
    function syncSelectionState(id, isSelected) {
        if (!id) return;
        
        // Get ID prefix and suffix
        const parts = id.split('_');
        if (parts.length < 2) return;
        
        const prefix = parts[0];
        const suffix = parts[1];
        
        // Get all elements with IDs containing this suffix (both in table and grid)
        const selector = `[id$="_${suffix}"]`;
        const relatedElements = document.querySelectorAll(selector);
        
        // Find related rows and grid items
        let tableRows = [];
        let gridItems = [];
        
        relatedElements.forEach(el => {
            // Find containing row
            let row = el.closest('.tr.g1, .tr.g2');
            if (row) {
                tableRows.push(row);
                // Also get the next row (detail row)
                if (row.nextElementSibling && !row.nextElementSibling.classList.contains('g1') && 
                    !row.nextElementSibling.classList.contains('g2')) {
                    tableRows.push(row.nextElementSibling);
                }
            }
            
            // Find containing grid item
            let gridItem = el.closest('.torrent-entry');
            if (gridItem) {
                gridItems.push(gridItem);
            }
        });
        
        // Apply selection state to all related elements
        tableRows = [...new Set(tableRows)]; // Remove duplicates
        tableRows.forEach(row => {
            if (isSelected) {
                row.classList.add('selected');
            } else {
                row.classList.remove('selected');
            }
        });
        
        gridItems = [...new Set(gridItems)]; // Remove duplicates
        gridItems.forEach(item => {
            if (isSelected) {
                item.classList.add('selected');
            } else {
                item.classList.remove('selected');
            }
        });
    }

    function addSwitchToGridLayoutButton() {
        const button = document.createElement('button');
        button.textContent = 'Switch to Grid Layout';
        button.id = 'switchLayoutButton';
        button.style.position = 'fixed';
        button.style.top = '10px';
        button.style.right = '20px';
        button.style.zIndex = '1000';
        button.setAttribute('data-current-layout', 'table');
        button.addEventListener('click', (e) => {
            // Add visual feedback
            addButtonClickFeedback(button);
            toggleLayout();
        });
        document.body.appendChild(button);
    }

    function toggleLayout() {
        const button = document.getElementById('switchLayoutButton');
        const currentLayout = button.getAttribute('data-current-layout');
        
        if (currentLayout === 'table') {
            // Switch to grid layout
            const columnCount = 3;
            createGridLayout(columnCount);
            
            button.textContent = 'Switch to Table Layout';
            button.setAttribute('data-current-layout', 'grid');
        } else {
            // Switch back to table layout without reload
            const gridContainer = document.getElementById('torrent-grid-container');
            const originalTable = document.getElementById('original-torrent-table');
            
            if (gridContainer && originalTable) {
                // Hide grid, show table
                gridContainer.style.display = 'none';
                originalTable.style.display = 'table';
                
                button.textContent = 'Switch to Grid Layout';
                button.setAttribute('data-current-layout', 'table');
            }
        }
        
        // Update floating buttons visibility
        updateFloatingButtonsVisibility();
    }
    
    function switchToGridLayout() {
        const button = document.getElementById('switchLayoutButton');
        if (button.getAttribute('data-current-layout') === 'table') {
            toggleLayout();
        }
    }

    function deleteSelectedTorrents() {
        const selectedItems = document.querySelectorAll('.tr.g1.selected, .tr.g2.selected, .torrent-entry.selected');
        const deleteIds = [];
        
        selectedItems.forEach(item => {
            // Find delete link within the item
            const deleteLink = item.querySelector('a[href*="del="]');
            if (deleteLink) {
                const href = deleteLink.getAttribute('href');
                const match = href.match(/del=([^&]+)/);
                if (match && match[1]) {
                    deleteIds.push(match[1]);
                }
            }
        });
        
        if (deleteIds.length === 0) return;
        
        if (confirm(`Delete ${deleteIds.length} selected torrents?`)) {
            // Change button text to "Deleting..." after confirmation
            const originalWidth = deleteButton.offsetWidth;
            deleteButton.textContent = 'Deleting...';
            deleteButton.style.width = `${originalWidth}px`;
            
            // Process deletions sequentially to avoid overwhelming the server
            deleteSequentially(deleteIds, 0);
        }
    }

    function deleteSequentially(ids, index) {
        if (index >= ids.length) {
            // All done, refresh the page
            window.location.reload();
            return;
        }
        
        const id = ids[index];
        const xhr = new XMLHttpRequest();
        xhr.open('GET', `?p=1&del=${id}`, true);
        xhr.onload = function() {
            // Move to next deletion
            deleteSequentially(ids, index + 1);
        };
        xhr.onerror = function() {
            // Still try the next one
            deleteSequentially(ids, index + 1);
        };
        xhr.send();
    }

    function setupGridItemsEventHandlers() {
        const entries = document.querySelectorAll('.torrent-entry');
        entries.forEach(entry => {
            // Clear any existing handlers by cloning the node
            const newEntry = entry.cloneNode(true);
            entry.parentNode.replaceChild(newEntry, entry);
            
            // Add event stopping for buttons in grid view
            const deleteButton = newEntry.querySelector('a[href*="del"]');
            if (deleteButton) {
                deleteButton.addEventListener('click', (e) => {
                    e.stopPropagation();
                });
            }
            
            const downloadButtons = newEntry.querySelectorAll('input[type="image"]');
            downloadButtons.forEach(button => {
                button.addEventListener('click', (e) => {
                    e.stopPropagation();
                });
            });
            
            const fileInfoButtons = newEntry.querySelectorAll('a[rel="facebox"]');
            fileInfoButtons.forEach(button => {
                button.addEventListener('click', (e) => {
                    e.stopPropagation();
                });
            });
            
            // Main click handler for selection toggling
            newEntry.addEventListener('click', (e) => {
                // Prevent click propagation if this is a button
                if (e.target.closest('a[href*="del"]') || 
                    e.target.closest('input[type="image"]') ||
                    e.target.closest('a[rel="facebox"]')) {
                    return;
                }
                
                // Toggle selection state
                newEntry.classList.toggle('selected');
                if (newEntry.classList.contains('selected')) {
                    newEntry.style.backgroundColor = 'rgba(40, 167, 69, 0.15)';
                } else {
                    newEntry.style.backgroundColor = '';
                }
                
                // Get ID and sync with table view
                const id = getIdentifierFromElement(newEntry);
                if (id) {
                    const isNowSelected = newEntry.classList.contains('selected');
                    syncTableViewSelection(id, isNowSelected);
                }
                
                updateFloatingButtonsVisibility();
            });
        });
    }
    
    function syncTableViewSelection(id, isSelected) {
        if (!id) return;
        
        // Get ID suffix
        const parts = id.split('_');
        if (parts.length < 2) return;
        
        const suffix = parts[1];
        
        // Find table rows with this torrent ID
        const selector = `[id$="_${suffix}"]`;
        const originalTable = document.getElementById('original-torrent-table');
        if (!originalTable) return;
        
        const elements = originalTable.querySelectorAll(selector);
        elements.forEach(el => {
            const row = el.closest('.tr.g1, .tr.g2');
            if (row) {
                // Set selection state on main row
                if (isSelected) {
                    row.classList.add('selected');
                } else {
                    row.classList.remove('selected');
                }
                
                // Set selection state on detail row
                const nextRow = row.nextElementSibling;
                if (nextRow && !nextRow.classList.contains('g1') && !nextRow.classList.contains('g2')) {
                    if (isSelected) {
                        nextRow.classList.add('selected');
                    } else {
                        nextRow.classList.remove('selected');
                    }
                }
            }
        });
    }

    // Helper function to add visual feedback to buttons
    function addButtonClickFeedback(button, tempText = null) {
        // Store original text if we're changing it
        const originalText = tempText ? button.textContent : null;
        
        // Store original width to prevent layout shifts
        const originalWidth = button.offsetWidth;
        
        // Add animation class
        button.classList.add('button-clicked');
        
        // Change text if specified
        if (tempText) {
            button.textContent = tempText;
            // Ensure width doesn't change
            button.style.width = `${originalWidth}px`;
        }
        
        // Remove animation class and restore text after animation
        setTimeout(() => {
            button.classList.remove('button-clicked');
            if (originalText) {
                button.textContent = originalText;
                // Remove explicit width to allow natural sizing again
                button.style.width = '';
            }
        }, 500);
    }

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