document.addEventListener("DOMContentLoaded", () => { const downloadForm = document.getElementById("downloadForm"); const urlInput = document.getElementById("urlInput"); const syncLikesBtn = document.getElementById("syncLikesBtn"); const tasksList = document.getElementById("tasksList"); const historyBody = document.getElementById("historyBody"); const tracksCount = document.getElementById("tracksCount"); // Search elements const searchCard = document.getElementById("searchCard"); const searchQueryLabel = document.getElementById("searchQueryLabel"); const searchLoading = document.getElementById("searchLoading"); const searchTracksList = document.getElementById("searchTracksList"); const searchAlbumsList = document.getElementById("searchAlbumsList"); const closeSearchBtn = document.getElementById("closeSearchBtn"); // Load history and active tasks fetchHistory(); fetchTasks(); setInterval(fetchTasks, 2000); setInterval(fetchHistory, 10000); // Form submit downloadForm.addEventListener("submit", async (e) => { e.preventDefault(); const val = urlInput.value.trim(); if (!val) return; // Check if it's a direct URL if (val.startsWith("http://") || val.startsWith("https://")) { await startDownload(val); urlInput.value = ""; } else { // It's a search query! await performSearch(val); } }); // Close search closeSearchBtn.addEventListener("click", () => { searchCard.style.display = "none"; }); // Perform search async function performSearch(query) { searchCard.style.display = "block"; searchQueryLabel.textContent = `«${query}»`; searchLoading.style.display = "block"; searchTracksList.innerHTML = ""; searchAlbumsList.innerHTML = ""; try { const res = await fetch(`api/search?q=${encodeURIComponent(query)}`); const data = await res.json(); searchLoading.style.display = "none"; renderSearchResults(data); } catch (err) { searchLoading.style.display = "none"; searchTracksList.innerHTML = `

Ошибка поиска: ${err}

`; } } function renderSearchResults(data) { const tracks = data.tracks || []; const albums = data.albums || []; // Tracks if (tracks.length === 0) { searchTracksList.innerHTML = `

Треков не найдено

`; } else { searchTracksList.innerHTML = tracks.map(t => { const explicitBadge = t.explicit_status === 'explicit' ? `🔞 18+ Explicit` : (t.explicit_status === 'clean' ? `⚠️ Clean (Цензура)` : ''); const versionBadge = t.version ? `${escapeHtml(t.version)}` : ''; return `
${escapeHtml(t.title)} ${explicitBadge} ${versionBadge}
${t.service_name} • ${escapeHtml(t.artist)} • ${escapeHtml(t.album || 'Single')} (${t.duration})
💾 Скачать
`; }).join(""); } // Albums if (albums.length === 0) { document.getElementById("searchAlbumsSection").style.display = "none"; } else { document.getElementById("searchAlbumsSection").style.display = "block"; searchAlbumsList.innerHTML = albums.map(a => `
${escapeHtml(a.title)} ${a.service_name} • ${a.year || ''} • ${a.track_count ? a.track_count + ' треков' : ''}
`).join(""); } } // Audio Preview Player let currentPlayingUrl = null; window.playPreviewTrack = function(url, title, artist, cover, explicitStatus, previewUrl) { const bar = document.getElementById("audioPlayerBar"); const audio = document.getElementById("globalAudioPlayer"); const playerCover = document.getElementById("playerCover"); const playerTitle = document.getElementById("playerTitle"); const playerArtist = document.getElementById("playerArtist"); const playerBadge = document.getElementById("playerExplicitBadge"); const playerImportBtn = document.getElementById("playerImportBtn"); const playerDownloadBtn = document.getElementById("playerDownloadBtn"); if (currentPlayingUrl === url && !audio.paused) { audio.pause(); return; } currentPlayingUrl = url; playerCover.src = cover || "static/placeholder.png"; playerTitle.textContent = title; playerArtist.textContent = artist; if (explicitStatus === "explicit") { playerBadge.className = "badge badge-explicit"; playerBadge.textContent = "🔞 18+ Explicit (Без цензуры)"; playerBadge.style.display = "inline-flex"; } else if (explicitStatus === "clean") { playerBadge.className = "badge badge-clean"; playerBadge.textContent = "⚠️ Clean (С цензурой)"; playerBadge.style.display = "inline-flex"; } else { playerBadge.style.display = "none"; } playerImportBtn.onclick = () => handleImport(url); playerDownloadBtn.onclick = () => { window.location.href = "api/direct-download?url=" + encodeURIComponent(url); }; bar.style.display = "flex"; // Choose source: Apple 30s direct preview or server stream with HTTP Range const streamSrc = previewUrl || ("api/stream?url=" + encodeURIComponent(url)); audio.src = streamSrc; audio.play().catch(e => console.warn("Playback error:", e)); }; const playerCloseBtn = document.getElementById("playerCloseBtn"); if (playerCloseBtn) { playerCloseBtn.addEventListener("click", () => { const audio = document.getElementById("globalAudioPlayer"); audio.pause(); audio.src = ""; currentPlayingUrl = null; document.getElementById("audioPlayerBar").style.display = "none"; }); } // Global handler for import button window.handleImport = async function(url) { await startDownload(url); }; async function startDownload(url) { try { const res = await fetch("api/download", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ url }) }); const data = await res.json(); if (data.task_id) { fetchTasks(); window.scrollTo({ top: tasksList.offsetTop - 100, behavior: "smooth" }); } else { alert(data.error || "Ошибка запуска загрузки"); } } catch (err) { alert("Ошибка сети: " + err); } } // Sync likes button syncLikesBtn.addEventListener("click", async () => { if (!confirm("Запустить синхронизацию плейлиста «Мне нравится» из Яндекс Музыки?")) return; try { const res = await fetch("api/sync/yandex-likes", { method: "POST" }); const data = await res.json(); if (data.task_id) { fetchTasks(); } } catch (err) { alert("Ошибка запуска синхронизации: " + err); } }); async function fetchTasks() { try { const res = await fetch("api/tasks"); const tasks = await res.json(); renderTasks(tasks); } catch (err) { console.error(err); } } function renderTasks(tasks) { if (!tasks || tasks.length === 0) { tasksList.innerHTML = `

Нет активных загрузок

`; return; } tasksList.innerHTML = tasks.map(t => { const percent = t.total > 0 ? Math.round((t.progress / t.total) * 100) : 0; return `
${escapeHtml(t.target)} ${t.status}
${t.progress}/${t.total} (${percent}%) • ${escapeHtml(t.current_item || '')}
`; }).join(""); } async function fetchHistory() { try { const res = await fetch("api/history"); const data = await res.json(); if (tracksCount) tracksCount.textContent = `Загружено: ${data.total || 0}`; renderHistory(data.tracks || []); } catch (err) { console.error(err); } } function renderHistory(tracks) { if (!tracks || tracks.length === 0) { historyBody.innerHTML = `Пока нет загрузок`; return; } historyBody.innerHTML = tracks.map(t => ` ${t.service} ${escapeHtml(t.artist)} ${escapeHtml(t.title)} ${escapeHtml(t.album || 'Singles')} ${formatDate(t.downloaded_at)} `).join(""); } function escapeHtml(str) { if (!str) return ""; return str.replace(/&/g, "&").replace(//g, ">"); } function escapeAttr(str) { if (!str) return ""; return str.replace(/'/g, "\\'").replace(/"/g, """); } function formatDate(isoStr) { if (!isoStr) return ""; const d = new Date(isoStr); return d.toLocaleDateString() + " " + d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } // --- Smart Wave Logic --- const waveCountLabel = document.getElementById("waveCountLabel"); const waveSizeLabel = document.getElementById("waveSizeLabel"); const waveAnchorsContainer = document.getElementById("waveAnchorsContainer"); const waveAnchorsList = document.getElementById("waveAnchorsList"); const waveTracksList = document.getElementById("waveTracksList"); const waveLoadingText = document.getElementById("waveLoadingText"); const waveGenerateBtn = document.getElementById("waveGenerateBtn"); const waveRotateBtn = document.getElementById("waveRotateBtn"); const waveMoodSelect = document.getElementById("waveMoodSelect"); const waveContextLabel = document.getElementById("waveContextLabel"); fetchWaveStatus(); if (waveGenerateBtn) { waveGenerateBtn.addEventListener("click", async () => { waveGenerateBtn.disabled = true; const mood = waveMoodSelect ? waveMoodSelect.value : "auto"; if (waveLoadingText) { waveLoadingText.innerHTML = ` Qwen 2.5 отбирает треки под вайб...`; waveLoadingText.style.display = "inline-block"; } try { const res = await fetch("api/wave/generate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ mood: mood }) }); if (res.status === 401) { window.location.href = "login"; return; } const data = await res.json(); alert(`🌊 Волна обновлена под настроение [${data.mood_label || mood}]!\nДобавлено новых треков: ${data.added_count || 0}.`); await fetchWaveStatus(); } catch (err) { alert("Ошибка генерации волны: " + err); } finally { waveGenerateBtn.disabled = false; if (waveLoadingText) waveLoadingText.style.display = "none"; } }); } if (waveRotateBtn) { waveRotateBtn.addEventListener("click", async () => { waveRotateBtn.disabled = true; try { const res = await fetch("api/wave/rotate", { method: "POST" }); if (res.status === 401) { window.location.href = "login"; return; } const data = await res.json(); const promMsg = (data.promoted_count > 0) ? `⭐ Перенесено в библиотеку: ${data.promoted_count}` : "Нет лайкнутых для переноса"; const purMsg = (data.purged_count > 0) ? `🗑️ Удалено устаревших: ${data.purged_count}` : "Нет устаревших"; alert(`Ротация завершена!\n${promMsg}\n${purMsg}`); await fetchWaveStatus(); } catch (err) { alert("Ошибка ротации: " + err); } finally { waveRotateBtn.disabled = false; } }); } async function fetchWaveStatus() { try { const res = await fetch("api/wave/status"); if (res.status === 401) { window.location.href = "login"; return; } const data = await res.json(); renderWaveStatus(data); } catch (err) { console.error("Wave status error:", err); } } function renderWaveStatus(data) { if (waveCountLabel) waveCountLabel.textContent = `${data.count || 0} / ${data.capacity || 25} треков`; if (waveSizeLabel) waveSizeLabel.textContent = `${data.total_size_mb || 0} МБ`; // Context label if (waveContextLabel && data.current_context) { waveContextLabel.textContent = data.current_context.label || "🕒 Авто"; waveContextLabel.title = data.current_context.desc || ""; } // Anchors if (waveAnchorsContainer && waveAnchorsList) { const anchors = data.anchor_artists || []; if (anchors.length > 0) { waveAnchorsContainer.style.display = "block"; waveAnchorsList.innerHTML = anchors.map(a => `${escapeHtml(a)}`).join(""); } else { waveAnchorsContainer.style.display = "none"; } } // Tracks list if (waveTracksList) { const tracks = data.tracks || []; if (tracks.length === 0) { waveTracksList.innerHTML = `

Волна пуста. Нажмите «Обновить Волну» для генерации первого потока.

`; } else { waveTracksList.innerHTML = tracks.map(t => `
${escapeHtml(t.title)} ${escapeHtml(t.artist)} • ${t.size_mb} МБ (${t.age_days} дн. назад)
`).join(""); } } } });