Lezhin Comics Downloader

Scrape a full lezhin chapter. Creates a zip with PNGs

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Lezhin Comics Downloader
// @namespace    lezhin-comics-scraper
// @version      1.1.1
// @description  Scrape a full lezhin chapter. Creates a zip with PNGs
// @author       TzurS11
// @match        https://www.lezhinus.com/*
// @match        https://www.lezhin.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=lezhinus.com
// @grant        GM_setValue
// @grant        GM_getValue
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.6.0/jszip.min.js
// @license      MIT
// ==/UserScript==

(function() {
    async function getImageSrcFromElement(imageElementString,index) {
        // Create a temporary div element to hold the image element
        const tempDiv = document.createElement('div');
        tempDiv.innerHTML = imageElementString;

        // Find the image element within the temporary div
        const imageElement = tempDiv.querySelector('img');

        if (imageElement) {
            const imageUrl = imageElement.src;

            try {
                await timeout(500);
                const response = await fetch(imageUrl);
                const blob = await response.blob();

                // Create a temporary anchor element to trigger the download
                const downloadLink = document.createElement('a');
                downloadLink.href = URL.createObjectURL(blob);
                downloadLink.download = `image-${index}.jpg`;
                downloadLink.style.display = 'none';

                return downloadLink;
            } catch (error) {
                return false;
            }
        } else {
            return false
        }
    }



    function timeout(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }


    function downloadImagesAsZip(anchorElements) {
        const zip = new JSZip();

        const promises = Array.from(anchorElements).map((anchor, index) => {
            const url = anchor.href;
            return fetch(url)
                .then(response => response.blob())
                .then(blob => {
                const filename = `image_${index + 1}.jpg`; // Customize the filename as needed
                zip.file(filename, blob);
            });
        });

        Promise.all(promises)
            .then(() => {
            return zip.generateAsync({ type: 'blob' });
        })
            .then(zipBlob => {
            let pathNames = window.location.pathname.split('/');
            const zipFilename = `${pathNames[3]} ${pathNames[4]}.zip`;
            const link = document.createElement('a');
            link.href = URL.createObjectURL(zipBlob);
            link.download = zipFilename;
            link.style.display = 'none';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        })
            .catch(error => {
            console.error('Error:', error);
        });
    }

    function waitForImgInDiv(targetDiv) {
        return new Promise(resolve => {

            if (!(targetDiv instanceof HTMLDivElement)) {
                resolve(false);
                return;

            }

            // Check if there's already an <img> element inside the div
            if (targetDiv.querySelector('img')) {
                resolve(true);
                return;
            }

            const observer = new MutationObserver(mutations => {
                mutations.forEach(mutation => {
                    mutation.addedNodes.forEach(node => {
                        if (node instanceof HTMLImageElement && node.tagName === 'IMG') {
                            observer.disconnect(); // Stop observing once an <img> is added
                            resolve(true);
                            return;
                        }
                    });
                });
            });

            const config = { childList: true, subtree: true };
            observer.observe(targetDiv, config);

            let skip = setTimeout(async () => {
                observer.disconnect(); // Disconnect the observer
                resolve(true); // Resolve with true after the timeout
            }, 1000);
        });
    }


    function waitForDivChanges(divId) {
        return new Promise(resolve => {
            const targetDiv = document.getElementById(divId);

            if (!targetDiv) {
                console.error(`Element with ID "${divId}" not found.`);
                resolve(false);
                return;
            }

            const observer = new MutationObserver(mutations => {
                mutations.forEach(mutation => {
                    if (mutation.type === 'attributes' || mutation.type === 'characterData') {
                        resolve(true);
                        observer.disconnect();
                    } else if (mutation.type === 'childList') {
                        for (const addedNode of mutation.addedNodes) {
                            if (addedNode instanceof HTMLElement && addedNode.id === divId) {
                                resolve(true);
                                observer.disconnect();
                                return;
                            }
                        }
                    }
                });
            });

            const config = { attributes: true, childList: true, characterData: true, subtree: true };
            observer.observe(targetDiv, config);
        });
    }

    let pathNames = window.location.pathname.split('/');
    if(pathNames[2] == "comic"){
        waitForDivChanges("scroll-list").then(x=>{
            let button = document.createElement("button");
            button.innerHTML = "Download Chapter";
            button.style.position = "fixed";
            button.style.bottom = 0
            button.style.zIndex = 10000;
            button.style.fontSize = "30px"
            button.style.marginLeft = "10px"
            button.id = "DownloadButton"
            button.onclick=function(){downloadChapter()}
            document.body.appendChild(button);
        })
    }



    let scrollList = document.getElementById("scroll-list");
    if(scrollList != null){
        if(localStorage.getItem("firstTime") == null){
            localStorage.setItem("firstTime", "no")
            alert("To use the Lezhin downloader wait for a button to appear on the bottom left corner and press it. This alert will not appear again.")
        }
    }

    async function downloadChapter(){
        let downloadButton= document.getElementById("DownloadButton");
        downloadButton.innerHTML = "Scraping..."
        downloadButton.disabled = true;
        let elements = []
        if(scrollList == null){
            alert("You are not viewing a chapter");
            return;
        }
        let descendents = scrollList.getElementsByTagName('*');
        let imgLen = descendents.length;
        for (let i = 0; i < descendents.length; ++i) {
            downloadButton.innerHTML = `Scraping... (${i+1}/${imgLen})`
            let element = descendents[i];
            element.scrollIntoView();
            await waitForImgInDiv(element)
            let image = element.innerHTML;
            //await timeout(500);
            let res = await getImageSrcFromElement(image,i);
            if (res != false){
                elements.push(res)
                //console.log(i+1 + "/" + imgLen)
            }
        }
        //console.log("creating zip")
        downloadImagesAsZip(elements)
        downloadButton.innerHTML = "Download Chapter";
        downloadButton.disabled = false;
    }
})();