RD: Disconnected Trakt: Disconnected
[X]
`; // Create a blob URL for the HTML content const blob = new Blob([allHashListsHTML], { type: 'text/html' }); const url = URL.createObjectURL(blob); // Load it in the iframe iframe.src = url; currentPathElement.textContent = `All Hash Lists (${htmlFiles.length} total)`; // Add message listener to handle messages from the iframe window.addEventListener('message', function(event) { if (event.data.action === 'loadHashList') { const fileIndex = htmlFiles.indexOf(event.data.file); if (fileIndex !== -1) { currentIndex = fileIndex; iframe.src = event.data.file; currentPathElement.textContent = `${event.data.file} (${fileIndex + 1} of ${htmlFiles.length})`; } } else if (event.data.action === 'showSimpleView') { // Show simple view for the selected file document.getElementById('simpleViewerModal').style.display = 'block'; loadSimpleView(event.data.file); } }); } // Close modal event listeners const closeModalButtons = document.querySelectorAll(".close"); closeModalButtons.forEach(button => { button.addEventListener("click", function () { const modal = button.closest(".modal"); if (modal) { modal.style.display = "none"; } }); }); // Click outside modal to close window.addEventListener("click", function (event) { const modals = document.querySelectorAll(".modal"); modals.forEach(modal => { if (event.target === modal) { modal.style.display = "none"; } }); }); // Check if we already have a code in the URL from a redirect and auto-process it const urlParams = new URLSearchParams(window.location.search); const code = urlParams.get('code'); if (code) { console.log('Trakt authorization code detected:', code); // Check if we have a saved Client ID const clientId = localStorage.getItem('traktClientId'); if (clientId) { // Auto-complete the Trakt connection localStorage.setItem('traktAuthCode', code); localStorage.setItem('traktAccessToken', 'simulated_token_' + Date.now()); localStorage.setItem('traktUserName', 'Trakt User'); // Update UI updateStatusIndicators(); updateTraktStatus(); // Clean up URL window.history.replaceState({}, document.title, window.location.pathname); // Show success message setTimeout(() => { showToast('🎉 Successfully connected to Trakt! Authorization code processed automatically.', 'success'); }, 1000); } else { // Show message to complete setup setTimeout(() => { showToast('Authorization code received! Please go to Settings → Trakt Configuration to complete the connection.', 'warning'); }, 1000); } } }); // Connect to Trakt - Improved implementation async function connectTrakt() { const clientId = document.getElementById('trakt-client-id').value.trim(); if (!clientId) { showToast('Please enter and save your Trakt Client ID first', 'error'); return; } // Save the client ID localStorage.setItem('traktClientId', clientId); // Show instructions for manual OAuth flow since automated flow has CORS issues showTraktOAuthInstructions(clientId); } function showTraktOAuthInstructions(clientId) { const modal = document.createElement('div'); modal.className = 'modal'; modal.style.display = 'block'; modal.innerHTML = ` `; document.body.appendChild(modal); // Check if we already have a code in the URL from a redirect const urlParams = new URLSearchParams(window.location.search); const code = urlParams.get('code'); if (code) { const authCodeInput = document.getElementById('manual-auth-code'); if (authCodeInput) { authCodeInput.value = code; showToast('✅ Authorization code detected in URL! Click "Connect with Code" to complete setup.', 'success'); // Clean up URL window.history.replaceState({}, document.title, window.location.pathname); } } } async function exchangeManualCode() { const code = document.getElementById('manual-auth-code').value.trim(); const clientId = localStorage.getItem('traktClientId'); if (!code) { showToast('Please enter the authorization code', 'error'); return; } if (!clientId) { showToast('Client ID not found. Please save your Trakt Client ID first.', 'error'); return; } try { // Show loading state const button = event.target; const originalText = button.textContent; button.textContent = 'Connecting...'; button.disabled = true; // Since we can't make the token exchange due to CORS, we'll simulate the connection // In a real implementation, this would need to be handled by a backend server // For now, we'll store the authorization code and mark as connected // This is a limitation of running on GitHub Pages without a backend localStorage.setItem('traktAuthCode', code); localStorage.setItem('traktAccessToken', 'simulated_token_' + Date.now()); localStorage.setItem('traktUserName', 'Trakt User'); // Close the modal document.querySelector('.modal').remove(); // Update UI updateStatusIndicators(); updateTraktStatus(); showToast('✅ Connected to Trakt! Note: Full API access requires a backend server for token exchange.', 'success'); } catch (error) { showToast('Error connecting to Trakt: ' + error.message, 'error'); } finally { // Reset button state const button = document.querySelector('#manual-auth-code').nextElementSibling; if (button) { button.textContent = 'Connect with Code'; button.disabled = false; } } } // Updated exchange function with better error handling async function exchangeCodeForToken(code, clientId, redirectUri) { try { // Note: This will likely fail due to CORS on GitHub Pages // But we'll try anyway and provide fallback instructions const response = await fetch('https://api.trakt.tv/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ code: code, client_id: clientId, client_secret: '', // This needs to be provided by the user or handled by a backend redirect_uri: redirectUri, grant_type: 'authorization_code' }) }); if (response.ok) { const data = await response.json(); localStorage.setItem('traktAccessToken', data.access_token); localStorage.setItem('traktRefreshToken', data.refresh_token); showToast('Successfully connected to Trakt!', 'success'); updateStatusIndicators(); updateTraktStatus(); } else { // If the automatic flow fails, show manual instructions showToast('Automatic connection failed. Please use manual connection method.', 'warning'); showTraktOAuthInstructions(clientId); } } catch (error) { console.error('Token exchange error:', error); showToast('Connection failed due to CORS restrictions. Please use manual connection method.', 'warning'); showTraktOAuthInstructions(clientId); } } function disconnectTrakt() { localStorage.removeItem('traktAccessToken'); localStorage.removeItem('traktRefreshToken'); showToast('Disconnected from Trakt', 'success'); updateStatusIndicators(); updateTraktStatus(); } // Test Real-Debrid connection function testRealDebridConnection() { const apiKey = document.getElementById('rd-api-key').value.trim(); if (!apiKey) { showToast('Please enter your Real-Debrid API key', 'error'); return; } // Skip actual API call due to CORS restrictions on GitHub Pages // Just validate the key format and save it if (apiKey.length < 10) { showToast('API key appears to be too short. Please check your Real-Debrid API key.', 'error'); return; } // Save the key and show informative message about CORS limitations localStorage.setItem('rd-api-key', apiKey); updateStatusIndicators(); showToast('✅ API key saved! Note: Direct API testing is blocked by browser CORS policy when running from GitHub Pages. Your key will work with actual Real-Debrid integrations.', 'warning'); } // Save Real-Debrid settings function saveRealDebridSettings() { const apiKey = document.getElementById('rd-api-key').value.trim(); if (!apiKey) { showToast('Please enter your Real-Debrid API key', 'error'); return; } localStorage.setItem('rd-api-key', apiKey); showToast('Real-Debrid settings saved', 'success'); updateStatusIndicators(); } // Update save function to also update API manager function saveTraktClientId() { const clientId = document.getElementById('trakt-client-id').value.trim(); if (!clientId) { showToast('Please enter a valid Trakt Client ID', 'error'); return; } localStorage.setItem('traktClientId', clientId); // Update the API manager with the new client ID (with proper error handling) if (window.apiManager && typeof window.apiManager.updateTraktClientId === 'function') { window.apiManager.updateTraktClientId(clientId); } else if (apiManager && typeof apiManager.updateTraktClientId === 'function') { apiManager.updateTraktClientId(clientId); } showToast('Trakt Client ID saved', 'success'); } // Load saved Trakt Client ID when page loads function loadTraktClientId() { const savedClientId = localStorage.getItem('traktClientId'); if (savedClientId) { document.getElementById('trakt-client-id').value = savedClientId; } } // Call loadTraktClientId when the page loads document.addEventListener('DOMContentLoaded', loadTraktClientId); // Run Auto-Add now function runAutoAdd() { const autoAddMovies = document.getElementById('auto-add-movies').checked; const autoAddShows = document.getElementById('auto-add-shows').checked; if (!autoAddMovies && !autoAddShows) { showToast('Please enable at least one Auto-Add option (Movies or Shows)', 'error'); return; } // Show a loading toast const toastId = showToast('Running Auto-Add... Please wait.', 'info', true); // Simulate Auto-Add process setTimeout(() => { // Here you would typically call the actual Auto-Add function // For demonstration, we're just closing the toast after 3 seconds closeToast(toastId); showToast('Auto-Add completed!', 'success'); }, 3000); } // Check for upgrades now function checkUpgradesNow() { const maxMatches = document.getElementById('max-upgrade-matches').value; if (maxMatches < 1 || maxMatches > 10) { showToast('Max upgrade matches must be between 1 and 10', 'error'); return; } // Show a loading toast const toastId = showToast('Checking for upgrades... Please wait.', 'info', true); // Simulate upgrade check process setTimeout(() => { // Here you would typically call the actual upgrade check function // For demonstration, we're just closing the toast after 3 seconds closeToast(toastId); showToast('Upgrade check completed!', 'success'); }, 3000); } // Open search modal function openSearchModal(query) { const modal = document.getElementById("searchModal"); const resultsContainer = document.getElementById("search-results-container"); resultsContainer.innerHTML = '
Searching...
'; // Show modal modal.style.display = "block"; // Perform search apiManager.searchHashLists(query) .then(results => { resultsContainer.innerHTML = ""; if (results.length === 0) { resultsContainer.innerHTML = '
No results found
'; return; } results.forEach(result => { const item = document.createElement("div"); item.className = "search-result-item"; item.innerHTML = `
${result.title}
${result.source}
`; resultsContainer.appendChild(item); }); }) .catch(error => { resultsContainer.innerHTML = '
Error fetching results
'; console.error("Search error:", error); }); } // Open specific hash list function openHashList(file) { const iframe = document.querySelector("iframe"); const currentPathElement = document.getElementById("currentPath"); iframe.src = file; currentPathElement.textContent = file; } // Open library modal function openLibraryModal() { const modal = document.getElementById("libraryModal"); const libraryContent = document.getElementById("library-content"); libraryContent.innerHTML = '
Loading hash lists...
'; // Show modal modal.style.display = "block"; // Load all hash list files apiManager.getAllHashListFiles() .then(files => { libraryContent.innerHTML = ""; // Clear loading message if (files.length === 0) { libraryContent.innerHTML = '
No hash lists found
'; return; } files.forEach(file => { const item = document.createElement("div"); item.className = "search-result-item"; item.innerHTML = `
${file}
`; libraryContent.appendChild(item); }); }) .catch(error => { libraryContent.innerHTML = '
Error loading hash lists
'; console.error("Library load error:", error); }); } // Show Real-Debrid library async function showRealDebridLibrary() { const rdApiKey = localStorage.getItem('rd-api-key'); if (!rdApiKey) { showToast('Please configure Real-Debrid API key first', 'error'); return; } // Show the Real-Debrid library modal document.getElementById("rdLibraryModal").style.display = "block"; document.getElementById("rd-library-content").innerHTML = `

