Manga downloader for pocket.shonenmagazine.com
// ==UserScript== // @name MagapokeDownloader // @namespace https://github.com/Timesient/manga-download-scripts // @version 0.6 // @license GPL-3.0 // @author Timesient // @description Manga downloader for pocket.shonenmagazine.com // @icon https://pocket.shonenmagazine.com/img/favicon.ico // @homepageURL https://greatest.deepsurf.us/scripts/536294-magapokedownloader // @supportURL https://github.com/Timesient/manga-download-scripts/issues // @match https://pocket.shonenmagazine.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://unpkg.com/[email protected]/crypto-js.js // @require https://update.greatest.deepsurf.us/scripts/451810/1398192/ImageDownloaderLib.js // @grant GM_info // @grant GM_xmlhttpRequest // @grant unsafeWindow // ==/UserScript== (async function(axios, JSZip, saveAs, CryptoJS, ImageDownloader) { 'use strict'; // reload page when enter or leave chapter const re = /https:\/\/pocket\.shonenmagazine\.com\/title\/\d+\/episode\/\d+/; 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; // build config const config = { episodeID: window.location.pathname.match(/\/episode\/(?<episodeID>\d+)/).groups.episodeID, apiServerUrl: 'https://api.pocket.shonenmagazine.com', apiServerSecureUrl: 'https://se-api.pocket.shonenmagazine.com', platform: 3, } // get episode data const episodeData = await fetch(`${config.apiServerUrl}/episode/list`, { method: 'POST', credentials: 'include', headers: { 'content-type': 'application/x-www-form-urlencoded', 'X-Manga-Hash': getServiceHash({ episode_id_list: config.episodeID }), 'X-Manga-Is-Crawler': 'false', 'X-Manga-Platform': config.platform }, body: `episode_id_list=${config.episodeID}` }).then(res => res.json()); // get page data const pageData = await fetch(`${config.apiServerSecureUrl}/web/episode/viewer?episode_id=${config.episodeID}`, { method: 'GET', credentials: 'include', headers: { 'X-Manga-Hash': getServiceHash({ episode_id: config.episodeID }), 'X-Manga-Is-Crawler': 'false', 'X-Manga-Platform': config.platform } }).then(res => res.json()); // get WASM tools const wasmTools = await getWASMTools(); // get title and url of images const title = episodeData.episode_list.pop().episode_name; const imageURLs = pageData.page_list; // setup ImageDownloader ImageDownloader.init({ maxImageAmount: imageURLs.length, getImagePromises, title }); // collect promises of image function getImagePromises(startNum, endNum) { return imageURLs .slice(startNum - 1, endNum) .map(url => getDecryptedImage(url) .then(ImageDownloader.fulfillHandler) .catch(ImageDownloader.rejectHandler) ); } // get promise of decrypted image function getDecryptedImage(url) { return new Promise(async resolve => { const imageArrayBuffer = await new Promise(_resolve => { GM_xmlhttpRequest({ method: 'GET', 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 context = canvas.getContext('2d'); canvas.width = this.width; canvas.height = this.height; context.drawImage(this, 0, 0); const increment = wasmTools.exports.__wbindgen_add_to_stack_pointer(-16); const convertedSeed = pageData.scramble_seed ? wasmTools.convert(pageData.scramble_seed, wasmTools.exports.__wbindgen_export2, wasmTools.exports.__wbindgen_export3) : 0; wasmTools.exports.run(increment, wasmTools.save(context), wasmTools.save(this), 4, pageData.title_id, pageData.episode_id, convertedSeed, wasmTools.getN()); canvas.toBlob(resolve, 'image/jpeg', 1); } }); } function getServiceHash(params) { const SHA256 = (stringContent) => CryptoJS.SHA256(stringContent).toString(CryptoJS.enc.Hex); const SHA512 = (stringContent) => CryptoJS.SHA512(stringContent).toString(CryptoJS.enc.Hex); const keys = Object.keys(params).sort(); const arr = []; for (const key of keys) { arr.push(`${SHA256(key)}_${SHA512(params[key])}`); } const part1 = SHA256(arr.toString()); const part2 = `${SHA256('')}_${SHA512('')}`; return SHA512(`${part1}${part2}`); } async function getWASMTools() { // load WASM file const wasmArrayBuffer = await fetch(document.head.querySelector('link[href$=".wasm"]').href).then(res => res.arrayBuffer()); const wasmImportList = await WebAssembly.compile(wasmArrayBuffer).then(module => WebAssembly.Module.imports(module)); const wasmExports = await WebAssembly.instantiate(wasmArrayBuffer, getImportObject(wasmImportList)).then(wasmInstance => wasmInstance.instance.exports); let S = null; function H() { if (S === null || S.byteLength === 0) S = new Uint8Array(wasmExports.memory.buffer); return S; } let k = 0; function Ce(e, t) { let A = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); A.decode(); k += t; if (k >= 2146435072) { A = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); A.decode(); k = t } return A.decode(H().subarray(e, e + t)); } function X(e, t) { e = e >>> 0; return Ce(e, t); } let p = new Array(128).fill(void 0); p.push(void 0, null, !0, !1); function g(e) { return p[e]; } let E = p.length; function Z(e) { E === p.length && p.push(p.length + 1); const t = E; E = p[t]; p[t] = e; return t; } function M(e, t) { try { return e.apply(this, t) } catch (n) { wasmExports.__wbindgen_export(Z(n)) } } let P = 128; function save(e) { if (P == 1) throw new Error('out of js stack'); p[--P] = e; return P; } const C = new TextEncoder; C.encodeInto = function(e, t) { const n = C.encode(e); t.set(n); return { read: e.length, written: n.length }; } let N = 0; function getN() { return N; } function convert(e, t, n) { if (n === void 0) { const i = C.encode(e) const u = t(i.length, 1) >>> 0; H().subarray(u, u + i.length).set(i); N = i.length; return u; } let s = e.length; let a = t(s, 1) >>> 0; const o = H(); let r = 0; for (; r < s; r++) { const i = e.charCodeAt(r); if (i > 127) break; o[a + r] = i; } if (r !== s) { r !== 0 && (e = e.slice(r)); a = n(a, s, s = r + e.length * 3, 1) >>> 0; const i = H().subarray(a + r, a + s) const u = C.encodeInto(e, i); r += u.written; a = n(a, s, r, 1) >>> 0; } N = r; return a; } function getImportObject(importList) { const e = { wbg: {} }; importList .filter(importItem => importItem.module === 'wbg' && importItem.kind === 'function') .forEach(importItem => { if (importItem.name.includes('_wbindgen_throw_')) { e.wbg[importItem.name] = function(t, n) { throw new Error(t, n); } } if (importItem.name.includes('_clearRect_')) { e.wbg[importItem.name] = function(t, n, s, a, o) { g(t).clearRect(n, s, a, o); } } if (importItem.name.includes('_drawImage_')) { e.wbg[importItem.name] = function() { if (arguments.length === 4) { return M(function(t, n, s, a) { g(t).drawImage(g(n), s, a); }, arguments); } if (arguments.length === 10) { return M(function(t, n, s, a, o, r, i, u, d, c) { g(t).drawImage(g(n), s, a, o, r, i, u, d, c); }, arguments); } } } if (importItem.name.includes('_instanceof_CanvasRenderingContext2d_')) { e.wbg[importItem.name] = function(t) { let n; try { n = g(t) instanceof CanvasRenderingContext2D; } catch { n = false; } return n; } } if (importItem.name.includes('_instanceof_OffscreenCanvasRenderingContext2d_')) { e.wbg[importItem.name] = function(t) { let n; try { n = g(t) instanceof OffscreenCanvasRenderingContext2D; } catch { n = false; } return n; } } if (importItem.name.includes('_width_')) { e.wbg[importItem.name] = function(t) { return g(t).width; } } if (importItem.name.includes('_height_')) { e.wbg[importItem.name] = function(t) { return g(t).height; } } if (importItem.name.includes('_wbindgen_cast_')) { e.wbg[importItem.name] = function(t, n) { const s = X(t, n); return Z(s); } } }); return e; } return { exports: wasmExports, convert, save, getN }; } })(axios, JSZip, saveAs, CryptoJS, ImageDownloader);