/** * Lycostorrent - Page Découvrir (Version simplifiée) * 2 catégories : Films récents / Séries en cours * Avec pré-cache des torrents */ // ============================================================ // ÉTAT GLOBAL // ============================================================ let currentCategory = 'movies'; let currentMedia = null; let torrentClientEnabled = false; let cachedData = {}; // Cache local des données // ============================================================ // INITIALISATION // ============================================================ document.addEventListener('DOMContentLoaded', () => { initTabs(); checkTorrentClient(); loadCategory('movies'); }); function initTabs() { document.querySelectorAll('.discover-tab').forEach(tab => { tab.addEventListener('click', () => { const category = tab.dataset.category; // Mettre à jour l'UI document.querySelectorAll('.discover-tab').forEach(t => t.classList.remove('active')); tab.classList.add('active'); // Charger la catégorie loadCategory(category); }); }); } async function checkTorrentClient() { try { const response = await fetch('/api/torrent-client/status'); const data = await response.json(); torrentClientEnabled = data.success && data.enabled && data.connected; } catch (error) { torrentClientEnabled = false; } } // ============================================================ // CHARGEMENT DES DONNÉES // ============================================================ async function loadCategory(category) { currentCategory = category; const grid = document.getElementById('discoverGrid'); const loader = document.getElementById('discoverLoader'); const empty = document.getElementById('discoverEmpty'); // Afficher le loader grid.innerHTML = ''; loader.classList.remove('hidden'); empty.classList.add('hidden'); hideCacheInfo(); // Essayer de charger depuis le cache d'abord try { const cacheResponse = await fetch(`/api/cache/data/discover/${category}`); const cacheData = await cacheResponse.json(); if (cacheData.success && cacheData.cached && cacheData.data && cacheData.data.length > 0) { loader.classList.add('hidden'); cachedData[category] = cacheData.data; const mediaType = category === 'movies' ? 'movie' : 'tv'; renderGrid(cacheData.data, mediaType, true); showCacheInfo(cacheData.timestamp); console.log(`📦 Discover ${category} chargé depuis le cache: ${cacheData.data.length} résultats`); return; } } catch (error) { console.log('Pas de cache disponible, chargement en direct...'); } // Si pas de cache, charger en direct await loadCategoryLive(category); } async function loadCategoryLive(category) { const grid = document.getElementById('discoverGrid'); const loader = document.getElementById('discoverLoader'); const empty = document.getElementById('discoverEmpty'); grid.innerHTML = ''; loader.classList.remove('hidden'); empty.classList.add('hidden'); hideCacheInfo(); try { const response = await fetch(`/api/discover/${category}`); const data = await response.json(); loader.classList.add('hidden'); if (data.success && data.results && data.results.length > 0) { cachedData[category] = data.results; renderGrid(data.results, data.media_type, false); } else { empty.classList.remove('hidden'); empty.querySelector('p').textContent = data.error || 'Aucun résultat trouvé'; } } catch (error) { loader.classList.add('hidden'); empty.classList.remove('hidden'); empty.querySelector('p').textContent = 'Erreur de chargement'; console.error('Erreur:', error); } } function renderGrid(results, mediaType, fromCache) { const grid = document.getElementById('discoverGrid'); grid.innerHTML = results.map((item, index) => { const posterUrl = item.poster_path ? `https://image.tmdb.org/t/p/w300${item.poster_path}` : null; const title = item.title || item.name; const year = (item.release_date || item.first_air_date || '').substring(0, 4); const rating = item.vote_average ? item.vote_average.toFixed(1) : '--'; const type = mediaType === 'movie' ? '🎬' : '📺'; // Indicateur de torrents disponibles (si depuis le cache) const torrentCount = item.torrent_count || 0; const torrentBadge = fromCache && torrentCount > 0 ? `🧲 ${torrentCount}` : ''; return `
${posterUrl ? `${escapeHtml(title)}` : `
${type}
` } ⭐ ${rating} ${torrentBadge}
${escapeHtml(title)}
${year || 'N/A'} ${type}
`; }).join(''); } // Afficher les infos du cache function showCacheInfo(timestamp) { const cacheInfo = document.getElementById('cacheInfo'); const cacheTimestampEl = document.getElementById('cacheTimestamp'); if (cacheInfo && timestamp) { const date = new Date(timestamp); const now = new Date(); const diffMinutes = Math.floor((now - date) / 60000); let timeAgo; if (diffMinutes < 1) { timeAgo = "à l'instant"; } else if (diffMinutes < 60) { timeAgo = `il y a ${diffMinutes} min`; } else { const hours = Math.floor(diffMinutes / 60); timeAgo = `il y a ${hours}h`; } cacheTimestampEl.textContent = timeAgo; cacheInfo.classList.remove('hidden'); } } function hideCacheInfo() { const cacheInfo = document.getElementById('cacheInfo'); if (cacheInfo) { cacheInfo.classList.add('hidden'); } } function refreshLive() { loadCategoryLive(currentCategory); } // ============================================================ // MODAL DÉTAILS // ============================================================ async function openDetail(id, mediaType, index) { const modal = document.getElementById('detailModal'); const listEl = document.getElementById('torrentsList'); const loadingEl = document.getElementById('torrentsLoading'); const emptyEl = document.getElementById('torrentsEmpty'); // Réinitialiser listEl.innerHTML = ''; loadingEl.classList.add('hidden'); emptyEl.classList.add('hidden'); modal.classList.remove('hidden'); // Vérifier si on a les données en cache local (avec détails + torrents pré-chargés) const category = mediaType === 'movie' ? 'movies' : 'tv'; const cachedItem = cachedData[category] ? cachedData[category][index] : null; // Si les détails sont pré-cachés, on les utilise directement (INSTANTANÉ) if (cachedItem && cachedItem.details_cached) { console.log(`📦 Détails + torrents depuis le cache pour: ${cachedItem.title || cachedItem.name}`); currentMedia = cachedItem; currentMedia.media_type = mediaType; // Afficher les détails depuis le cache renderDetailFromCache(cachedItem, mediaType); // Afficher les torrents depuis le cache if (cachedItem.torrents && cachedItem.torrents.length > 0) { renderTorrents(cachedItem.torrents); } else { emptyEl.classList.remove('hidden'); } return; } // Sinon, fallback : charger depuis l'API (pour les items sans cache) try { const response = await fetch(`/api/discover/detail/${mediaType}/${id}`); const data = await response.json(); if (data.success) { currentMedia = data.detail; currentMedia.media_type = mediaType; renderDetail(data.detail, mediaType); // Si on a des torrents pré-cachés, les afficher if (cachedItem && cachedItem.torrents && cachedItem.torrents.length > 0) { renderTorrents(cachedItem.torrents); } else { // Sinon, rechercher en direct searchTorrents(data.detail, mediaType); } } else { closeDetailModal(); alert('Erreur lors du chargement des détails'); } } catch (error) { console.error('Erreur:', error); closeDetailModal(); } } // Afficher les détails depuis le cache (nouvelle fonction) function renderDetailFromCache(item, mediaType) { const title = item.title || item.name; const year = (item.release_date || item.first_air_date || '').substring(0, 4); const posterUrl = item.poster_path ? `https://image.tmdb.org/t/p/w300${item.poster_path}` : '/static/icons/icon-192x192.png'; document.getElementById('detailPoster').src = posterUrl; document.getElementById('detailPoster').alt = title; document.getElementById('detailTitle').textContent = title; document.getElementById('detailYear').textContent = year; document.getElementById('detailRating').textContent = `⭐ ${item.vote_average ? item.vote_average.toFixed(1) : '--'}`; document.getElementById('detailOverview').textContent = item.overview || 'Aucune description disponible.'; // Genres const genresContainer = document.getElementById('detailGenres'); if (item.genres && item.genres.length > 0) { genresContainer.innerHTML = item.genres.map(g => `${g.name}`).join(''); } else { genresContainer.innerHTML = ''; } // Bande-annonce YouTube const trailerSection = document.getElementById('detailTrailer'); const trailerFrame = document.getElementById('trailerFrame'); if (item.trailer_url) { trailerFrame.src = item.trailer_url; trailerSection.classList.remove('hidden'); } else { trailerFrame.src = ''; trailerSection.classList.add('hidden'); } } function renderDetail(detail, mediaType) { const title = detail.title || detail.name; const year = (detail.release_date || detail.first_air_date || '').substring(0, 4); const posterUrl = detail.poster_path ? `https://image.tmdb.org/t/p/w300${detail.poster_path}` : '/static/icons/icon-192x192.png'; document.getElementById('detailPoster').src = posterUrl; document.getElementById('detailPoster').alt = title; document.getElementById('detailTitle').textContent = title; document.getElementById('detailYear').textContent = year; document.getElementById('detailRating').textContent = `⭐ ${detail.vote_average ? detail.vote_average.toFixed(1) : '--'}`; document.getElementById('detailOverview').textContent = detail.overview || 'Aucune description disponible.'; // Genres const genresContainer = document.getElementById('detailGenres'); if (detail.genres && detail.genres.length > 0) { genresContainer.innerHTML = detail.genres.map(g => `${g.name}`).join(''); } else { genresContainer.innerHTML = ''; } // Bande-annonce YouTube const trailerSection = document.getElementById('detailTrailer'); const trailerFrame = document.getElementById('trailerFrame'); if (detail.trailer_url) { trailerFrame.src = detail.trailer_url; trailerSection.classList.remove('hidden'); } else { trailerFrame.src = ''; trailerSection.classList.add('hidden'); } } function closeDetailModal() { document.getElementById('detailModal').classList.add('hidden'); // Arrêter la vidéo YouTube document.getElementById('trailerFrame').src = ''; currentMedia = null; } // Fermer le modal en cliquant à l'extérieur document.getElementById('detailModal')?.addEventListener('click', (e) => { if (e.target.id === 'detailModal') { closeDetailModal(); } }); // Fermer avec Escape document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { closeDetailModal(); } }); // ============================================================ // RECHERCHE DE TORRENTS (fallback si pas en cache) // ============================================================ async function searchTorrents(detail, mediaType) { const loadingEl = document.getElementById('torrentsLoading'); const listEl = document.getElementById('torrentsList'); const emptyEl = document.getElementById('torrentsEmpty'); loadingEl.classList.remove('hidden'); listEl.innerHTML = ''; emptyEl.classList.add('hidden'); const title = detail.title || detail.name; const originalTitle = detail.original_title || detail.original_name || ''; const year = (detail.release_date || detail.first_air_date || '').substring(0, 4); try { const response = await fetch('/api/discover/search-torrents', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: title, original_title: originalTitle, year: year, media_type: mediaType, tmdb_id: detail.id }) }); const data = await response.json(); loadingEl.classList.add('hidden'); if (data.success && data.results && data.results.length > 0) { renderTorrents(data.results); } else { emptyEl.classList.remove('hidden'); } } catch (error) { loadingEl.classList.add('hidden'); emptyEl.classList.remove('hidden'); console.error('Erreur recherche torrents:', error); } } function renderTorrents(torrents) { const listEl = document.getElementById('torrentsList'); listEl.innerHTML = torrents.slice(0, 20).map((torrent, index) => { const size = torrent.Size ? formatSize(torrent.Size) : 'N/A'; const seeds = torrent.Seeders || 0; const quality = torrent.parsed?.quality || ''; const tracker = torrent.Tracker || torrent.TrackerName || 'Unknown'; const magnetUrl = torrent.MagnetUri || ''; const downloadUrl = torrent.Link || ''; const detailsUrl = torrent.Details || torrent.Guid || ''; const torrentUrl = magnetUrl || downloadUrl; return `
${escapeHtml(torrent.Title)}
📡 ${escapeHtml(tracker)} 💾 ${size} 🌱 ${seeds} ${quality ? `${escapeHtml(quality)}` : ''}
${detailsUrl ? `🔗` : ''} ${magnetUrl ? `🧲` : ''} ${downloadUrl ? `⬇️` : ''} ${torrentClientEnabled && torrentUrl ? `` : ''}
`; }).join(''); } function handleSendToClient(url, buttonId) { const button = document.getElementById(buttonId); sendToClient(url, button); } // ============================================================ // ENVOI AU CLIENT TORRENT // ============================================================ async function sendToClient(url, buttonElement) { if (!url) return; showTorrentOptionsModal(url, buttonElement); } async function showTorrentOptionsModal(url, button) { let modal = document.getElementById('torrentOptionsModal'); if (!modal) { modal = document.createElement('div'); modal.id = 'torrentOptionsModal'; modal.className = 'torrent-options-modal'; modal.innerHTML = `

