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:
310
content.js
Normal file
310
content.js
Normal file
@@ -0,0 +1,310 @@
|
||||
// Content Script para Manga Mass Downloader
|
||||
// Basado en addon original - SIMPLIFICADO
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
let selectedMangas = new Set();
|
||||
let checkboxesAdded = false;
|
||||
|
||||
// Escuchar mensajes del popup
|
||||
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||
if (request.action === 'getSelectedMangas') {
|
||||
const selected = Array.from(selectedMangas);
|
||||
sendResponse({ mangas: selected });
|
||||
} else if (request.action === 'clearSelection') {
|
||||
selectedMangas.clear();
|
||||
updateSelectedCount();
|
||||
sendResponse({ success: true });
|
||||
} else if (request.action === 'getImageUrls') {
|
||||
getImageUrlsForManga(request.manga)
|
||||
.then(imageUrls => sendResponse({ imageUrls }))
|
||||
.catch(error => sendResponse({ error: error.message }));
|
||||
return true;
|
||||
} else if (request.action === 'extractAllMangas') {
|
||||
const allMangas = extractAllMangasFromPage();
|
||||
sendResponse({ mangas: allMangas });
|
||||
}
|
||||
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
|
||||
let actualTotalPages = 1;
|
||||
const pageInfo = doc.querySelector('.gpc, .gt, #gdn + span');
|
||||
if (pageInfo) {
|
||||
const pageText = pageInfo.textContent.trim();
|
||||
const 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);
|
||||
}
|
||||
}
|
||||
|
||||
// Procesar todas las páginas
|
||||
for (let page = 1; page <= actualTotalPages; page++) {
|
||||
const pageUrl = page === 1 ? baseUrl : `${baseUrl}?p=${page}`;
|
||||
const pageResponse = await fetch(pageUrl, {
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (!pageResponse.ok) continue;
|
||||
|
||||
const pageHtml = await pageResponse.text();
|
||||
const pageDoc = parser.parseFromString(pageHtml, 'text/html');
|
||||
|
||||
const links = pageDoc.querySelectorAll('a[href*="/s/"]');
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
console.log(` 📝 [${index}] Creando checkbox para manga ID: ${match ? match[1] : 'N/A'}`);
|
||||
|
||||
// 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();
|
||||
const match = link.href.match(/\/g\/(\d+)\/([a-f0-9]+)/);
|
||||
if (match) {
|
||||
if (checkbox.checked) {
|
||||
selectedMangas.add(match[1]);
|
||||
} else {
|
||||
selectedMangas.delete(match[1]);
|
||||
}
|
||||
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() {
|
||||
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);
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (window.location.href.includes('/g/')) return;
|
||||
|
||||
setTimeout(() => {
|
||||
addCheckboxes();
|
||||
addSelectAllButton();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
|
||||
})();
|
||||
Reference in New Issue
Block a user