/** * Lycostorrent - Latest Releases * Page des nouveautés avec enrichissement TMDb/Last.fm */ // Variables globales let selectedCategory = 'movies'; let selectedTrackers = []; let availableTrackers = []; let allResults = []; let selectedYears = ['all']; // Par défaut: tous // Images par défaut en base64 (évite les problèmes d'échappement) const DEFAULT_POSTER_B64 = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyODAiIGhlaWdodD0iNDIwIj48cmVjdCB3aWR0aD0iMjgwIiBoZWlnaHQ9IjQyMCIgZmlsbD0iIzMzMyIvPjx0ZXh0IHg9IjE0MCIgeT0iMjAwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmaWxsPSIjNjY2IiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iNDAiPvCfjqw8L3RleHQ+PHRleHQgeD0iMTQwIiB5PSIyNDAiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZpbGw9IiM2NjYiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxNCI+Tm8gSW1hZ2U8L3RleHQ+PC9zdmc+'; const DEFAULT_COVER_B64 = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MDAiIGhlaWdodD0iNDAwIj48cmVjdCB3aWR0aD0iNDAwIiBoZWlnaHQ9IjQwMCIgZmlsbD0iIzMzMyIvPjx0ZXh0IHg9IjIwMCIgeT0iMTkwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmaWxsPSIjNjY2IiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iNjAiPvCfjrU8L3RleHQ+PHRleHQgeD0iMjAwIiB5PSIyNDAiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZpbGw9IiM2NjYiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxOCI+Tm8gQ292ZXI8L3RleHQ+PC9zdmc+'; const DEFAULT_BACKDROP_B64 = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iNDAwIj48cmVjdCB3aWR0aD0iOTAwIiBoZWlnaHQ9IjQwMCIgZmlsbD0iIzIyMiIvPjwvc3ZnPg=='; function getDefaultPosterUrl() { return DEFAULT_POSTER_B64; } function getDefaultCoverUrl() { return DEFAULT_COVER_B64; } function getDefaultBackdropUrl() { return DEFAULT_BACKDROP_B64; } // Initialisation document.addEventListener('DOMContentLoaded', function() { console.log('🚀 Page latest.js chargée'); initializeApp(); }); function initializeApp() { // Vérifier le client torrent en premier checkTorrentClient(); loadTrackers(); // Event listeners document.getElementById('toggleTrackers').addEventListener('click', toggleTrackersPanel); document.getElementById('selectAllTrackers').addEventListener('click', selectAllTrackers); document.getElementById('deselectAllTrackers').addEventListener('click', deselectAllTrackers); document.getElementById('loadLatestBtn').addEventListener('click', () => loadLatestReleases(true)); // Bouton refresh live (dans le header des résultats) document.getElementById('refreshLiveBtn')?.addEventListener('click', () => loadLatestReleases(true)); // Pastilles d'années document.querySelectorAll('.year-pill').forEach(pill => { pill.addEventListener('click', function() { handleYearPillClick(this); }); }); // Catégories document.querySelectorAll('.category-btn').forEach(btn => { btn.addEventListener('click', function() { selectCategory(this.dataset.category); }); }); // Modal document.querySelector('.modal-close').addEventListener('click', closeModal); document.getElementById('detailsModal').addEventListener('click', function(e) { if (e.target === this) closeModal(); }); // Gestion erreurs images document.addEventListener('error', function(e) { if (e.target.tagName === 'IMG') { const fallback = e.target.dataset.fallback; if (fallback === 'poster') e.target.src = getDefaultPosterUrl(); else if (fallback === 'cover') e.target.src = getDefaultCoverUrl(); else if (fallback === 'backdrop') e.target.src = getDefaultBackdropUrl(); } }, true); // Charger depuis le cache au démarrage (après chargement des trackers) setTimeout(() => { loadFromCacheOrLive(); }, 500); } // Gestion des pastilles d'années function handleYearPillClick(pill) { const year = pill.dataset.year; if (year === 'all') { // Clic sur "Tous" -> désactive tout le reste selectedYears = ['all']; document.querySelectorAll('.year-pill').forEach(p => p.classList.remove('active')); pill.classList.add('active'); } else { // Clic sur une année spécifique // Retirer "all" s'il était sélectionné if (selectedYears.includes('all')) { selectedYears = []; document.querySelector('.year-pill[data-year="all"]').classList.remove('active'); } // Toggle l'année cliquée if (selectedYears.includes(year)) { // Désactiver selectedYears = selectedYears.filter(y => y !== year); pill.classList.remove('active'); // Si plus rien de sélectionné, réactiver "Tous" if (selectedYears.length === 0) { selectedYears = ['all']; document.querySelector('.year-pill[data-year="all"]').classList.add('active'); } } else { // Activer selectedYears.push(year); pill.classList.add('active'); } } // Re-filtrer les résultats if (allResults.length > 0) { displayResults(allResults); } } // Chargement des trackers (inclut les RSS pour les nouveautés) async function loadTrackers() { try { showLoader(true); const response = await fetch('/api/trackers?include_rss=true'); const data = await response.json(); if (data.success) { availableTrackers = data.trackers; displayTrackers(availableTrackers); } else { showMessage('Erreur lors du chargement des trackers', 'error'); } } catch (error) { console.error('Erreur chargement trackers:', error); showMessage('Impossible de charger les trackers', 'error'); } finally { showLoader(false); } } function displayTrackers(trackers) { const trackersList = document.getElementById('trackersList'); if (trackers.length === 0) { trackersList.innerHTML = '
Aucun tracker configuré
'; return; } // Trackers sélectionnés par défaut const defaultTrackers = ['yggtorrent', 'sharewood-api']; trackersList.innerHTML = ''; trackers.forEach(tracker => { const trackerItem = document.createElement('div'); trackerItem.className = 'tracker-item'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.id = `tracker-${tracker.id}`; checkbox.value = tracker.id; checkbox.checked = defaultTrackers.includes(tracker.id.toLowerCase().replace(/\s+/g, '-')); checkbox.addEventListener('change', updateSelectedTrackers); const label = document.createElement('label'); label.htmlFor = `tracker-${tracker.id}`; label.textContent = tracker.name; // Badge de source let sourceBadge = ''; if (tracker.sources && tracker.sources.length > 0) { if (tracker.sources.includes('rss')) { sourceBadge = 'RSS'; } else if (tracker.sources.includes('jackett') && tracker.sources.includes('prowlarr')) { sourceBadge = 'J+P'; } else if (tracker.sources.includes('jackett')) { sourceBadge = 'J'; } else if (tracker.sources.includes('prowlarr')) { sourceBadge = 'P'; } } else if (tracker.source) { if (tracker.source === 'jackett') { sourceBadge = 'J'; } else if (tracker.source === 'prowlarr') { sourceBadge = 'P'; } } trackerItem.appendChild(checkbox); trackerItem.appendChild(label); if (sourceBadge) { const badgeSpan = document.createElement('span'); badgeSpan.innerHTML = sourceBadge; trackerItem.appendChild(badgeSpan.firstChild); } trackersList.appendChild(trackerItem); }); updateSelectedTrackers(); } function updateSelectedTrackers() { selectedTrackers = Array.from(document.querySelectorAll('#trackersList input[type="checkbox"]:checked')) .map(cb => cb.value); } function toggleTrackersPanel() { document.getElementById('trackersPanel').classList.toggle('hidden'); } function selectAllTrackers() { document.querySelectorAll('#trackersList input[type="checkbox"]').forEach(cb => cb.checked = true); updateSelectedTrackers(); } function deselectAllTrackers() { document.querySelectorAll('#trackersList input[type="checkbox"]').forEach(cb => cb.checked = false); updateSelectedTrackers(); } function selectCategory(category) { selectedCategory = category; document.querySelectorAll('.category-btn').forEach(btn => btn.classList.remove('active')); event.target.classList.add('active'); // Charger depuis le cache si disponible loadFromCacheOrLive(); } // Variable pour savoir si on utilise le cache let usingCache = false; // Vérifier et charger depuis le cache au démarrage async function loadFromCacheOrLive() { try { // Vérifier si le cache existe pour cette catégorie const response = await fetch(`/api/cache/data/latest/${selectedCategory}`); const data = await response.json(); if (data.success && data.cached && data.data && data.data.length > 0) { // Afficher les données du cache usingCache = true; allResults = data.data; displayResults(allResults); showCacheInfo(data.timestamp); console.log(`📦 Chargé depuis le cache: ${data.data.length} résultats`); } } catch (error) { console.log('Pas de cache disponible'); } } // Afficher les infos du cache function showCacheInfo(timestamp) { const cacheInfo = document.getElementById('cacheInfo'); const cacheTimestamp = 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`; } cacheTimestamp.textContent = timeAgo; cacheInfo.classList.remove('hidden'); } } // Masquer les infos du cache function hideCacheInfo() { const cacheInfo = document.getElementById('cacheInfo'); if (cacheInfo) { cacheInfo.classList.add('hidden'); } usingCache = false; } // Chargement des dernières sorties (en direct) async function loadLatestReleases(forceRefresh = true) { if (selectedTrackers.length === 0) { showMessage('Veuillez sélectionner au moins un tracker', 'error'); return; } const limit = parseInt(document.getElementById('limitSelect').value); try { showLoader(true); hideCacheInfo(); const response = await fetch('/api/latest', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ trackers: selectedTrackers, category: selectedCategory, limit: limit }) }); const data = await response.json(); if (data.success) { allResults = data.results; displayResults(allResults); if (allResults.length > 0) { showMessage(`${allResults.length} nouveautés trouvées`, 'success'); } else { showMessage('Aucune nouveauté trouvée', 'info'); } } else { showMessage(data.error || 'Erreur lors de la récupération', 'error'); } } catch (error) { console.error('Erreur:', error); showMessage('Erreur lors de la récupération des nouveautés', 'error'); } finally { showLoader(false); } } function displayResults(results) { const resultsSection = document.getElementById('latestResults'); const resultsGrid = document.getElementById('resultsGrid'); const resultsCount = document.getElementById('resultsCount'); const yearFiltersSection = document.getElementById('yearFilters'); const filterCountSpan = document.getElementById('filterCount'); // Afficher la section des filtres yearFiltersSection.classList.remove('hidden'); // Filtrer par années sélectionnées let filteredResults = results; if (!selectedYears.includes('all')) { filteredResults = results.filter(result => { const tmdb = result.tmdb || {}; const year = tmdb.year ? parseInt(tmdb.year) : null; // Si pas d'année TMDb, on garde le résultat (on ne peut pas filtrer) if (!year) return true; // Vérifier si l'année correspond à une des années sélectionnées for (const selectedYear of selectedYears) { if (selectedYear === 'old') { // ≤2022 if (year <= 2022) return true; } else { // Année spécifique if (year === parseInt(selectedYear)) return true; } } return false; }); } // Mettre à jour le compteur de filtre if (!selectedYears.includes('all')) { const yearsText = selectedYears.map(y => y === 'old' ? '≤2022' : y).join(', '); filterCountSpan.textContent = `(${filteredResults.length}/${results.length})`; } else { filterCountSpan.textContent = ''; } if (filteredResults.length === 0) { resultsSection.classList.remove('hidden'); resultsCount.textContent = `0 nouveauté (${results.length} total)`; resultsGrid.innerHTML = 'Aucun résultat pour les années sélectionnées
'; return; } resultsSection.classList.remove('hidden'); if (!selectedYears.includes('all')) { resultsCount.textContent = `${filteredResults.length} nouveauté${filteredResults.length > 1 ? 's' : ''} sur ${results.length}`; } else { resultsCount.textContent = `${filteredResults.length} nouveauté${filteredResults.length > 1 ? 's' : ''}`; } resultsGrid.innerHTML = ''; filteredResults.forEach(result => { const card = createCard(result); resultsGrid.appendChild(card); }); } function createCard(group) { const card = document.createElement('div'); card.className = 'release-card'; const mainTorrent = group.torrents[0]; const tmdb = group.tmdb || {}; const music = group.music || {}; const isMusic = group.is_music || false; const isAnime = group.is_anime || false; let title = tmdb.title || music.album || mainTorrent.Title || 'Sans titre'; let year = tmdb.year || ''; let overview = escapeHtml(tmdb.overview || ''); let posterUrl = sanitizeUrl(tmdb.poster_url || music.cover_url) || getDefaultPosterUrl(); let torrentUrl = sanitizeUrl(mainTorrent.Details || mainTorrent.Guid) || ''; let uniqueId = `result-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; let variantsCount = group.torrents.length; let contentType = '🎬'; if (isMusic && music.artist) { contentType = '🎵'; title = `${music.artist} - ${music.album}`; overview = ` Artiste: ${escapeHtml(music.artist)}${escapeHtml(artist)}
${hasLastFmData ? ` ` : ` `}${escapeHtml(originalTitle)}
` : ''}${escapeHtml(overview)}