// ==UserScript== // @name Yanmaga Manga Downloader + Scroll + Webtoon Merge // @namespace yanmaga-downloader // @version 1.1 // @description Descarga imágenes de capítulos de yanmaga.jp, con scroll automático y opción webtoon (una imagen larga combinada) // @author // @license MIT // @match https://yanmaga.jp/episodes/* // @require https://cdn.jsdelivr.net/npm/jszip@3/dist/jszip.min.js // @require https://cdn.jsdelivr.net/npm/file-saver@2/dist/FileSaver.min.js // @grant none // @downloadURL none // ==/UserScript== (function() { 'use strict'; // Crear UI const container = document.createElement('div'); container.style.cssText = ` position: fixed; top: 10px; right: 10px; z-index: 9999; display: flex; flex-direction: column; gap: 5px; `; document.body.appendChild(container); const downloadBtn = document.createElement('button'); downloadBtn.textContent = '📥 Descargar ZIP'; const mergeBtn = document.createElement('button'); mergeBtn.textContent = '🖼️ Descargar Webtoon (1 imagen)'; const scrollBtn = document.createElement('button'); scrollBtn.textContent = '⬇️ Auto Scroll'; [downloadBtn, mergeBtn, scrollBtn].forEach(btn => { btn.style.cssText = ` background: #e11d48; color: white; padding: 8px 12px; border: none; border-radius: 5px; font-size: 14px; cursor: pointer; `; container.appendChild(btn); }); // Botón ZIP downloadBtn.addEventListener('click', async () => { downloadBtn.disabled = true; downloadBtn.textContent = 'Descargando ZIP...'; try { const zip = new JSZip(); const images = await getMangaImages(); for (let i = 0; i < images.length; i++) { const blob = await fetchBlob(images[i].src); zip.file(images[i].name, blob); logDimensions(blob, images[i].name); } const zipContent = await zip.generateAsync({ type: 'blob' }); saveAs(zipContent, 'yanmaga_capitulo.zip'); alert(`ZIP completo: ${images.length} imágenes`); } catch (err) { console.error(err); alert('Error al descargar ZIP'); } downloadBtn.disabled = false; downloadBtn.textContent = '📥 Descargar ZIP'; }); // Botón MERGE mergeBtn.addEventListener('click', async () => { mergeBtn.disabled = true; mergeBtn.textContent = 'Generando imagen...'; try { const images = await getMangaImages(); const bitmaps = await Promise.all(images.map(img => fetchImageBitmap(img.src))); const width = Math.max(...bitmaps.map(b => b.width)); const height = bitmaps.reduce((sum, b) => sum + b.height, 0); const canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); let offsetY = 0; for (const bitmap of bitmaps) { ctx.drawImage(bitmap, 0, offsetY); offsetY += bitmap.height; } canvas.toBlob(blob => { saveAs(blob, 'yanmaga_webtoon.jpg'); }, 'image/jpeg', 1); } catch (e) { console.error(e); alert('Error al generar imagen webtoon'); } mergeBtn.disabled = false; mergeBtn.textContent = '🖼️ Descargar Webtoon (1 imagen)'; }); // Botón SCROLL scrollBtn.addEventListener('click', () => { let current = 0; const images = [...document.querySelectorAll('img[src*="/pages/"]')]; if (images.length === 0) return alert('No hay imágenes detectadas.'); scrollBtn.disabled = true; const interval = setInterval(() => { if (current >= images.length) { clearInterval(interval); scrollBtn.disabled = false; return; } images[current].scrollIntoView({ behavior: 'smooth' }); current++; }, 1000); }); // Funciones auxiliares async function getMangaImages() { const imgs = [...document.querySelectorAll('img[src*="/pages/"]')]; return imgs.map((img, i) => ({ src: img.src, name: String(i + 1).padStart(3, '0') + '.jpg' })); } async function fetchBlob(url) { const res = await fetch(url); return res.blob(); } async function fetchImageBitmap(url) { const res = await fetch(url); const blob = await res.blob(); return createImageBitmap(blob); } function logDimensions(blob, name) { const img = new Image(); img.onload = () => { console.log(`${name}: ${img.width}x${img.height}`); }; img.src = URL.createObjectURL(blob); } })();