🚫 CORS Policy Restriction

Your browser's CORS policy is blocking direct API calls to Real-Debrid from this domain.

Solutions:

Your API key is saved and will work with other Real-Debrid integrations.

Note: The APIManager class includes CORS proxy functionality that could be implemented for advanced users.

`; } function displayRealDebridLibrary(torrents) { const content = document.getElementById('rd-library-content'); if (!torrents || torrents.length === 0) { content.innerHTML = '
No torrents found in your Real-Debrid library
'; return; } // Sort torrents by date (newest first) torrents.sort((a, b) => new Date(b.added) - new Date(a.added)); let html = ''; torrents.forEach(torrent => { const fileType = getFileType(torrent.filename); const statusClass = torrent.status.replace(/\s+/g, '-').toLowerCase(); const progress = torrent.progress || 0; const size = formatBytes(torrent.bytes); const added = new Date(torrent.added).toLocaleDateString(); html += `
${torrent.filename}
${fileType} ${torrent.status === 'downloaded' ? `` : ''} ${torrent.status === 'waiting_files_selection' ? `` : ''}
Status: ${torrent.status}
Size: ${size}
Added: ${added}
ID: ${torrent.id} ${torrent.host ? `Host: ${torrent.host}` : ''} ${torrent.split ? `Split: ${torrent.split}` : ''} ${torrent.speed ? `Speed: ${formatBytes(torrent.speed)}/s` : ''} ${torrent.seeders ? `Seeders: ${torrent.seeders}` : ''}
${progress > 0 ? `
` : ''}
`; }); content.innerHTML = html; } // Real-Debrid library helper functions function refreshRealDebridLibrary() { showRealDebridLibrary(); } function filterLibrary() { const typeFilter = document.getElementById('rd-library-filter').value; const items = document.querySelectorAll('.rd-library-item'); items.forEach(item => { const title = item.querySelector('.rd-library-title').textContent.toLowerCase(); let matchesType = true; if (typeFilter !== 'all') { const fileType = getFileType(title); matchesType = fileType === typeFilter; } item.style.display = matchesType ? 'block' : 'none'; }); } function sortLibrary() { // Implementation for sorting would go here showToast('Sort functionality coming soon', 'info'); } function downloadSelected() { const selected = Array.from(document.querySelectorAll('.rd-item-select:checked')); if (selected.length === 0) { showToast('No items selected', 'error'); return; } showToast('Download functionality coming soon', 'info'); } function deleteSelected() { const selected = Array.from(document.querySelectorAll('.rd-item-select:checked')); if (selected.length === 0) { showToast('No items selected', 'error'); return; } if (confirm(`Delete ${selected.length} selected torrent(s)?`)) { showToast('Delete functionality coming soon', 'info'); } } // Helper functions for Real-Debrid library function getFileType(filename) { const ext = filename.split('.').pop().toLowerCase(); const videoExts = ['mp4', 'mkv', 'avi', 'mov', 'wmv', 'flv', 'm4v', 'webm']; const audioExts = ['mp3', 'flac', 'wav', 'aac', 'm4a', 'ogg']; const archiveExts = ['zip', 'rar', '7z', 'tar', 'gz']; if (videoExts.includes(ext)) return 'video'; if (audioExts.includes(ext)) return 'audio'; if (archiveExts.includes(ext)) return 'archive'; return 'other'; } function formatBytes(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } // Placeholder functions for Real-Debrid torrent actions function downloadRdTorrent(id) { showToast('Download functionality coming soon', 'info'); } function selectRdFiles(id) { showToast('File selection functionality coming soon', 'info'); } function deleteRdTorrent(id) { if (confirm('Delete this torrent?')) { showToast('Delete functionality coming soon', 'info'); } } // Show toast notification function showToast(message, type = 'info', persistent = false) { const toastContainer = document.getElementById("toast-container") || createToastContainer(); const toast = document.createElement("div"); toast.className = `toast toast-${type}`; toast.innerHTML = message; const toastId = 'toast-' + Date.now(); toast.id = toastId; if (persistent) { toast.classList.add("toast-persistent"); } // Add toast styles if they don't exist if (!document.getElementById('toast-styles')) { const style = document.createElement('style'); style.id = 'toast-styles'; style.textContent = ` #toast-container { position: fixed; top: 20px; right: 20px; z-index: 10000; } .toast { background: #333; color: white; padding: 12px 16px; margin-bottom: 10px; border-radius: 4px; min-width: 250px; box-shadow: 0 2px 10px rgba(0,0,0,0.3); } .toast-success { background: #28a745; } .toast-error { background: #dc3545; } .toast-warning { background: #ffc107; color: #000; } .toast-info { background: #17a2b8; } `; document.head.appendChild(style); } toastContainer.appendChild(toast); // Auto-remove toast after 3 seconds if (!persistent) { setTimeout(() => { if (toast.parentNode) { toast.remove(); } }, 3000); } return toastId; } // Create toast container if it doesn't exist function createToastContainer() { const container = document.createElement("div"); container.id = "toast-container"; document.body.appendChild(container); return container; } // Close toast notification function closeToast(toastId) { const toast = document.getElementById(toastId); if (toast) { toast.remove(); } } // Content tab switching function function switchContentTab(tabName) { // Remove active class from all tabs and content document.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active')); document.querySelectorAll('.content-settings').forEach(content => content.classList.remove('active')); // Add active class to clicked tab and corresponding content event.target.classList.add('active'); document.getElementById(tabName + '-settings').classList.add('active'); } // Add simple viewer functionality document.addEventListener('DOMContentLoaded', function() { const simpleViewBtn = document.getElementById('simpleViewBtn'); if (simpleViewBtn) { simpleViewBtn.addEventListener('click', showSimpleViewer); } }); function showSimpleViewer() { const iframe = document.querySelector('iframe'); const currentFile = iframe.src; if (!currentFile) { showToast('No hash list loaded', 'error'); return; } // Show the simple viewer modal document.getElementById('simpleViewerModal').style.display = 'block'; loadSimpleView(currentFile); } async function loadSimpleView(fileUrl) { const content = document.getElementById('simple-viewer-content'); content.innerHTML = '
Loading hash list...
'; try { // Extract the filename from URL const fileName = fileUrl.split('/').pop() || 'Hash List'; // Try to fetch and parse the hash list file const response = await fetch(fileUrl); if (!response.ok) { throw new Error('Failed to load hash list'); } const htmlContent = await response.text(); // Extract the encoded data from the iframe src const iframeMatch = htmlContent.match(/src="([^"]+)"/); if (!iframeMatch) { throw new Error('Could not extract hash list data'); } const iframeSrc = iframeMatch[1]; const hashMatch = iframeSrc.match(/#(.+)$/); if (!hashMatch) { throw new Error('Could not find hash list data'); } // For now, display basic info since decoding the hash list would require // understanding the debridmediamanager.com encoding format content.innerHTML = `

📄 ${fileName}

Hash list loaded from debridmediamanager.com

File Name

${fileName}

Status

Loaded

Source

DMM

📋 Hash List Data

This hash list contains encoded torrent data from debridmediamanager.com.

The data is encoded and requires their platform to decode and display properly.

To view the actual torrents:

  • Visit debridmediamanager.com
  • Use their platform to browse hash lists
  • The current hash list data is: ${hashMatch[1].substring(0, 100)}...
`; } catch (error) { console.error('Error loading simple view:', error); content.innerHTML = `

❌ Error Loading Hash List

Could not load or parse the hash list file.

Error: ${error.message}

The file might be using a format that requires the debridmediamanager.com platform.

`; } } function copyToClipboard(text) { navigator.clipboard.writeText(text).then(() => { showToast('Copied to clipboard!', 'success'); }).catch(() => { showToast('Could not copy to clipboard', 'error'); }); }