// ==UserScript==
// @name ComiciViewerDownloader
// @namespace https://github.com/Timesient/manga-download-scripts
// @version 0.9
// @license GPL-3.0
// @author Timesient
// @description Manga downloader specific for Comici Viewer
// @icon https://www.google.com/s2/favicons?sz=64&domain=youngchampion.jp
// @homepageURL https://greasyfork.org/scripts/463181-comiciviewerdownloader
// @supportURL https://github.com/Timesient/manga-download-scripts/issues
// @match https://youngchampion.jp/episodes/*
// @match https://younganimal.com/episodes/*
// @match https://bigcomics.jp/episodes/*
// @match https://comicride.jp/episodes/*
// @match https://kansai.mag-garden.co.jp/episodes/*
// @match https://championcross.jp/episodes/*
// @match https://comic.j-nbooks.jp/episodes/*
// @match https://comic-growl.com/episodes/*
// @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 episode id and domain
const { id, domain } = await new Promise(resolve => {
const timer = setInterval(() => {
const viewerElement = document.getElementById('comici-viewer');
if (viewerElement) {
clearInterval(timer);
resolve({
id: viewerElement.getAttribute('comici-viewer-id'),
domain: viewerElement.dataset.apiDomain
});
}
}, 500);
});
// get title and amount of pages
const { title, pageCount } = await axios({
method: 'GET',
url: `https://${domain}/book/episodeInfo?comici-viewer-id=${id}`
}).then(res => {
const episodes = res.data.result;
const currentEpisode = episodes.find(episode => episode.id === id);
return {
title: currentEpisode.name,
pageCount: currentEpisode.page_count
}
});
// get data of pages
const userId = document.getElementById('login_user_id').textContent || '0';
const pages = await axios.get(`https://${domain}/book/contentsInfo?user-id=${userId}&comici-viewer-id=${id}&page-from=0&page-to=${pageCount}`).then(res => res.data.result);
// setup ImageDownloader
ImageDownloader.init({
maxImageAmount: pageCount,
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 promise of decrypted image
function getDecryptedImage(data) {
return new Promise(async resolve => {
const imageArrayBuffer = await axios.get(data.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 context = canvas.getContext('2d');
canvas.width = this.width;
canvas.height = this.height;
context.drawImage(this, 0, 0);
// get scramble dict
const dict = [];
const dictTemplete = JSON.parse('[[0,0],[0,1],[0,2],[0,3],[1,0],[1,1],[1,2],[1,3],[2,0],[2,1],[2,2],[2,3],[3,0],[3,1],[3,2],[3,3]]');
const scrambleOrders = JSON.parse(data.scramble);
for (let i = 0; i < dictTemplete.length; i++) {
dict.push(dictTemplete[scrambleOrders[i]]);
}
// start unscrambling
const pieceWidth = Math.floor(data.width / 4);
const pieceHeight = Math.floor(data.height / 4);
let dictCounter = 0;
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
const x = dict[dictCounter][0];
const y = dict[dictCounter][1];
context.drawImage(this, pieceWidth * x, pieceHeight * y, pieceWidth, pieceHeight, pieceWidth * i, pieceHeight * j, pieceWidth, pieceHeight);
dictCounter++;
}
}
// output unscrambled image
canvas.toBlob(resolve);
}
});
}
})(axios, JSZip, saveAs, ImageDownloader);