diff --git a/web/src/engine/transformers/BookmarkConverter.ts b/web/src/engine/transformers/BookmarkConverter.ts index f69e7fef7b..a58cfa6e90 100644 --- a/web/src/engine/transformers/BookmarkConverter.ts +++ b/web/src/engine/transformers/BookmarkConverter.ts @@ -23,9 +23,11 @@ export const legacyWebsiteIdentifierMap = new Map([ [ 'gateanimemanga', 'gatemanga' ], [ 'imperioscans', 'neroxus' ], [ 'instamanhwa', 'xmanhwa' ], + [ 'kissaway', 'klmanga' ], [ 'kisscomic', 'readcomiconline' ], [ 'komikav', 'apkomik' ], [ 'kumascans', 'retsu' ], + [ 'lovehug', 'welovemanga' ], [ 'lyrascans', 'quantumscans' ], [ 'mangamx', 'mangaoni' ], [ 'manganel', 'manganato' ], diff --git a/web/src/engine/transformers/BookmarkConverter_test.ts b/web/src/engine/transformers/BookmarkConverter_test.ts index 84eae37dec..42976dbc3c 100644 --- a/web/src/engine/transformers/BookmarkConverter_test.ts +++ b/web/src/engine/transformers/BookmarkConverter_test.ts @@ -25,9 +25,11 @@ const legacyWebsiteIdentifierMapTestCases = [ { sourceID: 'gateanimemanga', targetID: 'gatemanga' }, { sourceID: 'imperioscans', targetID: 'neroxus' }, { sourceID: 'instamanhwa', targetID: 'xmanhwa' }, + { sourceID: 'kissaway', targetID: 'klmanga' }, { sourceID: 'kisscomic', targetID: 'readcomiconline' }, { sourceID: 'komikav', targetID: 'apkomik' }, { sourceID: 'kumascans', targetID: 'retsu' }, + { sourceID: 'lovehug', targetID: 'welovemanga' }, { sourceID: 'lyrascans', targetID: 'quantumscans' }, { sourceID: 'mangamx', targetID: 'mangaoni' }, { sourceID: 'manganel', targetID: 'manganato' }, diff --git a/web/src/engine/websites/HolyManga.ts b/web/src/engine/websites/HolyManga.ts new file mode 100644 index 0000000000..c47c745c65 --- /dev/null +++ b/web/src/engine/websites/HolyManga.ts @@ -0,0 +1,22 @@ +import { Tags } from '../Tags'; +import icon from './HolyManga.webp'; +import * as Common from './decorators/Common'; +import { FetchWindowScript } from '../platform/FetchProvider'; +import { FlatManga, MangaLabelExtractor, queryMangaTitle } from './templates/FlatManga'; + +@Common.MangaCSS(/^https:\/\/w\d+\.holymanga\.net\/[^/]+\.html$/, queryMangaTitle, MangaLabelExtractor) +export default class extends FlatManga { + + public constructor() { + super('holymanga', 'Holy Manga', 'https://w34.holymanga.net', Tags.Media.Manga, Tags.Media.Manhwa, Tags.Language.English, Tags.Accessibility.DomainRotation); + } + + public override get Icon() { + return icon; + } + + public override async Initialize(): Promise { + this.URI.href = await FetchWindowScript(new Request(this.URI), 'window.location.origin'); + console.log(`Assigned URL '${this.URI}' to ${this.Title}`); + } +} \ No newline at end of file diff --git a/web/src/engine/websites/HolyManga.webp b/web/src/engine/websites/HolyManga.webp new file mode 100644 index 0000000000..af196fb3da Binary files /dev/null and b/web/src/engine/websites/HolyManga.webp differ diff --git a/web/src/engine/websites/HolyManga_e2e.ts b/web/src/engine/websites/HolyManga_e2e.ts new file mode 100644 index 0000000000..c73c2e2baf --- /dev/null +++ b/web/src/engine/websites/HolyManga_e2e.ts @@ -0,0 +1,22 @@ +import { TestFixture } from '../../../test/WebsitesFixture'; + +new TestFixture({ + plugin: { + id: 'holymanga', + title: 'Holy Manga' + }, + container: { + url: 'https://w34.holymanga.net/manga-grand-metal-organs.html', + id: '/manga-grand-metal-organs.html', + title: 'Grand Metal Organs' + }, + child: { + id: '/read-grand-metal-organs-chapter-3.1.html', + title: 'Vol.1 Chapter 3.1' + }, + entry: { + index: 0, + size: 382_318, + type: 'image/jpeg' + } +}).AssertWebsite(); \ No newline at end of file diff --git a/web/src/engine/websites/KLManga.ts b/web/src/engine/websites/KLManga.ts new file mode 100644 index 0000000000..ad3efb2622 --- /dev/null +++ b/web/src/engine/websites/KLManga.ts @@ -0,0 +1,18 @@ +import { Tags } from '../Tags'; +import icon from './KLManga.webp'; +import * as Common from './decorators/Common'; +import { FlatManga, MangaExtractor, pageScript, pathSinglePageManga, queryMangas } from './templates/FlatManga'; + +@Common.MangasSinglePagesCSS([pathSinglePageManga], queryMangas, MangaExtractor) +@Common.ChaptersSinglePageJS(`[...document.querySelectorAll('ul a.chapter')].map(chapter => { return {id: chapter.pathname, title : chapter.title.trim()};})`, 1500) +@Common.PagesSinglePageJS(pageScript, 1500) +export default class extends FlatManga { + + public constructor() { + super('klmanga', 'KLManga', 'https://klz9.com', Tags.Language.Japanese, Tags.Media.Manga, Tags.Source.Aggregator); + } + + public override get Icon() { + return icon; + } +} \ No newline at end of file diff --git a/web/src/engine/websites/KLManga.webp b/web/src/engine/websites/KLManga.webp new file mode 100644 index 0000000000..d61638e7fa Binary files /dev/null and b/web/src/engine/websites/KLManga.webp differ diff --git a/web/src/engine/websites/KLManga_e2e.ts b/web/src/engine/websites/KLManga_e2e.ts new file mode 100644 index 0000000000..4a280067b6 --- /dev/null +++ b/web/src/engine/websites/KLManga_e2e.ts @@ -0,0 +1,22 @@ +import { TestFixture } from '../../../test/WebsitesFixture'; + +new TestFixture({ + plugin: { + id: 'klmanga', + title: 'KLManga' + }, + container: { + url: 'https://klz9.com/ybed-isekai-koushoku-musou-roku-isekai-tensei-no-chie-to-chikara-wo-tada-hitasura-xxxx-suru-tame-ni-tsukau.html', + id: '/ybed-isekai-koushoku-musou-roku-isekai-tensei-no-chie-to-chikara-wo-tada-hitasura-xxxx-suru-tame-ni-tsukau.html', + title: 'Isekai Koushoku Musou Roku - Isekai Tensei No Chie To Chikara Wo, Tada Hitasura Xxxx Suru Tame Ni Tsukau' + }, + child: { + id: '/jxsh-isekai-koushoku-musou-roku-isekai-tensei-no-chie-to-chikara-wo-tada-hitasura-xxxx-suru-tame-ni-tsukau-chapter-16.html', + title: 'Chapter 16' + }, + entry: { + index: 0, + size: 494_933, + type: 'image/jpeg' + } +}).AssertWebsite(); \ No newline at end of file diff --git a/web/src/engine/websites/MangaGun.ts b/web/src/engine/websites/MangaGun.ts new file mode 100644 index 0000000000..bdd7241e11 --- /dev/null +++ b/web/src/engine/websites/MangaGun.ts @@ -0,0 +1,17 @@ +import { Tags } from '../Tags'; +import icon from './MangaGun.webp'; +import * as Common from './decorators/Common'; +import { FlatManga, chapterScript, pageScript } from './templates/FlatManga'; + +@Common.ChaptersSinglePageJS(chapterScript, 1500) +@Common.PagesSinglePageJS(pageScript, 1500) +export default class extends FlatManga { + + public constructor() { + super('mangagun', 'MangaGun', 'https://mangagun.net', Tags.Language.English, Tags.Media.Manga, Tags.Media.Manhua, Tags.Media.Manhwa, Tags.Source.Aggregator); + } + + public override get Icon() { + return icon; + } +} \ No newline at end of file diff --git a/web/src/engine/websites/MangaGun.webp b/web/src/engine/websites/MangaGun.webp new file mode 100644 index 0000000000..ff56c9a305 Binary files /dev/null and b/web/src/engine/websites/MangaGun.webp differ diff --git a/web/src/engine/websites/MangaGun_e2e.ts b/web/src/engine/websites/MangaGun_e2e.ts new file mode 100644 index 0000000000..f6269f6f41 --- /dev/null +++ b/web/src/engine/websites/MangaGun_e2e.ts @@ -0,0 +1,22 @@ +import { TestFixture } from '../../../test/WebsitesFixture'; + +new TestFixture({ + plugin: { + id: 'mangagun', + title: 'MangaGun' + }, + container: { + url: 'https://mangagun.net/manga-oshi-no-ko-raw.html', + id: '/manga-oshi-no-ko-raw.html', + title: 'OSHI NO KO' + }, + child: { + id: '/read-oshi-no-ko-raw-chapter-146.html', + title: 'Chapter 146' + }, + entry: { + index: 0, + size: 297_654, + type: 'image/jpeg', + } +}).AssertWebsite(); \ No newline at end of file diff --git a/web/src/engine/websites/MangaTR.ts b/web/src/engine/websites/MangaTR.ts new file mode 100644 index 0000000000..c72e2ba3f4 --- /dev/null +++ b/web/src/engine/websites/MangaTR.ts @@ -0,0 +1,58 @@ +import { Tags } from '../Tags'; +import icon from './MangaTR.webp'; +import { Chapter, type MangaPlugin, type Manga } from '../providers/MangaPlugin'; +import { FetchCSS, FetchWindowScript } from '../platform/FetchProvider'; +import * as Common from './decorators/Common'; +import { CleanTitle, FlatManga, PageLinkExtractor, queryMangas, queryPages } from './templates/FlatManga'; + +function MangaLabelExtractor(element: HTMLTitleElement) { + return element.text.split(' - ')[0].trim(); +} + +@Common.MangaCSS(/^{origin}\/manga-[^/]+\.html$/, 'body title', MangaLabelExtractor) +@Common.PagesSinglePageCSS(queryPages, PageLinkExtractor) +export default class extends FlatManga { + public constructor() { + super('mangatr', `Manga-TR`, 'https://manga-tr.com', Tags.Language.Turkish, Tags.Media.Manga, Tags.Source.Aggregator); + } + public override get Icon() { + return icon; + } + + public override async Initialize(): Promise { + const request = new Request(new URL('/manga-list.html', this.URI)); + return FetchWindowScript(request, `window.cookieStore.set('read_type', '1')`, 0, 30000); + } + + public override async FetchMangas(provider: MangaPlugin): Promise { + return (await Common.FetchMangasSinglePagesCSS.call(this, provider, ['/manga-list.html'], queryMangas)).filter(manga => manga.Title); + } + + public override async FetchChapters(manga: Manga): Promise { + const chapterList = []; + for (let page = 1, run = true; run; page++) { + const chapters = await this.GetChaptersFromPage(manga, page); + chapters.length > 0 ? chapterList.push(...chapters) : run = false; + } + return chapterList; + } + private async GetChaptersFromPage(manga: Manga, page: number): Promise{ + const mangaSlug = manga.Identifier.match(/manga-([^/]+)\.html/)[1]; + const url = new URL(`/cek/fetch_pages_manga.php?manga_cek=${mangaSlug}`, this.URI); + const request = new Request(url, { + method: 'POST', + body: 'page=' + page, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'x-requested-with': 'XMLHttpRequest' + } + }); + + const data = await FetchCSS(request, 'table.table tr td.table-bordered:first-of-type > a'); + return data.map(chapter => { + const title = CleanTitle(chapter.text.replace(manga.Title, '')) || chapter.text.trim(); + return new Chapter(this, manga, chapter.pathname, title); + }); + } + +} \ No newline at end of file diff --git a/web/src/engine/websites/legacy/MangaTR.webp b/web/src/engine/websites/MangaTR.webp similarity index 100% rename from web/src/engine/websites/legacy/MangaTR.webp rename to web/src/engine/websites/MangaTR.webp diff --git a/web/src/engine/websites/MangaTR_e2e.ts b/web/src/engine/websites/MangaTR_e2e.ts new file mode 100644 index 0000000000..51e3919a78 --- /dev/null +++ b/web/src/engine/websites/MangaTR_e2e.ts @@ -0,0 +1,22 @@ +import { TestFixture } from '../../../test/WebsitesFixture'; + +new TestFixture({ + plugin: { + id: 'mangatr', + title: 'Manga-TR' + }, + container: { + url: 'https://manga-tr.com/manga-mairimashita-iruma-kun.html', + id: '/manga-mairimashita-iruma-kun.html', + title: 'Mairimashita! Iruma-kun' + }, + child: { + id: '/id-153837-read-mairimashita-iruma-kun-chapter-174.html', + title: '174' + }, + entry: { + index: 1, + size: 342_746, + type: 'image/webp' + } +}).AssertWebsite(); \ No newline at end of file diff --git a/web/src/engine/websites/ManhwaFullNet.ts b/web/src/engine/websites/ManhwaFullNet.ts new file mode 100644 index 0000000000..9807072226 --- /dev/null +++ b/web/src/engine/websites/ManhwaFullNet.ts @@ -0,0 +1,13 @@ +import { Tags } from '../Tags'; +import icon from './HolyManga.webp'; +import { FlatManga } from './templates/FlatManga'; +export default class extends FlatManga { + + public constructor() { + super('manhwafullnet', 'ManhwaFull(.net)', 'https://manhwafull.net', Tags.Media.Manga, Tags.Media.Manhwa, Tags.Language.English); + } + + public override get Icon() { + return icon; + } +} \ No newline at end of file diff --git a/web/src/engine/websites/ManhwaFullNet_e2e.ts b/web/src/engine/websites/ManhwaFullNet_e2e.ts new file mode 100644 index 0000000000..8ccc7b7a80 --- /dev/null +++ b/web/src/engine/websites/ManhwaFullNet_e2e.ts @@ -0,0 +1,22 @@ +import { TestFixture } from '../../../test/WebsitesFixture'; + +new TestFixture({ + plugin: { + id: 'manhwafullnet', + title: 'ManhwaFull(.net)', + }, + container: { + url: 'https://manhwafull.net/manga-dance-dance-danseur.html', + id: '/manga-dance-dance-danseur.html', + title: 'Dance Dance Danseur' + }, + child: { + id: '/read-dance-dance-danseur-chapter-155.html', + title: 'Vol.17 Chapter 155' + }, + entry: { + index: 0, + size: 183_322, + type: 'image/jpeg' + } +}).AssertWebsite(); \ No newline at end of file diff --git a/web/src/engine/websites/NicoManga.ts b/web/src/engine/websites/NicoManga.ts new file mode 100644 index 0000000000..9b1c8c0fb9 --- /dev/null +++ b/web/src/engine/websites/NicoManga.ts @@ -0,0 +1,49 @@ +import { Tags } from '../Tags'; +import icon from './NicoManga.webp'; +import { Manga, type MangaPlugin } from '../providers/MangaPlugin'; +import * as Common from './decorators/Common'; +import { FetchJSON } from '../platform/FetchProvider'; +import { CleanTitle, FlatManga, chapterScript, pageScript } from './templates/FlatManga'; + +type APIMangas = { + manga_list: { + name: string, + slug: string + }[], + lang: { + manga_slug: string + } +} + +@Common.ChaptersSinglePageJS(chapterScript, 500) +@Common.PagesSinglePageJS(pageScript, 1500) +export default class extends FlatManga { + + public constructor() { + super('nicomanga', 'NicoManga', 'https://nicomanga.com', Tags.Language.Japanese, Tags.Media.Manga, Tags.Source.Aggregator); + } + + public override get Icon() { + return icon; + } + + public override async FetchMangas(provider: MangaPlugin): Promise { + const mangaList: Manga[] = []; + for (let page = 1, run = true; run; page++) { + const mangas = await this.GetMangasFromPageAJAX(provider, page); + mangaList.isMissingLastItemFrom(mangas) ? mangaList.push(...mangas) : run = false; + } + return mangaList.distinct(); + } + private async GetMangasFromPageAJAX(provider: MangaPlugin, page: number): Promise { + const request = new Request(new URL(`/app/manga/controllers/cont.display.homeTopday.php?page=${page}`, this.URI), { + headers: { + Referer: new URL('/manga-list.html', this.URI.origin).href, + 'X-Requested-With': 'XMLHttpRequest' + } + }); + const { manga_list, lang: { manga_slug } } = await FetchJSON(request); + return manga_list.map(manga => new Manga(this, provider, `/${manga_slug}-${manga.slug}.html`, CleanTitle(manga.name.trim()))); + } + +} \ No newline at end of file diff --git a/web/src/engine/websites/NicoManga.webp b/web/src/engine/websites/NicoManga.webp new file mode 100644 index 0000000000..92fb7462f8 Binary files /dev/null and b/web/src/engine/websites/NicoManga.webp differ diff --git a/web/src/engine/websites/NicoManga_e2e.ts b/web/src/engine/websites/NicoManga_e2e.ts new file mode 100644 index 0000000000..e8b7e2deb7 --- /dev/null +++ b/web/src/engine/websites/NicoManga_e2e.ts @@ -0,0 +1,23 @@ +import { TestFixture } from '../../../test/WebsitesFixture'; + +new TestFixture({ + plugin: { + id: 'nicomanga', + title: 'NicoManga' + }, + container: { + url: 'https://nicomanga.com/manga-kage-no-jitsuryokusha-ni-naritakute-raw.html', + id: '/manga-kage-no-jitsuryokusha-ni-naritakute-raw.html', + title: 'TO BE A POWER IN THE SHADOWS!' + }, + child: { + id: '/read-kage-no-jitsuryokusha-ni-naritakute-raw-chapter-60.2.html', + title: 'Chapter 60.2', + timeout: 15000 + }, + entry: { + index: 0, + size: 708_342, + type: 'image/jpeg' + } +}).AssertWebsite(); \ No newline at end of file diff --git a/web/src/engine/websites/OlimpoScans.ts b/web/src/engine/websites/OlimpoScans.ts new file mode 100644 index 0000000000..15605c71f3 --- /dev/null +++ b/web/src/engine/websites/OlimpoScans.ts @@ -0,0 +1,24 @@ +import { Tags } from '../Tags'; +import icon from './OlimpoScans.webp'; +import { type Chapter, Page } from '../providers/MangaPlugin'; +import { FlatManga } from './templates/FlatManga'; + +export default class extends FlatManga { + public constructor() { + super('olimposcans', `OlimpoScans`, 'https://leerolimpo.com', Tags.Media.Manga, Tags.Media.Manhua, Tags.Media.Manhwa, Tags.Language.Spanish, Tags.Source.Scanlator); + } + + public override get Icon() { + return icon; + } + + public override async FetchPages(chapter: Chapter): Promise { + const pages = await super.FetchPages.call(this, chapter); + return pages.map(page => new Page(this, chapter, this.StripSearch(page.Link), page.Parameters)); + } + + private StripSearch(link: URL): URL { + link.pathname = link.pathname.replace(/&.*/g, ''); + return link; + } +} diff --git a/web/src/engine/websites/OlimpoScans.webp b/web/src/engine/websites/OlimpoScans.webp new file mode 100644 index 0000000000..1dde330fea Binary files /dev/null and b/web/src/engine/websites/OlimpoScans.webp differ diff --git a/web/src/engine/websites/OlimpoScans_e2e.ts b/web/src/engine/websites/OlimpoScans_e2e.ts new file mode 100644 index 0000000000..7c33f40c09 --- /dev/null +++ b/web/src/engine/websites/OlimpoScans_e2e.ts @@ -0,0 +1,22 @@ +import { TestFixture } from '../../../test/WebsitesFixture'; + +new TestFixture({ + plugin: { + id: 'olimposcans', + title: 'OlimpoScans' + }, + container: { + url: 'https://leerolimpo.com/comic-la-venganza-del-sabueso-de-sangre-de-hierro.html', + id: '/comic-la-venganza-del-sabueso-de-sangre-de-hierro.html', + title: 'LA VENGANZA DEL SABUESO DE SANGRE DE HIERRO' + }, + child: { + id: '/leer-la-venganza-del-sabueso-de-sangre-de-hierro-capitulo-94.html', + title: 'Capítulo 94', + }, + entry: { + index: 1, + size: 225_908, + type: 'image/webp' + } +}).AssertWebsite(); \ No newline at end of file diff --git a/web/src/engine/websites/RawInu.ts b/web/src/engine/websites/RawInu.ts new file mode 100644 index 0000000000..bc550dcadf --- /dev/null +++ b/web/src/engine/websites/RawInu.ts @@ -0,0 +1,22 @@ +import { Tags } from '../Tags'; +import icon from './RawInu.webp'; +import * as Common from './decorators/Common'; +import { FetchWindowScript } from '../platform/FetchProvider'; +import { FlatManga, MangaLabelExtractor, chapterScript, pageScript } from './templates/FlatManga'; + +@Common.MangaCSS(/^{origin}\/[^/]+\.html$/, 'li.breadcrumb-item.active', MangaLabelExtractor) +@Common.ChaptersSinglePageJS(chapterScript, 2500) +@Common.PagesSinglePageJS(pageScript, 1500) +export default class extends FlatManga { + public constructor() { + super('rawinu', 'RawInu', 'https://rawinu.com', Tags.Media.Manga, Tags.Language.Japanese, Tags.Source.Aggregator); + } + + public override get Icon() { + return icon; + } + + public override async Initialize(): Promise { + return await FetchWindowScript(new Request(new URL('/manga-list.html', this.URI)), 'true', 3000, 15000);//trigger antiDDOSS + } +} \ No newline at end of file diff --git a/web/src/engine/websites/RawInu.webp b/web/src/engine/websites/RawInu.webp new file mode 100644 index 0000000000..5344c94286 Binary files /dev/null and b/web/src/engine/websites/RawInu.webp differ diff --git a/web/src/engine/websites/RawInu_e2e.ts b/web/src/engine/websites/RawInu_e2e.ts new file mode 100644 index 0000000000..17a9f2c4ba --- /dev/null +++ b/web/src/engine/websites/RawInu_e2e.ts @@ -0,0 +1,22 @@ +import { TestFixture } from '../../../test/WebsitesFixture'; + +new TestFixture({ + plugin: { + id: 'rawinu', + title: 'RawInu' + }, + container: { + url: 'https://rawinu.com/manga-tatari-watari.html', + id: '/manga-tatari-watari.html', + title: 'TATARI (WATARI)' + }, + child: { + id: '/manga/tatari-watari/chapter-44.html', + title: 'Chapter 44' + }, + entry: { + index: 0, + size: 548_603, + type: 'image/jpeg' + } +}).AssertWebsite(); \ No newline at end of file diff --git a/web/src/engine/websites/WeLoMa.ts b/web/src/engine/websites/WeLoMa.ts new file mode 100644 index 0000000000..975d6f138b --- /dev/null +++ b/web/src/engine/websites/WeLoMa.ts @@ -0,0 +1,20 @@ +import { Tags } from '../Tags'; +import icon from './WeLoMa.webp'; +import * as Common from './decorators/Common'; +import { FetchWindowScript } from '../platform/FetchProvider'; +import { FlatManga, MangaLabelExtractor, queryMangaTitle } from './templates/FlatManga'; + +@Common.MangaCSS(/^{origin}\/[^/]+\/$/, queryMangaTitle, MangaLabelExtractor) +export default class extends FlatManga { + public constructor() { + super('weloma', `WeLoMa`, 'https://weloma.art', Tags.Media.Manga, Tags.Language.Japanese, Tags.Source.Aggregator); + } + + public override get Icon() { + return icon; + } + + public override async Initialize(): Promise { + return await FetchWindowScript(new Request(new URL('/manga-list.html', this.URI)), 'true', 3000, 15000);//trigger antiDDOSS + } +} \ No newline at end of file diff --git a/web/src/engine/websites/legacy/WeLoMa.webp b/web/src/engine/websites/WeLoMa.webp similarity index 100% rename from web/src/engine/websites/legacy/WeLoMa.webp rename to web/src/engine/websites/WeLoMa.webp diff --git a/web/src/engine/websites/WeLoMa_e2e.ts b/web/src/engine/websites/WeLoMa_e2e.ts new file mode 100644 index 0000000000..6cb12719a0 --- /dev/null +++ b/web/src/engine/websites/WeLoMa_e2e.ts @@ -0,0 +1,22 @@ +import { TestFixture } from '../../../test/WebsitesFixture'; + +new TestFixture({ + plugin: { + id: 'weloma', + title: 'WeLoMa' + }, + container: { + url: 'https://weloma.art/429/', + id: '/429/', + title: 'SLIME TAOSHITE 300-NEN, SHIRANAI UCHI NI LEVEL MAX NI NATTEMASHITA' + }, + child: { + id: '/429/131864/', + title: 'Chapter 73.1', + }, + entry: { + index: 0, + size: 100_474, + type: 'image/webp' + } +}).AssertWebsite(); \ No newline at end of file diff --git a/web/src/engine/websites/WeLoveManga.ts b/web/src/engine/websites/WeLoveManga.ts new file mode 100644 index 0000000000..30e05fc694 --- /dev/null +++ b/web/src/engine/websites/WeLoveManga.ts @@ -0,0 +1,22 @@ +import { Tags } from '../Tags'; +import icon from './WeLoveManga.webp'; +import * as Common from './decorators/Common'; +import { FetchWindowScript } from '../platform/FetchProvider'; +import { FlatManga, MangaLabelExtractor, chapterScript, pageScript, queryMangaTitle } from './templates/FlatManga'; + +@Common.MangaCSS(/^{origin}\/(mgraw-)?\d+\/$/, queryMangaTitle, MangaLabelExtractor) +@Common.ChaptersSinglePageJS(chapterScript, 1500) +@Common.PagesSinglePageJS(pageScript, 1500) +export default class extends FlatManga { + public constructor() { + super('welovemanga', 'WeloveManga', 'https://welovemanga.one', Tags.Language.Japanese, Tags.Media.Manga, Tags.Source.Aggregator); + } + + public override get Icon() { + return icon; + } + + public override async Initialize(): Promise { + return await FetchWindowScript(new Request(new URL('/manga-list.html', this.URI)), 'true', 3000, 15000);//trigger antiDDOSS + } +} \ No newline at end of file diff --git a/web/src/engine/websites/legacy/WeLoveManga.webp b/web/src/engine/websites/WeLoveManga.webp similarity index 100% rename from web/src/engine/websites/legacy/WeLoveManga.webp rename to web/src/engine/websites/WeLoveManga.webp diff --git a/web/src/engine/websites/WeLoveManga_e2e.ts b/web/src/engine/websites/WeLoveManga_e2e.ts new file mode 100644 index 0000000000..759705236d --- /dev/null +++ b/web/src/engine/websites/WeLoveManga_e2e.ts @@ -0,0 +1,23 @@ +import { TestFixture } from '../../../test/WebsitesFixture'; + +new TestFixture({ + plugin: { + id: 'welovemanga', + title: 'WeloveManga' + }, + container: { + url: 'https://welovemanga.one/1067/', + id: '/1067/', + title: 'TONO KANRI O SHITE MIYOU', + timeout: 15000 + }, + child: { + id: '/read-tono-kanri-o-shite-miyou-raw-chapter-65.2.html', + title: 'Chapter 65.2', + }, + entry: { + index: 0, + size: 219_948, + type: 'image/jpeg' + } +}).AssertWebsite(); \ No newline at end of file diff --git a/web/src/engine/websites/_index.ts b/web/src/engine/websites/_index.ts index 80cb16171e..eec7ba423d 100755 --- a/web/src/engine/websites/_index.ts +++ b/web/src/engine/websites/_index.ts @@ -202,6 +202,7 @@ export { default as Hitomi } from './Hitomi'; export { default as HiveScans } from './HiveScans'; export { default as HniScanTrad } from './HniScanTrad'; export { default as HoiFansub } from './HoiFansub'; +export { default as HolyManga } from './HolyManga'; export { default as HorrorFC } from './HorrorFC'; export { default as HqNow } from './HqNow'; export { default as HunterScan } from './HunterScan'; @@ -237,6 +238,7 @@ export { default as KingOfShojo } from './KingOfShojo'; export { default as Kiryuu } from './Kiryuu'; export { default as KissmangaIN } from './KissmangaIN'; export { default as KlikManga } from './KlikManga'; +export { default as KLManga } from './KLManga'; export { default as KnightNoFansub } from './KnightNoFansub'; export { default as KolNovel } from './KolNovel'; export { default as KomBatch } from './KomBatch'; @@ -325,6 +327,7 @@ export { default as MangaGeko } from './MangaGeko'; export { default as MangaGezgini } from './MangaGezgini'; export { default as MangaGG } from './MangaGG'; export { default as MangaGo } from './MangaGo'; +export { default as MangaGun } from './MangaGun'; export { default as MangaHack } from './MangaHack'; export { default as MangaHasu } from './MangaHasu'; export { default as MangaHentai } from './MangaHentai'; @@ -395,6 +398,7 @@ export { default as MangaTepesi } from './MangaTepesi'; export { default as MangaTilkisi } from './MangaTilkisi'; export { default as MangaTitan } from './MangaTitan'; export { default as MangaTown } from './MangaTown'; +export { default as MangaTR } from './MangaTR'; export { default as MangaTRNet } from './MangaTRNet'; export { default as MangaTube } from './MangaTube'; export { default as MangaUpGlobal } from './MangaUpGlobal'; @@ -425,6 +429,7 @@ export { default as Manhwaclan } from './Manhwaclan'; export { default as ManhwaClub } from './ManhwaClub'; export { default as ManhwaDashRaw } from './ManhwaDashRaw'; export { default as ManhwaEighteen } from './ManhwaEighteen'; +export { default as ManhwaFullNet } from './ManhwaFullNet'; export { default as ManhwaHentai } from './ManhwaHentai'; export { default as ManhwaHentaiMe } from './ManhwaHentaiMe'; export { default as ManhwaHub } from './ManhwaHub'; @@ -485,6 +490,7 @@ export { default as Ngomik } from './Ngomik'; export { default as NHentai } from './NHentai'; export { default as NHentaiCom } from './NHentaiCom'; export { default as NiceOppai } from './NiceOppai'; +export { default as NicoManga } from './NicoManga'; export { default as NicoNicoSeiga } from './NicoNicoSeiga'; export { default as Nightow } from './Nightow'; export { default as NightScans } from './NightScans'; @@ -497,6 +503,7 @@ export { default as NoraNoFansub } from './NoraNoFansub'; export { default as Noromax } from './Noromax'; export { default as NovelMic } from './NovelMic'; export { default as NoxScans } from './NoxScans'; +export { default as OlimpoScans } from './OlimpoScans'; export { default as OlympusScanlation } from './OlympusScanlation'; export { default as Opiatoon } from './Opiatoon'; export { default as Oremanga } from './Oremanga'; @@ -540,6 +547,7 @@ export { default as RavenSeries } from './RavenSeries'; export { default as RavensScansEN } from './RavensScansEN'; export { default as RavensScansES } from './RavensScansES'; export { default as RawDevart } from './RawDevart'; +export { default as RawInu } from './RawInu'; export { default as Rawkuma } from './Rawkuma'; export { default as RawLazy } from './RawLazy'; export { default as RawMangatop } from './RawMangatop'; @@ -707,6 +715,8 @@ export { default as WebtoonTR } from './WebtoonTR'; export { default as WebtoonTRNET } from './WebtoonTRNET'; export { default as WebtoonXYZ } from './WebtoonXYZ'; export { default as WeebCentral } from './WeebCentral'; +export { default as WeLoMa } from './WeLoMa'; +export { default as WeLoveManga } from './WeLoveManga'; export { default as WestManga } from './WestManga'; export { default as WinterScan } from './WinterScan'; export { default as Wnacg } from './Wnacg'; @@ -756,9 +766,7 @@ export { default as EpikManga } from './legacy/EpikManga'; export { default as Futabanet } from './legacy/Futabanet'; export { default as GammaPlus } from './legacy/GammaPlus'; export { default as Guoman8 } from './legacy/Guoman8'; -export { default as HolyManga } from './legacy/HolyManga'; export { default as KanMan } from './legacy/KanMan'; -export { default as KissAway } from './legacy/KissAway'; export { default as kuman5 } from './legacy/kuman5'; export { default as LectorManga } from './legacy/LectorManga'; export { default as LezhinEN } from './legacy/LezhinEN'; @@ -780,7 +788,6 @@ export { default as MangaToonES } from './legacy/MangaToonES'; export { default as MangaToonID } from './legacy/MangaToonID'; export { default as MangaToonTH } from './legacy/MangaToonTH'; export { default as MangaToonVI } from './legacy/MangaToonVI'; -export { default as MangaTR } from './legacy/MangaTR'; export { default as MangaZukiRAWS } from './legacy/MangaZukiRAWS'; export { default as ManhuaTai } from './legacy/ManhuaTai'; export { default as MeioNovel } from './legacy/MeioNovel'; @@ -834,8 +841,6 @@ export { default as VizShonenJump } from './legacy/VizShonenJump'; export { default as WanPaMan } from './legacy/WanPaMan'; export { default as WBNovel } from './legacy/WBNovel'; export { default as WebComicGamma } from './legacy/WebComicGamma'; -export { default as WeLoMa } from './legacy/WeLoMa'; -export { default as WeLoveManga } from './legacy/WeLoveManga'; export { default as WoopRead } from './legacy/WoopRead'; export { default as WordRain } from './legacy/WordRain'; export { default as WuxiaWorld } from './legacy/WuxiaWorld'; diff --git a/web/src/engine/websites/legacy/HolyManga.ts b/web/src/engine/websites/legacy/HolyManga.ts deleted file mode 100755 index ada788dd0b..0000000000 --- a/web/src/engine/websites/legacy/HolyManga.ts +++ /dev/null @@ -1,53 +0,0 @@ -// Auto-Generated export from HakuNeko Legacy -// See: https://gist.github.com/ronny1982/0c8d5d4f0bd9c1f1b21dbf9a2ffbfec9 - -import { Tags } from '../../Tags'; -import icon from './HolyManga.webp'; -import { DecoratableMangaScraper } from '../../providers/MangaPlugin'; -import { FetchWindowScript } from '../../platform/FetchProvider'; - -export default class extends DecoratableMangaScraper { - - public constructor() { - super('holymanga', `Holy Manga`, 'https://holymanga.net', Tags.Media.Manga, Tags.Media.Manhwa, Tags.Language.English, Tags.Accessibility.DomainRotation); - } - - public override get Icon() { - return icon; - } - - public override async Initialize(): Promise { - this.URI.href = await FetchWindowScript(new Request(this.URI), 'window.location.origin'); - console.log(`Assigned URL '${this.URI}' to ${this.Title}`); - } -} - -// Original Source -/* -class HolyManga extends WordPressZbulu { - - constructor() { - super(); - super.id = 'holymanga'; - super.label = 'Holy Manga'; - this.tags = [ 'manga', 'english' ]; - this.url = 'https://holymanga.net'; - } - - canHandleURI(uri) { - return /https?:\/\/w+\d*.holymanga.net/.test(uri.origin); - } - - async _initializeConnector() { - let uri = new URL(this.url); - let request = new Request(uri.href, this.requestOptions); - this.url = await Engine.Request.fetchUI(request, `window.location.origin`); - console.log(`Assigned URL '${this.url}' to ${this.label}`); - } - - async _getPages(chapter) { - const pages = await super._getPages(chapter); - return pages.filter(page => !/cover.png$/.test(page)); - } -} -*/ \ No newline at end of file diff --git a/web/src/engine/websites/legacy/HolyManga.webp b/web/src/engine/websites/legacy/HolyManga.webp deleted file mode 100644 index a700d33fa2..0000000000 Binary files a/web/src/engine/websites/legacy/HolyManga.webp and /dev/null differ diff --git a/web/src/engine/websites/legacy/KissAway.ts b/web/src/engine/websites/legacy/KissAway.ts deleted file mode 100755 index 526fba9423..0000000000 --- a/web/src/engine/websites/legacy/KissAway.ts +++ /dev/null @@ -1,32 +0,0 @@ -// Auto-Generated export from HakuNeko Legacy -// See: https://gist.github.com/ronny1982/0c8d5d4f0bd9c1f1b21dbf9a2ffbfec9 - -//import { Tags } from '../../Tags'; -import icon from './KissAway.webp'; -import { DecoratableMangaScraper } from '../../providers/MangaPlugin'; - -export default class extends DecoratableMangaScraper { - - public constructor() { - super('kissaway', `KLManga`, 'https://klz9.com' /*, Tags.Language.English, Tags ... */); - } - - public override get Icon() { - return icon; - } -} - -// Original Source -/* -class KissAway extends FlatManga { - - constructor() { - super(); - super.id = 'kissaway'; - super.label = 'KLManga'; - this.tags = [ 'manga', 'raw', 'japanese' ]; - this.url = 'https://klmanga.com'; - this.requestOptions.headers.set('x-referer', this.url); - } -} -*/ \ No newline at end of file diff --git a/web/src/engine/websites/legacy/KissAway.webp b/web/src/engine/websites/legacy/KissAway.webp deleted file mode 100644 index 45deb2ab41..0000000000 Binary files a/web/src/engine/websites/legacy/KissAway.webp and /dev/null differ diff --git a/web/src/engine/websites/legacy/MangaTR.ts b/web/src/engine/websites/legacy/MangaTR.ts deleted file mode 100755 index 43eedb1c53..0000000000 --- a/web/src/engine/websites/legacy/MangaTR.ts +++ /dev/null @@ -1,84 +0,0 @@ -// Auto-Generated export from HakuNeko Legacy -// See: https://gist.github.com/ronny1982/0c8d5d4f0bd9c1f1b21dbf9a2ffbfec9 - -//import { Tags } from '../../Tags'; -import icon from './MangaTR.webp'; -import { DecoratableMangaScraper } from '../../providers/MangaPlugin'; - -export default class extends DecoratableMangaScraper { - - public constructor() { - super('mangatr', `Manga-TR`, 'https://manga-tr.com' /*, Tags.Language.English, Tags ... */); - } - - public override get Icon() { - return icon; - } -} - -// Original Source -/* -class MangaTR extends FlatManga { - - constructor() { - super(); - super.id = 'mangatr'; - super.label = 'Manga-TR'; - this.tags = [ 'manga', 'turkish' ]; - this.url = 'https://manga-tr.com'; - - this.queryMangaTitle = 'meta[property="og:title"]'; - this.queryMangas = 'div.container a[data-toggle="mangapop"]'; - this.requestOptions.headers.set('x-referer', this.url); - this.requestOptions.headers.set('x-cookie', 'read_type=1'); - } - - async _initializeConnector() { - for(let path of [this.path, '/manga-list.html']) { - const uri = new URL(path, this.url); - const request = new Request(uri, this.requestOptions); - return Engine.Request.fetchUI(request, '', 30000, true); - } - } - - async _getMangaFromURI(uri) { - const manga = await super._getMangaFromURI(uri); - manga.title = manga.title.split(' - ').shift().trim(); - return manga; - } - - async _getChapters(manga) { - let chapterList = []; - let uri = new URL(manga.id, this.url); - let request = new Request(uri, this.requestOptions); - let data = await this.fetchRegex(request, /"([^"]*cek\/fetch_pages_manga.php\?manga_cek=[^"]*)"/g); - request = new Request(new URL(data[0], this.url), this.requestOptions); - request.headers.set('x-requested-with', 'XMLHttpRequest'); - data = await this.fetchDOM(request, 'ul.pagination1 li:last-of-type a'); - const pageCount = data.length > 0 ? parseInt(data.pop().dataset.page) : 1; - for(let page = 1; page <= pageCount; page++) { - let chapters = await this._getChaptersFromPage(manga, request.url, page); - chapterList.push(...chapters); - } - return chapterList; - } - - async _getChaptersFromPage(manga, action, page) { - const request = new Request(action, { - method: 'POST', - body: 'page=' + page, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'x-requested-with': 'XMLHttpRequest' - } - }); - const data = await this.fetchDOM(request, 'table.table tr td.table-bordered:first-of-type > a'); - return data.map(element => { - return { - id: this.getRootRelativeOrAbsoluteLink(element, this.url), - title: element.text.replace(manga.title, '').trim() - }; - }); - } -} -*/ \ No newline at end of file diff --git a/web/src/engine/websites/legacy/WeLoMa.ts b/web/src/engine/websites/legacy/WeLoMa.ts deleted file mode 100755 index 15850f540d..0000000000 --- a/web/src/engine/websites/legacy/WeLoMa.ts +++ /dev/null @@ -1,112 +0,0 @@ -// Auto-Generated export from HakuNeko Legacy -// See: https://gist.github.com/ronny1982/0c8d5d4f0bd9c1f1b21dbf9a2ffbfec9 - -//import { Tags } from '../../Tags'; -import icon from './WeLoMa.webp'; -import { DecoratableMangaScraper } from '../../providers/MangaPlugin'; - -export default class extends DecoratableMangaScraper { - - public constructor() { - super('weloma', `WeLoMa`, 'https://weloma.art' /*, Tags.Language.English, Tags ... */); - } - - public override get Icon() { - return icon; - } -} - -// Original Source -/* -class WeLoMa extends FlatManga { - - constructor() { - super(); - super.id = 'weloma'; - super.label = 'WeLoveManga'; - this.tags = ['manga', 'hentai', 'raw', 'japanese']; - this.url = 'https://weloma.net'; - this.path = '/list-manga.html'; - this.requestOptions.headers.set('x-referer', this.url); - - this.queryMangaTitle = 'ul.manga-info h3'; - this.queryMangas = 'div.card-body div.series-title a'; - this.queryChapters = 'ul.list-chapters > a'; - this.queryChapterTitle = 'div.chapter-name'; - this.queryPages = 'div.chapter-content embed'; - } - - async _initializeConnector() { - for (let path of [this.path, '/0/']) { - const uri = new URL(path, this.url); - const request = new Request(uri, this.requestOptions); - return Engine.Request.fetchUI(request, '', 30000, true); - } - } - - async _getMangas() { - let mangaList = []; - const uri = new URL(this.path, this.url); - const request = new Request(uri, this.requestOptions); - const data = await this.fetchDOM(request, 'ul.pagination li:nth-last-of-type(2) a'); - const pageCount = parseInt(data[0].text.trim()); - for (let page = 1; page <= pageCount; page++) { - let mangas = await this._getMangasFromPage(page); - mangaList.push(...mangas); - await this.wait(5000); - } - return mangaList; - } - - async _getMangasFromPage(page) { - const uri = new URL(this.path, this.url); - uri.searchParams.set('page', page); - const request = new Request(uri, this.requestOptions); - const data = await this.fetchDOM(request, this.queryMangas); - return data.map(element => { - return { - id: this.getRootRelativeOrAbsoluteLink(element, this.url), - title: element.text.trim() - }; - }); - } - - async _getChapters(manga) { - const script = ` - new Promise(resolve => { - const chapters = [...document.querySelectorAll('ul.list-chapters > a')].map(element => { - return { - id: element.pathname, - title: element.title - }; - }); - resolve(chapters); - }); - `; - const uri = new URL(manga.id, this.url); - const request = new Request(uri, this.requestOptions); - return Engine.Request.fetchUI(request, script); - } - - async _getPages(chapter) { - const uri = new URL(chapter.id, this.url); - const request = new Request(uri, this.requestOptions); - const data = await this.fetchDOM(request, this.queryPages); - return data.map(element => { - const link = [...element.attributes] - .filter(attribute => !['src', 'class', 'alt'].includes(attribute.name)) - .map(attribute => { - try { - return atob(attribute.value.trim()); - } catch (_) { - return attribute.value.trim(); - } - }) - .find(value => { - return /^http/.test(value); - }); - return this.createConnectorURI(this.getAbsolutePath(link || element, request.url)); - }); - } -} -*/ \ No newline at end of file diff --git a/web/src/engine/websites/legacy/WeLoveManga.ts b/web/src/engine/websites/legacy/WeLoveManga.ts deleted file mode 100755 index f2b30c1796..0000000000 --- a/web/src/engine/websites/legacy/WeLoveManga.ts +++ /dev/null @@ -1,111 +0,0 @@ -// Auto-Generated export from HakuNeko Legacy -// See: https://gist.github.com/ronny1982/0c8d5d4f0bd9c1f1b21dbf9a2ffbfec9 - -//import { Tags } from '../../Tags'; -import icon from './WeLoveManga.webp'; -import { DecoratableMangaScraper } from '../../providers/MangaPlugin'; - -export default class extends DecoratableMangaScraper { - - public constructor() { - super('lovehug', `WeloveManga`, 'https://welovemanga.one' /*, Tags.Language.English, Tags ... */); - } - - public override get Icon() { - return icon; - } -} - -// Original Source -/* -class WeLoveManga extends FlatManga { - - constructor() { - super(); - super.id = 'lovehug'; - super.label = 'WeLoveManga'; - this.tags = [ 'manga', 'hentai', 'raw', 'japanese' ]; - this.url = 'https://welovemanga.net'; - this.path = '/manga-list.html'; - this.requestOptions.headers.set('x-referer', this.url); - - this.queryMangaTitle = 'ul.manga-info h3'; - this.queryMangas = 'div.card-body div.series-title a'; - this.queryChapters = 'ul.list-chapters > a'; - this.queryChapterTitle = 'div.chapter-name'; - } - - async _initializeConnector() { - for(let path of [this.path, '/0/0/']) { - const uri = new URL(path, this.url); - const request = new Request(uri, this.requestOptions); - return Engine.Request.fetchUI(request, '', 30000, true); - } - } - - async _getMangas() { - let mangaList = []; - const uri = new URL(this.path, this.url); - const request = new Request(uri, this.requestOptions); - const data = await this.fetchDOM(request, 'ul.pagination li:nth-last-of-type(2) a'); - const pageCount = parseInt(data[0].text.trim()); - for(let page = 1; page <= pageCount; page++) { - let mangas = await this._getMangasFromPage(page); - mangaList.push(...mangas); - await this.wait(5000); - } - return mangaList; - } - - async _getMangasFromPage(page) { - const uri = new URL(this.path, this.url); - uri.searchParams.set('page', page); - const request = new Request(uri, this.requestOptions); - const data = await this.fetchDOM(request, this.queryMangas); - return data.map(element => { - return { - id: this.getRootRelativeOrAbsoluteLink(element, this.url), - title: element.text.trim() - }; - }); - } - - async _getChapters(manga) { - const script = ` - new Promise(resolve => { - const chapters = [...document.querySelectorAll('ul.list-chapters > a')].map(element => { - return { - id: element.pathname, - title: element.title - }; - }); - resolve(chapters); - }); - `; - const uri = new URL(manga.id, this.url); - const request = new Request(uri, this.requestOptions); - return Engine.Request.fetchUI(request, script); - } - - async _getPages(chapter) { - const uri = new URL(chapter.id, this.url); - const request = new Request(uri, this.requestOptions); - const data = await this.fetchDOM(request, this.queryPages); - return data.map(element => { - const link = [ ...element.attributes] - .filter(attribute => !['src', 'class', 'alt'].includes(attribute.name)) - .map(attribute => { - try { - return atob(attribute.value.trim()); - } catch(_) { - return attribute.value.trim(); - } - }) - .find(value => { - return /^http/.test(value); - }); - return this.createConnectorURI(this.getAbsolutePath(link || element, request.url)); - }); - } -} -*/ \ No newline at end of file diff --git a/web/src/engine/websites/templates/FlatManga.ts b/web/src/engine/websites/templates/FlatManga.ts new file mode 100644 index 0000000000..531279549e --- /dev/null +++ b/web/src/engine/websites/templates/FlatManga.ts @@ -0,0 +1,110 @@ +import { AddAntiScrapingDetection, FetchRedirection } from "../../platform/AntiScrapingDetection"; +import { FetchCSS } from "../../platform/FetchProvider"; +import { DecoratableMangaScraper, type Chapter, type MangaScraper } from "../../providers/MangaPlugin"; +import { Page } from "../../providers/MangaPlugin"; +import * as Common from '../decorators/Common'; + +AddAntiScrapingDetection(async (invoke) => { + const result = await invoke(`document.documentElement.innerHTML.includes('ct_anti_ddos_key')`);// Sample => Mangagun, NicoManga, Rawinu, Weloma, WeloveManga + if (result) { + await new Promise(resolve => setTimeout(resolve, 3000)); + return FetchRedirection.Automatic; + } else return undefined; + +}); + +export function MangaLabelExtractor(element: HTMLElement) { + return CleanTitle(element.getAttribute('text') ? element.getAttribute('text') : element.textContent); +} + +export function MangaExtractor(anchor: HTMLAnchorElement) { + return { + id: anchor.pathname, + title: CleanTitle(anchor.getAttribute('text') ? anchor.text : anchor.textContent) + }; +} + +export function CleanTitle(title: string): string { + title = title.replace(/\s*[~\-―〜]\s*RAW\s*\(MANGA\)\s*$/i, '').trim(); + title = title.replace(/\s*[~\-―〜]\s*(MANGA)?\s*RAW\s*$/i, '').trim(); + title = title.replace(/\(raw\)/i, '').trim(); + return title.replace(/\(manga\)/i, '').trim(); +} + +export function PageLinkExtractor(this: MangaScraper, element: E): string { + let page = element.dataset.aload || element.dataset.src || element.dataset.srcset || element.dataset.original || element.dataset.pagespeedLazySrc || element.src; + try { + page = window.atob(page); + } catch { } + return page.replace(/\n/g, ''); + +} + +export const pathSinglePageManga = '/manga-list.html?listType=allABC'; +const pathMultiPageManga = '/manga-list.html?page={page}'; + +export const queryMangaTitle = [ + 'li:last-of-type span[itemprop="name"]', + 'ul.manga-info h3', + 'ul.manga-info h1', +].join(','); + +export const queryMangas = [ + 'span[data-toggle="mangapop"] a', + 'div.media h3.media-heading a', + 'div.container a[data-toggle="mangapop"]', + 'div.card-body div.series-title a' +].join(','); + +const queryChapters = [ + 'div#tab-chapper table tr td a.chapter', + 'ul.list-chapters > a', + 'div#tab-chapper div#list-chapters span.title a.chapter' +].join(','); + +export const queryPages = [ + 'img.chapter-img', + 'div#chapter-content img', + 'div.chapter-content img' +].join(','); + +export const chapterScript = ` + [...document.querySelectorAll('ul.list-chapters > a')].map(chapter => { + return { + id: chapter.pathname, + title : chapter.title.trim() + }; + }); +`; + +export const pageScript = ` + new Promise(resolve => { + const images = [...document.querySelectorAll('${queryPages}')] + .filter(element => element.alt != 'olimposcans.com' && !element.alt.includes('nicoscan')) + .map(element => { + const page = element.dataset.aload || element.dataset.src || element.dataset.srcset || element.dataset.original || element.dataset.pagespeedLazySrc || element.src; + try { + page = window.atob(page); + } catch {} + return page.replace(/\\n/g, ''); + }); + resolve(images); + }); +`; + +@Common.MangaCSS(/^{origin}\/[^/]+\.html$/, queryMangaTitle, MangaLabelExtractor) +@Common.MangasMultiPageCSS(pathMultiPageManga, queryMangas, 1, 1, 0, MangaExtractor) +@Common.ChaptersSinglePageCSS(queryChapters, Common.AnchorInfoExtractor(true)) +@Common.ImageAjax() + +export class FlatManga extends DecoratableMangaScraper { + + public override async FetchPages(chapter: Chapter): Promise { + const request = new Request(new URL(chapter.Identifier, this.URI)); + const data = await FetchCSS(request, queryPages); + return data.map(element => { + const link = new URL(PageLinkExtractor.call(this, element), request.url); + return new Page(this, chapter, link, { Referer: this.URI.href }); + }); + } +} \ No newline at end of file diff --git a/web/src/engine/websites/templates/FlatManga_e2e.ts b/web/src/engine/websites/templates/FlatManga_e2e.ts new file mode 100644 index 0000000000..2367f499dc --- /dev/null +++ b/web/src/engine/websites/templates/FlatManga_e2e.ts @@ -0,0 +1,10 @@ +import '../HolyManga_e2e'; +import '../KLManga_e2e'; +import '../MangaGun_e2e'; +import '../MangaTR_e2e'; +import '../ManhwaFullNet_e2e'; +import '../NicoManga_e2e'; +import '../OlimpoScans_e2e'; +import '../RawInu_e2e'; +import '../WeLoMa_e2e'; +import '../WeLoveManga_e2e'; \ No newline at end of file