🏠 Home 

CycomiDownloader

Manga downloader for cycomi.com


安装此脚本?
// ==UserScript==
// @name         CycomiDownloader
// @namespace    https://github.com/Timesient/manga-download-scripts
// @version      0.4
// @license      GPL-3.0
// @author       Timesient
// @description  Manga downloader for cycomi.com
// @icon         https://cycomi.com/favicon.ico
// @homepageURL  https://greasyfork.org/scripts/467898-cycomidownloader
// @supportURL   https://github.com/Timesient/manga-download-scripts/issues
// @match        https://cycomi.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.greasyfork.org/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:\/\/cycomi\.com\/viewer\/chapter\/.*/;
const oldHref = window.location.href;
const timer = setInterval(() => {
const newHref = window.location.href;
if (newHref === oldHref) 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;
// collect essential data
const chapterId = Number(window.location.pathname.split('/').pop());
const {
titleId,
titleName,
name: chapterName
} = await axios.get(`https://web.cycomi.com/api/chapter/detail?chapterId=${chapterId}`).then(res => res.data.data);
// get pages
const pages = await axios({
method: 'POST',
url: 'https://web.cycomi.com/api/chapter/page/list',
data: { titleId, chapterId },
withCredentials: true
}).then(res => res.data.data.pages.filter(page => /\/(?<hash>[0-9a-zA-Z]{32})\//.test(new URL(page.image).pathname)));
// setup ImageDownloader
ImageDownloader.init({
maxImageAmount: pages.length,
getImagePromises,
title: `${titleName} ${chapterName}`,
zipOptions: { base64: true }
});
// collect promises of image
function getImagePromises(startNum, endNum) {
return pages
.slice(startNum - 1, endNum)
.map(page => getImage(page)
.then(ImageDownloader.fulfillHandler)
.catch(ImageDownloader.rejectHandler)
);
}
// get promise of image
async function getImage(page) {
const hash = new URL(page.image).pathname.match(/\/(?<hash>[0-9a-zA-Z]{32})\//).groups.hash;
const encryptedImageData = await axios({
method: 'GET',
url: page.image,
responseType: 'arraybuffer'
}).then(res => res.data);
const decrypt = async (e, t) => {
let n = (e => {
let t = new Uint8Array(256);
t.forEach((e, n)=>{
t[n] = n
});
let n = 0;
return t.forEach((i,r)=>{
n = (n + t[r] + e.charCodeAt(r % e.length)) % 256;
let l = t[r];
t[r] = t[n],
t[n] = l
}),
t
})(t)
, i = 0
, r = 0
, l = new Uint8Array(e.length);
for (let t = 0, a = e.length; t < a; t++) {
r = (r + n[i = (i + 1) % 256]) % 256;
let a = n[i % 256];
n[i % 256] = n[r],
n[r] = a;
let o = n[(n[i] + n[r]) % 256];
l[t] = o ^ e[t]
}
const dataURL = await new Promise((resolve, reject) => {
let i = new FileReader;
i.addEventListener('error', reject);
i.addEventListener('load', () => resolve(i.r###lt));
i.readAsDataURL(new Blob([l]));
});
return dataURL.split(',')[1];
}
return await decrypt(new Uint8Array(encryptedImageData), hash);
}
})(axios, JSZip, saveAs, ImageDownloader);