From e8263965f572939576210333a549d749069e1ca7 Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Mon, 11 Dec 2023 17:40:37 +0100 Subject: [PATCH 01/31] add HeanCMS decorator and update YugenMangaES --- web/src/engine/websites/YugenMangasES.ts | 15 +- web/src/engine/websites/decorators/HeanCMS.ts | 276 ++++++++++++++++++ 2 files changed, 283 insertions(+), 8 deletions(-) create mode 100644 web/src/engine/websites/decorators/HeanCMS.ts diff --git a/web/src/engine/websites/YugenMangasES.ts b/web/src/engine/websites/YugenMangasES.ts index f3b1374639..1d7fc5d686 100755 --- a/web/src/engine/websites/YugenMangasES.ts +++ b/web/src/engine/websites/YugenMangasES.ts @@ -1,16 +1,15 @@ import { Tags } from '../Tags'; import icon from './YugenMangasES.webp'; import { DecoratableMangaScraper } from '../providers/MangaPlugin'; -import * as Madara from './decorators/WordPressMadara'; -import * as Common from './decorators/Common'; +import * as HeamCMS from './decorators/HeanCMS'; -//TODO: needs to be recoded with novel support && Yugenmanga API +const apiUrl = 'https://api.yugenmangas.net'; -@Madara.MangaCSS(/^{origin}\/series\/[^/]+\/$/) -@Madara.MangasMultiPageAJAX() -@Madara.ChaptersSinglePageAJAXv2() -@Madara.PagesSinglePageCSS() -@Common.ImageAjax() +@HeamCMS.MangaCSS(/^{origin}\/series\/[^/]+$/, apiUrl) +@HeamCMS.MangasMultiPageAJAX(apiUrl) +@HeamCMS.ChaptersSinglePageAJAX(apiUrl) +@HeamCMS.PagesSinglePageAJAX(apiUrl) +@HeamCMS.ImageAjax() export default class extends DecoratableMangaScraper { public constructor() { diff --git a/web/src/engine/websites/decorators/HeanCMS.ts b/web/src/engine/websites/decorators/HeanCMS.ts new file mode 100644 index 0000000000..213b36aa67 --- /dev/null +++ b/web/src/engine/websites/decorators/HeanCMS.ts @@ -0,0 +1,276 @@ +import { FetchRequest, FetchJSON, FetchWindowScript } from '../../FetchProvider'; +import { type MangaScraper, type MangaPlugin, Manga, Chapter, Page } from '../../providers/MangaPlugin'; +import type { Priority } from '../../taskpool/TaskPool'; +import * as Common from './Common'; + +//TODO: get novel color theme from settings and apply them to the script somehow. Setting must ofc have been initializated before +//add a listener on the aforementionned setting + +const DefaultNovelScript = ` + new Promise((resolve, reject) => { + document.body.style.width = '56em'; + let container = document.querySelector('div.container'); + container.style.maxWidth = '56em'; + container.style.padding = '0'; + container.style.margin = '0'; + let novel = document.querySelector('div#reader-container'); + novel.style.padding = '1.5em'; + [...novel.querySelectorAll(":not(:empty)")].forEach(ele => { + ele.style.backgroundColor = 'black' + ele.style.color = 'white' + }) + novel.style.backgroundColor = 'black' + novel.style.color = 'white' + let script = document.createElement('script'); + script.onerror = error => reject(error); + script.onload = async function() { + try { + let canvas = await html2canvas(novel); + resolve([canvas.toDataURL('image/png')]); + } catch (error){ + reject(error) + } + } + script.src = 'https://html2canvas.hertzen.com/dist/html2canvas.min.js'; + document.body.appendChild(script); + }); + `; + +type APIManga = { + title: string + series_type: 'Comic' | 'Novel', + series_slug: string, + seasons? : APISeason[] +} + +type APIResult<T> ={ + data : T[] +} + +type APISeason = { + index: number, + chapters: APIChapter[] +} + +type APIChapter = { + index: string, + chapter_name: string, + chapter_title: string, + chapter_slug: string, +} + +type APIPages = { + chapter_type: 'Comic' | 'Novel', + paywall: boolean, + data: string[] | string +} + +/*************************************************** + ******** Manga from URL Extraction Methods ******** + ***************************************************/ + +/** + * An extension method for extracting a single manga from the given {@link url} using the HeanCMS api url {@link apiUrl}. + * @param this - A reference to the {@link MangaScraper} instance which will be used as context for this method + * @param provider - A reference to the {@link MangaPlugin} which shall be assigned as parent for the extracted manga + * @param url - the manga url + * @param apiUrl - The url of the HeanCMS api for the website + */ +export async function FetchMangaCSS(this: MangaScraper, provider: MangaPlugin, url: string, apiUrl: string): Promise<Manga> { + const slug = new URL(url).pathname.split('/')[2]; + const request = new FetchRequest(new URL(`/series/${slug}`, apiUrl).href); + const { title, series_slug } = await FetchJSON<APIManga>(request); + return new Manga(this, provider, series_slug, title); +} + +/** + * An extension method for extracting a single manga from any url using the HeanCMS api url {@link apiUrl}. + * @param pattern - An expression to check if a manga can be extracted from an url or not, it may contain the placeholders `{origin}` and `{hostname}` which will be replaced with the corresponding parameters based on the website's base URL + * @param apiUrl - The url of the HeanCMS api for the website + */ +export function MangaCSS(pattern: RegExp, apiURL: string) { + return function DecorateClass<T extends Common.Constructor>(ctor: T, context?: ClassDecoratorContext): T { + Common.ThrowOnUnsupportedDecoratorContext(context); + return class extends ctor { + public ValidateMangaURL(this: MangaScraper, url: string): boolean { + const source = pattern.source.replaceAll('{origin}', this.URI.origin).replaceAll('{hostname}', this.URI.hostname); + return new RegExp(source, pattern.flags).test(url); + } + public async FetchManga(this: MangaScraper, provider: MangaPlugin, url: string): Promise<Manga> { + return FetchMangaCSS.call(this, provider, url, apiURL); + } + }; + }; +} + +/*********************************************** + ******** Manga List Extraction Methods ******** + ***********************************************/ + +/** + * An extension method for extracting multiple mangas using the HeanCMS api url {@link apiUrl}. + * The range begins with 1 and is incremented until no more new mangas can be extracted. + * @param this - A reference to the {@link MangaScraper} instance which will be used as context for this method + * @param provider - A reference to the {@link MangaPlugin} which shall be assigned as parent for the extracted mangas + * @param apiUrl - The url of the HeanCMS api for the website + * @param throttle - A delay [ms] for each request (only required for rate-limited websites) + */ +export async function FetchMangasMultiPageAJAX(this: MangaScraper, provider: MangaPlugin, apiUrl: string, throttle = 0): Promise<Manga[]> { + const mangaList: Manga[] = []; + for (let page = 1, run = true; run; page++) { + const mangas = await getMangaFromPage.call(this, provider, page, apiUrl); + mangas.length > 0 ? mangaList.push(...mangas) : run = false; + await new Promise(resolve => setTimeout(resolve, throttle)); + } + return mangaList; +} + +async function getMangaFromPage(this: MangaScraper, provider: MangaPlugin, page: number, apiUrl: string): Promise<Manga[]> { + const request = new FetchRequest(new URL(`/query?series_type=All&order=asc&perPage=100&page=${page}`, apiUrl).href); + const { data } = await FetchJSON<APIResult<APIManga>>(request); + if (data.length) { + return data.map((manga) => new Manga(this, provider, manga.series_slug, manga.title)); + } + return []; +} + +/** + * A class decorator that adds the ability to extract multiple mangas from a range of pages using the HeanCMS api url {@link apiUrl}. + * @param apiUrl - The url of the HeanCMS api for the website + * @param throttle - A delay [ms] for each request (only required for rate-limited websites) + */ +export function MangasMultiPageAJAX(apiUrl : string, throttle = 0) { + return function DecorateClass<T extends Common.Constructor>(ctor: T, context?: ClassDecoratorContext): T { + Common.ThrowOnUnsupportedDecoratorContext(context); + return class extends ctor { + public async FetchMangas(this: MangaScraper, provider: MangaPlugin): Promise<Manga[]> { + return FetchMangasMultiPageAJAX.call(this, provider, apiUrl, throttle); + } + }; + }; +} + +/************************************************* + ******** Chapter List Extraction Methods ******** + *************************************************/ + +/** + * An extension method for extracting all chapters for the given {@link manga} using the HeanCMS api url {@link apiUrl}. + * @param this - A reference to the {@link MangaScraper} instance which will be used as context for this method + * @param manga - A reference to the {@link Manga} which shall be assigned as parent for the extracted chapters + * @param apiUrl - The url of the HeanCMS api for the website + */ +export async function FetchChaptersSinglePageAJAX(this: MangaScraper, manga: Manga, apiUrl: string): Promise<Chapter[]> { + const request = new FetchRequest(new URL(`/series/${manga.Identifier}`, apiUrl).href); + const { seasons } = await FetchJSON<APIManga>(request); + const chapterList: Chapter[] = []; + + seasons.map((season) => season.chapters.map((chapter) => { + const id = JSON.stringify({ + series: manga.Identifier, + chapter: chapter.chapter_slug + }); + const title = `${seasons.length > 1 ? 'S' + season.index : ''} ${chapter.chapter_name} ${chapter.chapter_title || ''}`.trim(); + chapterList.push(new Chapter(this, manga, id, title)); + })); + return chapterList; +} + +/** + * A class decorator that adds the ability to extract all chapters for a given manga from this website using the HeanCMS api url {@link apiUrl}. + * @param apiUrl - The url of the HeanCMS api for the website + */ +export function ChaptersSinglePageAJAX(apiUrl: string) { + return function DecorateClass<T extends Common.Constructor>(ctor: T, context?: ClassDecoratorContext): T { + Common.ThrowOnUnsupportedDecoratorContext(context); + return class extends ctor { + public async FetchChapters(this: MangaScraper, manga: Manga): Promise<Chapter[]> { + return FetchChaptersSinglePageAJAX.call(this, manga, apiUrl); + } + }; + }; +} + +/********************************************** + ******** Page List Extraction Methods ******** + **********************************************/ +/** + * An extension method for extracting all pages for the given {@link chapter} using the HeanCMS api url {@link apiUrl}. + * @param this - A reference to the {@link MangaScraper} instance which will be used as context for this method + * @param chapter - A reference to the {@link Chapter} which shall be assigned as parent for the extracted pages + * @param apiUrl - The url of the HeanCMS api for the website + */ +export async function FetchPagesSinglePageAJAX(this: MangaScraper, chapter: Chapter, apiUrl: string ): Promise<Page[]> { + const id = JSON.parse(chapter.Identifier); + const request = new FetchRequest(new URL(`/chapter/${id.series}/${id.chapter}`, apiUrl).href); + const { chapter_type, data, paywall } = await FetchJSON<APIPages>(request); + + // check for paywall + if (data.length < 1 && paywall) { + throw new Error(`${chapter.Title} is paywalled. Please login.`); //localize this + } + + // check if novel + if (chapter_type.toLowerCase() === 'novel') { + return [new Page(this, chapter, new URL(`/series/${id.series}/${id.chapter}`, this.URI), { type: chapter_type })]; + } + + return (data as string[]).map(image => new Page(this, chapter, new URL(image), { type: chapter_type })); +} + +/** + * A class decorator that adds the ability to extract all pages for a given chapter using the HeanCMS api url {@link apiUrl}. + * @param apiUrl - The url of the HeanCMS api for the website + */ +export function PagesSinglePageAJAX(apiUrl: string) { + return function DecorateClass<T extends Common.Constructor>(ctor: T, context?: ClassDecoratorContext): T { + Common.ThrowOnUnsupportedDecoratorContext(context); + return class extends ctor { + public async FetchPages(this: MangaScraper, chapter: Chapter): Promise<Page[]> { + return FetchPagesSinglePageAJAX.call(this, chapter, apiUrl); + } + }; + }; +} + +/*********************************************** + ******** Image Data Extraction Methods ******** + ***********************************************/ + +/** + * An extension method to get the image data for the given {@link page} according to an XHR based-approach. + * @param this - A reference to the {@link MangaScraper} instance which will be used as context for this method + * @param page - A reference to the {@link Page} containing the necessary information to acquire the image data + * @param priority - The importance level for ordering the request for the image data within the internal task pool + * @param signal - An abort signal that can be used to cancel the request for the image data + * @param detectMimeType - Force a fingerprint check of the image data to detect its mime-type (instead of relying on the Content-Type header) + * @param deProxifyLink - Remove common image proxies (default false) + * @param novelScript - a custom script to get and transform the novel text into a dataURL + */ +export async function FetchImageAjax(this: MangaScraper, page: Page, priority: Priority, signal: AbortSignal, detectMimeType = false, deProxifyLink = true, novelScript = DefaultNovelScript): Promise<Blob> { + if (page.Parameters?.type as string === 'Comic') { + return Common.FetchImageAjax.call(this, page, priority, signal, detectMimeType, deProxifyLink); + } else { + //TODO: test if used want to export the NOVEL as HTML? + + const request = new FetchRequest(page.Link.href); + const data = await FetchWindowScript<string>(request, novelScript, 500, 10000); + return Common.FetchImageAjax.call(this, new Page(this, page.Parent as Chapter, new URL(data)), priority, signal, false, false); + } +} + +/** + * A class decorator that adds the ability to get the image data for a given page by loading the source asynchronous with the `Fetch API`. + * @param detectMimeType - Force a fingerprint check of the image data to detect its mime-type (instead of relying on the Content-Type header) + * @param deProxifyLink - Remove common image proxies (default false) + */ +export function ImageAjax(detectMimeType = false, deProxifyLink = true, novelScript: string = DefaultNovelScript) { + return function DecorateClass<T extends Common.Constructor>(ctor: T, context?: ClassDecoratorContext): T { + Common.ThrowOnUnsupportedDecoratorContext(context); + return class extends ctor { + public async FetchImage(this: MangaScraper, page: Page, priority: Priority, signal: AbortSignal): Promise<Blob> { + return FetchImageAjax.call(this, page, priority, signal, detectMimeType, deProxifyLink, novelScript); + } + }; + }; +} \ No newline at end of file From 2b679f91659d5ee52868c3f3b5c52c67348b27b6 Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Mon, 11 Dec 2023 18:15:00 +0100 Subject: [PATCH 02/31] small fixes and add tests --- web/src/engine/websites/YugenMangasES.ts | 2 +- web/src/engine/websites/YugenMangasES_e2e.ts | 46 ++++++++++++++----- web/src/engine/websites/decorators/HeanCMS.ts | 12 ++--- 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/web/src/engine/websites/YugenMangasES.ts b/web/src/engine/websites/YugenMangasES.ts index 1d7fc5d686..ec61d4c42d 100755 --- a/web/src/engine/websites/YugenMangasES.ts +++ b/web/src/engine/websites/YugenMangasES.ts @@ -9,7 +9,7 @@ const apiUrl = 'https://api.yugenmangas.net'; @HeamCMS.MangasMultiPageAJAX(apiUrl) @HeamCMS.ChaptersSinglePageAJAX(apiUrl) @HeamCMS.PagesSinglePageAJAX(apiUrl) -@HeamCMS.ImageAjax() +@HeamCMS.ImageAjax(true) export default class extends DecoratableMangaScraper { public constructor() { diff --git a/web/src/engine/websites/YugenMangasES_e2e.ts b/web/src/engine/websites/YugenMangasES_e2e.ts index cdba38882e..28faa8fe52 100755 --- a/web/src/engine/websites/YugenMangasES_e2e.ts +++ b/web/src/engine/websites/YugenMangasES_e2e.ts @@ -1,25 +1,49 @@ -import { TestFixture } from '../../../test/WebsitesFixture'; +import { TestFixture } from '../../../test/WebsitesFixture'; -const config = { +const ComicConfig = { plugin: { id: 'yugenmangas-es', title: 'YugenMangas (ES)' }, container: { - url: 'https://yugenmangas.com/series/demon-king-cheat-system/', - id: JSON.stringify({ post: '18638', slug: '/series/demon-king-cheat-system/' }), - title: 'Demon King Cheat System' + url: 'https://yugenmangas.lat/series/la-gran-duquesa-del-norte-era-una-villana-en-secreto', + id: 'la-gran-duquesa-del-norte-era-una-villana-en-secreto', + title: 'La Gran Duquesa Del Norte Era Una Villana En Secreto' }, child: { - id: '/series/demon-king-cheat-system/capitulo/', - title: 'Capitulo' + id: 'capitulo-102', + title: 'Capitulo 102 FINAL' }, entry: { - index: 0, - size: 772_248, + index: 1, + size: 3_064_669, type: 'image/jpeg' } }; -const fixture = new TestFixture(config); -describe(fixture.Name, () => fixture.AssertWebsite()); \ No newline at end of file +const ComicFixture = new TestFixture(ComicConfig); +describe(ComicFixture.Name, () => ComicFixture.AssertWebsite()); + +const NovelConfig = { + plugin: { + id: 'yugenmangas-es', + title: 'YugenMangas (ES)' + }, + container: { + url: 'https://yugenmangas.lat/series/esposo-villano-deberias-estar-obsesionado-con-esa-persona-', + id: 'esposo-villano-deberias-estar-obsesionado-con-esa-persona-', + title: 'Esposo villano, deberías estar obsesionado con esa persona.' + }, + child: { + id: 'capitulo-1', + title: 'Capítulo 1' + }, + entry: { + index: 0, + size: 556_399, + type: 'image/png' + } +}; + +const NovelFixture = new TestFixture(NovelConfig); +describe(NovelFixture.Name, () => NovelFixture.AssertWebsite()); \ No newline at end of file diff --git a/web/src/engine/websites/decorators/HeanCMS.ts b/web/src/engine/websites/decorators/HeanCMS.ts index 213b36aa67..3c6de2a437 100644 --- a/web/src/engine/websites/decorators/HeanCMS.ts +++ b/web/src/engine/websites/decorators/HeanCMS.ts @@ -166,10 +166,7 @@ export async function FetchChaptersSinglePageAJAX(this: MangaScraper, manga: Man const chapterList: Chapter[] = []; seasons.map((season) => season.chapters.map((chapter) => { - const id = JSON.stringify({ - series: manga.Identifier, - chapter: chapter.chapter_slug - }); + const id = chapter.chapter_slug; const title = `${seasons.length > 1 ? 'S' + season.index : ''} ${chapter.chapter_name} ${chapter.chapter_title || ''}`.trim(); chapterList.push(new Chapter(this, manga, id, title)); })); @@ -201,8 +198,7 @@ export function ChaptersSinglePageAJAX(apiUrl: string) { * @param apiUrl - The url of the HeanCMS api for the website */ export async function FetchPagesSinglePageAJAX(this: MangaScraper, chapter: Chapter, apiUrl: string ): Promise<Page[]> { - const id = JSON.parse(chapter.Identifier); - const request = new FetchRequest(new URL(`/chapter/${id.series}/${id.chapter}`, apiUrl).href); + const request = new FetchRequest(new URL(`/chapter/${chapter.Parent.Identifier}/${chapter.Identifier}`, apiUrl).href); const { chapter_type, data, paywall } = await FetchJSON<APIPages>(request); // check for paywall @@ -212,7 +208,7 @@ export async function FetchPagesSinglePageAJAX(this: MangaScraper, chapter: Chap // check if novel if (chapter_type.toLowerCase() === 'novel') { - return [new Page(this, chapter, new URL(`/series/${id.series}/${id.chapter}`, this.URI), { type: chapter_type })]; + return [new Page(this, chapter, new URL(`/series/${chapter.Parent.Identifier}/${chapter.Identifier}`, this.URI), { type: chapter_type })]; } return (data as string[]).map(image => new Page(this, chapter, new URL(image), { type: chapter_type })); @@ -254,7 +250,7 @@ export async function FetchImageAjax(this: MangaScraper, page: Page, priority: P //TODO: test if used want to export the NOVEL as HTML? const request = new FetchRequest(page.Link.href); - const data = await FetchWindowScript<string>(request, novelScript, 500, 10000); + const data = await FetchWindowScript<string>(request, novelScript, 1000, 10000); return Common.FetchImageAjax.call(this, new Page(this, page.Parent as Chapter, new URL(data)), priority, signal, false, false); } } From e91776a57fa269721cbc4193a52e7771b2aaef50 Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Mon, 11 Dec 2023 18:42:01 +0100 Subject: [PATCH 03/31] add PerfScan --- web/src/engine/websites/PerfScan.ts | 22 ++++++++++ web/src/engine/websites/PerfScan.webp | Bin 0 -> 2696 bytes web/src/engine/websites/PerfScan_e2e.ts | 49 +++++++++++++++++++++++ web/src/engine/websites/YugenMangasES.ts | 2 +- web/src/engine/websites/_index.ts | 1 + 5 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 web/src/engine/websites/PerfScan.ts create mode 100644 web/src/engine/websites/PerfScan.webp create mode 100644 web/src/engine/websites/PerfScan_e2e.ts diff --git a/web/src/engine/websites/PerfScan.ts b/web/src/engine/websites/PerfScan.ts new file mode 100644 index 0000000000..afbf8d1fe1 --- /dev/null +++ b/web/src/engine/websites/PerfScan.ts @@ -0,0 +1,22 @@ +import { Tags } from '../Tags'; +import icon from './PerfScan.webp'; +import { DecoratableMangaScraper } from '../providers/MangaPlugin'; +import * as HeamCMS from './decorators/HeanCMS'; + +const apiUrl = 'https://api.perf-scan.fr'; + +@HeamCMS.MangaCSS(/^{origin}\/series\/[^/]+$/, apiUrl) +@HeamCMS.MangasMultiPageAJAX(apiUrl) +@HeamCMS.ChaptersSinglePageAJAX(apiUrl) +@HeamCMS.PagesSinglePageAJAX(apiUrl) +@HeamCMS.ImageAjax() +export default class extends DecoratableMangaScraper { + + public constructor() { + super('perfscan', 'Perf Scan', 'https://perf-scan.fr', Tags.Media.Manga, Tags.Media.Manhwa, Tags.Media.Manhua, Tags.Media.Novel, Tags.Language.French, Tags.Source.Scanlator); + } + + public override get Icon() { + return icon; + } +} \ No newline at end of file diff --git a/web/src/engine/websites/PerfScan.webp b/web/src/engine/websites/PerfScan.webp new file mode 100644 index 0000000000000000000000000000000000000000..c8da0f7c97160254eb71cb1b774231e6853489c0 GIT binary patch literal 2696 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 CP(E7# literal 0 HcmV?d00001 diff --git a/web/src/engine/websites/PerfScan_e2e.ts b/web/src/engine/websites/PerfScan_e2e.ts new file mode 100644 index 0000000000..05a2b334b7 --- /dev/null +++ b/web/src/engine/websites/PerfScan_e2e.ts @@ -0,0 +1,49 @@ +import { TestFixture } from '../../../test/WebsitesFixture'; + +const ComicConfig = { + plugin: { + id: 'perfscan', + title: 'Perf Scan' + }, + container: { + url: 'https://perf-scan.fr/series/martial-peak-1702249200756', + id: 'martial-peak-1702249200756', + title: 'Martial Peak' + }, + child: { + id: 'chapitre-1298', + title: 'S6 Chapitre 1298' + }, + entry: { + index: 2, + size: 215_840, + type: 'image/webp' + } +}; + +const ComicFixture = new TestFixture(ComicConfig); +describe(ComicFixture.Name, () => ComicFixture.AssertWebsite()); + +const NovelConfig = { + plugin: { + id: 'perfscan', + title: 'Perf Scan' + }, + container: { + url: 'https://perf-scan.fr/series/demonic-emperor-novel-1702249200676', + id: 'demonic-emperor-novel-1702249200676', + title: 'Demonic emperor - Novel' + }, + child: { + id: 'chapitre-492', + title: 'S4 Chapitre 492' + }, + entry: { + index: 0, + size: 789_130, + type: 'image/png' + } +}; + +const NovelFixture = new TestFixture(NovelConfig); +describe(NovelFixture.Name, () => NovelFixture.AssertWebsite()); \ No newline at end of file diff --git a/web/src/engine/websites/YugenMangasES.ts b/web/src/engine/websites/YugenMangasES.ts index ec61d4c42d..6d07fcd83a 100755 --- a/web/src/engine/websites/YugenMangasES.ts +++ b/web/src/engine/websites/YugenMangasES.ts @@ -13,7 +13,7 @@ const apiUrl = 'https://api.yugenmangas.net'; export default class extends DecoratableMangaScraper { public constructor() { - super('yugenmangas-es', 'YugenMangas (ES)', 'https://yugenmangas.lat', Tags.Media.Manga, Tags.Media.Manhwa, Tags.Media.Manhua, Tags.Language.Spanish); + super('yugenmangas-es', 'YugenMangas (ES)', 'https://yugenmangas.lat', Tags.Media.Manga, Tags.Media.Manhwa, Tags.Media.Manhua, Tags.Media.Novel, Tags.Language.Spanish, Tags.Source.Aggregator); } public override get Icon() { diff --git a/web/src/engine/websites/_index.ts b/web/src/engine/websites/_index.ts index 9118a80ff0..68f425ae82 100755 --- a/web/src/engine/websites/_index.ts +++ b/web/src/engine/websites/_index.ts @@ -425,6 +425,7 @@ export { default as PatyScans } from './PatyScans'; export { default as PCNet } from './PCNet'; export { default as PelaTeam } from './PelaTeam'; export { default as Penlab } from './Penlab'; +export { default as PerfScan } from './PerfScan'; export { default as PhoenixScansIT } from './PhoenixScansIT'; export { default as Piccoma } from './Piccoma'; export { default as PiccomaFR } from './PiccomaFR'; From a7aeaf643c3206df0511f4fcf58402f53d2acc26 Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Mon, 11 Dec 2023 22:22:37 +0100 Subject: [PATCH 04/31] add OmegaScans --- web/src/engine/websites/OmegaScans.ts | 22 ++++++++ web/src/engine/websites/OmegaScans.webp | Bin 0 -> 2362 bytes web/src/engine/websites/OmegaScans_e2e.ts | 49 ++++++++++++++++++ web/src/engine/websites/_index.ts | 1 + web/src/engine/websites/decorators/HeanCMS.ts | 2 +- 5 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 web/src/engine/websites/OmegaScans.ts create mode 100644 web/src/engine/websites/OmegaScans.webp create mode 100644 web/src/engine/websites/OmegaScans_e2e.ts diff --git a/web/src/engine/websites/OmegaScans.ts b/web/src/engine/websites/OmegaScans.ts new file mode 100644 index 0000000000..c944d06f25 --- /dev/null +++ b/web/src/engine/websites/OmegaScans.ts @@ -0,0 +1,22 @@ +import { Tags } from '../Tags'; +import icon from './OmegaScans.webp'; +import { DecoratableMangaScraper } from '../providers/MangaPlugin'; +import * as HeamCMS from './decorators/HeanCMS'; + +const apiUrl = 'https://api.omegascans.org'; + +@HeamCMS.MangaCSS(/^{origin}\/series\/[^/]+$/, apiUrl) +@HeamCMS.MangasMultiPageAJAX(apiUrl) +@HeamCMS.ChaptersSinglePageAJAX(apiUrl) +@HeamCMS.PagesSinglePageAJAX(apiUrl) +@HeamCMS.ImageAjax(true) +export default class extends DecoratableMangaScraper { + + public constructor() { + super('omegascans', 'OmegaScans', 'https://omegascans.org', Tags.Media.Manga, Tags.Media.Manhwa, Tags.Media.Manhua, Tags.Media.Novel, Tags.Language.English, Tags.Source.Scanlator, Tags.Rating.Pornographic); + } + + public override get Icon() { + return icon; + } +} \ No newline at end of file diff --git a/web/src/engine/websites/OmegaScans.webp b/web/src/engine/websites/OmegaScans.webp new file mode 100644 index 0000000000000000000000000000000000000000..4a9338b49051dab4fba3ac4d9c558eb81fc7f6fe GIT binary patch literal 2362 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 literal 0 HcmV?d00001 diff --git a/web/src/engine/websites/OmegaScans_e2e.ts b/web/src/engine/websites/OmegaScans_e2e.ts new file mode 100644 index 0000000000..3b71966b5c --- /dev/null +++ b/web/src/engine/websites/OmegaScans_e2e.ts @@ -0,0 +1,49 @@ +import { TestFixture } from '../../../test/WebsitesFixture'; + +const ComicConfig = { + plugin: { + id: 'omegascans', + title: 'OmegaScans' + }, + container: { + url: 'https://omegascans.org/series/trapped-in-the-academys-eroge', + id: 'trapped-in-the-academys-eroge', + title: `Trapped in the Academy's Eroge` + }, + child: { + id: 'chapter-76', + title: 'Chapter 76' + }, + entry: { + index: 1, + size: 1_577_008, + type: 'image/jpeg' + } +}; + +const ComicFixture = new TestFixture(ComicConfig); +describe(ComicFixture.Name, () => ComicFixture.AssertWebsite()); + +const NovelConfig = { + plugin: { + id: 'omegascans', + title: 'OmegaScans' + }, + container: { + url: 'https://omegascans.org/series/the-scion-of-the-labyrinth-city', + id: 'the-scion-of-the-labyrinth-city', + title: 'The Scion of the Labyrinth City' + }, + child: { + id: 'chapter-28', + title: 'Chapter 28' + }, + entry: { + index: 0, + size: 892_312, + type: 'image/png' + } +}; + +const NovelFixture = new TestFixture(NovelConfig); +describe(NovelFixture.Name, () => NovelFixture.AssertWebsite()); \ No newline at end of file diff --git a/web/src/engine/websites/_index.ts b/web/src/engine/websites/_index.ts index 68f425ae82..0835f5259a 100755 --- a/web/src/engine/websites/_index.ts +++ b/web/src/engine/websites/_index.ts @@ -415,6 +415,7 @@ export { default as NoraNoFansub } from './NoraNoFansub'; export { default as Noromax } from './Noromax'; export { default as NovelMic } from './NovelMic'; export { default as OlympusScanlation } from './OlympusScanlation'; +export { default as OmegaScans } from './OmegaScans'; export { default as OnMangaMe } from './OnMangaMe'; export { default as Opiatoon } from './Opiatoon'; export { default as Oremanga } from './Oremanga'; diff --git a/web/src/engine/websites/decorators/HeanCMS.ts b/web/src/engine/websites/decorators/HeanCMS.ts index 3c6de2a437..f706933207 100644 --- a/web/src/engine/websites/decorators/HeanCMS.ts +++ b/web/src/engine/websites/decorators/HeanCMS.ts @@ -247,7 +247,7 @@ export async function FetchImageAjax(this: MangaScraper, page: Page, priority: P if (page.Parameters?.type as string === 'Comic') { return Common.FetchImageAjax.call(this, page, priority, signal, detectMimeType, deProxifyLink); } else { - //TODO: test if used want to export the NOVEL as HTML? + //TODO: test if user want to export the NOVEL as HTML? const request = new FetchRequest(page.Link.href); const data = await FetchWindowScript<string>(request, novelScript, 1000, 10000); From 237c012a1c3402a3193e04146d30c46bbdc2ca82 Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Mon, 11 Dec 2023 22:34:52 +0100 Subject: [PATCH 05/31] add TempleScan --- web/src/engine/websites/TempleScan.ts | 22 +++++++++++++++++++ web/src/engine/websites/TempleScan.webp | Bin 0 -> 1950 bytes web/src/engine/websites/TempleScan_e2e.ts | 25 ++++++++++++++++++++++ web/src/engine/websites/_index.ts | 1 + 4 files changed, 48 insertions(+) create mode 100644 web/src/engine/websites/TempleScan.ts create mode 100644 web/src/engine/websites/TempleScan.webp create mode 100644 web/src/engine/websites/TempleScan_e2e.ts diff --git a/web/src/engine/websites/TempleScan.ts b/web/src/engine/websites/TempleScan.ts new file mode 100644 index 0000000000..bf1f86ab88 --- /dev/null +++ b/web/src/engine/websites/TempleScan.ts @@ -0,0 +1,22 @@ +import { Tags } from '../Tags'; +import icon from './TempleScan.webp'; +import { DecoratableMangaScraper } from '../providers/MangaPlugin'; +import * as HeamCMS from './decorators/HeanCMS'; + +const apiUrl = 'https://api.templescan.net'; + +@HeamCMS.MangaCSS(/^{origin}\/comic\/[^/]+$/, apiUrl) +@HeamCMS.MangasMultiPageAJAX(apiUrl) +@HeamCMS.ChaptersSinglePageAJAX(apiUrl) +@HeamCMS.PagesSinglePageAJAX(apiUrl) +@HeamCMS.ImageAjax(true) +export default class extends DecoratableMangaScraper { + + public constructor() { + super('templescan', 'TempleScan', 'https://templescan.net', Tags.Media.Manga, Tags.Media.Manhwa, Tags.Media.Manhua, Tags.Language.English, Tags.Source.Scanlator); + } + + public override get Icon() { + return icon; + } +} \ No newline at end of file diff --git a/web/src/engine/websites/TempleScan.webp b/web/src/engine/websites/TempleScan.webp new file mode 100644 index 0000000000000000000000000000000000000000..a85135682e061a2f4dfc9605cb86a1bd38cfa1cf GIT binary patch literal 1950 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 literal 0 HcmV?d00001 diff --git a/web/src/engine/websites/TempleScan_e2e.ts b/web/src/engine/websites/TempleScan_e2e.ts new file mode 100644 index 0000000000..260f9f0294 --- /dev/null +++ b/web/src/engine/websites/TempleScan_e2e.ts @@ -0,0 +1,25 @@ +import { TestFixture } from '../../../test/WebsitesFixture'; + +const ComicConfig = { + plugin: { + id: 'templescan', + title: 'TempleScan' + }, + container: { + url: 'https://templescan.net/comic/predatory-marriage-complete-edition', + id: 'predatory-marriage-complete-edition', + title: `Predatory Marriage (Complete Edition)` + }, + child: { + id: 'chapter-15', + title: 'Chapter 15' + }, + entry: { + index: 0, + size: 2_953_394, + type: 'image/jpeg' + } +}; + +const ComicFixture = new TestFixture(ComicConfig); +describe(ComicFixture.Name, () => ComicFixture.AssertWebsite()); \ No newline at end of file diff --git a/web/src/engine/websites/_index.ts b/web/src/engine/websites/_index.ts index 0835f5259a..577837081f 100755 --- a/web/src/engine/websites/_index.ts +++ b/web/src/engine/websites/_index.ts @@ -520,6 +520,7 @@ export { default as TCBScans } from './TCBScans'; export { default as Team1x1 } from './Team1x1'; export { default as TecnoScan } from './TecnoScan'; export { default as Tempestfansub } from './Tempestfansub'; +export { default as TempleScan } from './TempleScan'; export { default as TenshiID } from './TenshiID'; export { default as TheGuildScans } from './TheGuildScans'; export { default as TonariNoYoungJump } from './TonariNoYoungJump'; From 460c12394b5d85217f878d5c9d2b5869b4566cb0 Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Fri, 5 Jan 2024 17:18:13 +0100 Subject: [PATCH 06/31] Update _index.ts --- web/src/engine/websites/_index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/src/engine/websites/_index.ts b/web/src/engine/websites/_index.ts index 010135bb09..eae9c9b7ea 100755 --- a/web/src/engine/websites/_index.ts +++ b/web/src/engine/websites/_index.ts @@ -418,6 +418,7 @@ export { default as NoraNoFansub } from './NoraNoFansub'; export { default as Noromax } from './Noromax'; export { default as NovelMic } from './NovelMic'; export { default as OlympusScanlation } from './OlympusScanlation'; +export { default as OmegaScans } from './OmegaScans'; export { default as OnMangaMe } from './OnMangaMe'; export { default as Opiatoon } from './Opiatoon'; export { default as Oremanga } from './Oremanga'; @@ -429,6 +430,7 @@ export { default as PCNet } from './PCNet'; export { default as PeanuToon } from './PeanuToon'; export { default as PelaTeam } from './PelaTeam'; export { default as Penlab } from './Penlab'; +export { default as PerfScan } from './PerfScan'; export { default as PhenixScans } from './PhenixScans'; export { default as PhoenixScansIT } from './PhoenixScansIT'; export { default as Piccoma } from './Piccoma'; @@ -534,6 +536,7 @@ export { default as Team1x1 } from './Team1x1'; export { default as TecnoScan } from './TecnoScan'; export { default as Tempestfansub } from './Tempestfansub'; export { default as TempestScans } from './TempestScans'; +export { default as TempleScan } from './TempleScan'; export { default as TenshiID } from './TenshiID'; export { default as TheGuildScans } from './TheGuildScans'; export { default as TitanManga } from './TitanManga'; From b12ee87b0746640c8e5d44f74598c415a013d6ec Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Sat, 6 Jan 2024 14:51:28 +0100 Subject: [PATCH 07/31] HeanCMS : fix pictures CDN --- web/src/engine/websites/decorators/HeanCMS.ts | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/web/src/engine/websites/decorators/HeanCMS.ts b/web/src/engine/websites/decorators/HeanCMS.ts index f706933207..e68291cf74 100644 --- a/web/src/engine/websites/decorators/HeanCMS.ts +++ b/web/src/engine/websites/decorators/HeanCMS.ts @@ -40,11 +40,11 @@ type APIManga = { title: string series_type: 'Comic' | 'Novel', series_slug: string, - seasons? : APISeason[] + seasons?: APISeason[] } -type APIResult<T> ={ - data : T[] +type APIResult<T> = { + data: T[] } type APISeason = { @@ -63,6 +63,9 @@ type APIPages = { chapter_type: 'Comic' | 'Novel', paywall: boolean, data: string[] | string + chapter: { + storage: string + } } /*************************************************** @@ -139,7 +142,7 @@ async function getMangaFromPage(this: MangaScraper, provider: MangaPlugin, page: * @param apiUrl - The url of the HeanCMS api for the website * @param throttle - A delay [ms] for each request (only required for rate-limited websites) */ -export function MangasMultiPageAJAX(apiUrl : string, throttle = 0) { +export function MangasMultiPageAJAX(apiUrl: string, throttle = 0) { return function DecorateClass<T extends Common.Constructor>(ctor: T, context?: ClassDecoratorContext): T { Common.ThrowOnUnsupportedDecoratorContext(context); return class extends ctor { @@ -197,21 +200,23 @@ export function ChaptersSinglePageAJAX(apiUrl: string) { * @param chapter - A reference to the {@link Chapter} which shall be assigned as parent for the extracted pages * @param apiUrl - The url of the HeanCMS api for the website */ -export async function FetchPagesSinglePageAJAX(this: MangaScraper, chapter: Chapter, apiUrl: string ): Promise<Page[]> { +export async function FetchPagesSinglePageAJAX(this: MangaScraper, chapter: Chapter, apiUrl: string): Promise<Page[]> { const request = new FetchRequest(new URL(`/chapter/${chapter.Parent.Identifier}/${chapter.Identifier}`, apiUrl).href); - const { chapter_type, data, paywall } = await FetchJSON<APIPages>(request); + const { chapter_type, data, paywall, chapter: { storage } } = await FetchJSON<APIPages>(request); - // check for paywall - if (data.length < 1 && paywall) { + if (paywall) { throw new Error(`${chapter.Title} is paywalled. Please login.`); //localize this } + //in case of novel data is the html string, in case of comic its an array of strings (pictures urls or pathnames) + if (!data || data.length < 1) return []; + // check if novel if (chapter_type.toLowerCase() === 'novel') { return [new Page(this, chapter, new URL(`/series/${chapter.Parent.Identifier}/${chapter.Identifier}`, this.URI), { type: chapter_type })]; } - return (data as string[]).map(image => new Page(this, chapter, new URL(image), { type: chapter_type })); + return (data as string[]).map(image => new Page(this, chapter, computePageUrl(image, storage, apiUrl), { type: chapter_type })); } /** @@ -269,4 +274,16 @@ export function ImageAjax(detectMimeType = false, deProxifyLink = true, novelScr } }; }; -} \ No newline at end of file +} +/** + * Compute the image full URL for HeanHMS based websites + * @param image - A string containing the full url ( for {@link storage} "s3") or the pathname ( for {@link storage} "local")) + * @param storage - As string representing the type of storage used : "s3" or "local" + * @param apiUrl - The url of the HeanCMS api for the website * + */ +function computePageUrl(image: string, storage: string, apiUrl: string): URL { + switch (storage) { + case "s3": return new URL(image); + case "local": return new URL(image, apiUrl); + } +} From 10913f13a8a591d38aaed85673a13b4b9befc4c3 Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Tue, 16 Jan 2024 13:55:41 +0100 Subject: [PATCH 08/31] improve platform abstraction --- web/src/engine/websites/decorators/HeanCMS.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/web/src/engine/websites/decorators/HeanCMS.ts b/web/src/engine/websites/decorators/HeanCMS.ts index e68291cf74..156d9ab4c7 100644 --- a/web/src/engine/websites/decorators/HeanCMS.ts +++ b/web/src/engine/websites/decorators/HeanCMS.ts @@ -1,4 +1,4 @@ -import { FetchRequest, FetchJSON, FetchWindowScript } from '../../FetchProvider'; +import { FetchJSON, FetchWindowScript } from '../../platform/FetchProvider'; import { type MangaScraper, type MangaPlugin, Manga, Chapter, Page } from '../../providers/MangaPlugin'; import type { Priority } from '../../taskpool/TaskPool'; import * as Common from './Common'; @@ -81,7 +81,7 @@ type APIPages = { */ export async function FetchMangaCSS(this: MangaScraper, provider: MangaPlugin, url: string, apiUrl: string): Promise<Manga> { const slug = new URL(url).pathname.split('/')[2]; - const request = new FetchRequest(new URL(`/series/${slug}`, apiUrl).href); + const request = new Request(new URL(`/series/${slug}`, apiUrl).href); const { title, series_slug } = await FetchJSON<APIManga>(request); return new Manga(this, provider, series_slug, title); } @@ -129,7 +129,7 @@ export async function FetchMangasMultiPageAJAX(this: MangaScraper, provider: Man } async function getMangaFromPage(this: MangaScraper, provider: MangaPlugin, page: number, apiUrl: string): Promise<Manga[]> { - const request = new FetchRequest(new URL(`/query?series_type=All&order=asc&perPage=100&page=${page}`, apiUrl).href); + const request = new Request(new URL(`/query?series_type=All&order=asc&perPage=100&page=${page}`, apiUrl).href); const { data } = await FetchJSON<APIResult<APIManga>>(request); if (data.length) { return data.map((manga) => new Manga(this, provider, manga.series_slug, manga.title)); @@ -164,7 +164,7 @@ export function MangasMultiPageAJAX(apiUrl: string, throttle = 0) { * @param apiUrl - The url of the HeanCMS api for the website */ export async function FetchChaptersSinglePageAJAX(this: MangaScraper, manga: Manga, apiUrl: string): Promise<Chapter[]> { - const request = new FetchRequest(new URL(`/series/${manga.Identifier}`, apiUrl).href); + const request = new Request(new URL(`/series/${manga.Identifier}`, apiUrl).href); const { seasons } = await FetchJSON<APIManga>(request); const chapterList: Chapter[] = []; @@ -201,7 +201,7 @@ export function ChaptersSinglePageAJAX(apiUrl: string) { * @param apiUrl - The url of the HeanCMS api for the website */ export async function FetchPagesSinglePageAJAX(this: MangaScraper, chapter: Chapter, apiUrl: string): Promise<Page[]> { - const request = new FetchRequest(new URL(`/chapter/${chapter.Parent.Identifier}/${chapter.Identifier}`, apiUrl).href); + const request = new Request(new URL(`/chapter/${chapter.Parent.Identifier}/${chapter.Identifier}`, apiUrl).href); const { chapter_type, data, paywall, chapter: { storage } } = await FetchJSON<APIPages>(request); if (paywall) { @@ -254,7 +254,7 @@ export async function FetchImageAjax(this: MangaScraper, page: Page, priority: P } else { //TODO: test if user want to export the NOVEL as HTML? - const request = new FetchRequest(page.Link.href); + const request = new Request(page.Link.href); const data = await FetchWindowScript<string>(request, novelScript, 1000, 10000); return Common.FetchImageAjax.call(this, new Page(this, page.Parent as Chapter, new URL(data)), priority, signal, false, false); } From 389cb58109ca0c67a637fa6fef5a0bb460621b72 Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Sun, 4 Feb 2024 14:12:03 +0100 Subject: [PATCH 09/31] remove YugenMangasES its dead --- web/src/engine/websites/YugenMangasES.ts | 22 --------- web/src/engine/websites/YugenMangasES.webp | Bin 1516 -> 0 bytes web/src/engine/websites/YugenMangasES_e2e.ts | 49 ------------------- web/src/engine/websites/_index.ts | 1 - 4 files changed, 72 deletions(-) delete mode 100755 web/src/engine/websites/YugenMangasES.ts delete mode 100644 web/src/engine/websites/YugenMangasES.webp delete mode 100755 web/src/engine/websites/YugenMangasES_e2e.ts diff --git a/web/src/engine/websites/YugenMangasES.ts b/web/src/engine/websites/YugenMangasES.ts deleted file mode 100755 index 6d07fcd83a..0000000000 --- a/web/src/engine/websites/YugenMangasES.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Tags } from '../Tags'; -import icon from './YugenMangasES.webp'; -import { DecoratableMangaScraper } from '../providers/MangaPlugin'; -import * as HeamCMS from './decorators/HeanCMS'; - -const apiUrl = 'https://api.yugenmangas.net'; - -@HeamCMS.MangaCSS(/^{origin}\/series\/[^/]+$/, apiUrl) -@HeamCMS.MangasMultiPageAJAX(apiUrl) -@HeamCMS.ChaptersSinglePageAJAX(apiUrl) -@HeamCMS.PagesSinglePageAJAX(apiUrl) -@HeamCMS.ImageAjax(true) -export default class extends DecoratableMangaScraper { - - public constructor() { - super('yugenmangas-es', 'YugenMangas (ES)', 'https://yugenmangas.lat', Tags.Media.Manga, Tags.Media.Manhwa, Tags.Media.Manhua, Tags.Media.Novel, Tags.Language.Spanish, Tags.Source.Aggregator); - } - - public override get Icon() { - return icon; - } -} \ No newline at end of file diff --git a/web/src/engine/websites/YugenMangasES.webp b/web/src/engine/websites/YugenMangasES.webp deleted file mode 100644 index abdf02c9e72a36dfff52e8483298aa427a09e8dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1516 zcmWIYbaQ*c%D@or>J$(bU=hK^z`$St#P(q1>FgXJ!35+oFiC(&7NAsaUP)1qyOTmh zWRwC(3<eUDiwj_EEHonn!?$Y;K$3ysvMv^RLPDG=sl~}aMIV5;qzGs^0|R3UkS&r9 zVON0INf34qh+PyC<P21^0La!zM`9--vCB&eN`PukfYc`!6oJ@xfNY!GoKlcoAOLbP zPikIhFp!P_Vi|^Fh9Cxa1}7k!ssIt5`HL7BEWR=@Fi${;CCy}D*xt&(AaM&Jrm}#6 zfxm@;VcYqF#G*v7V_1MBQ(77W!>8p847`yH48j)}7`UQg4n^oi#03t&DggD!0R5Q{ zgy{@945<tX44w>m49N_7K$a0Okr*<VIr;>6<TC?pTHum>ThX&hX;aSX+}qo7Z`*Er z8wsR9$Xc}f|L@N(DeKe11PvMg<eO-Qd_M5K@6WyX%k@XCjp`bn9Zr0I;^P^Sm9I`N zXOxsWZTY_IT9C`yt&KHbS3b!vI%0T?e}aUCUIUZu#N`4l?Zv8|p?&)<wVZL>_q*Dl z;+om?MW^0fsOifoRo8ImpD1AxcItYtarXn8Ddvu=b{}55<C&8E4?~YTv!%Q_EBUnN zD@NCHT)XJ3!q&(9>B~hOv!=BCi54q=O6jk&JmGJznsNW#3iYLGbD!F#thRTwc~pDy zg=uTS*;(NSx9oQ5acFP-XDRFZ^k+58=ht^jm(GdV{c%@=k;rA%AGM6FOw+E5x$#cz z=__8la&M!q`Ol}e3GS~Qx^j#kI3`wao8Bw-!uIN{t2dv7#`okg-1KMt#NY70)pkAK zwFz7{yQ}Zc?eABZzI2Ig;p-x?;2e3=kRsFQG~d@-KkJmZ_HtD(`pxQY9@;4)VK7fJ z_jl7pXM@eL*Oy!qw<+6e6XdH^`u5}2{Dw0bjY;aJ@k+nmCS3{8+;WXATl0e0&O45p z+h(WyZ+j)k)O?Id;;)Hnzo&EIub-V-K~E3oH%=+>eYbj=(8tDO`O@6Cyx03QEXxR< zziaBd4BKy_uKPNyA~y1LaMkm))UZde^-a4h6Cm`7iC<2PN%^?x0@eeUp6RKdxic%) ztF|H3vYT18ulc9-gTK$K4{Xxab14pyT+^c+&Zkk$k;Sy;+!?k#TXoepJ(m**7OE(_ zr^7tOrtKsDga2m2?%r8)vmJOFeD9p#f3hoWDi>?n!+_AUt_=rIMZ8mMbNqDL{6EvR zaElnf&Eg#^nD*G6IKACdKyu~;X^W3eMYmI*U3$Z$#rTHtb(NpNCVti*Y=?9XY|=ir z)RK`W<QRi`7jv&tqjow2GkdX8^*hIm+5>w<Z^SeHV0)lGM{jjyhLdE&Vus^hOb-wI z^|k7c4K)>S{CBG2sh1w}M|m0T#W%Uvr8VR){bZmORAJ|BD6%`&(!_e9#(_xZ%HJlw z5}i_yT=uNA<gA^tvSa!ut$a6*MfaD@Iq{BtMlR!jo0lG$sbY=$HmzLqY@ufy_l_(N z_S1~_?lW)Ny-GE<MQ$4F=L!CzjMo-yWjMU-{4(!CgFo>KPQ`mP1(xm%V)oefa-}<y z+#c0{`?5@5?>)6oWdN4c3VOf{I)RyCE~AzMg9C%z#f<eqE7Vk!7I82#NO5K|FbJ3j T2e4MO?qp#2_jzsuP%#4lnJ@7} diff --git a/web/src/engine/websites/YugenMangasES_e2e.ts b/web/src/engine/websites/YugenMangasES_e2e.ts deleted file mode 100755 index 28faa8fe52..0000000000 --- a/web/src/engine/websites/YugenMangasES_e2e.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { TestFixture } from '../../../test/WebsitesFixture'; - -const ComicConfig = { - plugin: { - id: 'yugenmangas-es', - title: 'YugenMangas (ES)' - }, - container: { - url: 'https://yugenmangas.lat/series/la-gran-duquesa-del-norte-era-una-villana-en-secreto', - id: 'la-gran-duquesa-del-norte-era-una-villana-en-secreto', - title: 'La Gran Duquesa Del Norte Era Una Villana En Secreto' - }, - child: { - id: 'capitulo-102', - title: 'Capitulo 102 FINAL' - }, - entry: { - index: 1, - size: 3_064_669, - type: 'image/jpeg' - } -}; - -const ComicFixture = new TestFixture(ComicConfig); -describe(ComicFixture.Name, () => ComicFixture.AssertWebsite()); - -const NovelConfig = { - plugin: { - id: 'yugenmangas-es', - title: 'YugenMangas (ES)' - }, - container: { - url: 'https://yugenmangas.lat/series/esposo-villano-deberias-estar-obsesionado-con-esa-persona-', - id: 'esposo-villano-deberias-estar-obsesionado-con-esa-persona-', - title: 'Esposo villano, deberías estar obsesionado con esa persona.' - }, - child: { - id: 'capitulo-1', - title: 'Capítulo 1' - }, - entry: { - index: 0, - size: 556_399, - type: 'image/png' - } -}; - -const NovelFixture = new TestFixture(NovelConfig); -describe(NovelFixture.Name, () => NovelFixture.AssertWebsite()); \ No newline at end of file diff --git a/web/src/engine/websites/_index.ts b/web/src/engine/websites/_index.ts index cb1164c5f5..9deb011b4d 100755 --- a/web/src/engine/websites/_index.ts +++ b/web/src/engine/websites/_index.ts @@ -600,7 +600,6 @@ export { default as YaoiHavenReborn } from './YaoiHavenReborn'; export { default as YaoiScan } from './YaoiScan'; export { default as YaoiToshokan } from './YaoiToshokan'; export { default as YawarakaSpirits } from './YawarakaSpirits'; -export { default as YugenMangasES } from './YugenMangasES'; export { default as YugenMangasPT } from './YugenMangasPT'; export { default as YumeKomik } from './YumeKomik'; export { default as YuriVerso } from './YuriVerso'; From cb7e37b945e63ce8abc02a26985843f929e7dc0f Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Mon, 26 Feb 2024 16:19:12 +0100 Subject: [PATCH 10/31] Update _index.ts --- web/src/engine/websites/_index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/src/engine/websites/_index.ts b/web/src/engine/websites/_index.ts index a13256c2ed..81bc8bd342 100755 --- a/web/src/engine/websites/_index.ts +++ b/web/src/engine/websites/_index.ts @@ -478,6 +478,7 @@ export { default as NovelMic } from './NovelMic'; export { default as NvManga } from './NvManga'; export { default as Nyrax } from './Nyrax'; export { default as OlympusScanlation } from './OlympusScanlation'; +export { default as OmegaScans } from './OmegaScans'; export { default as OnMangaMe } from './OnMangaMe'; export { default as Opiatoon } from './Opiatoon'; export { default as Oremanga } from './Oremanga'; @@ -489,6 +490,7 @@ export { default as PCNet } from './PCNet'; export { default as PeanuToon } from './PeanuToon'; export { default as PelaTeam } from './PelaTeam'; export { default as Penlab } from './Penlab'; +export { default as PerfScan } from './PerfScan'; export { default as PhenixScans } from './PhenixScans'; export { default as PhoenixScansIT } from './PhoenixScansIT'; export { default as Piccoma } from './Piccoma'; @@ -606,6 +608,7 @@ export { default as Team1x1 } from './Team1x1'; export { default as TecnoScan } from './TecnoScan'; export { default as Tempestfansub } from './Tempestfansub'; export { default as TempestScans } from './TempestScans'; +export { default as TempleScan } from './TempleScan'; export { default as TenshiID } from './TenshiID'; export { default as TheBlank } from './TheBlank'; export { default as TheGuildScans } from './TheGuildScans'; From 8fb06112f3ee914e2cda423c53699df858402eb3 Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Sat, 9 Mar 2024 13:40:55 +0100 Subject: [PATCH 11/31] minor code changes --- web/src/engine/websites/decorators/HeanCMS.ts | 67 +++++++++---------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/web/src/engine/websites/decorators/HeanCMS.ts b/web/src/engine/websites/decorators/HeanCMS.ts index 156d9ab4c7..e851bfe98a 100644 --- a/web/src/engine/websites/decorators/HeanCMS.ts +++ b/web/src/engine/websites/decorators/HeanCMS.ts @@ -7,33 +7,33 @@ import * as Common from './Common'; //add a listener on the aforementionned setting const DefaultNovelScript = ` - new Promise((resolve, reject) => { - document.body.style.width = '56em'; - let container = document.querySelector('div.container'); - container.style.maxWidth = '56em'; - container.style.padding = '0'; - container.style.margin = '0'; - let novel = document.querySelector('div#reader-container'); - novel.style.padding = '1.5em'; - [...novel.querySelectorAll(":not(:empty)")].forEach(ele => { - ele.style.backgroundColor = 'black' - ele.style.color = 'white' - }) - novel.style.backgroundColor = 'black' - novel.style.color = 'white' - let script = document.createElement('script'); - script.onerror = error => reject(error); - script.onload = async function() { - try { - let canvas = await html2canvas(novel); - resolve([canvas.toDataURL('image/png')]); - } catch (error){ - reject(error) - } - } - script.src = 'https://html2canvas.hertzen.com/dist/html2canvas.min.js'; - document.body.appendChild(script); - }); + new Promise((resolve, reject) => { + document.body.style.width = '56em'; + let container = document.querySelector('div.container'); + container.style.maxWidth = '56em'; + container.style.padding = '0'; + container.style.margin = '0'; + let novel = document.querySelector('div#reader-container'); + novel.style.padding = '1.5em'; + [...novel.querySelectorAll(":not(:empty)")].forEach(ele => { + ele.style.backgroundColor = 'black' + ele.style.color = 'white' + }) + novel.style.backgroundColor = 'black' + novel.style.color = 'white' + let script = document.createElement('script'); + script.onerror = error => reject(error); + script.onload = async function() { + try { + let canvas = await html2canvas(novel); + resolve([canvas.toDataURL('image/png')]); + } catch (error) { + reject(error) + } + } + script.src = 'https://html2canvas.hertzen.com/dist/html2canvas.min.js'; + document.body.appendChild(script); + }); `; type APIManga = { @@ -81,8 +81,7 @@ type APIPages = { */ export async function FetchMangaCSS(this: MangaScraper, provider: MangaPlugin, url: string, apiUrl: string): Promise<Manga> { const slug = new URL(url).pathname.split('/')[2]; - const request = new Request(new URL(`/series/${slug}`, apiUrl).href); - const { title, series_slug } = await FetchJSON<APIManga>(request); + const { title, series_slug } = await FetchJSON<APIManga>(new Request(new URL(`/series/${slug}`, apiUrl))); return new Manga(this, provider, series_slug, title); } @@ -129,7 +128,7 @@ export async function FetchMangasMultiPageAJAX(this: MangaScraper, provider: Man } async function getMangaFromPage(this: MangaScraper, provider: MangaPlugin, page: number, apiUrl: string): Promise<Manga[]> { - const request = new Request(new URL(`/query?series_type=All&order=asc&perPage=100&page=${page}`, apiUrl).href); + const request = new Request(new URL(`/query?series_type=All&order=asc&perPage=100&page=${page}`, apiUrl)); const { data } = await FetchJSON<APIResult<APIManga>>(request); if (data.length) { return data.map((manga) => new Manga(this, provider, manga.series_slug, manga.title)); @@ -164,7 +163,7 @@ export function MangasMultiPageAJAX(apiUrl: string, throttle = 0) { * @param apiUrl - The url of the HeanCMS api for the website */ export async function FetchChaptersSinglePageAJAX(this: MangaScraper, manga: Manga, apiUrl: string): Promise<Chapter[]> { - const request = new Request(new URL(`/series/${manga.Identifier}`, apiUrl).href); + const request = new Request(new URL(`/series/${manga.Identifier}`, apiUrl)); const { seasons } = await FetchJSON<APIManga>(request); const chapterList: Chapter[] = []; @@ -201,7 +200,7 @@ export function ChaptersSinglePageAJAX(apiUrl: string) { * @param apiUrl - The url of the HeanCMS api for the website */ export async function FetchPagesSinglePageAJAX(this: MangaScraper, chapter: Chapter, apiUrl: string): Promise<Page[]> { - const request = new Request(new URL(`/chapter/${chapter.Parent.Identifier}/${chapter.Identifier}`, apiUrl).href); + const request = new Request(new URL(`/chapter/${chapter.Parent.Identifier}/${chapter.Identifier}`, apiUrl)); const { chapter_type, data, paywall, chapter: { storage } } = await FetchJSON<APIPages>(request); if (paywall) { @@ -253,9 +252,7 @@ export async function FetchImageAjax(this: MangaScraper, page: Page, priority: P return Common.FetchImageAjax.call(this, page, priority, signal, detectMimeType, deProxifyLink); } else { //TODO: test if user want to export the NOVEL as HTML? - - const request = new Request(page.Link.href); - const data = await FetchWindowScript<string>(request, novelScript, 1000, 10000); + const data = await FetchWindowScript<string>(new Request(page.Link), novelScript, 1000, 10000); return Common.FetchImageAjax.call(this, new Page(this, page.Parent as Chapter, new URL(data)), priority, signal, false, false); } } From 712854406506b2e332faa115ca382e78bdf8796f Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Fri, 12 Apr 2024 13:50:13 +0200 Subject: [PATCH 12/31] Remove TempleScans it doesnt use HeanCMS anymore --- web/src/engine/websites/TempleScan.ts | 22 ------------------- web/src/engine/websites/TempleScan.webp | Bin 1950 -> 0 bytes web/src/engine/websites/TempleScan_e2e.ts | 25 ---------------------- web/src/engine/websites/_index.ts | 1 - 4 files changed, 48 deletions(-) delete mode 100644 web/src/engine/websites/TempleScan.ts delete mode 100644 web/src/engine/websites/TempleScan.webp delete mode 100644 web/src/engine/websites/TempleScan_e2e.ts diff --git a/web/src/engine/websites/TempleScan.ts b/web/src/engine/websites/TempleScan.ts deleted file mode 100644 index bf1f86ab88..0000000000 --- a/web/src/engine/websites/TempleScan.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Tags } from '../Tags'; -import icon from './TempleScan.webp'; -import { DecoratableMangaScraper } from '../providers/MangaPlugin'; -import * as HeamCMS from './decorators/HeanCMS'; - -const apiUrl = 'https://api.templescan.net'; - -@HeamCMS.MangaCSS(/^{origin}\/comic\/[^/]+$/, apiUrl) -@HeamCMS.MangasMultiPageAJAX(apiUrl) -@HeamCMS.ChaptersSinglePageAJAX(apiUrl) -@HeamCMS.PagesSinglePageAJAX(apiUrl) -@HeamCMS.ImageAjax(true) -export default class extends DecoratableMangaScraper { - - public constructor() { - super('templescan', 'TempleScan', 'https://templescan.net', Tags.Media.Manga, Tags.Media.Manhwa, Tags.Media.Manhua, Tags.Language.English, Tags.Source.Scanlator); - } - - public override get Icon() { - return icon; - } -} \ No newline at end of file diff --git a/web/src/engine/websites/TempleScan.webp b/web/src/engine/websites/TempleScan.webp deleted file mode 100644 index a85135682e061a2f4dfc9605cb86a1bd38cfa1cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1950 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 diff --git a/web/src/engine/websites/TempleScan_e2e.ts b/web/src/engine/websites/TempleScan_e2e.ts deleted file mode 100644 index 260f9f0294..0000000000 --- a/web/src/engine/websites/TempleScan_e2e.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { TestFixture } from '../../../test/WebsitesFixture'; - -const ComicConfig = { - plugin: { - id: 'templescan', - title: 'TempleScan' - }, - container: { - url: 'https://templescan.net/comic/predatory-marriage-complete-edition', - id: 'predatory-marriage-complete-edition', - title: `Predatory Marriage (Complete Edition)` - }, - child: { - id: 'chapter-15', - title: 'Chapter 15' - }, - entry: { - index: 0, - size: 2_953_394, - type: 'image/jpeg' - } -}; - -const ComicFixture = new TestFixture(ComicConfig); -describe(ComicFixture.Name, () => ComicFixture.AssertWebsite()); \ No newline at end of file diff --git a/web/src/engine/websites/_index.ts b/web/src/engine/websites/_index.ts index ecc62839d3..afabee320f 100755 --- a/web/src/engine/websites/_index.ts +++ b/web/src/engine/websites/_index.ts @@ -618,7 +618,6 @@ export { default as Team1x1 } from './Team1x1'; export { default as TecnoScan } from './TecnoScan'; export { default as Tempestfansub } from './Tempestfansub'; export { default as TempestScans } from './TempestScans'; -export { default as TempleScan } from './TempleScan'; export { default as TenshiID } from './TenshiID'; export { default as TheBlank } from './TheBlank'; export { default as TheGuildScans } from './TheGuildScans'; From a35928c48acd0e8219160397cab5ca62985c9025 Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Fri, 12 Apr 2024 13:52:25 +0200 Subject: [PATCH 13/31] Revert "Remove TempleScans" This reverts commit 712854406506b2e332faa115ca382e78bdf8796f. --- web/src/engine/websites/TempleScan.ts | 22 +++++++++++++++++++ web/src/engine/websites/TempleScan.webp | Bin 0 -> 1950 bytes web/src/engine/websites/TempleScan_e2e.ts | 25 ++++++++++++++++++++++ web/src/engine/websites/_index.ts | 1 + 4 files changed, 48 insertions(+) create mode 100644 web/src/engine/websites/TempleScan.ts create mode 100644 web/src/engine/websites/TempleScan.webp create mode 100644 web/src/engine/websites/TempleScan_e2e.ts diff --git a/web/src/engine/websites/TempleScan.ts b/web/src/engine/websites/TempleScan.ts new file mode 100644 index 0000000000..bf1f86ab88 --- /dev/null +++ b/web/src/engine/websites/TempleScan.ts @@ -0,0 +1,22 @@ +import { Tags } from '../Tags'; +import icon from './TempleScan.webp'; +import { DecoratableMangaScraper } from '../providers/MangaPlugin'; +import * as HeamCMS from './decorators/HeanCMS'; + +const apiUrl = 'https://api.templescan.net'; + +@HeamCMS.MangaCSS(/^{origin}\/comic\/[^/]+$/, apiUrl) +@HeamCMS.MangasMultiPageAJAX(apiUrl) +@HeamCMS.ChaptersSinglePageAJAX(apiUrl) +@HeamCMS.PagesSinglePageAJAX(apiUrl) +@HeamCMS.ImageAjax(true) +export default class extends DecoratableMangaScraper { + + public constructor() { + super('templescan', 'TempleScan', 'https://templescan.net', Tags.Media.Manga, Tags.Media.Manhwa, Tags.Media.Manhua, Tags.Language.English, Tags.Source.Scanlator); + } + + public override get Icon() { + return icon; + } +} \ No newline at end of file diff --git a/web/src/engine/websites/TempleScan.webp b/web/src/engine/websites/TempleScan.webp new file mode 100644 index 0000000000000000000000000000000000000000..a85135682e061a2f4dfc9605cb86a1bd38cfa1cf GIT binary patch literal 1950 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 literal 0 HcmV?d00001 diff --git a/web/src/engine/websites/TempleScan_e2e.ts b/web/src/engine/websites/TempleScan_e2e.ts new file mode 100644 index 0000000000..260f9f0294 --- /dev/null +++ b/web/src/engine/websites/TempleScan_e2e.ts @@ -0,0 +1,25 @@ +import { TestFixture } from '../../../test/WebsitesFixture'; + +const ComicConfig = { + plugin: { + id: 'templescan', + title: 'TempleScan' + }, + container: { + url: 'https://templescan.net/comic/predatory-marriage-complete-edition', + id: 'predatory-marriage-complete-edition', + title: `Predatory Marriage (Complete Edition)` + }, + child: { + id: 'chapter-15', + title: 'Chapter 15' + }, + entry: { + index: 0, + size: 2_953_394, + type: 'image/jpeg' + } +}; + +const ComicFixture = new TestFixture(ComicConfig); +describe(ComicFixture.Name, () => ComicFixture.AssertWebsite()); \ No newline at end of file diff --git a/web/src/engine/websites/_index.ts b/web/src/engine/websites/_index.ts index afabee320f..ecc62839d3 100755 --- a/web/src/engine/websites/_index.ts +++ b/web/src/engine/websites/_index.ts @@ -618,6 +618,7 @@ export { default as Team1x1 } from './Team1x1'; export { default as TecnoScan } from './TecnoScan'; export { default as Tempestfansub } from './Tempestfansub'; export { default as TempestScans } from './TempestScans'; +export { default as TempleScan } from './TempleScan'; export { default as TenshiID } from './TenshiID'; export { default as TheBlank } from './TheBlank'; export { default as TheGuildScans } from './TheGuildScans'; From 5796316933b4314f8f666ea5a30c00cc1ac79fca Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Sat, 13 Apr 2024 23:27:25 +0200 Subject: [PATCH 14/31] handle v2 api --- web/src/engine/websites/OmegaScans.ts | 2 +- web/src/engine/websites/OmegaScans_e2e.ts | 8 +- web/src/engine/websites/PerfScan.ts | 2 +- web/src/engine/websites/PerfScan_e2e.ts | 22 +-- web/src/engine/websites/TempleScan.ts | 4 +- web/src/engine/websites/TempleScan_e2e.ts | 4 +- web/src/engine/websites/decorators/HeanCMS.ts | 133 ++++++++++++++---- 7 files changed, 125 insertions(+), 50 deletions(-) diff --git a/web/src/engine/websites/OmegaScans.ts b/web/src/engine/websites/OmegaScans.ts index c944d06f25..6bd81f03c7 100644 --- a/web/src/engine/websites/OmegaScans.ts +++ b/web/src/engine/websites/OmegaScans.ts @@ -7,7 +7,7 @@ const apiUrl = 'https://api.omegascans.org'; @HeamCMS.MangaCSS(/^{origin}\/series\/[^/]+$/, apiUrl) @HeamCMS.MangasMultiPageAJAX(apiUrl) -@HeamCMS.ChaptersSinglePageAJAX(apiUrl) +@HeamCMS.ChaptersSinglePageAJAXv2(apiUrl) @HeamCMS.PagesSinglePageAJAX(apiUrl) @HeamCMS.ImageAjax(true) export default class extends DecoratableMangaScraper { diff --git a/web/src/engine/websites/OmegaScans_e2e.ts b/web/src/engine/websites/OmegaScans_e2e.ts index 3b71966b5c..b404947985 100644 --- a/web/src/engine/websites/OmegaScans_e2e.ts +++ b/web/src/engine/websites/OmegaScans_e2e.ts @@ -7,11 +7,11 @@ const ComicConfig = { }, container: { url: 'https://omegascans.org/series/trapped-in-the-academys-eroge', - id: 'trapped-in-the-academys-eroge', + id: JSON.stringify({ id: '8', slug: 'trapped-in-the-academys-eroge' }), title: `Trapped in the Academy's Eroge` }, child: { - id: 'chapter-76', + id: JSON.stringify({ id: '3245', slug: 'chapter-76' }), title: 'Chapter 76' }, entry: { @@ -31,11 +31,11 @@ const NovelConfig = { }, container: { url: 'https://omegascans.org/series/the-scion-of-the-labyrinth-city', - id: 'the-scion-of-the-labyrinth-city', + id: JSON.stringify({ id: '186', slug: 'the-scion-of-the-labyrinth-city' }), title: 'The Scion of the Labyrinth City' }, child: { - id: 'chapter-28', + id: JSON.stringify({ id: '3226', slug: 'chapter-28' }), title: 'Chapter 28' }, entry: { diff --git a/web/src/engine/websites/PerfScan.ts b/web/src/engine/websites/PerfScan.ts index afbf8d1fe1..f988ad90fa 100644 --- a/web/src/engine/websites/PerfScan.ts +++ b/web/src/engine/websites/PerfScan.ts @@ -7,7 +7,7 @@ const apiUrl = 'https://api.perf-scan.fr'; @HeamCMS.MangaCSS(/^{origin}\/series\/[^/]+$/, apiUrl) @HeamCMS.MangasMultiPageAJAX(apiUrl) -@HeamCMS.ChaptersSinglePageAJAX(apiUrl) +@HeamCMS.ChaptersSinglePageAJAXv2(apiUrl) @HeamCMS.PagesSinglePageAJAX(apiUrl) @HeamCMS.ImageAjax() export default class extends DecoratableMangaScraper { diff --git a/web/src/engine/websites/PerfScan_e2e.ts b/web/src/engine/websites/PerfScan_e2e.ts index 05a2b334b7..c3c945daaa 100644 --- a/web/src/engine/websites/PerfScan_e2e.ts +++ b/web/src/engine/websites/PerfScan_e2e.ts @@ -6,18 +6,18 @@ const ComicConfig = { title: 'Perf Scan' }, container: { - url: 'https://perf-scan.fr/series/martial-peak-1702249200756', - id: 'martial-peak-1702249200756', + url: 'https://perf-scan.fr/series/martial-peak', + id: JSON.stringify({ id: '1', slug: 'martial-peak' }), title: 'Martial Peak' }, child: { - id: 'chapitre-1298', - title: 'S6 Chapitre 1298' + id: JSON.stringify({ id: '28768', slug: 'chapitre-1298' }), + title: 'Chapitre 1298' }, entry: { index: 2, - size: 215_840, - type: 'image/webp' + size: 939_807, + type: 'image/jpg' } }; @@ -30,13 +30,13 @@ const NovelConfig = { title: 'Perf Scan' }, container: { - url: 'https://perf-scan.fr/series/demonic-emperor-novel-1702249200676', - id: 'demonic-emperor-novel-1702249200676', + url: 'https://perf-scan.fr/series/demonic-emperor-novel', + id: JSON.stringify({ id: '17', slug: 'demonic-emperor-novel' }), title: 'Demonic emperor - Novel' }, child: { - id: 'chapitre-492', - title: 'S4 Chapitre 492' + id: JSON.stringify({ id: '28668', slug: 'chapitre-492' }), + title: 'Chapitre 492' }, entry: { index: 0, @@ -46,4 +46,4 @@ const NovelConfig = { }; const NovelFixture = new TestFixture(NovelConfig); -describe(NovelFixture.Name, () => NovelFixture.AssertWebsite()); \ No newline at end of file +describe(NovelFixture.Name, () => NovelFixture.AssertWebsite()); diff --git a/web/src/engine/websites/TempleScan.ts b/web/src/engine/websites/TempleScan.ts index bf1f86ab88..7efdd73540 100644 --- a/web/src/engine/websites/TempleScan.ts +++ b/web/src/engine/websites/TempleScan.ts @@ -3,11 +3,11 @@ import icon from './TempleScan.webp'; import { DecoratableMangaScraper } from '../providers/MangaPlugin'; import * as HeamCMS from './decorators/HeanCMS'; -const apiUrl = 'https://api.templescan.net'; +const apiUrl = 'https://templescan.net/apiv1'; @HeamCMS.MangaCSS(/^{origin}\/comic\/[^/]+$/, apiUrl) @HeamCMS.MangasMultiPageAJAX(apiUrl) -@HeamCMS.ChaptersSinglePageAJAX(apiUrl) +@HeamCMS.ChaptersSinglePageAJAXv1(apiUrl) @HeamCMS.PagesSinglePageAJAX(apiUrl) @HeamCMS.ImageAjax(true) export default class extends DecoratableMangaScraper { diff --git a/web/src/engine/websites/TempleScan_e2e.ts b/web/src/engine/websites/TempleScan_e2e.ts index 260f9f0294..f13bd8e16c 100644 --- a/web/src/engine/websites/TempleScan_e2e.ts +++ b/web/src/engine/websites/TempleScan_e2e.ts @@ -7,11 +7,11 @@ const ComicConfig = { }, container: { url: 'https://templescan.net/comic/predatory-marriage-complete-edition', - id: 'predatory-marriage-complete-edition', + id: JSON.stringify({ id: '55', slug: 'predatory-marriage-complete-edition' }), title: `Predatory Marriage (Complete Edition)` }, child: { - id: 'chapter-15', + id: JSON.stringify({ id: '1028', slug: '83662-chapter-15' }), title: 'Chapter 15' }, entry: { diff --git a/web/src/engine/websites/decorators/HeanCMS.ts b/web/src/engine/websites/decorators/HeanCMS.ts index e851bfe98a..650e46ba81 100644 --- a/web/src/engine/websites/decorators/HeanCMS.ts +++ b/web/src/engine/websites/decorators/HeanCMS.ts @@ -36,15 +36,16 @@ const DefaultNovelScript = ` }); `; -type APIManga = { +type APIMangaV1 = { title: string + id: number, series_type: 'Comic' | 'Novel', series_slug: string, seasons?: APISeason[] } type APIResult<T> = { - data: T[] + data: T } type APISeason = { @@ -54,6 +55,7 @@ type APISeason = { type APIChapter = { index: string, + id: number, chapter_name: string, chapter_title: string, chapter_slug: string, @@ -64,10 +66,20 @@ type APIPages = { paywall: boolean, data: string[] | string chapter: { - storage: string + chapter_type: 'Comic' | 'Novel', + storage: string, + chapter_data?: { + images: string[] + } + } } +type MangaOrChapterId = { + id: string, + slug: string +} + /*************************************************** ******** Manga from URL Extraction Methods ******** ***************************************************/ @@ -81,8 +93,11 @@ type APIPages = { */ export async function FetchMangaCSS(this: MangaScraper, provider: MangaPlugin, url: string, apiUrl: string): Promise<Manga> { const slug = new URL(url).pathname.split('/')[2]; - const { title, series_slug } = await FetchJSON<APIManga>(new Request(new URL(`/series/${slug}`, apiUrl))); - return new Manga(this, provider, series_slug, title); + const { title, series_slug, id } = await FetchJSON<APIMangaV1>(new Request(new URL(`${apiUrl}/series/${slug}`))); + return new Manga(this, provider, JSON.stringify({ + id: id.toString(), + slug: series_slug + }), title); } /** @@ -119,19 +134,25 @@ export function MangaCSS(pattern: RegExp, apiURL: string) { */ export async function FetchMangasMultiPageAJAX(this: MangaScraper, provider: MangaPlugin, apiUrl: string, throttle = 0): Promise<Manga[]> { const mangaList: Manga[] = []; + //First loop get adult mangas + for (let page = 1, run = true; run; page++) { + const mangas = await getMangaFromPage.call(this, provider, page, apiUrl, true); + mangas.length > 0 ? mangaList.push(...mangas) : run = false; + await new Promise(resolve => setTimeout(resolve, throttle)); + } + //First loop get non adult mangas for (let page = 1, run = true; run; page++) { - const mangas = await getMangaFromPage.call(this, provider, page, apiUrl); + const mangas = await getMangaFromPage.call(this, provider, page, apiUrl, false); mangas.length > 0 ? mangaList.push(...mangas) : run = false; await new Promise(resolve => setTimeout(resolve, throttle)); } - return mangaList; + return mangaList.distinct(); } - -async function getMangaFromPage(this: MangaScraper, provider: MangaPlugin, page: number, apiUrl: string): Promise<Manga[]> { - const request = new Request(new URL(`/query?series_type=All&order=asc&perPage=100&page=${page}`, apiUrl)); - const { data } = await FetchJSON<APIResult<APIManga>>(request); +async function getMangaFromPage(this: MangaScraper, provider: MangaPlugin, page: number, apiUrl: string, adult: boolean): Promise<Manga[]> { + const request = new Request(new URL(`${apiUrl}/query?perPage=100&page=${page}&adult=${adult}`)); + const { data } = await FetchJSON<APIResult<APIMangaV1[]>>(request); if (data.length) { - return data.map((manga) => new Manga(this, provider, manga.series_slug, manga.title)); + return data.map((manga) => new Manga(this, provider, JSON.stringify({ id: manga.id.toString(), slug: manga.series_slug }), manga.title)); } return []; } @@ -162,29 +183,68 @@ export function MangasMultiPageAJAX(apiUrl: string, throttle = 0) { * @param manga - A reference to the {@link Manga} which shall be assigned as parent for the extracted chapters * @param apiUrl - The url of the HeanCMS api for the website */ -export async function FetchChaptersSinglePageAJAX(this: MangaScraper, manga: Manga, apiUrl: string): Promise<Chapter[]> { - const request = new Request(new URL(`/series/${manga.Identifier}`, apiUrl)); - const { seasons } = await FetchJSON<APIManga>(request); - const chapterList: Chapter[] = []; - - seasons.map((season) => season.chapters.map((chapter) => { - const id = chapter.chapter_slug; - const title = `${seasons.length > 1 ? 'S' + season.index : ''} ${chapter.chapter_name} ${chapter.chapter_title || ''}`.trim(); - chapterList.push(new Chapter(this, manga, id, title)); - })); - return chapterList; +export async function FetchChaptersSinglePageAJAXv1(this: MangaScraper, manga: Manga, apiUrl: string): Promise<Chapter[]> { + try { + const mangaslug = (JSON.parse(manga.Identifier) as MangaOrChapterId).slug; + const request = new Request(new URL(`${apiUrl}/series/${mangaslug}`)); + const { seasons } = await FetchJSON<APIMangaV1>(request); + const chapterList: Chapter[] = []; + + seasons.map((season) => season.chapters.map((chapter) => { + const id = JSON.stringify({ + id: chapter.id.toString(), + slug: chapter.chapter_slug + }); + const title = `${seasons.length > 1 ? 'S' + season.index : ''} ${chapter.chapter_name} ${chapter.chapter_title || ''}`.trim(); + chapterList.push(new Chapter(this, manga, id, title)); + })); + return chapterList; + } catch (error) { + return []; + } +} + +/** + * A class decorator that adds the ability to extract all chapters for a given manga from this website using the HeanCMS api url {@link apiUrl}. + * @param apiUrl - The url of the HeanCMS api for the website + */ +export function ChaptersSinglePageAJAXv1(apiUrl: string) { + return function DecorateClass<T extends Common.Constructor>(ctor: T, context?: ClassDecoratorContext): T { + Common.ThrowOnUnsupportedDecoratorContext(context); + return class extends ctor { + public async FetchChapters(this: MangaScraper, manga: Manga): Promise<Chapter[]> { + return FetchChaptersSinglePageAJAXv1.call(this, manga, apiUrl); + } + }; + }; +} + +/** + * An extension method for extracting all chapters for the given {@link manga} using the HeanCMS api url {@link apiUrl}. + * @param this - A reference to the {@link MangaScraper} instance which will be used as context for this method + * @param manga - A reference to the {@link Manga} which shall be assigned as parent for the extracted chapters + * @param apiUrl - The url of the HeanCMS api for the website + */ +export async function FetchChaptersSinglePageAJAXv2(this: MangaScraper, manga: Manga, apiUrl: string): Promise<Chapter[]> { + const mangaid: MangaOrChapterId = JSON.parse(manga.Identifier); + const { data } = await FetchJSON<APIResult<APIChapter[]>>(new Request(new URL(`${apiUrl}/chapter/query?series_id=${mangaid.id}&perPage=9999&page=1`))); + return data.map(chapter => new Chapter(this, manga, JSON.stringify({ + id: chapter.id.toString(), + slug: chapter.chapter_slug, + }), `${chapter.chapter_name} ${chapter.chapter_title || ''}`.trim())); + } /** * A class decorator that adds the ability to extract all chapters for a given manga from this website using the HeanCMS api url {@link apiUrl}. * @param apiUrl - The url of the HeanCMS api for the website */ -export function ChaptersSinglePageAJAX(apiUrl: string) { +export function ChaptersSinglePageAJAXv2(apiUrl: string) { return function DecorateClass<T extends Common.Constructor>(ctor: T, context?: ClassDecoratorContext): T { Common.ThrowOnUnsupportedDecoratorContext(context); return class extends ctor { public async FetchChapters(this: MangaScraper, manga: Manga): Promise<Chapter[]> { - return FetchChaptersSinglePageAJAX.call(this, manga, apiUrl); + return FetchChaptersSinglePageAJAXv2.call(this, manga, apiUrl); } }; }; @@ -200,9 +260,24 @@ export function ChaptersSinglePageAJAX(apiUrl: string) { * @param apiUrl - The url of the HeanCMS api for the website */ export async function FetchPagesSinglePageAJAX(this: MangaScraper, chapter: Chapter, apiUrl: string): Promise<Page[]> { - const request = new Request(new URL(`/chapter/${chapter.Parent.Identifier}/${chapter.Identifier}`, apiUrl)); - const { chapter_type, data, paywall, chapter: { storage } } = await FetchJSON<APIPages>(request); + const chapterid: MangaOrChapterId = JSON.parse(chapter.Identifier); + const mangaid: MangaOrChapterId = JSON.parse(chapter.Parent.Identifier); + const request = new Request(new URL(`${apiUrl}/chapter/${mangaid.slug}/${chapterid.slug}`)); + const data = await FetchJSON<APIPages>(request); + + // check for paywall + if (data.paywall) { + throw new Error(`${chapter.Title} is paywalled. Please login.`); + } + // check if novel + if (data.chapter.chapter_type.toLowerCase() === 'novel') { + return [new Page(this, chapter, new URL(`/series/${chapter.Parent.Identifier}/${chapter.Identifier}`, this.URI), { type: data.chapter_type })]; + } + + const listImages = data.data as string[] || data.chapter.chapter_data.images; + return listImages.map(image => new Page(this, chapter, computePageUrl(image, data.chapter.storage, apiUrl), { type: data.chapter.chapter_type })); + /* if (paywall) { throw new Error(`${chapter.Title} is paywalled. Please login.`); //localize this } @@ -215,7 +290,7 @@ export async function FetchPagesSinglePageAJAX(this: MangaScraper, chapter: Chap return [new Page(this, chapter, new URL(`/series/${chapter.Parent.Identifier}/${chapter.Identifier}`, this.URI), { type: chapter_type })]; } - return (data as string[]).map(image => new Page(this, chapter, computePageUrl(image, storage, apiUrl), { type: chapter_type })); + return (data as string[]).map(image => new Page(this, chapter, computePageUrl(image, storage, apiUrl), { type: chapter_type }));*/ } /** @@ -281,6 +356,6 @@ export function ImageAjax(detectMimeType = false, deProxifyLink = true, novelScr function computePageUrl(image: string, storage: string, apiUrl: string): URL { switch (storage) { case "s3": return new URL(image); - case "local": return new URL(image, apiUrl); + case "local": return new URL(`${apiUrl}/${image}`); } } From 09180560ddc9492a3c018e0bde99aff8a8593b9a Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Sun, 14 Apr 2024 11:18:51 +0200 Subject: [PATCH 15/31] better code --- web/src/engine/websites/decorators/HeanCMS.ts | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/web/src/engine/websites/decorators/HeanCMS.ts b/web/src/engine/websites/decorators/HeanCMS.ts index 650e46ba81..5e1bb9cde0 100644 --- a/web/src/engine/websites/decorators/HeanCMS.ts +++ b/web/src/engine/websites/decorators/HeanCMS.ts @@ -134,19 +134,15 @@ export function MangaCSS(pattern: RegExp, apiURL: string) { */ export async function FetchMangasMultiPageAJAX(this: MangaScraper, provider: MangaPlugin, apiUrl: string, throttle = 0): Promise<Manga[]> { const mangaList: Manga[] = []; - //First loop get adult mangas - for (let page = 1, run = true; run; page++) { - const mangas = await getMangaFromPage.call(this, provider, page, apiUrl, true); - mangas.length > 0 ? mangaList.push(...mangas) : run = false; - await new Promise(resolve => setTimeout(resolve, throttle)); - } - //First loop get non adult mangas - for (let page = 1, run = true; run; page++) { - const mangas = await getMangaFromPage.call(this, provider, page, apiUrl, false); - mangas.length > 0 ? mangaList.push(...mangas) : run = false; - await new Promise(resolve => setTimeout(resolve, throttle)); + + for (const adult of [true, false]) { //there is no "dont care if adult or not flag"" on "new" api, and old dont care about the flag + for (let page = 1, run = true; run; page++) { + const mangas = await getMangaFromPage.call(this, provider, page, apiUrl, adult); + mangas.length > 0 ? mangaList.push(...mangas) : run = false; + await new Promise(resolve => setTimeout(resolve, throttle)); + } } - return mangaList.distinct(); + return mangaList.distinct();//filter in case of old api } async function getMangaFromPage(this: MangaScraper, provider: MangaPlugin, page: number, apiUrl: string, adult: boolean): Promise<Manga[]> { const request = new Request(new URL(`${apiUrl}/query?perPage=100&page=${page}&adult=${adult}`)); From 4008c38ca4501db0bdd94ef2e4f87f439941f9e0 Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Sun, 14 Apr 2024 11:20:14 +0200 Subject: [PATCH 16/31] Modescanlor : use HeanCMS --- web/src/engine/websites/ModeScanlator.ts | 79 +++----------------- web/src/engine/websites/ModeScanlator_e2e.ts | 14 ++-- 2 files changed, 16 insertions(+), 77 deletions(-) diff --git a/web/src/engine/websites/ModeScanlator.ts b/web/src/engine/websites/ModeScanlator.ts index 4b1f2018a3..2e992da5da 100644 --- a/web/src/engine/websites/ModeScanlator.ts +++ b/web/src/engine/websites/ModeScanlator.ts @@ -1,83 +1,22 @@ import { Tags } from '../Tags'; import icon from './ModeScanlator.webp'; -import { type Chapter, DecoratableMangaScraper, Page } from '../providers/MangaPlugin'; -import * as Common from './decorators/Common'; -import { Fetch, FetchWindowScript } from '../platform/FetchProvider'; -import * as JSZip from 'jszip'; -import type { Priority } from '../taskpool/TaskPool'; +import { DecoratableMangaScraper } from '../providers/MangaPlugin'; +import * as HeamCMS from './decorators/HeanCMS'; -const pagescript = ` - new Promise((resolve, reject) => { - try { - resolve(urls); - } catch (error) { - const pages = [...document.querySelectorAll('div#imageContainer img')].map(image=> new URL(image.src, window.location.origin).href); - resolve(pages); - } - }); -`; +const apiUrl = 'https://api.modescanlator.com'; -function ChapterExtractor(anchor: HTMLAnchorElement) { - const id = anchor.pathname; - const title = anchor.querySelector('div.info__capitulo__obras span.numero__capitulo').textContent.trim(); - return { id, title }; -} +@HeamCMS.MangaCSS(/^{origin}\/series\/[^/]+$/, apiUrl) +@HeamCMS.MangasMultiPageAJAX(apiUrl) +@HeamCMS.ChaptersSinglePageAJAXv2(apiUrl) +@HeamCMS.PagesSinglePageAJAX(apiUrl) +@HeamCMS.ImageAjax() -@Common.MangaCSS(/^{origin}\/[^/]+\/$/, 'h1.desc__titulo__comic') -@Common.MangasSinglePageCSS('/todas-as-obras/', 'div.comics__all__box a.titulo__comic__allcomics') -@Common.ChaptersSinglePageCSS('ul.capitulos__lista a.link__capitulos', ChapterExtractor) export default class extends DecoratableMangaScraper { public constructor() { - super('modescanlator', `Mode Scanlator`, 'https://modescanlator.com', Tags.Language.Portuguese, Tags.Media.Manga, Tags.Media.Manhua, Tags.Media.Manhwa, Tags.Source.Scanlator), Tags.Accessibility.RegionLocked; + super('modescanlator', `Mode Scanlator`, 'https://modescanlator.com', Tags.Language.Portuguese, Tags.Media.Manga, Tags.Media.Manhua, Tags.Media.Manhwa, Tags.Source.Scanlator, Tags.Accessibility.RegionLocked); } public override get Icon() { return icon; } - - public override async FetchPages(chapter: Chapter): Promise<Page[]> { - //1st : first fetch zip files urls - let request = new Request(new URL(chapter.Identifier, this.URI).href); - const files: string[] = await FetchWindowScript(request, pagescript); - if (files.length == 0) return []; - - //if files are zip - if (files[0].endsWith('.zip')) { - //handle zip files - const pages : Page[]= []; - for (const zipurl of files) { - request = new Request(new URL(zipurl, this.URI).href); - const response = await Fetch(request); - const zipdata = await response.arrayBuffer(); - const zipfile = await JSZip.loadAsync(zipdata); - const fileNames = Object.keys(zipfile.files).sort((a, b) => this.extractNumber(a) - this.extractNumber(b)); - for (const fileName of fileNames) { - if (!fileName.match(/\.(s)$/i)) { //if extension is not .s (for svg), its a picture - pages.push(new Page(this, chapter, new URL(zipurl, this.URI), { filename: fileName })); - } - } - } - return pages; - - } else { //we have normal pictures links - return files.map(file => new Page(this, chapter, new URL(file))); - } - - } - extractNumber(fileName) : number { - return parseInt(fileName.split(".")[0]); - } - - public override async FetchImage(page: Page, priority: Priority, signal: AbortSignal): Promise<Blob> { - if (page.Link.href.endsWith('.zip')) { - const request = new Request(new URL(page.Link, this.URI).href); - const response = await Fetch(request); - const zipdata = await response.arrayBuffer(); - const zipfile = await JSZip.loadAsync(zipdata); - const zipEntry = zipfile.files[page.Parameters['filename'] as string]; - const imagebuffer = await zipEntry.async('nodebuffer'); - return Common.GetTypedData(imagebuffer); - } else return Common.FetchImageAjax.call(this, page, priority, signal); - } - } \ No newline at end of file diff --git a/web/src/engine/websites/ModeScanlator_e2e.ts b/web/src/engine/websites/ModeScanlator_e2e.ts index 924bbb256d..f8aac6ea05 100644 --- a/web/src/engine/websites/ModeScanlator_e2e.ts +++ b/web/src/engine/websites/ModeScanlator_e2e.ts @@ -6,18 +6,18 @@ const config: Config = { title: 'Mode Scanlator' }, container: { - url: 'https://modescanlator.com/eternal-first-son-in-law/', - id: '/eternal-first-son-in-law/', - title: 'Eternal First Son-In-Law', + url: 'https://modescanlator.com/series/uma-lenda-do-vento', + id: JSON.stringify({ id: '36', slug: 'uma-lenda-do-vento' }), + title: 'Uma Lenda do Vento', }, child: { - id: '/eternal-first-son-in-law/299/', - title: 'Capítulo 299', + id: JSON.stringify({ id: '2420', slug: 'capitulo-126' }), + title: 'Capítulo 126', }, entry: { index: 0, - size: 157_765, - type: 'image/avif' + size: 1_222_675, + type: 'image/jpeg' } }; From e4c5aff4fb75651e12932b83b8248624b292b688 Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Sat, 27 Apr 2024 14:04:24 +0200 Subject: [PATCH 17/31] fix case --- web/src/engine/websites/decorators/HeanCMS.ts | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/web/src/engine/websites/decorators/HeanCMS.ts b/web/src/engine/websites/decorators/HeanCMS.ts index 5e1bb9cde0..b57dc1b894 100644 --- a/web/src/engine/websites/decorators/HeanCMS.ts +++ b/web/src/engine/websites/decorators/HeanCMS.ts @@ -137,14 +137,14 @@ export async function FetchMangasMultiPageAJAX(this: MangaScraper, provider: Man for (const adult of [true, false]) { //there is no "dont care if adult or not flag"" on "new" api, and old dont care about the flag for (let page = 1, run = true; run; page++) { - const mangas = await getMangaFromPage.call(this, provider, page, apiUrl, adult); + const mangas = await GetMangaFromPage.call(this, provider, page, apiUrl, adult); mangas.length > 0 ? mangaList.push(...mangas) : run = false; await new Promise(resolve => setTimeout(resolve, throttle)); } } return mangaList.distinct();//filter in case of old api } -async function getMangaFromPage(this: MangaScraper, provider: MangaPlugin, page: number, apiUrl: string, adult: boolean): Promise<Manga[]> { +async function GetMangaFromPage(this: MangaScraper, provider: MangaPlugin, page: number, apiUrl: string, adult: boolean): Promise<Manga[]> { const request = new Request(new URL(`${apiUrl}/query?perPage=100&page=${page}&adult=${adult}`)); const { data } = await FetchJSON<APIResult<APIMangaV1[]>>(request); if (data.length) { @@ -271,22 +271,7 @@ export async function FetchPagesSinglePageAJAX(this: MangaScraper, chapter: Chap } const listImages = data.data as string[] || data.chapter.chapter_data.images; - return listImages.map(image => new Page(this, chapter, computePageUrl(image, data.chapter.storage, apiUrl), { type: data.chapter.chapter_type })); - - /* - if (paywall) { - throw new Error(`${chapter.Title} is paywalled. Please login.`); //localize this - } - - //in case of novel data is the html string, in case of comic its an array of strings (pictures urls or pathnames) - if (!data || data.length < 1) return []; - - // check if novel - if (chapter_type.toLowerCase() === 'novel') { - return [new Page(this, chapter, new URL(`/series/${chapter.Parent.Identifier}/${chapter.Identifier}`, this.URI), { type: chapter_type })]; - } - - return (data as string[]).map(image => new Page(this, chapter, computePageUrl(image, storage, apiUrl), { type: chapter_type }));*/ + return listImages.map(image => new Page(this, chapter, ComputePageUrl(image, data.chapter.storage, apiUrl), { type: data.chapter.chapter_type })); } /** @@ -349,7 +334,7 @@ export function ImageAjax(detectMimeType = false, deProxifyLink = true, novelScr * @param storage - As string representing the type of storage used : "s3" or "local" * @param apiUrl - The url of the HeanCMS api for the website * */ -function computePageUrl(image: string, storage: string, apiUrl: string): URL { +function ComputePageUrl(image: string, storage: string, apiUrl: string): URL { switch (storage) { case "s3": return new URL(image); case "local": return new URL(`${apiUrl}/${image}`); From 9ec054e36f83d0ee3a781374cf76ec40f6097ae7 Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Tue, 7 May 2024 12:10:49 +0200 Subject: [PATCH 18/31] update e2e tests to use vitest --- web/src/engine/websites/OmegaScans_e2e.ts | 7 ++++--- web/src/engine/websites/PerfScan_e2e.ts | 7 ++++--- web/src/engine/websites/TempleScan_e2e.ts | 5 +++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/web/src/engine/websites/OmegaScans_e2e.ts b/web/src/engine/websites/OmegaScans_e2e.ts index b404947985..f68697f47e 100644 --- a/web/src/engine/websites/OmegaScans_e2e.ts +++ b/web/src/engine/websites/OmegaScans_e2e.ts @@ -1,4 +1,5 @@ -import { TestFixture } from '../../../test/WebsitesFixture'; +import { describe } from 'vitest'; +import { TestFixture } from '../../../test/WebsitesFixture'; const ComicConfig = { plugin: { @@ -22,7 +23,7 @@ const ComicConfig = { }; const ComicFixture = new TestFixture(ComicConfig); -describe(ComicFixture.Name, () => ComicFixture.AssertWebsite()); +describe(ComicFixture.Name, async () => (await ComicFixture.Connect()).AssertWebsite()); const NovelConfig = { plugin: { @@ -46,4 +47,4 @@ const NovelConfig = { }; const NovelFixture = new TestFixture(NovelConfig); -describe(NovelFixture.Name, () => NovelFixture.AssertWebsite()); \ No newline at end of file +describe(NovelFixture.Name, async () => (await NovelFixture.Connect()).AssertWebsite()); \ No newline at end of file diff --git a/web/src/engine/websites/PerfScan_e2e.ts b/web/src/engine/websites/PerfScan_e2e.ts index c3c945daaa..f1e4d4344b 100644 --- a/web/src/engine/websites/PerfScan_e2e.ts +++ b/web/src/engine/websites/PerfScan_e2e.ts @@ -1,4 +1,5 @@ -import { TestFixture } from '../../../test/WebsitesFixture'; +import { describe } from 'vitest'; +import { TestFixture } from '../../../test/WebsitesFixture'; const ComicConfig = { plugin: { @@ -22,7 +23,7 @@ const ComicConfig = { }; const ComicFixture = new TestFixture(ComicConfig); -describe(ComicFixture.Name, () => ComicFixture.AssertWebsite()); +describe(ComicFixture.Name, async () => (await ComicFixture.Connect()).AssertWebsite()); const NovelConfig = { plugin: { @@ -46,4 +47,4 @@ const NovelConfig = { }; const NovelFixture = new TestFixture(NovelConfig); -describe(NovelFixture.Name, () => NovelFixture.AssertWebsite()); +describe(NovelFixture.Name, async () => (await NovelFixture.Connect()).AssertWebsite()); \ No newline at end of file diff --git a/web/src/engine/websites/TempleScan_e2e.ts b/web/src/engine/websites/TempleScan_e2e.ts index f13bd8e16c..f883e0af3a 100644 --- a/web/src/engine/websites/TempleScan_e2e.ts +++ b/web/src/engine/websites/TempleScan_e2e.ts @@ -1,4 +1,5 @@ -import { TestFixture } from '../../../test/WebsitesFixture'; +import { describe } from 'vitest'; +import { TestFixture } from '../../../test/WebsitesFixture'; const ComicConfig = { plugin: { @@ -22,4 +23,4 @@ const ComicConfig = { }; const ComicFixture = new TestFixture(ComicConfig); -describe(ComicFixture.Name, () => ComicFixture.AssertWebsite()); \ No newline at end of file +describe(ComicFixture.Name, async () => (await ComicFixture.Connect()).AssertWebsite()); \ No newline at end of file From e2effeb58ee80152df9b5f50744bd79a66a23b7a Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:05:16 +0200 Subject: [PATCH 19/31] Update ModeScanlator.ts --- web/src/engine/websites/ModeScanlator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/engine/websites/ModeScanlator.ts b/web/src/engine/websites/ModeScanlator.ts index 2e992da5da..41f191f888 100644 --- a/web/src/engine/websites/ModeScanlator.ts +++ b/web/src/engine/websites/ModeScanlator.ts @@ -14,7 +14,7 @@ const apiUrl = 'https://api.modescanlator.com'; export default class extends DecoratableMangaScraper { public constructor() { - super('modescanlator', `Mode Scanlator`, 'https://modescanlator.com', Tags.Language.Portuguese, Tags.Media.Manga, Tags.Media.Manhua, Tags.Media.Manhwa, Tags.Source.Scanlator, Tags.Accessibility.RegionLocked); + super('modescanlator', `Mode Scanlator`, 'https://site.modescanlator.com', Tags.Language.Portuguese, Tags.Media.Manga, Tags.Media.Manhua, Tags.Media.Manhwa, Tags.Source.Scanlator); } public override get Icon() { return icon; From 6b5f32addb83d2dd6b3ca47ff6f0577ec89cb291 Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:17:27 +0200 Subject: [PATCH 20/31] Update HeanCMS.ts --- web/src/engine/websites/decorators/HeanCMS.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/web/src/engine/websites/decorators/HeanCMS.ts b/web/src/engine/websites/decorators/HeanCMS.ts index b57dc1b894..b91127140a 100644 --- a/web/src/engine/websites/decorators/HeanCMS.ts +++ b/web/src/engine/websites/decorators/HeanCMS.ts @@ -195,6 +195,7 @@ export async function FetchChaptersSinglePageAJAXv1(this: MangaScraper, manga: M chapterList.push(new Chapter(this, manga, id, title)); })); return chapterList; + /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ } catch (error) { return []; } From 33840ac73e170fc253d70491d289f7be300645c0 Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Wed, 14 Aug 2024 21:59:07 +0200 Subject: [PATCH 21/31] fix modescanlator tests --- web/src/engine/websites/ModeScanlator_e2e.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/engine/websites/ModeScanlator_e2e.ts b/web/src/engine/websites/ModeScanlator_e2e.ts index a1560455eb..88da43d0e5 100644 --- a/web/src/engine/websites/ModeScanlator_e2e.ts +++ b/web/src/engine/websites/ModeScanlator_e2e.ts @@ -7,7 +7,7 @@ const config: Config = { title: 'Mode Scanlator' }, container: { - url: 'https://modescanlator.com/series/uma-lenda-do-vento', + url: 'https://site.modescanlator.com/series/uma-lenda-do-vento', id: JSON.stringify({ id: '36', slug: 'uma-lenda-do-vento' }), title: 'Uma Lenda do Vento', }, From 06795369ae2bd14488b698aec015902d21a96297 Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Wed, 21 Aug 2024 17:30:23 +0200 Subject: [PATCH 22/31] remove TempleScan no more HeanCMS --- web/src/engine/websites/TempleScan.ts | 22 ------------------ web/src/engine/websites/TempleScan.webp | Bin 1950 -> 0 bytes web/src/engine/websites/TempleScan_e2e.ts | 26 ---------------------- web/src/engine/websites/_index.ts | 2 ++ 4 files changed, 2 insertions(+), 48 deletions(-) delete mode 100644 web/src/engine/websites/TempleScan.ts delete mode 100644 web/src/engine/websites/TempleScan.webp delete mode 100644 web/src/engine/websites/TempleScan_e2e.ts diff --git a/web/src/engine/websites/TempleScan.ts b/web/src/engine/websites/TempleScan.ts deleted file mode 100644 index 7efdd73540..0000000000 --- a/web/src/engine/websites/TempleScan.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Tags } from '../Tags'; -import icon from './TempleScan.webp'; -import { DecoratableMangaScraper } from '../providers/MangaPlugin'; -import * as HeamCMS from './decorators/HeanCMS'; - -const apiUrl = 'https://templescan.net/apiv1'; - -@HeamCMS.MangaCSS(/^{origin}\/comic\/[^/]+$/, apiUrl) -@HeamCMS.MangasMultiPageAJAX(apiUrl) -@HeamCMS.ChaptersSinglePageAJAXv1(apiUrl) -@HeamCMS.PagesSinglePageAJAX(apiUrl) -@HeamCMS.ImageAjax(true) -export default class extends DecoratableMangaScraper { - - public constructor() { - super('templescan', 'TempleScan', 'https://templescan.net', Tags.Media.Manga, Tags.Media.Manhwa, Tags.Media.Manhua, Tags.Language.English, Tags.Source.Scanlator); - } - - public override get Icon() { - return icon; - } -} \ No newline at end of file diff --git a/web/src/engine/websites/TempleScan.webp b/web/src/engine/websites/TempleScan.webp deleted file mode 100644 index a85135682e061a2f4dfc9605cb86a1bd38cfa1cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1950 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 diff --git a/web/src/engine/websites/TempleScan_e2e.ts b/web/src/engine/websites/TempleScan_e2e.ts deleted file mode 100644 index f883e0af3a..0000000000 --- a/web/src/engine/websites/TempleScan_e2e.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { describe } from 'vitest'; -import { TestFixture } from '../../../test/WebsitesFixture'; - -const ComicConfig = { - plugin: { - id: 'templescan', - title: 'TempleScan' - }, - container: { - url: 'https://templescan.net/comic/predatory-marriage-complete-edition', - id: JSON.stringify({ id: '55', slug: 'predatory-marriage-complete-edition' }), - title: `Predatory Marriage (Complete Edition)` - }, - child: { - id: JSON.stringify({ id: '1028', slug: '83662-chapter-15' }), - title: 'Chapter 15' - }, - entry: { - index: 0, - size: 2_953_394, - type: 'image/jpeg' - } -}; - -const ComicFixture = new TestFixture(ComicConfig); -describe(ComicFixture.Name, async () => (await ComicFixture.Connect()).AssertWebsite()); \ No newline at end of file diff --git a/web/src/engine/websites/_index.ts b/web/src/engine/websites/_index.ts index 03fdf40e7b..540902ec3d 100755 --- a/web/src/engine/websites/_index.ts +++ b/web/src/engine/websites/_index.ts @@ -501,6 +501,7 @@ export { default as NovelMic } from './NovelMic'; export { default as NvManga } from './NvManga'; export { default as Nyrax } from './Nyrax'; export { default as OlympusScanlation } from './OlympusScanlation'; +export { default as OmegaScans } from './OmegaScans'; export { default as OnMangaMe } from './OnMangaMe'; export { default as Opiatoon } from './Opiatoon'; export { default as Oremanga } from './Oremanga'; @@ -513,6 +514,7 @@ export { default as PCNet } from './PCNet'; export { default as PeanuToon } from './PeanuToon'; export { default as PelaTeam } from './PelaTeam'; export { default as Penlab } from './Penlab'; +export { default as PerfScan } from './PerfScan'; export { default as PhenixScans } from './PhenixScans'; export { default as PhoenixScansIT } from './PhoenixScansIT'; export { default as Piccoma } from './Piccoma'; From 32b81f024f05903a3934728e632a7d3b3c2252fd Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Mon, 23 Sep 2024 13:22:14 +0200 Subject: [PATCH 23/31] Update HeanCMS.ts --- web/src/engine/websites/decorators/HeanCMS.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/web/src/engine/websites/decorators/HeanCMS.ts b/web/src/engine/websites/decorators/HeanCMS.ts index b91127140a..a4ebb72f47 100644 --- a/web/src/engine/websites/decorators/HeanCMS.ts +++ b/web/src/engine/websites/decorators/HeanCMS.ts @@ -147,10 +147,8 @@ export async function FetchMangasMultiPageAJAX(this: MangaScraper, provider: Man async function GetMangaFromPage(this: MangaScraper, provider: MangaPlugin, page: number, apiUrl: string, adult: boolean): Promise<Manga[]> { const request = new Request(new URL(`${apiUrl}/query?perPage=100&page=${page}&adult=${adult}`)); const { data } = await FetchJSON<APIResult<APIMangaV1[]>>(request); - if (data.length) { - return data.map((manga) => new Manga(this, provider, JSON.stringify({ id: manga.id.toString(), slug: manga.series_slug }), manga.title)); - } - return []; + return !data.length ? [] : data.map((manga) => new Manga(this, provider, JSON.stringify({ id: manga.id.toString(), slug: manga.series_slug }), manga.title)); + } /** @@ -195,8 +193,7 @@ export async function FetchChaptersSinglePageAJAXv1(this: MangaScraper, manga: M chapterList.push(new Chapter(this, manga, id, title)); })); return chapterList; - /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ - } catch (error) { + } catch { return []; } } @@ -262,11 +259,10 @@ export async function FetchPagesSinglePageAJAX(this: MangaScraper, chapter: Chap const request = new Request(new URL(`${apiUrl}/chapter/${mangaid.slug}/${chapterid.slug}`)); const data = await FetchJSON<APIPages>(request); - // check for paywall if (data.paywall) { throw new Error(`${chapter.Title} is paywalled. Please login.`); } - // check if novel + if (data.chapter.chapter_type.toLowerCase() === 'novel') { return [new Page(this, chapter, new URL(`/series/${chapter.Parent.Identifier}/${chapter.Identifier}`, this.URI), { type: data.chapter_type })]; } From 5d1a5a9ffabff31a9f03ed7f8e55015b5a1c9ede Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Sat, 28 Sep 2024 13:04:19 +0200 Subject: [PATCH 24/31] remove novel support & multiple fixes --- web/src/engine/websites/ModeScanlator.ts | 4 +- web/src/engine/websites/ModeScanlator_e2e.ts | 3 +- web/src/engine/websites/OmegaScans_e2e.ts | 26 +---- web/src/engine/websites/decorators/HeanCMS.ts | 97 +++++++------------ web/src/i18n/ILocale.ts | 5 + web/src/i18n/locales/en_US.ts | 2 + 6 files changed, 46 insertions(+), 91 deletions(-) diff --git a/web/src/engine/websites/ModeScanlator.ts b/web/src/engine/websites/ModeScanlator.ts index 41f191f888..80bfb94b4b 100644 --- a/web/src/engine/websites/ModeScanlator.ts +++ b/web/src/engine/websites/ModeScanlator.ts @@ -3,7 +3,7 @@ import icon from './ModeScanlator.webp'; import { DecoratableMangaScraper } from '../providers/MangaPlugin'; import * as HeamCMS from './decorators/HeanCMS'; -const apiUrl = 'https://api.modescanlator.com'; +const apiUrl = 'https://api.modescanlator.net'; @HeamCMS.MangaCSS(/^{origin}\/series\/[^/]+$/, apiUrl) @HeamCMS.MangasMultiPageAJAX(apiUrl) @@ -14,7 +14,7 @@ const apiUrl = 'https://api.modescanlator.com'; export default class extends DecoratableMangaScraper { public constructor() { - super('modescanlator', `Mode Scanlator`, 'https://site.modescanlator.com', Tags.Language.Portuguese, Tags.Media.Manga, Tags.Media.Manhua, Tags.Media.Manhwa, Tags.Source.Scanlator); + super('modescanlator', `Mode Scanlator`, 'https://site.modescanlator.net', Tags.Language.Portuguese, Tags.Media.Manga, Tags.Media.Manhua, Tags.Media.Manhwa, Tags.Source.Scanlator); } public override get Icon() { return icon; diff --git a/web/src/engine/websites/ModeScanlator_e2e.ts b/web/src/engine/websites/ModeScanlator_e2e.ts index 88da43d0e5..e574def997 100644 --- a/web/src/engine/websites/ModeScanlator_e2e.ts +++ b/web/src/engine/websites/ModeScanlator_e2e.ts @@ -7,9 +7,10 @@ const config: Config = { title: 'Mode Scanlator' }, container: { - url: 'https://site.modescanlator.com/series/uma-lenda-do-vento', + url: 'https://site.modescanlator.net/series/uma-lenda-do-vento', id: JSON.stringify({ id: '36', slug: 'uma-lenda-do-vento' }), title: 'Uma Lenda do Vento', + timeout: 10000 }, child: { id: JSON.stringify({ id: '2420', slug: 'capitulo-126' }), diff --git a/web/src/engine/websites/OmegaScans_e2e.ts b/web/src/engine/websites/OmegaScans_e2e.ts index f68697f47e..163a757026 100644 --- a/web/src/engine/websites/OmegaScans_e2e.ts +++ b/web/src/engine/websites/OmegaScans_e2e.ts @@ -23,28 +23,4 @@ const ComicConfig = { }; const ComicFixture = new TestFixture(ComicConfig); -describe(ComicFixture.Name, async () => (await ComicFixture.Connect()).AssertWebsite()); - -const NovelConfig = { - plugin: { - id: 'omegascans', - title: 'OmegaScans' - }, - container: { - url: 'https://omegascans.org/series/the-scion-of-the-labyrinth-city', - id: JSON.stringify({ id: '186', slug: 'the-scion-of-the-labyrinth-city' }), - title: 'The Scion of the Labyrinth City' - }, - child: { - id: JSON.stringify({ id: '3226', slug: 'chapter-28' }), - title: 'Chapter 28' - }, - entry: { - index: 0, - size: 892_312, - type: 'image/png' - } -}; - -const NovelFixture = new TestFixture(NovelConfig); -describe(NovelFixture.Name, async () => (await NovelFixture.Connect()).AssertWebsite()); \ No newline at end of file +describe(ComicFixture.Name, async () => (await ComicFixture.Connect()).AssertWebsite()); \ No newline at end of file diff --git a/web/src/engine/websites/decorators/HeanCMS.ts b/web/src/engine/websites/decorators/HeanCMS.ts index a4ebb72f47..112cf0df2e 100644 --- a/web/src/engine/websites/decorators/HeanCMS.ts +++ b/web/src/engine/websites/decorators/HeanCMS.ts @@ -1,40 +1,11 @@ -import { FetchJSON, FetchWindowScript } from '../../platform/FetchProvider'; +import { Exception } from '../../Error'; +import { FetchJSON } from '../../platform/FetchProvider'; import { type MangaScraper, type MangaPlugin, Manga, Chapter, Page } from '../../providers/MangaPlugin'; import type { Priority } from '../../taskpool/TaskPool'; import * as Common from './Common'; +import { WebsiteResourceKey as R } from '../../../i18n/ILocale'; -//TODO: get novel color theme from settings and apply them to the script somehow. Setting must ofc have been initializated before -//add a listener on the aforementionned setting - -const DefaultNovelScript = ` - new Promise((resolve, reject) => { - document.body.style.width = '56em'; - let container = document.querySelector('div.container'); - container.style.maxWidth = '56em'; - container.style.padding = '0'; - container.style.margin = '0'; - let novel = document.querySelector('div#reader-container'); - novel.style.padding = '1.5em'; - [...novel.querySelectorAll(":not(:empty)")].forEach(ele => { - ele.style.backgroundColor = 'black' - ele.style.color = 'white' - }) - novel.style.backgroundColor = 'black' - novel.style.color = 'white' - let script = document.createElement('script'); - script.onerror = error => reject(error); - script.onload = async function() { - try { - let canvas = await html2canvas(novel); - resolve([canvas.toDataURL('image/png')]); - } catch (error) { - reject(error) - } - } - script.src = 'https://html2canvas.hertzen.com/dist/html2canvas.min.js'; - document.body.appendChild(script); - }); - `; +// TODO: Add Novel support type APIMangaV1 = { title: string @@ -80,6 +51,10 @@ type MangaOrChapterId = { slug: string } +type PageType = { + type: 'Comic' | 'Novel' +} + /*************************************************** ******** Manga from URL Extraction Methods ******** ***************************************************/ @@ -92,7 +67,7 @@ type MangaOrChapterId = { * @param apiUrl - The url of the HeanCMS api for the website */ export async function FetchMangaCSS(this: MangaScraper, provider: MangaPlugin, url: string, apiUrl: string): Promise<Manga> { - const slug = new URL(url).pathname.split('/')[2]; + const slug = new URL(url).pathname.split('/').at(-1); const { title, series_slug, id } = await FetchJSON<APIMangaV1>(new Request(new URL(`${apiUrl}/series/${slug}`))); return new Manga(this, provider, JSON.stringify({ id: id.toString(), @@ -135,7 +110,7 @@ export function MangaCSS(pattern: RegExp, apiURL: string) { export async function FetchMangasMultiPageAJAX(this: MangaScraper, provider: MangaPlugin, apiUrl: string, throttle = 0): Promise<Manga[]> { const mangaList: Manga[] = []; - for (const adult of [true, false]) { //there is no "dont care if adult or not flag"" on "new" api, and old dont care about the flag + for (const adult of [true, false]) { //there is no "dont care if adult or not flag" on "new" api, and old dont care about the flag for (let page = 1, run = true; run; page++) { const mangas = await GetMangaFromPage.call(this, provider, page, apiUrl, adult); mangas.length > 0 ? mangaList.push(...mangas) : run = false; @@ -182,17 +157,19 @@ export async function FetchChaptersSinglePageAJAXv1(this: MangaScraper, manga: M const mangaslug = (JSON.parse(manga.Identifier) as MangaOrChapterId).slug; const request = new Request(new URL(`${apiUrl}/series/${mangaslug}`)); const { seasons } = await FetchJSON<APIMangaV1>(request); - const chapterList: Chapter[] = []; - - seasons.map((season) => season.chapters.map((chapter) => { - const id = JSON.stringify({ - id: chapter.id.toString(), - slug: chapter.chapter_slug + return seasons.reduce(async (accumulator: Promise<Chapter[]>, season) => { + const chapters = season.chapters.map(chapter => { + const id = JSON.stringify({ + id: chapter.id.toString(), + slug: chapter.chapter_slug + }); + const title = `${seasons.length > 1 ? 'S' + season.index : ''} ${chapter.chapter_name} ${chapter.chapter_title || ''}`.trim(); + return new Chapter(this, manga, id, title); }); - const title = `${seasons.length > 1 ? 'S' + season.index : ''} ${chapter.chapter_name} ${chapter.chapter_title || ''}`.trim(); - chapterList.push(new Chapter(this, manga, id, title)); - })); - return chapterList; + (await accumulator).concat(...chapters); + return accumulator; + }, Promise.resolve<Chapter[]>([])); + } catch { return []; } @@ -253,22 +230,21 @@ export function ChaptersSinglePageAJAXv2(apiUrl: string) { * @param chapter - A reference to the {@link Chapter} which shall be assigned as parent for the extracted pages * @param apiUrl - The url of the HeanCMS api for the website */ -export async function FetchPagesSinglePageAJAX(this: MangaScraper, chapter: Chapter, apiUrl: string): Promise<Page[]> { +export async function FetchPagesSinglePageAJAX(this: MangaScraper, chapter: Chapter, apiUrl: string): Promise<Page<PageType>[]> { const chapterid: MangaOrChapterId = JSON.parse(chapter.Identifier); const mangaid: MangaOrChapterId = JSON.parse(chapter.Parent.Identifier); - const request = new Request(new URL(`${apiUrl}/chapter/${mangaid.slug}/${chapterid.slug}`)); - const data = await FetchJSON<APIPages>(request); + const data = await FetchJSON<APIPages>(new Request(new URL(`${apiUrl}/chapter/${mangaid.slug}/${chapterid.slug}`))); if (data.paywall) { - throw new Error(`${chapter.Title} is paywalled. Please login.`); + throw new Exception(R.Plugin_Common_Chapter_UnavailableError); } if (data.chapter.chapter_type.toLowerCase() === 'novel') { - return [new Page(this, chapter, new URL(`/series/${chapter.Parent.Identifier}/${chapter.Identifier}`, this.URI), { type: data.chapter_type })]; + throw new Exception(R.Plugin_HeanCMS_ErrorNovelsNotSupported); } - const listImages = data.data as string[] || data.chapter.chapter_data.images; - return listImages.map(image => new Page(this, chapter, ComputePageUrl(image, data.chapter.storage, apiUrl), { type: data.chapter.chapter_type })); + const listImages = data.data && Array.isArray(data.data) ? data.data as string[] : data.chapter.chapter_data.images; + return listImages.map(image => new Page<PageType>(this, chapter, ComputePageUrl(image, data.chapter.storage, apiUrl), { type: data.chapter.chapter_type })); } /** @@ -279,7 +255,7 @@ export function PagesSinglePageAJAX(apiUrl: string) { return function DecorateClass<T extends Common.Constructor>(ctor: T, context?: ClassDecoratorContext): T { Common.ThrowOnUnsupportedDecoratorContext(context); return class extends ctor { - public async FetchPages(this: MangaScraper, chapter: Chapter): Promise<Page[]> { + public async FetchPages(this: MangaScraper, chapter: Chapter): Promise<Page<PageType>[]> { return FetchPagesSinglePageAJAX.call(this, chapter, apiUrl); } }; @@ -298,16 +274,11 @@ export function PagesSinglePageAJAX(apiUrl: string) { * @param signal - An abort signal that can be used to cancel the request for the image data * @param detectMimeType - Force a fingerprint check of the image data to detect its mime-type (instead of relying on the Content-Type header) * @param deProxifyLink - Remove common image proxies (default false) - * @param novelScript - a custom script to get and transform the novel text into a dataURL */ -export async function FetchImageAjax(this: MangaScraper, page: Page, priority: Priority, signal: AbortSignal, detectMimeType = false, deProxifyLink = true, novelScript = DefaultNovelScript): Promise<Blob> { - if (page.Parameters?.type as string === 'Comic') { +export async function FetchImageAjax(this: MangaScraper, page: Page, priority: Priority, signal: AbortSignal, detectMimeType = false, deProxifyLink = true): Promise<Blob> { + if (page.Parameters?.type === 'Comic') { return Common.FetchImageAjax.call(this, page, priority, signal, detectMimeType, deProxifyLink); - } else { - //TODO: test if user want to export the NOVEL as HTML? - const data = await FetchWindowScript<string>(new Request(page.Link), novelScript, 1000, 10000); - return Common.FetchImageAjax.call(this, new Page(this, page.Parent as Chapter, new URL(data)), priority, signal, false, false); - } + } else throw new Exception(R.Plugin_HeanCMS_ErrorNovelsNotSupported); } /** @@ -315,12 +286,12 @@ export async function FetchImageAjax(this: MangaScraper, page: Page, priority: P * @param detectMimeType - Force a fingerprint check of the image data to detect its mime-type (instead of relying on the Content-Type header) * @param deProxifyLink - Remove common image proxies (default false) */ -export function ImageAjax(detectMimeType = false, deProxifyLink = true, novelScript: string = DefaultNovelScript) { +export function ImageAjax(detectMimeType = false, deProxifyLink = true) { return function DecorateClass<T extends Common.Constructor>(ctor: T, context?: ClassDecoratorContext): T { Common.ThrowOnUnsupportedDecoratorContext(context); return class extends ctor { public async FetchImage(this: MangaScraper, page: Page, priority: Priority, signal: AbortSignal): Promise<Blob> { - return FetchImageAjax.call(this, page, priority, signal, detectMimeType, deProxifyLink, novelScript); + return FetchImageAjax.call(this, page, priority, signal, detectMimeType, deProxifyLink); } }; }; diff --git a/web/src/i18n/ILocale.ts b/web/src/i18n/ILocale.ts index 1259a45786..1734c34549 100644 --- a/web/src/i18n/ILocale.ts +++ b/web/src/i18n/ILocale.ts @@ -358,6 +358,11 @@ export enum WebsiteResourceKey { Plugin_SheepScanlations_Settings_PasswordInfo = 'Plugin_SheepScanlations_Settings_PasswordInfo', } +// [SECTION]: Template : HEANCMS +export enum WebsiteResourceKey { + Plugin_HeanCMS_ErrorNovelsNotSupported = 'Plugin_HeanCMS_ErrorNovelsNotSupported' +} + export const VariantResourceKey = { ...TagCategoryResourceKey, ...TagResourceKey, diff --git a/web/src/i18n/locales/en_US.ts b/web/src/i18n/locales/en_US.ts index 30ab0e01e8..ed15c75d7a 100644 --- a/web/src/i18n/locales/en_US.ts +++ b/web/src/i18n/locales/en_US.ts @@ -315,6 +315,8 @@ const translations: VariantResource = { Plugin_CuuTruyen_Error_NotProcessed: 'This chapter is still processing, please try again later.', + Plugin_HeanCMS_ErrorNovelsNotSupported: 'Novels are not (yet) supported in Hakuneko !', + Plugin_PocketComics_LanguageMismatchError: 'Unable to find manga {0} for selected language {1}', Plugin_SheepScanlations_Settings_Username: 'Username', From 1f6e066e4a15a76eeaf1d117d65a48d1cb2a6a31 Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Sun, 13 Oct 2024 11:13:05 +0200 Subject: [PATCH 25/31] turn HeanCMS into a template --- web/src/engine/websites/ModeScanlator.ts | 15 +- web/src/engine/websites/OmegaScans.ts | 12 +- web/src/engine/websites/PerfScan.ts | 12 +- web/src/engine/websites/decorators/HeanCMS.ts | 310 ------------------ web/src/engine/websites/templates/HeanCMS.ts | 125 +++++++ 5 files changed, 133 insertions(+), 341 deletions(-) delete mode 100644 web/src/engine/websites/decorators/HeanCMS.ts create mode 100644 web/src/engine/websites/templates/HeanCMS.ts diff --git a/web/src/engine/websites/ModeScanlator.ts b/web/src/engine/websites/ModeScanlator.ts index 80bfb94b4b..d53b5177f2 100644 --- a/web/src/engine/websites/ModeScanlator.ts +++ b/web/src/engine/websites/ModeScanlator.ts @@ -1,21 +1,14 @@ import { Tags } from '../Tags'; import icon from './ModeScanlator.webp'; -import { DecoratableMangaScraper } from '../providers/MangaPlugin'; -import * as HeamCMS from './decorators/HeanCMS'; +import { HeanCMS } from './templates/HeanCMS'; -const apiUrl = 'https://api.modescanlator.net'; - -@HeamCMS.MangaCSS(/^{origin}\/series\/[^/]+$/, apiUrl) -@HeamCMS.MangasMultiPageAJAX(apiUrl) -@HeamCMS.ChaptersSinglePageAJAXv2(apiUrl) -@HeamCMS.PagesSinglePageAJAX(apiUrl) -@HeamCMS.ImageAjax() - -export default class extends DecoratableMangaScraper { +export default class extends HeanCMS { public constructor() { super('modescanlator', `Mode Scanlator`, 'https://site.modescanlator.net', Tags.Language.Portuguese, Tags.Media.Manga, Tags.Media.Manhua, Tags.Media.Manhwa, Tags.Source.Scanlator); + this.apiUrl = this.URI.origin.replace('site', 'api'); } + public override get Icon() { return icon; } diff --git a/web/src/engine/websites/OmegaScans.ts b/web/src/engine/websites/OmegaScans.ts index 6bd81f03c7..8049fb5153 100644 --- a/web/src/engine/websites/OmegaScans.ts +++ b/web/src/engine/websites/OmegaScans.ts @@ -1,16 +1,8 @@ import { Tags } from '../Tags'; import icon from './OmegaScans.webp'; -import { DecoratableMangaScraper } from '../providers/MangaPlugin'; -import * as HeamCMS from './decorators/HeanCMS'; +import { HeanCMS } from './templates/HeanCMS'; -const apiUrl = 'https://api.omegascans.org'; - -@HeamCMS.MangaCSS(/^{origin}\/series\/[^/]+$/, apiUrl) -@HeamCMS.MangasMultiPageAJAX(apiUrl) -@HeamCMS.ChaptersSinglePageAJAXv2(apiUrl) -@HeamCMS.PagesSinglePageAJAX(apiUrl) -@HeamCMS.ImageAjax(true) -export default class extends DecoratableMangaScraper { +export default class extends HeanCMS { public constructor() { super('omegascans', 'OmegaScans', 'https://omegascans.org', Tags.Media.Manga, Tags.Media.Manhwa, Tags.Media.Manhua, Tags.Media.Novel, Tags.Language.English, Tags.Source.Scanlator, Tags.Rating.Pornographic); diff --git a/web/src/engine/websites/PerfScan.ts b/web/src/engine/websites/PerfScan.ts index f988ad90fa..755216ccd2 100644 --- a/web/src/engine/websites/PerfScan.ts +++ b/web/src/engine/websites/PerfScan.ts @@ -1,16 +1,8 @@ import { Tags } from '../Tags'; import icon from './PerfScan.webp'; -import { DecoratableMangaScraper } from '../providers/MangaPlugin'; -import * as HeamCMS from './decorators/HeanCMS'; +import { HeanCMS } from './templates/HeanCMS'; -const apiUrl = 'https://api.perf-scan.fr'; - -@HeamCMS.MangaCSS(/^{origin}\/series\/[^/]+$/, apiUrl) -@HeamCMS.MangasMultiPageAJAX(apiUrl) -@HeamCMS.ChaptersSinglePageAJAXv2(apiUrl) -@HeamCMS.PagesSinglePageAJAX(apiUrl) -@HeamCMS.ImageAjax() -export default class extends DecoratableMangaScraper { +export default class extends HeanCMS { public constructor() { super('perfscan', 'Perf Scan', 'https://perf-scan.fr', Tags.Media.Manga, Tags.Media.Manhwa, Tags.Media.Manhua, Tags.Media.Novel, Tags.Language.French, Tags.Source.Scanlator); diff --git a/web/src/engine/websites/decorators/HeanCMS.ts b/web/src/engine/websites/decorators/HeanCMS.ts deleted file mode 100644 index 112cf0df2e..0000000000 --- a/web/src/engine/websites/decorators/HeanCMS.ts +++ /dev/null @@ -1,310 +0,0 @@ -import { Exception } from '../../Error'; -import { FetchJSON } from '../../platform/FetchProvider'; -import { type MangaScraper, type MangaPlugin, Manga, Chapter, Page } from '../../providers/MangaPlugin'; -import type { Priority } from '../../taskpool/TaskPool'; -import * as Common from './Common'; -import { WebsiteResourceKey as R } from '../../../i18n/ILocale'; - -// TODO: Add Novel support - -type APIMangaV1 = { - title: string - id: number, - series_type: 'Comic' | 'Novel', - series_slug: string, - seasons?: APISeason[] -} - -type APIResult<T> = { - data: T -} - -type APISeason = { - index: number, - chapters: APIChapter[] -} - -type APIChapter = { - index: string, - id: number, - chapter_name: string, - chapter_title: string, - chapter_slug: string, -} - -type APIPages = { - chapter_type: 'Comic' | 'Novel', - paywall: boolean, - data: string[] | string - chapter: { - chapter_type: 'Comic' | 'Novel', - storage: string, - chapter_data?: { - images: string[] - } - - } -} - -type MangaOrChapterId = { - id: string, - slug: string -} - -type PageType = { - type: 'Comic' | 'Novel' -} - -/*************************************************** - ******** Manga from URL Extraction Methods ******** - ***************************************************/ - -/** - * An extension method for extracting a single manga from the given {@link url} using the HeanCMS api url {@link apiUrl}. - * @param this - A reference to the {@link MangaScraper} instance which will be used as context for this method - * @param provider - A reference to the {@link MangaPlugin} which shall be assigned as parent for the extracted manga - * @param url - the manga url - * @param apiUrl - The url of the HeanCMS api for the website - */ -export async function FetchMangaCSS(this: MangaScraper, provider: MangaPlugin, url: string, apiUrl: string): Promise<Manga> { - const slug = new URL(url).pathname.split('/').at(-1); - const { title, series_slug, id } = await FetchJSON<APIMangaV1>(new Request(new URL(`${apiUrl}/series/${slug}`))); - return new Manga(this, provider, JSON.stringify({ - id: id.toString(), - slug: series_slug - }), title); -} - -/** - * An extension method for extracting a single manga from any url using the HeanCMS api url {@link apiUrl}. - * @param pattern - An expression to check if a manga can be extracted from an url or not, it may contain the placeholders `{origin}` and `{hostname}` which will be replaced with the corresponding parameters based on the website's base URL - * @param apiUrl - The url of the HeanCMS api for the website - */ -export function MangaCSS(pattern: RegExp, apiURL: string) { - return function DecorateClass<T extends Common.Constructor>(ctor: T, context?: ClassDecoratorContext): T { - Common.ThrowOnUnsupportedDecoratorContext(context); - return class extends ctor { - public ValidateMangaURL(this: MangaScraper, url: string): boolean { - const source = pattern.source.replaceAll('{origin}', this.URI.origin).replaceAll('{hostname}', this.URI.hostname); - return new RegExp(source, pattern.flags).test(url); - } - public async FetchManga(this: MangaScraper, provider: MangaPlugin, url: string): Promise<Manga> { - return FetchMangaCSS.call(this, provider, url, apiURL); - } - }; - }; -} - -/*********************************************** - ******** Manga List Extraction Methods ******** - ***********************************************/ - -/** - * An extension method for extracting multiple mangas using the HeanCMS api url {@link apiUrl}. - * The range begins with 1 and is incremented until no more new mangas can be extracted. - * @param this - A reference to the {@link MangaScraper} instance which will be used as context for this method - * @param provider - A reference to the {@link MangaPlugin} which shall be assigned as parent for the extracted mangas - * @param apiUrl - The url of the HeanCMS api for the website - * @param throttle - A delay [ms] for each request (only required for rate-limited websites) - */ -export async function FetchMangasMultiPageAJAX(this: MangaScraper, provider: MangaPlugin, apiUrl: string, throttle = 0): Promise<Manga[]> { - const mangaList: Manga[] = []; - - for (const adult of [true, false]) { //there is no "dont care if adult or not flag" on "new" api, and old dont care about the flag - for (let page = 1, run = true; run; page++) { - const mangas = await GetMangaFromPage.call(this, provider, page, apiUrl, adult); - mangas.length > 0 ? mangaList.push(...mangas) : run = false; - await new Promise(resolve => setTimeout(resolve, throttle)); - } - } - return mangaList.distinct();//filter in case of old api -} -async function GetMangaFromPage(this: MangaScraper, provider: MangaPlugin, page: number, apiUrl: string, adult: boolean): Promise<Manga[]> { - const request = new Request(new URL(`${apiUrl}/query?perPage=100&page=${page}&adult=${adult}`)); - const { data } = await FetchJSON<APIResult<APIMangaV1[]>>(request); - return !data.length ? [] : data.map((manga) => new Manga(this, provider, JSON.stringify({ id: manga.id.toString(), slug: manga.series_slug }), manga.title)); - -} - -/** - * A class decorator that adds the ability to extract multiple mangas from a range of pages using the HeanCMS api url {@link apiUrl}. - * @param apiUrl - The url of the HeanCMS api for the website - * @param throttle - A delay [ms] for each request (only required for rate-limited websites) - */ -export function MangasMultiPageAJAX(apiUrl: string, throttle = 0) { - return function DecorateClass<T extends Common.Constructor>(ctor: T, context?: ClassDecoratorContext): T { - Common.ThrowOnUnsupportedDecoratorContext(context); - return class extends ctor { - public async FetchMangas(this: MangaScraper, provider: MangaPlugin): Promise<Manga[]> { - return FetchMangasMultiPageAJAX.call(this, provider, apiUrl, throttle); - } - }; - }; -} - -/************************************************* - ******** Chapter List Extraction Methods ******** - *************************************************/ - -/** - * An extension method for extracting all chapters for the given {@link manga} using the HeanCMS api url {@link apiUrl}. - * @param this - A reference to the {@link MangaScraper} instance which will be used as context for this method - * @param manga - A reference to the {@link Manga} which shall be assigned as parent for the extracted chapters - * @param apiUrl - The url of the HeanCMS api for the website - */ -export async function FetchChaptersSinglePageAJAXv1(this: MangaScraper, manga: Manga, apiUrl: string): Promise<Chapter[]> { - try { - const mangaslug = (JSON.parse(manga.Identifier) as MangaOrChapterId).slug; - const request = new Request(new URL(`${apiUrl}/series/${mangaslug}`)); - const { seasons } = await FetchJSON<APIMangaV1>(request); - return seasons.reduce(async (accumulator: Promise<Chapter[]>, season) => { - const chapters = season.chapters.map(chapter => { - const id = JSON.stringify({ - id: chapter.id.toString(), - slug: chapter.chapter_slug - }); - const title = `${seasons.length > 1 ? 'S' + season.index : ''} ${chapter.chapter_name} ${chapter.chapter_title || ''}`.trim(); - return new Chapter(this, manga, id, title); - }); - (await accumulator).concat(...chapters); - return accumulator; - }, Promise.resolve<Chapter[]>([])); - - } catch { - return []; - } -} - -/** - * A class decorator that adds the ability to extract all chapters for a given manga from this website using the HeanCMS api url {@link apiUrl}. - * @param apiUrl - The url of the HeanCMS api for the website - */ -export function ChaptersSinglePageAJAXv1(apiUrl: string) { - return function DecorateClass<T extends Common.Constructor>(ctor: T, context?: ClassDecoratorContext): T { - Common.ThrowOnUnsupportedDecoratorContext(context); - return class extends ctor { - public async FetchChapters(this: MangaScraper, manga: Manga): Promise<Chapter[]> { - return FetchChaptersSinglePageAJAXv1.call(this, manga, apiUrl); - } - }; - }; -} - -/** - * An extension method for extracting all chapters for the given {@link manga} using the HeanCMS api url {@link apiUrl}. - * @param this - A reference to the {@link MangaScraper} instance which will be used as context for this method - * @param manga - A reference to the {@link Manga} which shall be assigned as parent for the extracted chapters - * @param apiUrl - The url of the HeanCMS api for the website - */ -export async function FetchChaptersSinglePageAJAXv2(this: MangaScraper, manga: Manga, apiUrl: string): Promise<Chapter[]> { - const mangaid: MangaOrChapterId = JSON.parse(manga.Identifier); - const { data } = await FetchJSON<APIResult<APIChapter[]>>(new Request(new URL(`${apiUrl}/chapter/query?series_id=${mangaid.id}&perPage=9999&page=1`))); - return data.map(chapter => new Chapter(this, manga, JSON.stringify({ - id: chapter.id.toString(), - slug: chapter.chapter_slug, - }), `${chapter.chapter_name} ${chapter.chapter_title || ''}`.trim())); - -} - -/** - * A class decorator that adds the ability to extract all chapters for a given manga from this website using the HeanCMS api url {@link apiUrl}. - * @param apiUrl - The url of the HeanCMS api for the website - */ -export function ChaptersSinglePageAJAXv2(apiUrl: string) { - return function DecorateClass<T extends Common.Constructor>(ctor: T, context?: ClassDecoratorContext): T { - Common.ThrowOnUnsupportedDecoratorContext(context); - return class extends ctor { - public async FetchChapters(this: MangaScraper, manga: Manga): Promise<Chapter[]> { - return FetchChaptersSinglePageAJAXv2.call(this, manga, apiUrl); - } - }; - }; -} - -/********************************************** - ******** Page List Extraction Methods ******** - **********************************************/ -/** - * An extension method for extracting all pages for the given {@link chapter} using the HeanCMS api url {@link apiUrl}. - * @param this - A reference to the {@link MangaScraper} instance which will be used as context for this method - * @param chapter - A reference to the {@link Chapter} which shall be assigned as parent for the extracted pages - * @param apiUrl - The url of the HeanCMS api for the website - */ -export async function FetchPagesSinglePageAJAX(this: MangaScraper, chapter: Chapter, apiUrl: string): Promise<Page<PageType>[]> { - const chapterid: MangaOrChapterId = JSON.parse(chapter.Identifier); - const mangaid: MangaOrChapterId = JSON.parse(chapter.Parent.Identifier); - const data = await FetchJSON<APIPages>(new Request(new URL(`${apiUrl}/chapter/${mangaid.slug}/${chapterid.slug}`))); - - if (data.paywall) { - throw new Exception(R.Plugin_Common_Chapter_UnavailableError); - } - - if (data.chapter.chapter_type.toLowerCase() === 'novel') { - throw new Exception(R.Plugin_HeanCMS_ErrorNovelsNotSupported); - } - - const listImages = data.data && Array.isArray(data.data) ? data.data as string[] : data.chapter.chapter_data.images; - return listImages.map(image => new Page<PageType>(this, chapter, ComputePageUrl(image, data.chapter.storage, apiUrl), { type: data.chapter.chapter_type })); -} - -/** - * A class decorator that adds the ability to extract all pages for a given chapter using the HeanCMS api url {@link apiUrl}. - * @param apiUrl - The url of the HeanCMS api for the website - */ -export function PagesSinglePageAJAX(apiUrl: string) { - return function DecorateClass<T extends Common.Constructor>(ctor: T, context?: ClassDecoratorContext): T { - Common.ThrowOnUnsupportedDecoratorContext(context); - return class extends ctor { - public async FetchPages(this: MangaScraper, chapter: Chapter): Promise<Page<PageType>[]> { - return FetchPagesSinglePageAJAX.call(this, chapter, apiUrl); - } - }; - }; -} - -/*********************************************** - ******** Image Data Extraction Methods ******** - ***********************************************/ - -/** - * An extension method to get the image data for the given {@link page} according to an XHR based-approach. - * @param this - A reference to the {@link MangaScraper} instance which will be used as context for this method - * @param page - A reference to the {@link Page} containing the necessary information to acquire the image data - * @param priority - The importance level for ordering the request for the image data within the internal task pool - * @param signal - An abort signal that can be used to cancel the request for the image data - * @param detectMimeType - Force a fingerprint check of the image data to detect its mime-type (instead of relying on the Content-Type header) - * @param deProxifyLink - Remove common image proxies (default false) - */ -export async function FetchImageAjax(this: MangaScraper, page: Page, priority: Priority, signal: AbortSignal, detectMimeType = false, deProxifyLink = true): Promise<Blob> { - if (page.Parameters?.type === 'Comic') { - return Common.FetchImageAjax.call(this, page, priority, signal, detectMimeType, deProxifyLink); - } else throw new Exception(R.Plugin_HeanCMS_ErrorNovelsNotSupported); -} - -/** - * A class decorator that adds the ability to get the image data for a given page by loading the source asynchronous with the `Fetch API`. - * @param detectMimeType - Force a fingerprint check of the image data to detect its mime-type (instead of relying on the Content-Type header) - * @param deProxifyLink - Remove common image proxies (default false) - */ -export function ImageAjax(detectMimeType = false, deProxifyLink = true) { - return function DecorateClass<T extends Common.Constructor>(ctor: T, context?: ClassDecoratorContext): T { - Common.ThrowOnUnsupportedDecoratorContext(context); - return class extends ctor { - public async FetchImage(this: MangaScraper, page: Page, priority: Priority, signal: AbortSignal): Promise<Blob> { - return FetchImageAjax.call(this, page, priority, signal, detectMimeType, deProxifyLink); - } - }; - }; -} -/** - * Compute the image full URL for HeanHMS based websites - * @param image - A string containing the full url ( for {@link storage} "s3") or the pathname ( for {@link storage} "local")) - * @param storage - As string representing the type of storage used : "s3" or "local" - * @param apiUrl - The url of the HeanCMS api for the website * - */ -function ComputePageUrl(image: string, storage: string, apiUrl: string): URL { - switch (storage) { - case "s3": return new URL(image); - case "local": return new URL(`${apiUrl}/${image}`); - } -} diff --git a/web/src/engine/websites/templates/HeanCMS.ts b/web/src/engine/websites/templates/HeanCMS.ts new file mode 100644 index 0000000000..fd32576641 --- /dev/null +++ b/web/src/engine/websites/templates/HeanCMS.ts @@ -0,0 +1,125 @@ +import { Exception } from '../../Error'; +import { FetchJSON } from '../../platform/FetchProvider'; +import {type MangaPlugin, Manga, Chapter, Page, DecoratableMangaScraper } from '../../providers/MangaPlugin'; +import type { Priority } from '../../taskpool/TaskPool'; +import * as Common from '../decorators/Common'; +import { WebsiteResourceKey as R } from '../../../i18n/ILocale'; + +// TODO: Add Novel support + +type APIManga = { + title: string + id: number, + series_type: 'Comic' | 'Novel', + series_slug: string, +} + +type APIResult<T> = { + data: T +} + +type APIChapter = { + index: string, + id: number, + chapter_name: string, + chapter_title: string, + chapter_slug: string, +} + +type APIPages = { + chapter_type: 'Comic' | 'Novel', + paywall: boolean, + data: string[] | string + chapter: { + chapter_type: 'Comic' | 'Novel', + storage: string, + chapter_data?: { + images: string[] + } + + } +} + +type APIMediaID = { + id: string, + slug: string +} + +type PageType = { + type: 'Comic' | 'Novel' +} + +export class HeanCMS extends DecoratableMangaScraper { + + protected apiUrl = this.URI.origin.replace('://', '://api.'); + + public override ValidateMangaURL(url: string): boolean { + return new RegExpSafe(`^${this.URI.origin}/series/[^/]+$`).test(url); + } + + public override async FetchManga(provider: MangaPlugin, url: string): Promise<Manga> { + const slug = new URL(url).pathname.split('/').at(-1); + const { title, series_slug, id } = await FetchJSON<APIManga>(new Request(new URL(`${this.apiUrl}/series/${slug}`))); + return new Manga(this, provider, JSON.stringify({ + id: id.toString(), + slug: series_slug + }), title); + } + + public override async FetchMangas(provider: MangaPlugin): Promise<Manga[]> { + const mangaList: Manga[] = []; + for (const adult of [true, false]) { //adult flag mean ONLY adult, false mean ONLY all ages... Talk about stupid. Old api ignore that flag + for (let page = 1, run = true; run; page++) { + const mangas = await this.GetMangaFromPage(provider, page, adult); + mangas.length > 0 ? mangaList.push(...mangas) : run = false; + } + } + return mangaList.distinct();//filter in case of old api + } + + private async GetMangaFromPage(provider: MangaPlugin, page: number, adult: boolean): Promise<Manga[]> { + const request = new Request(new URL(`${this.apiUrl}/query?perPage=100&page=${page}&adult=${adult}`)); + const { data } = await FetchJSON<APIResult<APIManga[]>>(request); + return !data.length ? [] : data.map((manga) => new Manga(this, provider, JSON.stringify({ id: manga.id.toString(), slug: manga.series_slug }), manga.title)); + } + + public override async FetchChapters(manga: Manga): Promise<Chapter[]> { + const mangaid: APIMediaID = JSON.parse(manga.Identifier); + const { data } = await FetchJSON<APIResult<APIChapter[]>>(new Request(new URL(`${this.apiUrl}/chapter/query?series_id=${mangaid.id}&perPage=9999&page=1`))); + return data.map(chapter => new Chapter(this, manga, JSON.stringify({ + id: chapter.id.toString(), + slug: chapter.chapter_slug, + }), `${chapter.chapter_name} ${chapter.chapter_title || ''}`.trim())); + } + + public override async FetchPages(chapter: Chapter): Promise<Page<PageType>[]> { + const chapterid: APIMediaID = JSON.parse(chapter.Identifier); + const mangaid: APIMediaID = JSON.parse(chapter.Parent.Identifier); + const data = await FetchJSON<APIPages>(new Request(new URL(`${this.apiUrl}/chapter/${mangaid.slug}/${chapterid.slug}`))); + + if (data.paywall) { + throw new Exception(R.Plugin_Common_Chapter_UnavailableError); + } + + if (data.chapter.chapter_type.toLowerCase() === 'novel') { + throw new Exception(R.Plugin_HeanCMS_ErrorNovelsNotSupported); + } + //old API vs new + const listImages = data.data && Array.isArray(data.data) ? data.data as string[] : data.chapter.chapter_data.images; + return listImages.map(image => new Page<PageType>(this, chapter, this.ComputePageUrl(image, data.chapter.storage), { type: data.chapter.chapter_type })); + } + + private ComputePageUrl(image: string, storage: string): URL { + switch (storage) { + case "s3": return new URL(image); + case "local": return new URL(`${this.apiUrl}/${image}`); + } + } + + public override async FetchImage(page: Page<PageType>, priority: Priority, signal: AbortSignal, detectMimeType = true, deProxifyLink = true): Promise<Blob> { + if (page.Parameters?.type === 'Comic') { + return Common.FetchImageAjax.call(this, page, priority, signal, detectMimeType, deProxifyLink); + } else throw new Exception(R.Plugin_HeanCMS_ErrorNovelsNotSupported); + } + +} From b794e4d240acecb8bd600ed75d36520b3c817baa Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Sun, 13 Oct 2024 11:14:56 +0200 Subject: [PATCH 26/31] Update PerfScan_e2e.ts --- web/src/engine/websites/PerfScan_e2e.ts | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/web/src/engine/websites/PerfScan_e2e.ts b/web/src/engine/websites/PerfScan_e2e.ts index f1e4d4344b..542b1d01ba 100644 --- a/web/src/engine/websites/PerfScan_e2e.ts +++ b/web/src/engine/websites/PerfScan_e2e.ts @@ -24,27 +24,3 @@ const ComicConfig = { const ComicFixture = new TestFixture(ComicConfig); describe(ComicFixture.Name, async () => (await ComicFixture.Connect()).AssertWebsite()); - -const NovelConfig = { - plugin: { - id: 'perfscan', - title: 'Perf Scan' - }, - container: { - url: 'https://perf-scan.fr/series/demonic-emperor-novel', - id: JSON.stringify({ id: '17', slug: 'demonic-emperor-novel' }), - title: 'Demonic emperor - Novel' - }, - child: { - id: JSON.stringify({ id: '28668', slug: 'chapitre-492' }), - title: 'Chapitre 492' - }, - entry: { - index: 0, - size: 789_130, - type: 'image/png' - } -}; - -const NovelFixture = new TestFixture(NovelConfig); -describe(NovelFixture.Name, async () => (await NovelFixture.Connect()).AssertWebsite()); \ No newline at end of file From 26139912c0ab25e4d892bc76b6a7b702169c489c Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Sun, 27 Oct 2024 10:46:54 +0100 Subject: [PATCH 27/31] update tests --- web/src/engine/websites/OmegaScans_e2e.ts | 4 +--- web/src/engine/websites/PerfScan_e2e.ts | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/web/src/engine/websites/OmegaScans_e2e.ts b/web/src/engine/websites/OmegaScans_e2e.ts index 163a757026..c2088e5386 100644 --- a/web/src/engine/websites/OmegaScans_e2e.ts +++ b/web/src/engine/websites/OmegaScans_e2e.ts @@ -1,4 +1,3 @@ -import { describe } from 'vitest'; import { TestFixture } from '../../../test/WebsitesFixture'; const ComicConfig = { @@ -22,5 +21,4 @@ const ComicConfig = { } }; -const ComicFixture = new TestFixture(ComicConfig); -describe(ComicFixture.Name, async () => (await ComicFixture.Connect()).AssertWebsite()); \ No newline at end of file +new TestFixture(ComicConfig).AssertWebsite(); diff --git a/web/src/engine/websites/PerfScan_e2e.ts b/web/src/engine/websites/PerfScan_e2e.ts index 542b1d01ba..dd47595b19 100644 --- a/web/src/engine/websites/PerfScan_e2e.ts +++ b/web/src/engine/websites/PerfScan_e2e.ts @@ -1,4 +1,3 @@ -import { describe } from 'vitest'; import { TestFixture } from '../../../test/WebsitesFixture'; const ComicConfig = { @@ -22,5 +21,4 @@ const ComicConfig = { } }; -const ComicFixture = new TestFixture(ComicConfig); -describe(ComicFixture.Name, async () => (await ComicFixture.Connect()).AssertWebsite()); +new TestFixture(ComicConfig).AssertWebsite(); From 5ed360178992c52bd778f62687e953627d65c492 Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Sun, 3 Nov 2024 13:37:05 +0100 Subject: [PATCH 28/31] add base class tests --- web/src/engine/websites/templates/HeanCMS_e2e.ts | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 web/src/engine/websites/templates/HeanCMS_e2e.ts diff --git a/web/src/engine/websites/templates/HeanCMS_e2e.ts b/web/src/engine/websites/templates/HeanCMS_e2e.ts new file mode 100644 index 0000000000..73b905c3cc --- /dev/null +++ b/web/src/engine/websites/templates/HeanCMS_e2e.ts @@ -0,0 +1,3 @@ +import '../ModeScanlator_e2e'; +import '../OmegaScans_e2e'; +import '../PerfScan_e2e'; \ No newline at end of file From 9a87cb97ef76f7eeac8843d6c284d1833e71d613 Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Mon, 11 Nov 2024 21:37:29 +0100 Subject: [PATCH 29/31] remove modescanlator --- web/src/engine/websites/ModeScanlator.ts | 15 ----------- web/src/engine/websites/ModeScanlator.webp | Bin 1986 -> 0 bytes web/src/engine/websites/ModeScanlator_e2e.ts | 25 ------------------- web/src/engine/websites/_index.ts | 1 - 4 files changed, 41 deletions(-) delete mode 100644 web/src/engine/websites/ModeScanlator.ts delete mode 100644 web/src/engine/websites/ModeScanlator.webp delete mode 100644 web/src/engine/websites/ModeScanlator_e2e.ts diff --git a/web/src/engine/websites/ModeScanlator.ts b/web/src/engine/websites/ModeScanlator.ts deleted file mode 100644 index d53b5177f2..0000000000 --- a/web/src/engine/websites/ModeScanlator.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Tags } from '../Tags'; -import icon from './ModeScanlator.webp'; -import { HeanCMS } from './templates/HeanCMS'; - -export default class extends HeanCMS { - - public constructor() { - super('modescanlator', `Mode Scanlator`, 'https://site.modescanlator.net', Tags.Language.Portuguese, Tags.Media.Manga, Tags.Media.Manhua, Tags.Media.Manhwa, Tags.Source.Scanlator); - this.apiUrl = this.URI.origin.replace('site', 'api'); - } - - public override get Icon() { - return icon; - } -} \ No newline at end of file diff --git a/web/src/engine/websites/ModeScanlator.webp b/web/src/engine/websites/ModeScanlator.webp deleted file mode 100644 index 97464ba5e5d5e937b0ac64d356ba49996c98c6b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1986 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 UNECP;H3Ny;x4Xi~10=8j0Ps@h7XSbN diff --git a/web/src/engine/websites/ModeScanlator_e2e.ts b/web/src/engine/websites/ModeScanlator_e2e.ts deleted file mode 100644 index 06e976aeaf..0000000000 --- a/web/src/engine/websites/ModeScanlator_e2e.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { TestFixture, type Config } from '../../../test/WebsitesFixture'; - -const config: Config = { - plugin: { - id: 'modescanlator', - title: 'Mode Scanlator' - }, - container: { - url: 'https://site.modescanlator.net/series/uma-lenda-do-vento', - id: JSON.stringify({ id: '36', slug: 'uma-lenda-do-vento' }), - title: 'Uma Lenda do Vento', - timeout: 10000 - }, - child: { - id: JSON.stringify({ id: '2420', slug: 'capitulo-126' }), - title: 'Capítulo 126', - }, - entry: { - index: 0, - size: 1_222_675, - type: 'image/jpeg' - } -}; - -new TestFixture(config).AssertWebsite(); \ No newline at end of file diff --git a/web/src/engine/websites/_index.ts b/web/src/engine/websites/_index.ts index 970065c970..6be3e608cc 100755 --- a/web/src/engine/websites/_index.ts +++ b/web/src/engine/websites/_index.ts @@ -449,7 +449,6 @@ export { default as MindaFanSub } from './MindaFanSub'; export { default as MiniTwoScan } from './MiniTwoScan'; export { default as mkzhan } from './mkzhan'; export { default as MMFenix } from './MMFenix'; -export { default as ModeScanlator } from './ModeScanlator'; export { default as MonochromeScans } from './MonochromeScans'; export { default as MonoManga } from './MonoManga'; export { default as MonzeeKomik } from './MonzeeKomik'; From aabfe5434d4bd14d862de0aae84fd076d92cc94f Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Mon, 11 Nov 2024 20:45:44 +0000 Subject: [PATCH 30/31] Update HeanCMS_e2e.ts --- web/src/engine/websites/templates/HeanCMS_e2e.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web/src/engine/websites/templates/HeanCMS_e2e.ts b/web/src/engine/websites/templates/HeanCMS_e2e.ts index 73b905c3cc..2e7a936502 100644 --- a/web/src/engine/websites/templates/HeanCMS_e2e.ts +++ b/web/src/engine/websites/templates/HeanCMS_e2e.ts @@ -1,3 +1,2 @@ -import '../ModeScanlator_e2e'; import '../OmegaScans_e2e'; -import '../PerfScan_e2e'; \ No newline at end of file +import '../PerfScan_e2e'; From a224b215ef1a121c4385a29e7240a2d1a7a26c2c Mon Sep 17 00:00:00 2001 From: MikeZeDev <MikeZeDev@users.noreply.github.com> Date: Sun, 15 Dec 2024 12:11:25 +0100 Subject: [PATCH 31/31] Update _index.ts --- web/src/engine/websites/_index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/src/engine/websites/_index.ts b/web/src/engine/websites/_index.ts index 9f72c71025..a123cfc79b 100755 --- a/web/src/engine/websites/_index.ts +++ b/web/src/engine/websites/_index.ts @@ -491,6 +491,7 @@ export { default as Noromax } from './Noromax'; export { default as NovelMic } from './NovelMic'; export { default as NoxScans } from './NoxScans'; export { default as OlympusScanlation } from './OlympusScanlation'; +export { default as OmegaScans } from './OmegaScans'; export { default as Opiatoon } from './Opiatoon'; export { default as Oremanga } from './Oremanga'; export { default as OrigamiOrpheans } from './OrigamiOrpheans'; @@ -503,6 +504,7 @@ export { default as PCNet } from './PCNet'; export { default as PeanuToon } from './PeanuToon'; export { default as PelaTeam } from './PelaTeam'; export { default as Penlab } from './Penlab'; +export { default as PerfScan } from './PerfScan'; export { default as PhenixScans } from './PhenixScans'; export { default as PhoenixScansIT } from './PhoenixScansIT'; export { default as Piccoma } from './Piccoma';