Greasy Fork

Greasy Fork is available in English.

Yanmaga Manga Downloader + Scroll + Webtoon Merge

Descarga imágenes de capítulos de yanmaga.jp, con scroll automático y opción webtoon (una imagen larga combinada)

当前为 2025-06-17 提交的版本,查看 最新版本

// ==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
// ==/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);
    }

})();