您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址