Initial commit: Manga Mass Downloader Chrome Extension

 Features:
- Multi-selection checkboxes on manga listings
- Batch download selected manga or all manga from page
- Optimized parallel downloading (20ms delays, 5 concurrent)
- Visual progress tracking
- Popup UI for easy control
- Fixed duplicate checkbox issue with deduplication logic

📁 Files:
- manifest.json: Extension configuration
- content.js: Checkbox injection & manga detection
- background.js: Optimized download engine
- popup.html/js: User interface
- README.md: Complete documentation
This commit is contained in:
renato97
2025-11-04 04:25:25 +00:00
commit 829996b41e
7 changed files with 1148 additions and 0 deletions

224
popup.js Normal file
View File

@@ -0,0 +1,224 @@
// Popup JavaScript para Manga Mass Downloader
document.addEventListener('DOMContentLoaded', async () => {
const selectedCountElement = document.getElementById('selectedCount');
const downloadSelectedBtn = document.getElementById('downloadSelected');
const downloadAllBtn = document.getElementById('downloadAll');
const clearSelectionBtn = document.getElementById('clearSelection');
const progressDiv = document.getElementById('progress');
const progressFill = document.getElementById('progressFill');
const statusElement = document.getElementById('status');
let selectedMangas = [];
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
// Obtener manga seleccionados del content script
async function getSelectedMangas() {
if (!tab.url.includes('e-hentai.org')) {
selectedCountElement.textContent = '0';
return [];
}
return new Promise((resolve) => {
chrome.tabs.sendMessage(tab.id, { action: 'getSelectedMangas' }, (response) => {
if (chrome.runtime.lastError) {
console.error('Error:', chrome.runtime.lastError);
selectedCountElement.textContent = '0';
resolve([]);
return;
}
selectedMangas = response ? response.mangas : [];
selectedCountElement.textContent = selectedMangas.length;
resolve(selectedMangas);
});
});
}
// Actualizar contador
async function updateSelectedCount() {
await getSelectedMangas();
}
// Mostrar progreso
function showProgress(current, total, status) {
progressDiv.style.display = 'block';
const percentage = total > 0 ? Math.round((current / total) * 100) : 0;
progressFill.style.width = percentage + '%';
statusElement.textContent = status || `${current}/${total}`;
}
// Ocultar progreso
function hideProgress() {
progressDiv.style.display = 'none';
progressFill.style.width = '0%';
statusElement.textContent = '';
}
// Descargar manga seleccionados
downloadSelectedBtn.addEventListener('click', async () => {
await getSelectedMangas();
if (selectedMangas.length === 0) {
alert('No hay manga seleccionados. Ve a la página y selecciona algunos manga.');
return;
}
downloadSelectedBtn.disabled = true;
downloadAllBtn.disabled = true;
clearSelectionBtn.disabled = true;
showProgress(0, selectedMangas.length, 'Iniciando descargas...');
try {
// Descargar cada manga secuencialmente
for (let i = 0; i < selectedMangas.length; i++) {
const manga = selectedMangas[i];
showProgress(i + 1, selectedMangas.length, `Descargando: ${manga.title.substring(0, 30)}...`);
try {
// Obtener URLs de imágenes
const imageUrls = await getImageUrls(manga);
// Enviar al background para descarga
const response = await chrome.runtime.sendMessage({
action: 'downloadManga',
metadata: manga,
imageUrls: imageUrls
});
if (!response.success) {
console.error(`Error descargando ${manga.title}:`, response.error);
}
} catch (error) {
console.error(`Error en manga ${manga.title}:`, error.message);
}
}
showProgress(selectedMangas.length, selectedMangas.length, '¡Completado!');
statusElement.className = 'status success';
// Limpiar selección después de 3 segundos
setTimeout(() => {
chrome.tabs.sendMessage(tab.id, { action: 'clearSelection' }, async () => {
await updateSelectedCount();
});
}, 3000);
} catch (error) {
console.error('Error general:', error);
statusElement.textContent = 'Error: ' + error.message;
statusElement.className = 'status error';
} finally {
downloadSelectedBtn.disabled = false;
downloadAllBtn.disabled = false;
clearSelectionBtn.disabled = false;
}
});
// Descargar TODOS los manga de la página
downloadAllBtn.addEventListener('click', async () => {
if (!tab.url.includes('e-hentai.org')) {
alert('Esta extensión solo funciona en e-hentai.org');
return;
}
if (!confirm('¿Descargar TODOS los manga de esta página?')) {
return;
}
downloadSelectedBtn.disabled = true;
downloadAllBtn.disabled = true;
clearSelectionBtn.disabled = true;
showProgress(0, 100, 'Obteniendo lista de manga...');
try {
// Pedir al content script que extraiga todos los manga
const result = await new Promise((resolve) => {
chrome.tabs.sendMessage(tab.id, { action: 'extractAllMangas' }, (response) => {
resolve(response || { mangas: [] });
});
});
const allMangas = result.mangas || [];
showProgress(0, allMangas.length, `Encontrados ${allMangas.length} manga`);
// Descargar cada manga
for (let i = 0; i < allMangas.length; i++) {
const manga = allMangas[i];
showProgress(i + 1, allMangas.length, `Descargando: ${manga.title.substring(0, 30)}...`);
try {
const imageUrls = await getImageUrls(manga);
const response = await chrome.runtime.sendMessage({
action: 'downloadManga',
metadata: manga,
imageUrls: imageUrls
});
if (!response.success) {
console.error(`Error descargando ${manga.title}:`, response.error);
}
} catch (error) {
console.error(`Error en manga ${manga.title}:`, error.message);
}
}
showProgress(allMangas.length, allMangas.length, '¡Completado!');
statusElement.className = 'status success';
} catch (error) {
console.error('Error general:', error);
statusElement.textContent = 'Error: ' + error.message;
statusElement.className = 'status error';
} finally {
downloadSelectedBtn.disabled = false;
downloadAllBtn.disabled = false;
clearSelectionBtn.disabled = false;
}
});
// Limpiar selección
clearSelectionBtn.addEventListener('click', async () => {
chrome.tabs.sendMessage(tab.id, { action: 'clearSelection' }, async () => {
await updateSelectedCount();
hideProgress();
});
});
// Obtener URLs para un manga específico
async function getImageUrls(manga) {
return new Promise((resolve, reject) => {
chrome.tabs.sendMessage(
tab.id,
{ action: 'getImageUrls', manga: manga },
(response) => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
return;
}
if (response.error) {
reject(new Error(response.error));
return;
}
resolve(response.imageUrls);
}
);
});
}
// Escuchar mensajes del background
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'updateSelectedCount') {
selectedCountElement.textContent = request.count;
}
});
// Inicializar
updateSelectedCount();
// Actualizar contador cada 2 segundos
setInterval(updateSelectedCount, 2000);
});