📥 Options de téléchargement

`; document.body.appendChild(modal); modal.addEventListener('click', (e) => { if (e.target === modal) closeTorrentOptionsModal(); }); } const categorySelect = document.getElementById('torrentCategory'); const savePathInput = document.getElementById('torrentSavePath'); categorySelect.innerHTML = ''; let categoriesWithPaths = {}; try { const response = await fetch('/api/torrent-client/categories'); const data = await response.json(); categorySelect.innerHTML = ''; if (data.success && data.categories) { data.categories.forEach(cat => { categorySelect.innerHTML += ``; }); categoriesWithPaths = data.custom_categories || {}; } } catch (error) { categorySelect.innerHTML = ''; } categorySelect.onchange = () => { const selectedCat = categorySelect.value; if (selectedCat && categoriesWithPaths[selectedCat]) { savePathInput.value = categoriesWithPaths[selectedCat]; } else { savePathInput.value = ''; } }; savePathInput.value = ''; document.getElementById('torrentPaused').checked = false; const confirmBtn = document.getElementById('confirmTorrentAdd'); confirmBtn.onclick = async () => { const category = document.getElementById('torrentCategory').value; const savePath = document.getElementById('torrentSavePath').value.trim(); const paused = document.getElementById('torrentPaused').checked; closeTorrentOptionsModal(); await doSendToTorrentClient(url, button, category, savePath, paused); }; modal.classList.add('visible'); } function closeTorrentOptionsModal() { const modal = document.getElementById('torrentOptionsModal'); if (modal) { modal.classList.remove('visible'); } } async function doSendToTorrentClient(url, button, category, savePath, paused) { if (button) { button.textContent = '⏳'; button.disabled = true; } try { const body = { url: url }; if (category) body.category = category; if (savePath) body.save_path = savePath; if (paused) body.paused = paused; const response = await fetch('/api/torrent-client/add', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); const data = await response.json(); if (data.success) { if (button) { button.textContent = '✅'; setTimeout(() => { button.textContent = '📥'; button.disabled = false; }, 2000); } showToast('Torrent envoyé !', 'success'); } else { if (button) { button.textContent = '❌'; setTimeout(() => { button.textContent = '📥'; button.disabled = false; }, 2000); } showToast(data.error || 'Erreur', 'error'); } } catch (error) { if (button) { button.textContent = '❌'; setTimeout(() => { button.textContent = '📥'; button.disabled = false; }, 2000); } showToast('Erreur de connexion', 'error'); } } // ============================================================ // UTILITAIRES // ============================================================ function formatSize(bytes) { if (!bytes) return 'N/A'; const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(1024)); return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + sizes[i]; } function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function showToast(message, type = 'info') { let toast = document.getElementById('toast'); if (!toast) { toast = document.createElement('div'); toast.id = 'toast'; toast.className = 'toast'; document.body.appendChild(toast); } toast.textContent = message; toast.className = `toast ${type}`; toast.classList.remove('hidden'); setTimeout(() => { toast.classList.add('hidden'); }, 3000); }