/** * 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)}
Album: ${escapeHtml(music.album)}
${music.tags?.length ? `Genres: ${escapeHtml(music.tags.join(', '))}` : ''} `; } else if (isAnime) { contentType = '🎌'; } else if (tmdb.type === 'tv') { contentType = '📺'; } card.innerHTML = `
${escapeHtml(title)}
${contentType} ${isMusic ? 'Musique' : (isAnime ? 'Anime' : (tmdb.type === 'tv' ? 'Série' : 'Film'))}
${!isMusic && tmdb.vote_average ? `
⭐ ${tmdb.vote_average.toFixed(1)}
` : ''} ${isMusic && music.listeners ? `
👥 ${formatNumber(music.listeners)}
` : ''} ${variantsCount > 1 ? `
📦 ${variantsCount} versions
` : ''}
🌱 ${mainTorrent.Seeders || 0}
${escapeHtml(title)}
${year} ${escapeHtml(mainTorrent.Tracker)} 🔗
${overview}
${torrentUrl ? `🔗` : ''} ${mainTorrent.MagnetUri ? `🧲` : ''}
`; card.dataset.resultId = uniqueId; card.dataset.resultData = JSON.stringify(group); card.querySelector('.btn-details').addEventListener('click', function(e) { e.preventDefault(); showDetails(this.getAttribute('data-result-id')); }); return card; } function showDetails(resultId) { const card = document.querySelector(`[data-result-id="${resultId}"]`); if (!card) return; const group = JSON.parse(card.dataset.resultData); const isMusic = group.is_music || false; const isAnime = group.is_anime || false; const modal = document.getElementById('detailsModal'); const modalBody = document.getElementById('modalBody'); if (isMusic) { showMusicDetails(group, modalBody); } else { showVideoDetails(group, modalBody, isAnime); } modal.classList.remove('hidden'); } function showMusicDetails(group, modalBody) { const mainTorrent = group.torrents[0]; const music = group.music || {}; const coverUrl = sanitizeUrl(music.cover_url) || ''; const artist = music.artist || mainTorrent.Title?.split(' - ')[0] || 'Artiste inconnu'; const album = music.album || mainTorrent.Title?.split(' - ')[1] || mainTorrent.Title || 'Album inconnu'; const listeners = formatNumber(music.listeners || 0); const playcount = formatNumber(music.playcount || 0); const tags = music.tags || []; const url = music.url || ''; // Vérifier si on a des infos Last.fm const hasLastFmData = music.artist && music.album; // Si pas de cover, utiliser un placeholder const displayCover = coverUrl || 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MDAiIGhlaWdodD0iNDAwIj48cmVjdCB3aWR0aD0iNDAwIiBoZWlnaHQ9IjQwMCIgZmlsbD0iIzMzMyIvPjx0ZXh0IHg9IjIwMCIgeT0iMTkwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmaWxsPSIjNjY2IiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iNjAiPvCfjrU8L3RleHQ+PHRleHQgeD0iMjAwIiB5PSIyNDAiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZpbGw9IiM2NjYiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxOCI+Tm8gQ292ZXI8L3RleHQ+PC9zdmc+'; modalBody.innerHTML = ` `; } function showVideoDetails(group, modalBody, isAnime) { const mainTorrent = group.torrents[0]; const tmdb = group.tmdb || {}; const backdropUrl = tmdb.backdrop_url || tmdb.poster_url || getDefaultBackdropUrl(); const title = tmdb.title || mainTorrent.Title; const originalTitle = tmdb.original_title || ''; const overview = tmdb.overview || 'Synopsis non disponible'; const year = tmdb.year || ''; const rating = tmdb.vote_average ? tmdb.vote_average.toFixed(1) : 'N/A'; const trailerUrl = tmdb.trailer_url || ''; let youtubeId = ''; if (trailerUrl) { const match = trailerUrl.match(/[?&]v=([^&]+)/); youtubeId = match ? match[1] : ''; } let modalType = isAnime ? '🎌 Anime' : (tmdb.type === 'tv' ? '📺 Série' : '🎬 Film'); modalBody.innerHTML = ` `; } function createTorrentsTable(torrents, isMusic) { // Sur mobile on utilise la même structure que discover // Sur desktop on garde la table pour l'alignement // Version avec divs (comme discover) - fonctionne partout let html = `
`; torrents.forEach((torrent, index) => { const quality = extractQuality(torrent.Title); const language = extractLanguage(torrent.Title); const torrentUrl = torrent.Details || torrent.Guid || ''; html += `
${torrentUrl ? `${escapeHtml(torrent.Title)}` : escapeHtml(torrent.Title) }
📡 ${escapeHtml(torrent.Tracker)} 💾 ${torrent.SizeFormatted || 'N/A'} 🌱 ${torrent.Seeders || 0} ${quality ? `${quality}` : ''} ${language ? `${language}` : ''} ${index === 0 ? '👑 Meilleur' : ''}
${torrentUrl ? `🔗` : ''} ${torrent.MagnetUri ? `🧲` : ''} ${torrent.Link ? `⬇️` : ''} ${torrentClientEnabled && (torrent.MagnetUri || (torrentClientSupportsTorrentFiles && torrent.Link)) ? `` : ''}
`; }); html += `
`; return html; } function extractQuality(title) { const qualities = ['2160p', '4K', '1080p', '720p', '480p']; for (const q of qualities) { if (title.toLowerCase().includes(q.toLowerCase())) return q; } return null; } function extractLanguage(title) { const languages = { 'FRENCH': 'VF', 'TRUEFRENCH': 'VFF', 'VFF': 'VFF', 'VOSTFR': 'VOSTFR', 'MULTI': 'MULTI' }; const upper = title.toUpperCase(); for (const [key, val] of Object.entries(languages)) { if (upper.includes(key)) return val; } return null; } function formatNumber(num) { if (!num) return '0'; if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M'; if (num >= 1000) return (num / 1000).toFixed(1) + 'K'; return num.toString(); } function closeModal() { document.getElementById('detailsModal').classList.add('hidden'); } function showLoader(show) { document.getElementById('loader').classList.toggle('hidden', !show); } function showMessage(message, type = 'info') { const messageBox = document.getElementById('messageBox'); messageBox.textContent = message; messageBox.className = `message-box ${type}`; messageBox.classList.remove('hidden'); setTimeout(() => messageBox.classList.add('hidden'), 4000); } function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function sanitizeUrl(url) { if (!url) return ''; // Autoriser uniquement http, https, et magnet const allowedProtocols = ['http:', 'https:', 'magnet:']; try { // Pour les URLs magnet, vérifier le préfixe if (url.startsWith('magnet:')) { return url; } const parsed = new URL(url); if (!allowedProtocols.includes(parsed.protocol)) { console.warn('URL avec protocole non autorisé:', parsed.protocol); return ''; } return url; } catch (e) { // Si ce n'est pas une URL valide, retourner vide console.warn('URL invalide:', url); return ''; } } // ============================================================ // CLIENT TORRENT // ============================================================ let torrentClientEnabled = false; let torrentClientSupportsTorrentFiles = false; async function checkTorrentClient() { try { const response = await fetch('/api/torrent-client/status'); const data = await response.json(); torrentClientEnabled = data.success && data.enabled && data.connected; // Par défaut true si non spécifié (qBittorrent supporte les .torrent) torrentClientSupportsTorrentFiles = data.supportsTorrentFiles !== false; console.log('🔌 Client torrent:', torrentClientEnabled ? 'connecté' : 'non connecté', '| Supporte .torrent:', torrentClientSupportsTorrentFiles); } catch (error) { torrentClientEnabled = false; torrentClientSupportsTorrentFiles = false; console.log('🔌 Client torrent: erreur de connexion'); } } async function sendToTorrentClient(url, button) { if (!url) { showMessage('Aucun lien disponible', 'error'); return; } // Afficher le modal de sélection showTorrentOptionsModal(url, button); } async function showTorrentOptionsModal(url, button) { // Créer le modal s'il n'existe pas 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); // Fermer en cliquant à l'extérieur modal.addEventListener('click', (e) => { if (e.target === modal) closeTorrentOptionsModal(); }); } // Charger les catégories 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 += ``; }); // Stocker les chemins personnalisés categoriesWithPaths = data.custom_categories || {}; } } catch (error) { categorySelect.innerHTML = ''; } // Auto-remplir le chemin quand on sélectionne une catégorie categorySelect.onchange = () => { const selectedCat = categorySelect.value; if (selectedCat && categoriesWithPaths[selectedCat]) { savePathInput.value = categoriesWithPaths[selectedCat]; } else { savePathInput.value = ''; } }; // Reset les champs savePathInput.value = ''; document.getElementById('torrentPaused').checked = false; // Configurer le bouton de confirmation 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); }; // Afficher le modal 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) { const originalText = button.textContent; 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) { button.textContent = '✅'; showMessage('Torrent envoyé !', 'success'); setTimeout(() => { button.textContent = '📥'; button.disabled = false; }, 2000); } else { button.textContent = '❌'; showMessage(data.error || 'Erreur', 'error'); setTimeout(() => { button.textContent = '📥'; button.disabled = false; }, 2000); } } catch (error) { button.textContent = '❌'; showMessage('Erreur de connexion', 'error'); setTimeout(() => { button.textContent = '📥'; button.disabled = false; }, 2000); } } // Vérifier le client torrent au chargement checkTorrentClient();