const API = ''; const urlInput = document.getElementById('url-input'); const clearBtn = document.getElementById('clear-btn'); const fetchBtn = document.getElementById('fetch-btn'); const errorMsg = document.getElementById('error-msg'); const preview = document.getElementById('preview'); const thumbnail = document.getElementById('thumbnail'); const videoTitle = document.getElementById('video-title'); const uploaderEl = document.getElementById('uploader'); const durationEl = document.getElementById('duration'); const downloadBtn = document.getElementById('download-btn'); const progressWrap = document.getElementById('progress-wrap'); const progressFill = document.getElementById('progress-fill'); const progressLabel = document.getElementById('progress-label'); const cancelBtn = document.getElementById('cancel-btn'); const themeToggle = document.getElementById('theme-toggle'); themeToggle.addEventListener('click', () => { const next = document.documentElement.getAttribute('data-theme') === 'light' ? 'dark' : 'light'; document.documentElement.setAttribute('data-theme', next); localStorage.setItem('theme', next); }); let currentFileId = null; let currentAbort = null; let currentSse = null; cancelBtn.addEventListener('click', async (e) => { e.preventDefault(); e.stopPropagation(); if (currentAbort) { currentAbort.abort(); currentAbort = null; } if (currentSse) { currentSse.close(); currentSse = null; } if (currentFileId) { fetch(`${API}/cancel/${currentFileId}`, { method: 'POST' }).catch(() => {}); currentFileId = null; } progressWrap.style.display = 'none'; setProgress(0, ''); setLoading(downloadBtn, false); }); function resetUI() { urlInput.value = ''; clearBtn.style.display = 'none'; errorMsg.style.display = 'none'; preview.style.display = 'none'; downloadBtn.style.display = 'none'; progressWrap.style.display = 'none'; thumbnail.src = ''; } window.addEventListener('pageshow', resetUI); function showError(msg) { errorMsg.textContent = msg; errorMsg.style.display = 'block'; } function hideError() { errorMsg.style.display = 'none'; } function setLoading(btn, loading, text) { btn.disabled = loading; btn.style.opacity = loading ? '0.6' : '1'; btn.style.cursor = loading ? 'wait' : ''; if (text) { btn.dataset.label = btn.dataset.label || btn.textContent.trim(); } if (loading && text) { btn.textContent = text; } else if (!loading && btn.dataset.label) { if (btn.id === 'download-btn') { btn.innerHTML = ` Télécharger`; } else { btn.textContent = btn.dataset.label; } delete btn.dataset.label; } } function formatDuration(secs) { if (!secs) return null; const h = Math.floor(secs / 3600); const m = Math.floor((secs % 3600) / 60); const s = secs % 60; return h > 0 ? `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}` : `${m}:${String(s).padStart(2, '0')}`; } function formatCount(n) { if (n >= 1e9) return (n / 1e9).toFixed(1) + 'B'; if (n >= 1e6) return (n / 1e6).toFixed(1) + 'M'; if (n >= 1e3) return (n / 1e3).toFixed(1) + 'K'; return n.toString(); } function setProgress(pct, label) { progressFill.style.width = pct + '%'; progressLabel.innerHTML = label; } function formatEta(secs) { if (secs == null || secs < 0) return ''; if (secs < 60) return `Reste ${secs}s`; const m = Math.floor(secs / 60); const s = secs % 60; return `Reste ${m}m ${s}s`; } urlInput.addEventListener('input', () => { clearBtn.style.display = urlInput.value ? 'flex' : 'none'; }); clearBtn.addEventListener('click', () => { resetUI(); urlInput.focus(); }); urlInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') fetchBtn.click(); }); fetchBtn.addEventListener('click', async () => { const url = urlInput.value.trim(); if (!url) { showError('Veuillez d\'abord coller une URL de vidéo.'); return; } hideError(); preview.style.display = 'none'; downloadBtn.style.display = 'none'; progressWrap.style.display = 'none'; setLoading(fetchBtn, true, 'Recherche…'); try { const res = await fetch(`${API}/info`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url }), }); const data = await res.json(); if (!res.ok) { throw new Error(data.detail || 'Échec de la récupération des infos de la vidéo.'); } renderPreview(data); preview.style.display = 'flex'; downloadBtn.style.display = 'flex'; } catch (err) { showError(err.message || 'Échec de la récupération des infos de la vidéo.'); } finally { setLoading(fetchBtn, false); } }); function renderPreview(info) { thumbnail.src = info.thumbnail || ''; thumbnail.alt = info.title || 'Miniature de la vidéo'; thumbnail.style.display = info.thumbnail ? 'block' : 'none'; videoTitle.textContent = info.title || 'Titre inconnu'; uploaderEl.textContent = info.uploader || ''; uploaderEl.style.display = info.uploader ? '' : 'none'; const dur = formatDuration(info.duration); durationEl.textContent = dur || ''; durationEl.style.display = dur ? '' : 'none'; const viewsEl = document.getElementById('views'); const likesEl = document.getElementById('likes'); const dateEl = document.getElementById('upload-date'); viewsEl.textContent = info.view_count ? '👁 ' + formatCount(info.view_count) : ''; viewsEl.style.display = info.view_count ? '' : 'none'; likesEl.textContent = info.like_count ? '👍 ' + formatCount(info.like_count) : ''; likesEl.style.display = info.like_count ? '' : 'none'; if (info.upload_date && info.upload_date.length === 8) { const d = info.upload_date; const formatted = new Date( `${d.slice(0, 4)}-${d.slice(4, 6)}-${d.slice(6, 8)}` ).toLocaleDateString('fr-FR', { year: 'numeric', month: 'short', day: 'numeric', }); dateEl.textContent = '📅 ' + formatted; dateEl.style.display = ''; } else { dateEl.style.display = 'none'; } } downloadBtn.addEventListener('click', async () => { const url = urlInput.value.trim(); if (!url) return; hideError(); setLoading(downloadBtn, true, 'Téléchargement…'); progressWrap.style.display = 'block'; setProgress(0, 'Démarrage…'); const fileId = crypto.randomUUID(); currentFileId = fileId; const sse = new EventSource(`${API}/progress/${fileId}`); currentSse = sse; sse.onmessage = (e) => { const d = JSON.parse(e.data); if (d.status === 'starting') { setProgress(0, 'Démarrage…'); } else if (d.status === 'downloading') { const pct = d.percent || 0; const eta = d.eta != null ? formatEta(d.eta) : ''; const speed = d.speed != null ? `${d.speed} MB/s` : ''; const meta = [speed, eta].filter(Boolean).join(' · '); setProgress( pct, `${pct}%${meta ? ` ${meta}` : ''}` ); } else if (d.status === 'finished') { setProgress( 100, '100%Traitement…' ); } else if (d.status === 'ready') { sse.close(); currentSse = null; currentAbort = null; if (currentFileId === fileId) { setProgress( 100, 'Terminé — enregistrement dans vos téléchargements' ); window.location.href = `${API}/file/${fileId}`; setTimeout(() => { progressWrap.style.display = 'none'; }, 2500); } currentFileId = null; setLoading(downloadBtn, false); } else if (d.status === 'cancelled') { sse.close(); currentSse = null; currentAbort = null; progressWrap.style.display = 'none'; currentFileId = null; setLoading(downloadBtn, false); } else if (d.status === 'error') { sse.close(); currentSse = null; currentAbort = null; showError(d.error || 'Échec du téléchargement.'); progressWrap.style.display = 'none'; currentFileId = null; setLoading(downloadBtn, false); } }; sse.onerror = () => { sse.close(); currentSse = null; }; const abort = new AbortController(); currentAbort = abort; try { const res = await fetch(`${API}/download`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url, file_id: fileId, }), signal: abort.signal, }); const data = await res.json().catch(() => ({})); if (!res.ok) { throw new Error(data.detail || 'Échec du téléchargement.'); } if (data.status !== 'started') { throw new Error('Réponse inattendue du serveur.'); } } catch (err) { if (currentSse) { currentSse.close(); currentSse = null; } currentAbort = null; if (err.name !== 'AbortError') { showError(err.message || 'Échec du téléchargement.'); progressWrap.style.display = 'none'; } if (currentFileId === fileId) { currentFileId = null; } setLoading(downloadBtn, false); } });