From 9cf931341e0bc33848a052e8c782b1c2bb378940 Mon Sep 17 00:00:00 2001 From: MikeZeDev Date: Tue, 3 Oct 2023 18:21:31 +0000 Subject: [PATCH] FIX PixivComics : unscramble pictures (#6270) * FIX PixivComics : unscramble pictures * getPages ditched MD5 for SHA256 * getPages url endpoint is different * fetching image need a request header containing scrambling key * pages are scrambled * Update PixivComics.mjs * Update PixivComics.mjs --- src/web/mjs/connectors/PixivComics.mjs | 136 ++++++++++++++++++++++++- 1 file changed, 132 insertions(+), 4 deletions(-) diff --git a/src/web/mjs/connectors/PixivComics.mjs b/src/web/mjs/connectors/PixivComics.mjs index 80706a818de..30f0a4f074a 100644 --- a/src/web/mjs/connectors/PixivComics.mjs +++ b/src/web/mjs/connectors/PixivComics.mjs @@ -76,13 +76,141 @@ export default class PixivComics extends Connector { async _getPages(chapter) { const timestamp = new Date().toISOString().replace(/\.\d+Z$/, 'Z'); - const hash = CryptoJS.MD5(timestamp + 'mAtW1X8SzGS880fsjEXlM73QpS1i4kUMBhyhdaYySk8nWz533nrEunaSplg63fzT').toString(CryptoJS.enc.Hex); - const uri = new URL(`episodes/${chapter.id}/read`, this.apiURL); + const hash = CryptoJS.SHA256(timestamp + 'mAtW1X8SzGS880fsjEXlM73QpS1i4kUMBhyhdaYySk8nWz533nrEunaSplg63fzT').toString(CryptoJS.enc.Hex); + const uri = new URL(`episodes/${chapter.id}/read_v3`, this.apiURL); const request = new Request(uri, this.requestOptions); request.headers.set('x-requested-with', 'pixivcomic'); request.headers.set('x-client-time', timestamp); request.headers.set('x-client-hash', hash); const data = await this.fetchJSON(request); - return data.data.reading_episode.pages.map(image => this.createConnectorURI(image.url)); + return data.data.reading_episode.pages.map(image => this.createConnectorURI(image)); } -} \ No newline at end of file + + async _handleConnectorURI(payload) { + let request = new Request(payload.url, this.requestOptions); + request.headers.set('X-Cobalt-Thumber-Parameter-GridShuffle-Key', payload.key); + const res = await fetch(request); + const blob = await res.blob(); + const canvas = await this.descrambleImage(blob, payload); + const blobFinally = await this._canvasToBlob(canvas); + return this._blobToBuffer(blobFinally); + } + + async descrambleImage(blob, payload) { + const image = await createImageBitmap(blob); + const canvas = document.createElement('canvas'); + canvas.width = payload.width; + canvas.height = payload.height; + let ctx = canvas.getContext( '2d' ); + + ctx.drawImage(image, 0, 0); + const i = ctx.getImageData(0, 0, payload.width, payload.height); + const l = await descrambleData(i.data, 4, payload.width, payload.height, payload.gridsize, payload.gridsize, '4wXCKprMMoxnyJ3PocJFs4CYbfnbazNe', payload.key, true ); + const o = new ImageData(l, payload.width, payload.height); + ctx.putImageData(o, 0, 0); + return canvas; + } + + async _canvasToBlob(canvas) { + return new Promise(resolve => { + canvas.toBlob(data => { + resolve(data); + }, Engine.Settings.recompressionFormat.value, parseFloat(Engine.Settings.recompressionQuality.value) / 100); + }); + } +} + +class PixivShuffler { + next() { + let e = 9 * this.tj(5 * this.s[1] >>> 0, 7) >>> 0, + t = this.s[1] << 9 >>> 0; + return this.s[2] = (this.s[2] ^ this.s[0]) >>> 0, + this.s[3] = (this.s[3] ^ this.s[1]) >>> 0, + this.s[1] = (this.s[1] ^ this.s[2]) >>> 0, + this.s[0] = (this.s[0] ^ this.s[3]) >>> 0, + this.s[2] = (this.s[2] ^ t) >>> 0, + this.s[3] = this.tj(this.s[3], 11), + e; + } + constructor(e) { + if (4 !== e.length) throw Error('seed.length !== 4 (seed.length: '.concat(e.length, ')')); + this.s = new Uint32Array(e), + 0 === this.s[0] && + 0 === this.s[1] && + 0 === this.s[2] && + 0 === this.s[3] && + (this.s[0] = 1); + } + tj(e, t) { + return (e << (t %= 32) >>> 0 | e >>> 32 - t) >>> 0; + } + +} + +function convertWordArrayToUint8Array (wordArray) { + var len = wordArray.words.length, + u8_array = new Uint8Array(len << 2), + offset = 0, + word, + i; + for (i = 0; i < len; i++) { + word = wordArray.words[i]; + u8_array[offset++] = word >> 24; + u8_array[offset++] = word >> 16 & 255; + u8_array[offset++] = word >> 8 & 255; + u8_array[offset++] = word & 255; + } + return u8_array; +} + +async function descrambleData(e, t, i, r, n, s, a, l, o) { + + let d = Math.ceil(r / s), + c = Math.floor(i / n), + u = Array(d).fill(null).map(() => Array.from(Array(c).keys())); + { + let e = a +l;//new TextEncoder().encode(a + l); //key + magicvalue to bytes, for use with subtlecrypto SHA256 + let t = convertWordArrayToUint8Array(CryptoJS.SHA256(e)); //subtle.digest('SHA-256", e) => return arraybuffer + let arrayBuffer = t.buffer.slice(t.byteOffset, t.byteLength + t.byteOffset); //we need to properly pass the ArrayBuffer to make the Uint32Array since we used CryptoJS + let i = new Uint32Array(arrayBuffer, 0, 4); + let r = new PixivShuffler(i); + + for (let e = 0; e < 100; e++) r.next(); + for (let e = 0; e < d; e++) { + let t = u[e]; + for (let e = c - 1; e >= 1; e--) { + let i = r.next() % (e + 1), + n = t[e]; + t[e] = t[i], + t[i] = n; + } + } + } + if (o) for (let e = 0; e < d; e++) { + let t = u[e], + i = t.map((e, i) => t.indexOf(i)); + if (i.some(e => e < 0)) throw Error('Failed to reverse shuffle table'); + u[e] = i; + } + let h = new Uint8ClampedArray(e.length); + for (let a = 0; a < r; a++) { + let r = Math.floor(a / s), + l = u[r]; + for (let r = 0; r < c; r++) { + let s = l[r], + o = r * n, + d = (a * i + o) * t, + c = s * n, + u = (a * i + c) * t, + p = n * t; + for (let t = 0; t < p; t++) h[d + t] = e[u + t]; + } + { + let r = c * n, + s = (a * i + r) * t, + l = (a * i + i) * t; + for (let t = s; t < l; t++) h[t] = e[t]; + } + } + return h; +}