let currentResults = null; let searchInProgress = false; let downloadInProgress = false; let loadMoreInProgress = false; let selectedBooks = new Set(); let currentQuery = ""; let currentPage = 1; let totalPagesAvailable = 0; let headlessMode = true; let totalBooksCount = 0; async function searchBooks(resetResults = true) { if (searchInProgress) { return; } const query = document.getElementById("query").value.trim(); const headless = document.getElementById("headless").checked; headlessMode = headless; currentQuery = query; if (!query) { showError("Vui lòng nhập từ khóa tìm kiếm"); return; } if (query.length < 2) { showError("Từ khóa phải có ít nhất 2 ký tự"); return; } searchInProgress = true; if (resetResults) { selectedBooks.clear(); currentPage = 1; currentResults = null; } document.getElementById("loadingIndicator").style.display = "block"; document.getElementById("loadingMessage").textContent = resetResults ? "Đang tìm kiếm sách..." : `Đang tải trang ${currentPage + 1}...`; document.getElementById("errorMessage").style.display = "none"; if (resetResults) { document.getElementById("resultsContent").innerHTML = ""; } document.getElementById("progressText").textContent = "Đang khởi tạo..."; const searchBtn = document.getElementById("searchBtn"); searchBtn.disabled = true; searchBtn.textContent = resetResults ? "Đang tìm..." : "Đang tải..."; try { const nextPage = resetResults ? 1 : currentPage + 1; const response = await fetch("/scraperZLibrary/api/search", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ query: query, page: nextPage, headless: headless, }), }); const data = await response.json(); if (data.success) { if (resetResults) { currentResults = data; currentPage = data.current_page || 1; totalPagesAvailable = data.total_pages || 1; totalBooksCount = data.total_books_count || data.books.length; displayResults(data); } else { // Merge new books with existing results if (currentResults) { currentResults.books = [...currentResults.books, ...data.books]; currentResults.total_count = currentResults.books.length; currentPage = data.current_page; totalPagesAvailable = data.total_pages; totalBooksCount = data.total_books_count || totalBooksCount; appendBooksToGrid( data.books, currentResults.books.length - data.books.length, ); updateStatsBar(currentResults); updateResultCount(); updateSelectionInfo(); } else { currentResults = data; currentPage = data.current_page || 1; totalPagesAvailable = data.total_pages || 1; totalBooksCount = data.total_books_count || data.books.length; displayResults(data); } } } else { showError(data.error || "Có lỗi xảy ra khi tìm kiếm"); } } catch (error) { console.error("Search error:", error); showError("Không thể kết nối đến máy chủ."); } finally { searchInProgress = false; loadMoreInProgress = false; document.getElementById("loadingIndicator").style.display = "none"; searchBtn.disabled = false; searchBtn.textContent = "Tìm kiếm"; } } async function loadMoreBooks() { if (loadMoreInProgress || searchInProgress) { return; } if (currentPage >= totalPagesAvailable) { alert("Đã tải hết tất cả các trang!"); return; } loadMoreInProgress = true; await searchBooks(false); } function appendBooksToGrid(newBooks, startIndex) { const booksGrid = document.querySelector(".books-grid"); if (!booksGrid) return; let html = ""; newBooks.forEach((book, idx) => { const index = startIndex + idx; const bookId = `book-${index}`; html += `
${index + 1}. ${escapeHtml(book.title)}
${escapeHtml(book.authors.join(", ") || "Không rõ")}
NXB
${escapeHtml(book.publisher)}
Năm
${escapeHtml(book.year)}
Số trang
${escapeHtml(book.pages)}
Ngôn ngữ
${escapeHtml(book.language)}
Định dạng
${escapeHtml(book.file)}
${ book.link !== "N/A" ? `Xem sách →` : 'Không có liên kết' }
`; }); booksGrid.insertAdjacentHTML("beforeend", html); // Update load more button updateLoadMoreButton(); } function updateResultCount() { const resultHeader = document.querySelector("#resultCountHeader"); if (resultHeader) { resultHeader.textContent = `Kết quả (${currentResults.books.length} sách đã tải / ${totalBooksCount} tổng cộng)`; } } function updateStatsBar(data) { const statsBar = document.querySelector(".stats-bar"); if (statsBar) { statsBar.innerHTML = `
Tổng số sách
${totalBooksCount}
Từ khóa
"${escapeHtml(data.query)}"
Trang hiện tại
${currentPage} / ${totalPagesAvailable}
`; } } function updateLoadMoreButton() { const loadMoreContainer = document.getElementById("loadMoreContainer"); if (loadMoreContainer) { if (currentPage >= totalPagesAvailable) { loadMoreContainer.innerHTML = ` `; } else { loadMoreContainer.innerHTML = ` `; } } } function displayResults(data) { const resultsDiv = document.getElementById("resultsContent"); if (!data.books || data.books.length === 0) { resultsDiv.innerHTML = `
🔍

Không tìm thấy sách

Thử từ khóa khác hoặc kiểm tra lại chính tả

Từ khóa: "${escapeHtml(data.query)}"

`; return; } currentPage = data.current_page || 1; totalPagesAvailable = data.total_pages || 1; totalBooksCount = data.total_books_count || data.books.length; let html = `
Tổng số sách
${totalBooksCount}
Từ khóa
"${escapeHtml(data.query)}"
Trang hiện tại
${currentPage} / ${totalPagesAvailable}
Đã chọn 0 sách

Kết quả (${data.books.length} sách đã tải / ${totalBooksCount} tổng cộng)

`; data.books.forEach((book, index) => { const bookId = `book-${index}`; html += `
${index + 1}. ${escapeHtml(book.title)}
${escapeHtml(book.authors.join(", ") || "Không rõ")}
NXB
${escapeHtml(book.publisher)}
Năm
${escapeHtml(book.year)}
Số trang
${escapeHtml(book.pages)}
Ngôn ngữ
${escapeHtml(book.language)}
Định dạng
${escapeHtml(book.file)}
${ book.link !== "N/A" ? `Xem sách →` : 'Không có liên kết' }
`; }); html += "
"; // Add load more button html += `
`; if (currentPage < totalPagesAvailable) { html += ` `; } else { html += ` `; } html += `
`; // Add statistics if available if (data.statistics && Object.keys(data.statistics).length > 0) { html += '
'; html += "

Thống kê

"; html += '
'; const stats = data.statistics; if (stats.languages && Object.keys(stats.languages).length > 0) { const langHtml = Object.entries(stats.languages) .sort((a, b) => b[1] - a[1]) .slice(0, 5) .map(([lang, count]) => { const percentage = ((count / totalBooksCount) * 100).toFixed(1); return `
${escapeHtml(lang)}: ${count} (${percentage}%)
`; }) .join(""); html += `
Ngôn ngữ phổ biến
${langHtml}
`; } if (stats.formats && Object.keys(stats.formats).length > 0) { const formatHtml = Object.entries(stats.formats) .sort((a, b) => b[1] - a[1]) .slice(0, 5) .map(([fmt, count]) => { const percentage = ((count / totalBooksCount) * 100).toFixed(1); return `
${escapeHtml(fmt)}: ${count} (${percentage}%)
`; }) .join(""); html += `
Định dạng phổ biến
${formatHtml}
`; } if (stats.years && Object.keys(stats.years).length > 0) { const yearHtml = Object.entries(stats.years) .sort((a, b) => parseInt(b[0]) - parseInt(a[0])) .slice(0, 5) .map( ([year, count]) => `
${escapeHtml(year)}: ${count} sách
`, ) .join(""); html += `
Năm xuất bản gần đây
${yearHtml}
`; } html += "
"; html += "
"; } resultsDiv.innerHTML = html; updateSelectionInfo(); resultsDiv.scrollIntoView({ behavior: "smooth", block: "nearest" }); } function toggleBookSelection(index, checked) { if (checked) { selectedBooks.add(index); const card = document.getElementById(`book-${index}`); if (card) card.classList.add("selected"); } else { selectedBooks.delete(index); const card = document.getElementById(`book-${index}`); if (card) card.classList.remove("selected"); } updateSelectionInfo(); updateSelectAllCheckbox(); } function toggleSelectAll(checked) { if (!currentResults || !currentResults.books) return; if (checked) { currentResults.books.forEach((_, index) => { selectedBooks.add(index); const card = document.getElementById(`book-${index}`); if (card) card.classList.add("selected"); const checkbox = document.querySelector( `[data-book-index="${index}"]`, ); if (checkbox) checkbox.checked = true; }); } else { selectedBooks.clear(); document .querySelectorAll(".book-card") .forEach((card) => card.classList.remove("selected")); document .querySelectorAll(".book-checkbox") .forEach((cb) => (cb.checked = false)); } updateSelectionInfo(); } function clearSelection() { selectedBooks.clear(); document .querySelectorAll(".book-card") .forEach((card) => card.classList.remove("selected")); document .querySelectorAll(".book-checkbox") .forEach((cb) => (cb.checked = false)); const selectAllCheckbox = document.getElementById("selectAllCheckbox"); if (selectAllCheckbox) selectAllCheckbox.checked = false; updateSelectionInfo(); } function updateSelectionInfo() { const count = selectedBooks.size; const infoElement = document.getElementById("selectionInfo"); if (infoElement) { infoElement.textContent = `Đã chọn ${count} / ${currentResults?.books.length || 0} sách`; } } function updateSelectAllCheckbox() { if (!currentResults || !currentResults.books) return; const selectAll = document.getElementById("selectAllCheckbox"); if (selectAll) { selectAll.checked = selectedBooks.size === currentResults.books.length && currentResults.books.length > 0; } } async function downloadSelectedBooks() { if (downloadInProgress) { alert("Đang tải sách, vui lòng đợi..."); return; } if (!currentResults || !currentResults.books) { alert("Không có sách để tải"); return; } if (selectedBooks.size === 0) { alert("Vui lòng chọn ít nhất một cuốn sách"); return; } const selectedBooksList = Array.from(selectedBooks) .sort((a, b) => a - b) .map((index) => currentResults.books[index]); downloadInProgress = true; const headless = document.getElementById("headless").checked; // Show progress UI document.getElementById("loadingIndicator").style.display = "block"; document.getElementById("loadingMessage").textContent = `Đang tải ${selectedBooksList.length} sách...`; let successCount = 0; let failedCount = 0; try { // POST to get the SSE stream const response = await fetch("/scraperZLibrary/api/download/stream", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ books: selectedBooksList, query: currentResults.query, headless: headless, }), }); if (!response.ok) throw new Error("Không thể bắt đầu tải xuống"); const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n\n"); buffer = lines.pop(); // keep incomplete chunk for (const chunk of lines) { const line = chunk.replace(/^data:\s*/, "").trim(); if (!line) continue; let event; try { event = JSON.parse(line); } catch { continue; } if (event.type === "progress") { document.getElementById("loadingMessage").textContent = `[${event.index}/${event.total}] Đang tải: ${event.title.substring(0, 50)}...`; document.getElementById("progressText").textContent = `Đã xong: ${successCount} ✓ Lỗi: ${failedCount} ✗`; } else if (event.type === "ready") { successCount++; document.getElementById("progressText").textContent = `Đã xong: ${successCount} ✓ Lỗi: ${failedCount} ✗`; // Trigger instant download via hidden const a = document.createElement("a"); a.href = `/scraperZLibrary/api/download/file/${event.token}`; a.download = event.filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); // Small gap so the browser doesn't block rapid-fire downloads await new Promise((r) => setTimeout(r, 600)); } else if (event.type === "error") { failedCount++; console.warn(`Failed: ${event.title} — ${event.message}`); document.getElementById("progressText").textContent = `Đã xong: ${successCount} ✓ Lỗi: ${failedCount} ✗`; } else if (event.type === "done") { document.getElementById("loadingMessage").textContent = `Hoàn tất! ${event.success} thành công, ${event.failed} thất bại`; } } } } catch (error) { console.error("Stream download error:", error); alert("Không thể tải sách: " + error.message); } finally { downloadInProgress = false; setTimeout(() => { document.getElementById("loadingIndicator").style.display = "none"; }, 2000); } } function showError(message) { const errorDiv = document.getElementById("errorMessage"); errorDiv.textContent = message; errorDiv.style.display = "block"; document.getElementById("resultsContent").innerHTML = `

Tìm kiếm thất bại

${escapeHtml(message)}

Vui lòng thử lại với từ khóa khác.

`; errorDiv.scrollIntoView({ behavior: "smooth", block: "nearest" }); } function downloadResults(format) { if (!currentResults) { alert("Không có kết quả để tải xuống"); return; } fetch(`/scraperZLibrary/api/download/${format}`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(currentResults), }) .then((response) => { if (!response.ok) { throw new Error("Tải xuống thất bại"); } return response.blob(); }) .then((blob) => { const url = window.URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `zlib_${currentResults.query.replace(/\s+/g, "_")}_ket_qua.${format}`; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); }) .catch((error) => { console.error("Download error:", error); alert("Không thể tải file: " + error.message); }); } function copyResultsToClipboard() { if (!currentResults) { alert("Không có kết quả để sao chép"); return; } let summary = `KẾT QUẢ TÌM KIẾM Z-LIBRARY\n`; summary += `========================\n`; summary += `Từ khóa: ${currentResults.query}\n`; summary += `Tổng số sách: ${totalBooksCount}\n`; summary += `Đã tải: ${currentResults.books.length} sách\n`; summary += `========================\n\n`; currentResults.books.slice(0, 20).forEach((book, index) => { summary += `${index + 1}. ${book.title}\n`; summary += ` Tác giả: ${book.authors.join(", ")}\n`; summary += ` Năm: ${book.year} | Ngôn ngữ: ${book.language} | Định dạng: ${book.file}\n`; summary += ` Link: ${book.link}\n\n`; }); if (currentResults.books.length > 20) { summary += `... và ${currentResults.books.length - 20} sách khác\n`; } navigator.clipboard .writeText(summary) .then(() => { alert("Đã sao chép tóm tắt!"); }) .catch((err) => { console.error("Failed to copy:", err); alert("Không thể sao chép"); }); } function escapeHtml(text) { if (typeof text !== "string") return text; const div = document.createElement("div"); div.textContent = text; return div.innerHTML; } document .getElementById("query") .addEventListener("keypress", function (e) { if (e.key === "Enter") { searchBooks(true); } }); document.addEventListener("keydown", function (e) { if ((e.ctrlKey || e.metaKey) && e.key === "k") { e.preventDefault(); document.getElementById("query").focus(); } }); const progressMessages = [ "Đang khởi tạo...", "Đang kết nối Z-Library...", "Đang lấy dữ liệu sách...", "Đang xử lý kết quả...", "Sắp xong...", ]; let messageIndex = 0; const progressInterval = setInterval(() => { if (searchInProgress || downloadInProgress) { messageIndex = (messageIndex + 1) % progressMessages.length; const progressElement = document.getElementById("progressText"); if (progressElement) { progressElement.textContent = progressMessages[messageIndex]; } } }, 2000); window.addEventListener("beforeunload", () => { clearInterval(progressInterval); });