YnjnDownloader

Manga downloader for ynjn.jp

// ==UserScript==
// @name         YnjnDownloader
// @namespace    https://github.com/Timesient/manga-download-scripts
// @version      0.7
// @license      GPL-3.0
// @author       Timesient
// @description  Manga downloader for ynjn.jp
// @icon         https://public.ynjn.jp/web/img/common/favicon.ico
// @homepageURL  https://greasyfork.org/scripts/455206-ynjndownloader
// @supportURL   https://github.com/Timesient/manga-download-scripts/issues
// @match        https://ynjn.jp/viewer/*
// @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.greasyfork.org/scripts/451810/1398192/ImageDownloaderLib.js
// @grant        GM_info
// @grant        GM_xmlhttpRequest
// ==/UserScript==

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

  // get title and pages
  const { title_id, episode_id } = window.location.href.match(/viewer\/(?<title_id>\d+)\/(?<episode_id>\d+)/).groups;
  const config = await axios.get(`https://webapi.ynjn.jp/viewer?title_id=${title_id}&episode_id=${episode_id}`).then(res => res.data.data);
  const title = config.viewer_navigation.name;
  const pages = config.pages.filter(page => page.manga_page).map(page => page.manga_page);

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

  // 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 new Promise(resolve => {
        GM_xmlhttpRequest({
          method: 'GET',
          url: page.page_image_url,
          responseType: 'arraybuffer',
          onload: res => resolve(res.response)
        });
      });

      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.image_horizontal_size;
        canvas.height = page.image_vertical_size;

        // draw pieces on correct position
        const pieceWidth = Math.floor(this.width / 4);
        const pieceHeight = Math.floor(this.height / 4);
        for (let i = 0; i < 16; i++) {
          let srcX = i % 4 * pieceWidth;
          let srcY = Math.floor(i / 4) * pieceHeight;
          let j = i % 4 * 4 + Math.floor(i / 4);
          let destX = j % 4 * pieceWidth;
          let destY = Math.floor(j / 4) * pieceHeight;
          ctx.drawImage(this, srcX, srcY, pieceWidth, pieceHeight, destX, destY, pieceWidth, pieceHeight);
        }

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

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