n210-dl

Script used to download manga R18 on a domain of nhentai

Verzia zo dňa 13.04.2024. Pozri najnovšiu verziu.

// ==UserScript==
// @name         n210-dl
// @namespace    https://gist.github.com/hiroshil
// @version      0.0.1
// @description  Script used to download manga R18 on a domain of nhentai
// @license      MIT
// @author       hiroshil
// @source       https://gist.github.com/hiroshil/86723bc557efa88931cf6b135e42a2b2
// @match        http*://nhentai.to/g/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=nhentai.to
// ==/UserScript==
/* jshint esversion: 8 */

//https://stackoverflow.com/questions/5525071/how-to-wait-until-an-element-exists
//https://stackoverflow.com/questions/30008114/how-do-i-promisify-native-xhr
//https://stackoverflow.com/questions/5582574/how-to-check-if-a-string-contains-text-from-an-array-of-substrings-in-javascript

function waitForElm(selector) {
    return new Promise(resolve => {
        if (document.querySelectorAll(selector).length) {
            return resolve(document.querySelectorAll(selector));
        }
        const observer = new MutationObserver(mutations => {
            if (document.querySelectorAll(selector).length) {
                resolve(document.querySelectorAll(selector));
                observer.disconnect();
            }
        });
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    });
}
function waitForXHRRequest(url,method,responseType) {
	return new Promise( (resolve, reject) => {
		var res = new XMLHttpRequest();
		res.responseType = responseType;
		res.open(method, url, true);
		res.send();
		res.onload = () => {
			if (res.status >= 200 && res.status < 400) {
				resolve(res.response);
			} else {
				reject(false);
			}
		}
	});

}
function sleeper(ms) {
  return function(x) {
    return new Promise(resolve => setTimeout(() => resolve(x), ms));
  };
}
function parseStr(text) {
  /*
  Checks if the string matches the format "number-number" with the second number larger.

  Args:
      text: The string to check.

  Returns:
      Two numbers if the string matches the format and the second number is larger, False otherwise.
  */
  const pattern = /^\d+-\d+$/; // Matches "number-number" format
  const match = pattern.exec(text);

  if (match) {
    const [num1, num2] = match[0].split("-"); // Split into two numbers
    if (parseInt(num1) < parseInt(num2)){ // Check if second number is larger
        return [parseInt(num1), parseInt(num2)]
    }
  }

  return false;
}
async function downloadChapter(id=null,filename=null, callback=(e)=>{}, ret = 3, skipCr = true){
    console.log(id);
    const dlBtn = document.querySelector("#dlzip");
    if (!(dlBtn.classList.contains("btn-disabled"))) {
        dlBtn.classList.add("btn-disabled");
        var s_p = 1;
        var e_p = gallery.num_pages;
        let pt = prompt("Please enter the number of pages you want to download (Pattern: first page-last page. Example: 15-30)", "Click Continue to download all");
        const pg = parseStr(pt)
        if (pg) {
            s_p = pg[0];
            e_p = pg[1];
        }
        if (pt != null) {
            const srcUrl = document.querySelector("#cover > a > img").src;
            const media_url_ = new URL(srcUrl).origin;
            const ext = srcUrl.split(".").at(-1);
            const zipFileWriter = new zip.BlobWriter();
            const zipWriter = new zip.ZipWriter(zipFileWriter);
            for (let i = s_p; i <= e_p; i++) {
                const fname = i.toString() + "." + ext;
                const src = media_url_ + "/galleries/" + id + "/"+ fname;
                console.log((i+1).toString() + "/" + e_p.toString() + ": downloading "+src); //debug
                var blob;
                do {
                    blob = await waitForXHRRequest(src,'GET','blob');
                    ret -= 1;
                    if (!blob && !ret) {
                        alert("Error while downloading file: " + src)
                    }
                    else
                    {
                        await sleeper(200);
                    }
                }
                while (!blob && ret);
                if (blob.type.includes("image")){
                    const blobReader = new zip.BlobReader(blob);
                    await zipWriter.add(fname, blobReader);
                }
            }
            await zipWriter.close();
            const zipFileBlob = await zipFileWriter.getData();
            const anchor = document.createElement("a");
            const clickEvent = new MouseEvent("click");
            anchor.href = window.URL.createObjectURL(zipFileBlob);
            anchor.download = filename;
            anchor.dispatchEvent(clickEvent);
        }
    }
    callback(dlBtn);
}
function setupButton(jNode) {
    const btnDiv = document.querySelector(".buttons"); // Get the last child of the div with class "buttons"
    jNode.forEach(function(dlElement) {
        const clonedElement = dlElement.cloneNode(true); // Clone the element with all its content
        clonedElement.id = "dlzip";
        clonedElement.classList.remove("btn-disabled");
        clonedElement.querySelector(".top").textContent = "Click to download as zip";

        btnDiv.appendChild(clonedElement); // Append the cloned element to the last child

        clonedElement.addEventListener("click", (e)=>{
            downloadChapter(gallery.media_id, gallery.title.japanese + ".zip", (el)=>{ el.classList.remove("btn-disabled"); });
        });
    });
}
async function main(){
    var xhr = await waitForXHRRequest("https://raw.githubusercontent.com/gildas-lormeau/zip.js/v2.7.17/dist/zip.js","GET",undefined);
    eval(xhr);
    const btn = await waitForElm('#download');
    setupButton(btn);
}
main();