JumptoonDownloader

Manga downloader for jumptoon.com

// ==UserScript==
// @name         JumptoonDownloader
// @namespace    https://github.com/Timesient/manga-download-scripts
// @version      0.3
// @license      GPL-3.0
// @author       Timesient
// @description  Manga downloader for jumptoon.com
// @icon         https://jumptoon.com/favicon.ico
// @homepageURL  https://gf.qytechs.cn/scripts/497457-jumptoondownloader
// @supportURL   https://github.com/Timesient/manga-download-scripts/issues
// @match        https://jumptoon.com/*
// @require      https://unpkg.com/[email protected]/dist/axios.min.js
// @require      https://unpkg.com/[email protected]/dist/jszip.min.js
// @require      https://unpkg.com/[email protected]/dist/FileSaver.min.js
// @require      https://update.gf.qytechs.cn/scripts/451810/1398192/ImageDownloaderLib.js
// @grant        GM_info
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(async function(axios, JSZip, saveAs, ImageDownloader) {
  'use strict';

  // reload page when enter or leave chapter
  const re = /https:\/\/jumptoon\.com\/series\/.*\/episodes\/(?<episodeID>\d+)\/.*/;
  const oldHref = window.location.href;
  const timer = setInterval(() => {
    const newHref = window.location.href;
    if (re.exec(newHref)?.groups?.episodeID === re.exec(oldHref)?.groups?.episodeID) return;
    if (re.test(newHref) || re.test(oldHref)) {
      clearInterval(timer);
      window.location.reload();
    }
  }, 200);

  // return if not reading chapter now
  if (!re.test(oldHref)) return;

  // get episode content
  unsafeWindow.__userscript_temp__ = [];
  const functionBodyString = Array
    .from(document.querySelectorAll('script'))
    .filter(element => element.textContent.includes('seriesEpisodeContent'))
    .pop()
    .textContent
    .replace('self.__next_f.push', 'window.__userscript_temp__.push');
  new Function(functionBodyString)();
  const contentString = unsafeWindow.__userscript_temp__.pop().pop();
  const content = JSON.parse(contentString.slice(contentString.indexOf('[['))).shift().pop().seriesEpisodeContent;

  // get title & pages & seed
  const title = `${content.seriesEpisodeEdge.node.notation}${content.seriesEpisodeEdge.node.title ? ' ' + content.seriesEpisodeEdge.node.title : ''}`;
  const pages = content.pageList;
  const seed = `${content.seriesId}:${content.number}`.split('').reduce((acc, cur) => acc + cur.codePointAt(0), 0);
  const scrambleAlgorithmType = content.scrambleAlgorithmType;

  // setup ImageDownloader
  ImageDownloader.init({
    maxImageAmount: pages.length,
    getImagePromises,
    title
  });

  // add style patch
  const downloadBtn = document.getElementById('ImageDownloader-DownloadButton');
  if (downloadBtn) downloadBtn.style.textAlign = 'center';

  // collect promises of image
  function getImagePromises(startNum, endNum) {
    return pages
      .slice(startNum - 1, endNum)
      .map(page => getDecryptedImage(page)
        .then(ImageDownloader.fulfillHandler)
        .catch(ImageDownloader.rejectHandler)
      );
  }

  // get decrypted image
  function getDecryptedImage(page) {
    return new Promise(async resolve => {
      const imageArrayBuffer = await axios.get(page.imageUrl, { responseType: 'arraybuffer' }).then(res => res.data);
      const image = document.createElement('img');
      image.src = 'data:image/jpg;base64,' + window.btoa(new Uint8Array(imageArrayBuffer).reduce((data, byte) => data + String.fromCharCode(byte), ''));
      image.onload = function () {
        // create canvas
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        canvas.width = page.width;
        canvas.height = page.height;

        // get coords
        const coords = getCoords(seed, scrambleAlgorithmType, page.width, this.width, page.height);

        // draw pieces
        for (const coord of coords) {
          ctx.drawImage(this, ...coord);
        }

        canvas.toBlob(resolve);
      }
    });
  }

  // seed, scrambleAlgorithmType, page.width, scrambledImage.width, page.height
  function getCoords(e, i, t, s, r) { 
    const X = (e) => ({
      next: () => e = (1664525 * e + 1013904223) % 4294967296
    });

    const e4 = (e, i, t) => {
      let s = Array.from({ length: i}, (e, i) => i)
        , r = i;
      0 !== t && r--;
      let a = X(e);
      for (let e = r; e > 1; e--) {
        let i = a.next() % e;
        [s[i],s[e - 1]] = [s[e - 1], s[i]]
      }
      return s;
    }

    const eQ = {
      V1: {
        splitWidth: 12,
        paddingWidth: 3,
        blankWidth: 3
      },
      V2: {
        splitWidth: 20,
        paddingWidth: 15,
        blankWidth: 1
      }
    }

    let {splitWidth: a, blankWidth: n, paddingWidth: o} = eQ[i]
      , l = a + n + 2 * o
      , d = Math.floor(s / l)
      , c = t % a
      , u = e4(e, d, c)
      , m = Array.from({ length: d })
      , p = [];
    for (let e of u)
      m[u[e]] = e;
    for (let e = 0; e < d; e++) {
      let i = m[e] * l + o
        , t = e * a;
      p.push([i, 0, a, r, t, 0, a, r])
    }
    if (c) {
      let e = d * l + o
        , i = d * a;
      p.push([e, 0, c, r, i, 0, c, r])
    }
    return p;
  }

})(axios, JSZip, saveAs, ImageDownloader);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址