Real-Debrid Enhancer

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

当前为 2025-03-13 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==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);
    }
})();