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 `
${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");
fetchWaveStatus();
if (waveGenerateBtn) {
waveGenerateBtn.addEventListener("click", async () => {
waveGenerateBtn.disabled = true;
if (waveLoadingText) waveLoadingText.style.display = "inline-block";
try {
const res = await fetch("api/wave/generate", { method: "POST" });
const data = await res.json();
alert(`Волна обновлена! Добавлено: ${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" });
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");
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} МБ`;
// 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("");
}
}
}
});