// Content Script para Manga Mass Downloader
// Basado en addon original - SIMPLIFICADO
(function() {
'use strict';
let selectedMangas = new Set();
let checkboxesAdded = false;
// Map para almacenar metadatos completos de manga por ID
let mangaMetadata = new Map();
// Crear popup flotante de progreso
function createFloatingProgressPopup() {
console.log('🎨 Creando popup de progreso...');
// Remover popup anterior si existe
const existingPopup = document.getElementById('mass-downloader-progress-popup');
if (existingPopup) {
console.log('🗑️ Removiendo popup anterior');
existingPopup.remove();
}
const popup = document.createElement('div');
popup.id = 'mass-downloader-progress-popup';
// Estilos inline para máxima compatibilidad
popup.style.position = 'fixed';
popup.style.top = '20px';
popup.style.right = '20px';
popup.style.width = '320px';
popup.style.background = '#667eea';
popup.style.backgroundImage = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
popup.style.color = 'white';
popup.style.padding = '20px';
popup.style.borderRadius = '12px';
popup.style.boxShadow = '0 8px 32px rgba(0,0,0,0.5)';
popup.style.zIndex = '999999'; // ¡Muy alto!
popup.style.fontFamily = 'Arial, sans-serif';
popup.style.display = 'block'; // Visible por defecto
popup.style.backdropFilter = 'blur(10px)';
popup.innerHTML = `
📦 Mass Downloader
Seleccionados: 0
`;
// Agregar event listeners a los botones
setTimeout(() => {
const btnDownloadSelected = document.getElementById('btn-download-selected');
const btnDownloadAll = document.getElementById('btn-download-all');
const btnClearSelection = document.getElementById('btn-clear-selection');
if (btnDownloadSelected) {
btnDownloadSelected.addEventListener('click', () => {
console.log('🎯 Click: Descargar Seleccionados');
// Enviar mensaje al popup.js para ejecutar la descarga
chrome.runtime.sendMessage({ action: 'triggerDownloadSelected' });
});
}
if (btnDownloadAll) {
btnDownloadAll.addEventListener('click', () => {
console.log('🎯 Click: Descargar TODOS');
chrome.runtime.sendMessage({ action: 'triggerDownloadAll' });
});
}
if (btnClearSelection) {
btnClearSelection.addEventListener('click', () => {
console.log('🎯 Click: Limpiar Selección');
selectedMangas.clear();
updateSelectedCount();
const countEl = document.getElementById('selected-count');
if (countEl) countEl.textContent = '0';
});
}
// Actualizar contador inicialmente
const countEl = document.getElementById('selected-count');
if (countEl) countEl.textContent = selectedMangas.size.toString();
}, 100);
console.log('✅ Popup creado, agregando al DOM...');
document.body.appendChild(popup);
console.log('✅ Popup agregado al DOM:', popup);
return popup;
}
// Actualizar progreso
function updateProgress(current, total, title, status) {
console.log('📊 updateProgress() llamado:', { current, total, title, status });
let popup = document.getElementById('mass-downloader-progress-popup');
if (!popup) {
console.log('⚠️ Popup no existe, creando uno nuevo...');
popup = createFloatingProgressPopup();
} else {
console.log('✅ Popup existe, actualizando...');
}
const percent = total > 0 ? Math.round((current / total) * 100) : 0;
console.log(`📈 Porcentaje calculado: ${percent}%`);
// Cambiar a panel de progreso
const controlPanel = document.getElementById('control-panel');
const progressPanel = document.getElementById('progress-panel');
if (controlPanel && progressPanel) {
controlPanel.style.display = 'none';
progressPanel.style.display = 'block';
console.log('🔄 Cambiado a panel de progreso');
}
const titleEl = document.getElementById('progress-title');
const countEl = document.getElementById('progress-count');
const percentEl = document.getElementById('progress-percent');
const statusEl = document.getElementById('progress-status');
const barEl = document.getElementById('progress-bar-fill');
console.log('🔍 Elementos encontrados:', {
titleEl: !!titleEl,
countEl: !!countEl,
percentEl: !!percentEl,
statusEl: !!statusEl,
barEl: !!barEl
});
if (titleEl) {
titleEl.textContent = title || 'Descargando...';
console.log('📝 Título actualizado:', titleEl.textContent);
}
if (countEl) {
countEl.textContent = `${current} / ${total}`;
console.log('🔢 Contador actualizado:', countEl.textContent);
}
if (percentEl) {
percentEl.textContent = `${percent}%`;
console.log('📊 Porcentaje actualizado:', percentEl.textContent);
}
if (statusEl) {
statusEl.textContent = status || '';
console.log('💬 Status actualizado:', statusEl.textContent);
}
if (barEl) {
barEl.style.width = `${percent}%`;
console.log('📊 Barra actualizada:', `${percent}%`);
}
popup.style.display = 'block';
popup.style.opacity = '1';
console.log('✅ Popup visible, display:', popup.style.display);
console.log('✅ Popup en pantalla:', popup.getBoundingClientRect());
}
// Ocultar progreso
function hideProgress() {
console.log('🙈 hideProgress() llamado');
const popup = document.getElementById('mass-downloader-progress-popup');
if (popup) {
// Cambiar de vuelta al panel de control
const controlPanel = document.getElementById('control-panel');
const progressPanel = document.getElementById('progress-panel');
if (controlPanel && progressPanel) {
progressPanel.style.display = 'none';
controlPanel.style.display = 'block';
console.log('🔄 Cambiado a panel de control');
// Actualizar contador
const countEl = document.getElementById('selected-count');
if (countEl) {
countEl.textContent = selectedMangas.size.toString();
console.log('🔢 Contador actualizado:', countEl.textContent);
}
}
// NO remover el popup, solo cambiar de panel
}
}
// Escuchar mensajes del popup
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
console.log('📨 MENSAJE RECIBIDO:', request.action, request);
if (request.action === 'getSelectedMangas') {
// Devolver objetos completos en lugar de solo IDs
const selectedObjects = [];
selectedMangas.forEach(id => {
const mangaObj = mangaMetadata.get(id);
if (mangaObj) {
selectedObjects.push(mangaObj);
}
});
console.log('📤 Enviando mangas seleccionados:', selectedObjects.length);
sendResponse({ mangas: selectedObjects });
} else if (request.action === 'clearSelection') {
selectedMangas.clear();
updateSelectedCount();
sendResponse({ success: true });
} else if (request.action === 'getImageUrls') {
console.log('📸 Solicitando URLs de imágenes para:', request.manga.title);
getImageUrlsForManga(request.manga)
.then(imageUrls => sendResponse({ imageUrls }))
.catch(error => sendResponse({ error: error.message }));
return true;
} else if (request.action === 'extractAllMangas') {
const allMangas = extractAllMangasFromPage();
console.log('📤 Enviando todos los mangas:', allMangas.length);
sendResponse({ mangas: allMangas });
} else if (request.action === 'showProgress') {
console.log('📊 Mostrando progreso:', request);
updateProgress(request.current, request.total, request.title, request.status);
sendResponse({ success: true });
} else if (request.action === 'hideProgress') {
console.log('🙈 Ocultando progreso');
hideProgress();
sendResponse({ success: true });
} else if (request.action === 'updateSelectedCount') {
const countEl = document.getElementById('selected-count');
if (countEl) {
countEl.textContent = request.count.toString();
console.log('🔢 Contador actualizado:', countEl.textContent);
}
sendResponse({ success: true });
} else if (request.action === 'triggerDownloadSelected') {
console.log('🎯 Trigger: Descargar Seleccionados');
downloadSelectedMangas();
sendResponse({ success: true });
} else if (request.action === 'triggerDownloadAll') {
console.log('🎯 Trigger: Descargar TODOS');
downloadAllMangas();
sendResponse({ success: true });
} else {
console.log('❓ Acción desconocida:', request.action);
}
return true;
});
// Extraer TODOS los manga de la página
function extractAllMangasFromPage() {
const mangas = [];
const links = document.querySelectorAll('a[href*="/g/"]');
links.forEach(link => {
const href = link.href;
const match = href.match(/\/g\/(\d+)\/([a-f0-9]+)/);
if (match) {
const title = link.textContent.trim() || 'Untitled';
mangas.push({
id: match[1],
token: match[2],
title: title,
url: href,
baseUrl: `https://e-hentai.org/g/${match[1]}/${match[2]}/`
});
}
});
return mangas;
}
// Obtener URLs de imágenes para un manga
async function getImageUrlsForManga(manga) {
const imageUrls = [];
const baseUrl = manga.baseUrl;
try {
const response = await fetch(baseUrl, {
credentials: 'include'
});
if (!response.ok) {
throw new Error(`Error ${response.status}`);
}
const html = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
// Detectar páginas - Múltiples patrones
let actualTotalPages = 1;
console.log('🔍 Iniciando detección de páginas para:', manga.title);
const pageInfo = doc.querySelector('.gpc, .gt, #gdn + span');
if (pageInfo) {
const pageText = pageInfo.textContent.trim();
console.log('🔍 Page info encontrado:', pageText);
// Patrón 1: "Showing 1 - 20 of 220 images"
let pageMatch = pageText.match(/Showing\s+1\s*-\s*\d+\s+of\s+(\d+)\s+images/i);
if (pageMatch) {
const totalImages = parseInt(pageMatch[1]);
actualTotalPages = Math.ceil(totalImages / 20);
console.log(`✓ Patrón "Showing": ${totalImages} imágenes = ${actualTotalPages} páginas`);
} else {
// Patrón 2: "5 pages" o "X pages"
pageMatch = pageText.match(/(\d+)\s+pages?/i);
if (pageMatch) {
actualTotalPages = parseInt(pageMatch[1]);
console.log(`✓ Patrón "pages": ${actualTotalPages} páginas`);
}
}
}
// Fallback: Buscar en todo el documento
if (actualTotalPages === 1) {
const allText = doc.body.textContent;
const pageMatch = allText.match(/(\d+)\s+pages?/i);
if (pageMatch) {
actualTotalPages = parseInt(pageMatch[1]);
console.log(`✓ Fallback: Encontradas ${actualTotalPages} páginas en el documento`);
} else {
console.log('⚠️ No se detectaron múltiples páginas, asumiendo 1 página');
}
}
console.log(`📄 Total páginas a procesar: ${actualTotalPages}`);
// Procesar todas las páginas
for (let page = 1; page <= actualTotalPages; page++) {
const pageUrl = page === 1 ? baseUrl : `${baseUrl}?p=${page}`;
console.log(`📄 Procesando página ${page}/${actualTotalPages}: ${pageUrl}`);
const pageResponse = await fetch(pageUrl, {
credentials: 'include'
});
if (!pageResponse.ok) {
console.error(`❌ Error ${pageResponse.status} en página ${page}`);
continue;
}
const pageHtml = await pageResponse.text();
const pageDoc = parser.parseFromString(pageHtml, 'text/html');
const links = pageDoc.querySelectorAll('a[href*="/s/"]');
console.log(`✓ Página ${page}: Encontrados ${links.length} enlaces de imagen`);
links.forEach(link => {
const href = link.href;
if (href && href.includes('/s/')) {
imageUrls.push({
url: href,
index: imageUrls.length
});
}
});
if (page < actualTotalPages) {
await new Promise(resolve => setTimeout(resolve, 20));
}
}
console.log(`✅ Total de imágenes encontradas: ${imageUrls.length}`);
return imageUrls;
} catch (error) {
console.error('Error:', error);
throw error;
}
}
// Agregar checkbox a enlaces de galerías
function addCheckboxes() {
console.log('\n==== addCheckboxes() INICIADO ====');
console.log('Timestamp:', new Date().toLocaleTimeString());
const existingCheckboxes = document.querySelectorAll('.mass-downloader-checkbox').length;
console.log('📊 Checkboxes existentes antes de limpiar:', existingCheckboxes);
// SOLUCIÓN DEFINITIVA: Limpiar TODOS los checkboxes existentes
document.querySelectorAll('.mass-downloader-checkbox').forEach(cb => cb.remove());
selectedMangas.clear();
console.log('🧹 Checkboxes limpiados');
// Obtener TODOS los enlaces
const allLinks = document.querySelectorAll('a[href*="/g/"]');
console.log('🔍 Total enlaces encontrados:', allLinks.length);
// Log detallado de todos los enlaces
const allLinkData = [];
allLinks.forEach((link, index) => {
const match = link.href.match(/\/g\/(\d+)\/([a-f0-9]+)/);
if (match) {
allLinkData.push({
index: index,
id: match[1],
href: link.href.substring(0, 60) + '...',
parentTag: link.parentElement.tagName,
parentClass: link.parentElement.className
});
}
});
console.log('📋 Detalle de enlaces (primeros 5):', allLinkData.slice(0, 5));
// DEDUPLICACIÓN ROBUSTA: Trackear IDs únicos durante la iteración
const seenIds = new Set();
const uniqueLinks = [];
allLinks.forEach(link => {
const match = link.href.match(/\/g\/(\d+)/);
if (match) {
const galleryId = match[1];
if (!seenIds.has(galleryId)) {
seenIds.add(galleryId);
uniqueLinks.push(link);
console.log(` ➕ ID único encontrado: ${galleryId} (href: ${link.href.substring(0, 50)}...)`);
} else {
console.log(` ⏭️ ID duplicado omitido: ${galleryId}`);
}
}
});
console.log('✅ Total IDs únicos (sin duplicados):', uniqueLinks.length);
console.log('✅ Set "seenIds" size:', seenIds.size);
// Crear checkboxes SOLO para enlaces únicos
let count = 0;
console.log('\n🎯 CREANDO CHECKBOXES:');
uniqueLinks.forEach((link, index) => {
const match = link.href.match(/\/g\/(\d+)\/([a-f0-9]+)/);
const parent = link.parentElement;
// Verificar que este parent no tenga ya un checkbox
if (parent.querySelector('.mass-downloader-checkbox')) {
console.log(` ⚠️ [${index}] Parent ya tiene checkbox, saltando...`);
return;
}
const mangaId = match ? match[1] : null;
const token = match ? match[2] : null;
const title = link.textContent.trim() || 'Untitled';
console.log(` 📝 [${index}] Creando checkbox para manga ID: ${mangaId}`);
// Guardar metadatos completos en el Map
if (mangaId && token) {
const metadata = {
id: mangaId,
token: token,
title: title,
url: link.href,
baseUrl: `https://e-hentai.org/g/${mangaId}/${token}/`
};
mangaMetadata.set(mangaId, metadata);
console.log(` 💾 Metadatos guardados para ID ${mangaId}: ${title.substring(0, 30)}...`);
}
// Crear checkbox
const container = document.createElement('div');
container.className = 'mass-downloader-checkbox';
container.style.cssText = `
position: absolute;
top: 5px;
left: 5px;
z-index: 9999;
background: rgba(0,0,0,0.8);
padding: 4px 8px;
border-radius: 4px;
`;
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.style.cssText = 'width: 16px; height: 16px;';
const label = document.createElement('label');
label.textContent = 'Select';
label.style.cssText = 'color: white; font-size: 11px; margin-left: 4px;';
checkbox.addEventListener('change', (e) => {
e.stopPropagation();
if (mangaId) {
if (checkbox.checked) {
selectedMangas.add(mangaId);
console.log(` ☑️ Manga ${mangaId} seleccionado`);
} else {
selectedMangas.delete(mangaId);
console.log(` ☐ Manga ${mangaId} deseleccionado`);
}
updateSelectedCount();
}
});
container.appendChild(checkbox);
container.appendChild(label);
// Agregar al parent
if (getComputedStyle(parent).position === 'static') {
parent.style.position = 'relative';
}
parent.appendChild(container);
count++;
console.log(` ✅ Checkbox ${count} agregado y appendeado al parent <${parent.tagName}>`);
});
console.log('\n==== RESUMEN FINAL ====');
console.log('🎉 Total checkboxes CREADOS:', count);
console.log('📊 Total checkboxes en DOM:', document.querySelectorAll('.mass-downloader-checkbox').length);
console.log('📊 Set selectedMangas size:', selectedMangas.size);
console.log('==== addCheckboxes() COMPLETADO ====\n');
checkboxesAdded = true;
}
function updateSelectedCount() {
// Actualizar contador en el control panel
const countEl = document.getElementById('selected-count');
if (countEl) {
countEl.textContent = selectedMangas.size.toString();
console.log('🔢 Control panel actualizado:', selectedMangas.size);
}
// También enviar al popup (para compatibilidad)
chrome.runtime.sendMessage({
action: 'updateSelectedCount',
count: selectedMangas.size
}).catch(() => {});
}
function addSelectAllButton() {
if (document.querySelector('.mass-downloader-select-all')) return;
const button = document.createElement('button');
button.className = 'mass-downloader-select-all';
button.textContent = 'Select All';
button.style.cssText = `
position: fixed;
bottom: 20px;
left: 20px;
padding: 10px 20px;
background: #667eea;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
z-index: 10000;
`;
let allSelected = false;
button.addEventListener('click', () => {
allSelected = !allSelected;
const checkboxes = document.querySelectorAll('.mass-downloader-checkbox input');
if (allSelected) {
checkboxes.forEach(cb => {
cb.checked = true;
const match = cb.closest('div').parentElement.querySelector('a[href*="/g/"]').href.match(/\/g\/(\d+)/);
if (match) selectedMangas.add(match[1]);
});
button.textContent = 'Deselect All';
} else {
checkboxes.forEach(cb => cb.checked = false);
selectedMangas.clear();
button.textContent = 'Select All';
}
updateSelectedCount();
});
document.body.appendChild(button);
}
// Descargar manga seleccionados
async function downloadSelectedMangas() {
console.log('📥 Iniciando descarga de manga seleccionados...');
const selectedObjects = [];
selectedMangas.forEach(id => {
const mangaObj = mangaMetadata.get(id);
if (mangaObj) {
selectedObjects.push(mangaObj);
}
});
if (selectedObjects.length === 0) {
alert('No hay manga seleccionados. Selecciona algunos manga primero.');
return;
}
console.log(`📦 Manga seleccionados: ${selectedObjects.length}`);
// Descargar cada manga
for (let i = 0; i < selectedObjects.length; i++) {
const manga = selectedObjects[i];
const title = manga.title ? manga.title.substring(0, 50) : 'Manga sin título';
updateProgress(i, selectedObjects.length, title, `Descargando manga ${i + 1} de ${selectedObjects.length}...`);
try {
// Obtener URLs de imágenes
const imageUrls = await getImageUrlsForManga(manga);
console.log(`✅ ${manga.title}: ${imageUrls.length} imágenes encontradas`);
// 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);
} else {
console.log(`✅ Descargado: ${manga.title}`);
}
} catch (error) {
console.error(`❌ Error en manga ${manga.title}:`, error.message);
}
}
updateProgress(selectedObjects.length, selectedObjects.length, '¡Completado!', 'Descarga finalizada');
setTimeout(() => {
hideProgress();
}, 2000);
}
// Descargar TODOS los manga de la página
async function downloadAllMangas() {
console.log('📥 Iniciando descarga de TODOS los manga...');
const allMangas = extractAllMangasFromPage();
if (allMangas.length === 0) {
alert('No se encontraron manga en esta página.');
return;
}
console.log(`📦 Manga encontrados: ${allMangas.length}`);
// Descargar cada manga
for (let i = 0; i < allMangas.length; i++) {
const manga = allMangas[i];
const title = manga.title ? manga.title.substring(0, 50) : 'Manga sin título';
updateProgress(i, allMangas.length, title, `Descargando manga ${i + 1} de ${allMangas.length}...`);
try {
// Obtener URLs de imágenes
const imageUrls = await getImageUrlsForManga(manga);
console.log(`✅ ${manga.title}: ${imageUrls.length} imágenes encontradas`);
// 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);
} else {
console.log(`✅ Descargado: ${manga.title}`);
}
} catch (error) {
console.error(`❌ Error en manga ${manga.title}:`, error.message);
}
}
updateProgress(allMangas.length, allMangas.length, '¡Completado!', 'Descarga finalizada');
setTimeout(() => {
hideProgress();
}, 2000);
}
function init() {
if (window.location.href.includes('/g/')) return;
setTimeout(() => {
addCheckboxes();
addSelectAllButton();
createFloatingProgressPopup(); // Crear control panel automáticamente
}, 1000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();