From c5ac82ebcf545a28126eb5c4a0cccd0e1c72e25c Mon Sep 17 00:00:00 2001 From: MikeZeDev Date: Wed, 4 Oct 2023 08:22:38 +0000 Subject: [PATCH] FIX XcalibrScans : unscrambling pictures (#224) * FIX XcalibrScans : unscrambling pictures * Update XCaliBRScans.ts * requested changes on Xcalibr --- web/src/engine/websites/XCaliBRScans.ts | 174 ++++++++++++++++---- web/src/engine/websites/XCaliBRScans_e2e.ts | 19 ++- 2 files changed, 151 insertions(+), 42 deletions(-) diff --git a/web/src/engine/websites/XCaliBRScans.ts b/web/src/engine/websites/XCaliBRScans.ts index 18e173b054..974a48f40c 100644 --- a/web/src/engine/websites/XCaliBRScans.ts +++ b/web/src/engine/websites/XCaliBRScans.ts @@ -1,15 +1,55 @@ -// Auto-Generated export from HakuNeko Legacy import { Tags } from '../Tags'; import icon from './XCaliBRScans.webp'; +import { Page, type Chapter } from '../providers/MangaPlugin'; import { DecoratableMangaScraper } from '../providers/MangaPlugin'; import * as MangaStream from './decorators/WordPressMangaStream'; import * as Common from './decorators/Common'; +import { Fetch, FetchRequest, FetchWindowScript } from '../FetchProvider'; +import type { Priority } from '../taskpool/TaskPool'; +import DeProxify from '../transformers/ImageLinkDeProxifier'; -@MangaStream.MangaCSS(/^https?:\/\/xcalibrscans\.com\/manga\/[^/]+\/$/) -@MangaStream.MangasSinglePageCSS() +type pageScriptResult = { + imagz: string[], + scrambled: number +} + +const pagescript = ` + new Promise((resolve, reject) => { + let scramble_method = 0; + if (document.querySelector('div#readerarea div.kage div.sword_box div.sword img')){ + scramble_method = 2; + } + else if (document.querySelector('div#readerarea div.kage img')){ + scramble_method = 1; + }; + if(window.ts_reader && ts_reader.params.sources) { + resolve({ + imagz : ts_reader.params.sources.shift().images, + scrambled : scramble_method + }); + } + else { + setTimeout(() => { + try { + const images = [...document.querySelectorAll('div#readerarea p img')]; + const imgz = images.map(image => image.dataset['lazySrc'] || image.dataset['src'] || image.getAttribute('original') || image.src); + resolve({ + imagz : imgz, + scrambled : scramble_method + }); + } + catch(error) { + reject(error); + } + }, + 2500); + } + }); +`; + +@MangaStream.MangaCSS(/^https?:\/\/xcalibrscans\.com\/webcomics\/manga\/[^/]+\/$/) +@MangaStream.MangasSinglePageCSS(undefined, '/webcomics/manga/list-mode/') @MangaStream.ChaptersSinglePageCSS() -@MangaStream.PagesSinglePageCSS() -@Common.ImageAjax() export default class extends DecoratableMangaScraper { public constructor() { @@ -19,36 +59,104 @@ export default class extends DecoratableMangaScraper { public override get Icon() { return icon; } -} -// Original Source -/* -class XCaliBRScans extends WordPressMangastream { - - constructor() { - super(); - super.id = 'xcalibrscans'; - super.label = 'xCaliBR Scans'; - this.tags = [ 'webtoon', 'english' ]; - this.url = 'https://xcalibrscans.com'; - this.path = '/manga/list-mode/'; - this.requestOptions.headers.set('x-referer', this.url); - - this.config = { - throttle: { - label: 'Throttle Requests [ms]', - description: 'Enter the timespan in [ms] to delay consecuitive HTTP requests.\nThe website may block images for to many consecuitive requests.', - input: 'numeric', - min: 1000, - max: 7500, - value: 1500 + public override async FetchPages(chapter: Chapter): Promise { + + //There are 3 different type of chapters scrambling + // Nothing : https://xcalibrscans.com/the-first-ancestor-in-history-chapter-1/ + // div#readerarea p img + // 0 + // flipped single : https://xcalibrscans.com/the-first-ancestor-in-history-chapter-86/ + // div#readerarea div.kage img + // 1 + // flipped and splitted : https://xcalibrscans.com/above-ten-thousand-people-chapter-175/ + // div#readerarea div.kage div.sword_box div.sword img + // 2 + + const uri = new URL(chapter.Identifier, this.URI); + const request = new FetchRequest(uri.href); + const data = await FetchWindowScript(request, pagescript, 2500); + const piclist = data.imagz.map(link => DeProxify(new URL(link)).href); + switch (data.scrambled) { + + case 0: + case 1: //Flip each picture, no grouping + return piclist.map(pic => { + return new Page(this, chapter, new URL(pic, this.URI), { scrambled: data.scrambled }); + }); + case 2: {//Flip and group by 2 + let count = 0; + const pages = []; + + //create one Page for each 2 pictures + for (let i = 0; i < piclist.length - 1; i += 2) { + const parameters = { scrambled: data.scrambled, secondaryPic: piclist[i + 1] }; + const page = new Page(this, chapter, new URL(piclist[i], this.URI), parameters); + pages.push(page); + count += 2; + } + //get remaining picture if number was odd + if (count < piclist.length) { + pages.push(new Page(this, chapter, new URL(piclist[count], this.URI), { scrambled: 0 })); + } + return pages; } - }; + default: + } } - async _getPages(chapter) { - const images = await super._getPages(chapter); - return images.map(image => this.createConnectorURI(image)); + public override async FetchImage(page: Page, priority: Priority, signal: AbortSignal): Promise { + const blobMainImage = await Common.FetchImage.call(this, page, priority, signal); + const bitmaps: ImageBitmap[] = []; + + switch (page.Parameters.scrambled) { + case 0: return blobMainImage; //No scrambling, return image + case 1: //Flip picture + return await this.flipPicture(await createImageBitmap(blobMainImage)); + case 2://Combine/Flip 2 pictures + { + bitmaps.push(await createImageBitmap(blobMainImage)); + //fetch second image + const pageUrl = (page.Parameters.secondaryPic) as string; + const request = new FetchRequest(pageUrl, { referrer: this.URI.href }); + const response = await Fetch(request); + const data = await response.blob(); + bitmaps.push(await createImageBitmap(data)); + return await this.composePuzzle(bitmaps); + } + default : + } + } -} -*/ \ No newline at end of file + + async flipPicture(bitmap: ImageBitmap): Promise { + return new Promise(resolve => { + const canvas = document.createElement('canvas'); + canvas.width = bitmap.width; + canvas.height = bitmap.height; + const ctx = canvas.getContext('2d'); + ctx.scale(-1, 1); + ctx.drawImage(bitmap, 0, 0, -bitmap.width, bitmap.height); + canvas.toBlob(data => { + resolve(data); + }, 'image/png', parseFloat('90') / 100); + }); + } + + async composePuzzle(bitmaps: ImageBitmap[]): Promise { + return new Promise(resolve => { + const canvas = document.createElement('canvas'); + const b1 = bitmaps[0]; + const b2 = bitmaps[1]; + canvas.width = b1.width + b2.width; + canvas.height = b1.height; + const ctx = canvas.getContext('2d'); + ctx.scale(-1, 1); + ctx.drawImage(b2, 0, 0, -b2.width, b2.height); + ctx.drawImage(b1, -b2.width, 0, -b1.width, b1.height); + canvas.toBlob(data => { + resolve(data); + }, 'image/png', parseFloat('90') / 100); + }); + } +} \ No newline at end of file diff --git a/web/src/engine/websites/XCaliBRScans_e2e.ts b/web/src/engine/websites/XCaliBRScans_e2e.ts index 2ec25c9511..3a02f77093 100644 --- a/web/src/engine/websites/XCaliBRScans_e2e.ts +++ b/web/src/engine/websites/XCaliBRScans_e2e.ts @@ -4,21 +4,22 @@ const config: Config = { plugin: { id: 'xcalibrscans', title: 'xCaliBR Scans' - }/*, + }, container: { - url: 'https://xcalibrscans.com/manga/.../', - id: '/manga/.../', - title: 'Manga ?' + url: 'https://xcalibrscans.com/webcomics/manga/above-ten-thousand-people/', + id: '/webcomics/manga/above-ten-thousand-people/', + title: 'Above Ten Thousand' }, child: { - id: '/manga/.../.../', - title: 'Chapter ?' + id: '/above-ten-thousand-people-chapter-175/', + title: 'Chapter 175', + timeout : 15000 }, entry: { index: 0, - size: -1, - type: 'image/jpeg' - }*/ + size: 5_771_792, + type: 'image/png' + } }; const fixture = new TestFixture(config);