From 4d53b8f576241d33694dce31ea3268124f1c31c1 Mon Sep 17 00:00:00 2001 From: barrientosvctor Date: Thu, 12 Oct 2023 22:35:26 -0500 Subject: [PATCH 01/16] chore: manganato provider --- src/index.ts | 2 + .../v1/manga/manganato/ManganatoRoutes.ts | 15 +++ .../sites/manga/manganato/Manganato.ts | 110 ++++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 src/routes/v1/manga/manganato/ManganatoRoutes.ts create mode 100644 src/scraper/sites/manga/manganato/Manganato.ts diff --git a/src/index.ts b/src/index.ts index da8c962..8f86d46 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,7 @@ import comick from "./routes/v1/manga/comick/ComickRoutes"; import inmanga from "./routes/v1/manga/inmanga/InmangaRoutes"; import nhentai from "./routes/v1/manga/nhentai/NhentaiRoutes" import mangareader from "./routes/v1/manga/mangareader/MangaReaderRoutes"; +import manganato from "./routes/v1/manga/manganato/ManganatoRoutes"; import helmet from "helmet"; import cors from 'cors' import WcoStream from "./routes/v1/anime/wcostream/wcostreamRoutes"; @@ -45,6 +46,7 @@ app.use(comick); app.use(inmanga); app.use(nhentai) app.use(mangareader); +app.use(manganato); /*Manga*/ diff --git a/src/routes/v1/manga/manganato/ManganatoRoutes.ts b/src/routes/v1/manga/manganato/ManganatoRoutes.ts new file mode 100644 index 0000000..3b6ffe0 --- /dev/null +++ b/src/routes/v1/manga/manganato/ManganatoRoutes.ts @@ -0,0 +1,15 @@ +import { Router } from "express"; +import { Manganato } from "../../../../scraper/sites/manga/manganato/Manganato"; + +const router = Router(); +const manganato = new Manganato(); + +router.get("/manga/manganato/title/:id", async (req, res) => { + const result = await manganato.GetMangaInfo( + req.params.id as unknown as string, + ); + + return res.status(200).send(result); +}); + +export default router; diff --git a/src/scraper/sites/manga/manganato/Manganato.ts b/src/scraper/sites/manga/manganato/Manganato.ts new file mode 100644 index 0000000..d1ac0dc --- /dev/null +++ b/src/scraper/sites/manga/manganato/Manganato.ts @@ -0,0 +1,110 @@ +import { Manga, MangaChapter } from "../../../../types/manga"; +import axios from "axios"; +import { load } from "cheerio"; +import { Image } from "../../../../types/image"; + +export class Manganato { + readonly url = "https://manganato.com"; + readonly chapURL = "https://chapmanganato.com"; + readonly name = "manganato"; + + private GetMangaDescription(data: cheerio.Root) { + if ( + data("div#panel-story-info-description").length == 0 && + data("div#panel-story-info-description h3").length == 0 + ) + return null; + + data("div#panel-story-info-description h3").remove(); + + return data("div#panel-story-info-description").text().trim(); + } + + private GetMangaStatus(data: cheerio.Root) { + const selector = data("div.panel-story-info > div.story-info-right > table > tbody > tr:nth-child(3) > td.table-value"); + + if (selector.length == 0) + return null; + + if (selector.text().trim() == "Ongoing") + return "ongoing"; + else + return "completed"; + } + + private GetMangaAuthors(data: cheerio.Root): string[] | null { + const selector = data("div.panel-story-info > div.story-info-right > table > tbody > tr:nth-child(2) > td.table-value"); + + if (selector.length == 0 && selector.find("a.a-h").length == 0) + return null; + + return selector.find("a.a-h").map((_, element) => { + return data(element).text().trim(); + }).get(); + } + + private GetMangaGenres(data: cheerio.Root): string[] | null { + const selector = data("div.panel-story-info > div.story-info-right > table > tbody > tr:nth-child(4) > td.table-value"); + + if (selector.length == 0 && selector.find("a.a-h").length == 0) + return null; + + return selector.find("a.a-h").map((_, element) => { + return data(element).text().trim(); + }).get(); + } + + private isNsfw(data: cheerio.Root) { + const genres = this.GetMangaGenres(data); + + return genres.some(genre => genre === "Pornographic"); + } + + async GetMangaInfo(mangaId: string) { + const { data } = await axios.get(`${this.chapURL}/manga-${mangaId}`); + const $ = load(data); + + const manga = new Manga; + + const title = $("div.panel-story-info > div.story-info-right > h1").text().trim(); + const description = this.GetMangaDescription($); + const thumbnail = $("div.panel-story-info > div.story-info-left > span.info-image > img").attr("src"); + const altTitle = $("table > tbody > tr:nth-child(1) > td.table-value > h2").text().trim(); + const status = this.GetMangaStatus($); + const authors = this.GetMangaAuthors($); + const genres = this.GetMangaGenres($); + const chapters = $("div.panel-story-chapter-list").find("ul > li.a-h").map((_, element) => { + const chapter = new MangaChapter; + const url = $(element).find("a.chapter-name").attr("href"); + const chapterId = new URL(url).pathname.split("-").at(-1); + + chapter.id = Number(chapterId); + chapter.title = $(element).find("a.chapter-name").text().trim(); + chapter.url = `/manga/${this.name}/chapter/${mangaId}?num=${chapterId}`; + chapter.number = Number(chapterId); + chapter.images = null; + + return chapter; + }).get(); + + manga.id = mangaId; + manga.url = `/manga/${this.name}/title/${mangaId}`; + manga.title = title; + manga.altTitles = Array.of(altTitle); + manga.thumbnail = new Image(thumbnail); + manga.description = description; + manga.status = status; + manga.authors = authors; + manga.genres = genres; + manga.characters = null; + manga.chapters = chapters; + manga.volumes = null; + manga.isNSFW = this.isNsfw($); + + return manga; + } + + async Filter() {} + + async GetMangaChapters() {} +} From 2979510c862faa610a0079fe3f61943b152ca15a Mon Sep 17 00:00:00 2001 From: barrientosvctor Date: Fri, 13 Oct 2023 20:03:56 -0500 Subject: [PATCH 02/16] manga genres to filter by nsfw --- src/scraper/sites/manga/manganato/Manganato.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scraper/sites/manga/manganato/Manganato.ts b/src/scraper/sites/manga/manganato/Manganato.ts index d1ac0dc..f828c92 100644 --- a/src/scraper/sites/manga/manganato/Manganato.ts +++ b/src/scraper/sites/manga/manganato/Manganato.ts @@ -57,7 +57,7 @@ export class Manganato { private isNsfw(data: cheerio.Root) { const genres = this.GetMangaGenres(data); - return genres.some(genre => genre === "Pornographic"); + return genres.some(genre => genre === "Pornographic" || genre === "Mature" || genre === "Erotica"); } async GetMangaInfo(mangaId: string) { From ea69d46c18b300ff6e7ea08a75d182132a17e1ff Mon Sep 17 00:00:00 2001 From: barrientosvctor Date: Fri, 13 Oct 2023 20:48:41 -0500 Subject: [PATCH 03/16] feat(manganato): get manga chapters --- .../sites/manga/manganato/Manganato.ts | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/scraper/sites/manga/manganato/Manganato.ts b/src/scraper/sites/manga/manganato/Manganato.ts index f828c92..156e729 100644 --- a/src/scraper/sites/manga/manganato/Manganato.ts +++ b/src/scraper/sites/manga/manganato/Manganato.ts @@ -59,6 +59,12 @@ export class Manganato { return genres.some(genre => genre === "Pornographic" || genre === "Mature" || genre === "Erotica"); } + private GetMangaPages(data: cheerio.Root) { + if (data("div.container-chapter-reader").length == 0 && data("div.container-chapter-reader > img").length == 0) + return null; + + return data("div.container-chapter-reader > img").map((_, element) => data(element).attr("src")).get(); + } async GetMangaInfo(mangaId: string) { const { data } = await axios.get(`${this.chapURL}/manga-${mangaId}`); @@ -106,5 +112,20 @@ export class Manganato { async Filter() {} - async GetMangaChapters() {} + async GetMangaChapters(mangaId: string, chapterNumber: number) { + const { data } = await axios.get(`${this.chapURL}/manga-${mangaId}/chapter-${chapterNumber}`); + const $ = load(data); + + const images = this.GetMangaPages($); + const name = $("body > div.body-site > div:nth-child(1) > div.panel-breadcrumb > a").eq(-1).attr("title") || null; + const chapter = new MangaChapter; + + chapter.id = Number(chapterNumber); + chapter.title = name; + chapter.url = `/manga/${this.name}/chapter/${mangaId}?num=${chapterNumber}`; + chapter.number = Number(chapterNumber); + chapter.images = images; + + return chapter; + } } From b8cac1271791acff912da13b3f4b119207f6449f Mon Sep 17 00:00:00 2001 From: barrientosvctor Date: Fri, 13 Oct 2023 20:49:14 -0500 Subject: [PATCH 04/16] feat(route): get manga chapters for manganato --- src/routes/v1/manga/manganato/ManganatoRoutes.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/routes/v1/manga/manganato/ManganatoRoutes.ts b/src/routes/v1/manga/manganato/ManganatoRoutes.ts index 3b6ffe0..bcfa0a3 100644 --- a/src/routes/v1/manga/manganato/ManganatoRoutes.ts +++ b/src/routes/v1/manga/manganato/ManganatoRoutes.ts @@ -12,4 +12,10 @@ router.get("/manga/manganato/title/:id", async (req, res) => { return res.status(200).send(result); }); +router.get("/manga/manganato/chapter/:id", async (req, res) => { + const result = await manganato.GetMangaChapters(req.params.id as unknown as string, req.query.num as unknown as number); + + return res.status(200).send(result); +}); + export default router; From 921a2ec38d3673bdb9d283c8095e18536d02a372 Mon Sep 17 00:00:00 2001 From: barrientosvctor Date: Fri, 13 Oct 2023 20:54:37 -0500 Subject: [PATCH 05/16] fix: function responsibility --- src/scraper/sites/manga/manganato/Manganato.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/scraper/sites/manga/manganato/Manganato.ts b/src/scraper/sites/manga/manganato/Manganato.ts index 156e729..7680167 100644 --- a/src/scraper/sites/manga/manganato/Manganato.ts +++ b/src/scraper/sites/manga/manganato/Manganato.ts @@ -54,11 +54,10 @@ export class Manganato { }).get(); } - private isNsfw(data: cheerio.Root) { - const genres = this.GetMangaGenres(data); - + private isNsfw(genres: string[]) { return genres.some(genre => genre === "Pornographic" || genre === "Mature" || genre === "Erotica"); } + private GetMangaPages(data: cheerio.Root) { if (data("div.container-chapter-reader").length == 0 && data("div.container-chapter-reader > img").length == 0) return null; @@ -105,7 +104,7 @@ export class Manganato { manga.characters = null; manga.chapters = chapters; manga.volumes = null; - manga.isNSFW = this.isNsfw($); + manga.isNSFW = this.isNsfw(genres); return manga; } From c28606524ff30e95508b4439091aa1cb7d53ae90 Mon Sep 17 00:00:00 2001 From: barrientosvctor Date: Thu, 2 Nov 2023 20:02:18 -0500 Subject: [PATCH 06/16] chore(types): new types for manganato --- .../sites/manga/manganato/ManganatoTypes.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/scraper/sites/manga/manganato/ManganatoTypes.ts diff --git a/src/scraper/sites/manga/manganato/ManganatoTypes.ts b/src/scraper/sites/manga/manganato/ManganatoTypes.ts new file mode 100644 index 0000000..4c9a41b --- /dev/null +++ b/src/scraper/sites/manga/manganato/ManganatoTypes.ts @@ -0,0 +1,17 @@ +export interface IManganatoFilterParams { + /** Status */ + sts: string; + /** Order by */ + orby: string; + genres: string; + /** Results page */ + page: number; +}; + +export const genreList = { + action: 2, + manhua: 44 +} as const; + +export const orderByOptionsList = ["topview", "newest", "az"] as const; +export type orderByOptions = typeof orderByOptionsList[number]; From 5c83203c0053ac5509fb6dc3b0bfad917ff9d125 Mon Sep 17 00:00:00 2001 From: barrientosvctor Date: Thu, 2 Nov 2023 20:07:46 -0500 Subject: [PATCH 07/16] feat(manganato): url manager for manganato --- .../manga/manganato/ManganatoManagerUtils.ts | 12 ++++ .../manganato/managers/ManganatoManager.ts | 3 + .../manganato/managers/ManganatoURLManager.ts | 55 +++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 src/scraper/sites/manga/manganato/ManganatoManagerUtils.ts create mode 100644 src/scraper/sites/manga/manganato/managers/ManganatoManager.ts create mode 100644 src/scraper/sites/manga/manganato/managers/ManganatoURLManager.ts diff --git a/src/scraper/sites/manga/manganato/ManganatoManagerUtils.ts b/src/scraper/sites/manga/manganato/ManganatoManagerUtils.ts new file mode 100644 index 0000000..92dabde --- /dev/null +++ b/src/scraper/sites/manga/manganato/ManganatoManagerUtils.ts @@ -0,0 +1,12 @@ +import { ManganatoAdvancedSearchURLManager } from "./managers/ManganatoURLManager"; + +export class ManganatoManagerUtils { + private static instance: ManganatoManagerUtils; + readonly url: ManganatoAdvancedSearchURLManager = new ManganatoAdvancedSearchURLManager(); + + static get Instance() { + if (!this.instance) this.instance = new ManganatoManagerUtils(); + + return this.instance; + } +} diff --git a/src/scraper/sites/manga/manganato/managers/ManganatoManager.ts b/src/scraper/sites/manga/manganato/managers/ManganatoManager.ts new file mode 100644 index 0000000..7d0863a --- /dev/null +++ b/src/scraper/sites/manga/manganato/managers/ManganatoManager.ts @@ -0,0 +1,3 @@ +export abstract class ManganatoManager { + abstract generate(item: unknown, ...args: unknown[]): unknown; +} diff --git a/src/scraper/sites/manga/manganato/managers/ManganatoURLManager.ts b/src/scraper/sites/manga/manganato/managers/ManganatoURLManager.ts new file mode 100644 index 0000000..9f6c625 --- /dev/null +++ b/src/scraper/sites/manga/manganato/managers/ManganatoURLManager.ts @@ -0,0 +1,55 @@ +import { URLSearchParams } from "url"; +import { IManganatoFilterParams, genreList, orderByOptions, orderByOptionsList } from "../ManganatoTypes"; +import { ManganatoManager } from "./ManganatoManager"; + +export class ManganatoAdvancedSearchURLManager extends ManganatoManager { + private readonly baseURL = "https://manganato.com/advanced_search"; + private readonly separator = " "; + + private splitGenresToArray(genres: string) { + return genres.split(this.separator); + } + + private processGenres(genresArray: string[]): number[] { + let arrGenerated: number[] = []; + + for (let genre of genresArray) { + if (genreList[genre.toLowerCase()]) + arrGenerated.push(genreList[genre.toLowerCase()]); + else continue; + } + + return arrGenerated; + } + + private formatGenres(genresProcessedArray: number[]) { + return `_${genresProcessedArray.join("_")}_`; + } + + private processStatus(status: unknown) { + return (typeof status === "string" && (status.toLowerCase() === "ongoing" || status.toLowerCase() === "completed")) + ? status + : ""; + } + + private processOrderBy(order: unknown) { + return (typeof order === "string" && orderByOptionsList.includes(order.toLowerCase() as orderByOptions)) + ? order + : ""; + } + + generate(params: IManganatoFilterParams) { + const splitted = this.splitGenresToArray(params.genres); + const processed = this.processGenres(splitted); + + const urlParams = new URLSearchParams({ + s: "all", + g_i: processed.length ? this.formatGenres(processed) : "", + sts: this.processStatus(params.sts), + orby: this.processOrderBy(params.orby), + page: params.page ? params.page.toString() : "" + }); + + return `${this.baseURL}?${urlParams.toString()}`; + } +} From a8cdacff1ba58ca32bf18d7ca26e037bb1fe916b Mon Sep 17 00:00:00 2001 From: barrientosvctor Date: Thu, 2 Nov 2023 20:08:48 -0500 Subject: [PATCH 08/16] feat(manganato): filter --- .../sites/manga/manganato/Manganato.ts | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/scraper/sites/manga/manganato/Manganato.ts b/src/scraper/sites/manga/manganato/Manganato.ts index 7680167..28cf839 100644 --- a/src/scraper/sites/manga/manganato/Manganato.ts +++ b/src/scraper/sites/manga/manganato/Manganato.ts @@ -1,12 +1,16 @@ -import { Manga, MangaChapter } from "../../../../types/manga"; +import { IMangaResult, Manga, MangaChapter } from "../../../../types/manga"; import axios from "axios"; import { load } from "cheerio"; import { Image } from "../../../../types/image"; +import { ManganatoManagerUtils } from "./ManganatoManagerUtils"; +import { IManganatoFilterParams } from "./ManganatoTypes"; +import { ResultSearch } from "../../../../types/search"; export class Manganato { readonly url = "https://manganato.com"; readonly chapURL = "https://chapmanganato.com"; readonly name = "manganato"; + private readonly manager = ManganatoManagerUtils.Instance; private GetMangaDescription(data: cheerio.Root) { if ( @@ -65,6 +69,25 @@ export class Manganato { return data("div.container-chapter-reader > img").map((_, element) => data(element).attr("src")).get(); } + private GetMangaSearchResults(data: cheerio.Root): IMangaResult[] | null { + const section = data("div.panel-content-genres"); + if (section.length === 0) + return null; + + return section.find("div.content-genres-item").map((_, element) => { + const mangaResultId = data(element).find("a.genres-item-img").attr("href").split("-").at(-1); + const name = data(element).find("a.genres-item-img").attr("title").trim(); + + const mangaInfoResults: IMangaResult = { + id: mangaResultId, + title: name, + url: `/manga/${this.name}/title/${mangaResultId}` + } + + return mangaInfoResults; + }).get(); + } + async GetMangaInfo(mangaId: string) { const { data } = await axios.get(`${this.chapURL}/manga-${mangaId}`); const $ = load(data); @@ -109,7 +132,17 @@ export class Manganato { return manga; } - async Filter() {} + async Filter(params: IManganatoFilterParams) { + const url = this.manager.url.generate(params); + + const { data } = await axios.get(url); + const $ = load(data); + + const mangaResultSearch = new ResultSearch(); + mangaResultSearch.results = this.GetMangaSearchResults($); + + return mangaResultSearch; + } async GetMangaChapters(mangaId: string, chapterNumber: number) { const { data } = await axios.get(`${this.chapURL}/manga-${mangaId}/chapter-${chapterNumber}`); From 6a36e9f124cf2f53ea3dd30512896e3e920e80d1 Mon Sep 17 00:00:00 2001 From: barrientosvctor Date: Thu, 2 Nov 2023 20:09:48 -0500 Subject: [PATCH 09/16] feat(routes): filter router for manganato --- src/routes/v1/manga/manganato/ManganatoRoutes.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/routes/v1/manga/manganato/ManganatoRoutes.ts b/src/routes/v1/manga/manganato/ManganatoRoutes.ts index bcfa0a3..fb50991 100644 --- a/src/routes/v1/manga/manganato/ManganatoRoutes.ts +++ b/src/routes/v1/manga/manganato/ManganatoRoutes.ts @@ -12,6 +12,17 @@ router.get("/manga/manganato/title/:id", async (req, res) => { return res.status(200).send(result); }); +router.get("/manga/manganato/filter", async (req, res) => { + const result = await manganato.Filter({ + sts: req.query.status as unknown as string, + genres: req.query.genres as unknown as string, + orby: req.query.order as unknown as string, + page: req.query.page as unknown as number + }); + + return res.status(200).send(result); +}) + router.get("/manga/manganato/chapter/:id", async (req, res) => { const result = await manganato.GetMangaChapters(req.params.id as unknown as string, req.query.num as unknown as number); From 488084e54c88d02134acd447ce129e34e812e265 Mon Sep 17 00:00:00 2001 From: barrientosvctor Date: Sat, 4 Nov 2023 21:23:10 -0500 Subject: [PATCH 10/16] feat(types): manga genres id All manga genres are fully listed --- .../sites/manga/manganato/ManganatoTypes.ts | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/src/scraper/sites/manga/manganato/ManganatoTypes.ts b/src/scraper/sites/manga/manganato/ManganatoTypes.ts index 4c9a41b..3eb1d21 100644 --- a/src/scraper/sites/manga/manganato/ManganatoTypes.ts +++ b/src/scraper/sites/manga/manganato/ManganatoTypes.ts @@ -8,10 +8,50 @@ export interface IManganatoFilterParams { page: number; }; +export const orderByOptionsList = ["topview", "newest", "az"] as const; +export type orderByOptions = typeof orderByOptionsList[number]; + export const genreList = { action: 2, - manhua: 44 + adult: 3, + adventure: 4, + comedy: 6, + cooking: 7, + doujinshi: 9, + drama: 10, + ecchi: 11, + fantasy: 12, + genderbender: 13, + harem: 14, + historical: 15, + horror: 16, + josei: 17, + martialarts: 19, + mature: 20, + mecha: 21, + medical: 22, + mystery: 24, + oneshot: 25, + psychological: 26, + romance: 27, + schoollife: 28, + scifi: 29, + seinen: 30, + shoujo: 31, + shoujoai: 32, + shounen: 33, + shounenai: 34, + sliceoflife: 35, + smut: 36, + sports: 37, + supernatural: 38, + tragedy: 39, + webtoons: 40, + yaoi: 41, + yuri: 42, + manhwa: 43, + manhua: 44, + isekai: 45, + pornographic: 47, + erotica: 48 } as const; - -export const orderByOptionsList = ["topview", "newest", "az"] as const; -export type orderByOptions = typeof orderByOptionsList[number]; From 8e28585997f157d3214ca5836c269fa81e5e16fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=80=E3=82=A4=E3=83=AC=E3=82=AF=E3=83=88=E3=82=B3?= =?UTF-8?q?=E3=83=8D=E3=82=AF=E3=83=88?= <52444606+TokyoTF@users.noreply.github.com> Date: Sat, 13 Jan 2024 08:33:25 -0300 Subject: [PATCH 11/16] fix: Manganato/image CloudFlare Block --- .../sites/manga/manganato/Manganato.ts | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/scraper/sites/manga/manganato/Manganato.ts b/src/scraper/sites/manga/manganato/Manganato.ts index 28cf839..32e8cea 100644 --- a/src/scraper/sites/manga/manganato/Manganato.ts +++ b/src/scraper/sites/manga/manganato/Manganato.ts @@ -8,7 +8,7 @@ import { ResultSearch } from "../../../../types/search"; export class Manganato { readonly url = "https://manganato.com"; - readonly chapURL = "https://chapmanganato.com"; + readonly chapURL = "https://manganelo.tv" //chapmanganelo.com; readonly name = "manganato"; private readonly manager = ManganatoManagerUtils.Instance; @@ -66,7 +66,7 @@ export class Manganato { if (data("div.container-chapter-reader").length == 0 && data("div.container-chapter-reader > img").length == 0) return null; - return data("div.container-chapter-reader > img").map((_, element) => data(element).attr("src")).get(); + return data("div.container-chapter-reader > img").map((_, element) => data(element).attr("data-src")).get(); } private GetMangaSearchResults(data: cheerio.Root): IMangaResult[] | null { @@ -89,23 +89,24 @@ export class Manganato { } async GetMangaInfo(mangaId: string) { - const { data } = await axios.get(`${this.chapURL}/manga-${mangaId}`); + const { data } = await axios.get(`${this.chapURL}/manga/manga-${mangaId}`); const $ = load(data); - + const manga = new Manga; const title = $("div.panel-story-info > div.story-info-right > h1").text().trim(); const description = this.GetMangaDescription($); - const thumbnail = $("div.panel-story-info > div.story-info-left > span.info-image > img").attr("src"); + const thumbnail = this.chapURL + $("div.panel-story-info > div.story-info-left > span.info-image > img").attr("src"); const altTitle = $("table > tbody > tr:nth-child(1) > td.table-value > h2").text().trim(); const status = this.GetMangaStatus($); const authors = this.GetMangaAuthors($); const genres = this.GetMangaGenres($); const chapters = $("div.panel-story-chapter-list").find("ul > li.a-h").map((_, element) => { - const chapter = new MangaChapter; - const url = $(element).find("a.chapter-name").attr("href"); - const chapterId = new URL(url).pathname.split("-").at(-1); - + const chapter = new MangaChapter; + const url = $(element).find("a.chapter-name").attr("href"); + + const chapterId = url.substring(url.lastIndexOf("-") + 1); + chapter.id = Number(chapterId); chapter.title = $(element).find("a.chapter-name").text().trim(); chapter.url = `/manga/${this.name}/chapter/${mangaId}?num=${chapterId}`; @@ -145,13 +146,13 @@ export class Manganato { } async GetMangaChapters(mangaId: string, chapterNumber: number) { - const { data } = await axios.get(`${this.chapURL}/manga-${mangaId}/chapter-${chapterNumber}`); + const { data } = await axios.get(`${this.chapURL}/chapter/manga-${mangaId}/chapter-${chapterNumber}`); const $ = load(data); const images = this.GetMangaPages($); const name = $("body > div.body-site > div:nth-child(1) > div.panel-breadcrumb > a").eq(-1).attr("title") || null; const chapter = new MangaChapter; - + chapter.id = Number(chapterNumber); chapter.title = name; chapter.url = `/manga/${this.name}/chapter/${mangaId}?num=${chapterNumber}`; From 553d8587191c7e232c4b4884a4f71e51be82b775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=80=E3=82=A4=E3=83=AC=E3=82=AF=E3=83=88=E3=82=B3?= =?UTF-8?q?=E3=83=8D=E3=82=AF=E3=83=88?= <52444606+TokyoTF@users.noreply.github.com> Date: Sat, 13 Jan 2024 08:34:23 -0300 Subject: [PATCH 12/16] oth option --- src/scraper/sites/manga/manganato/Manganato.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scraper/sites/manga/manganato/Manganato.ts b/src/scraper/sites/manga/manganato/Manganato.ts index 32e8cea..d85da89 100644 --- a/src/scraper/sites/manga/manganato/Manganato.ts +++ b/src/scraper/sites/manga/manganato/Manganato.ts @@ -8,7 +8,7 @@ import { ResultSearch } from "../../../../types/search"; export class Manganato { readonly url = "https://manganato.com"; - readonly chapURL = "https://manganelo.tv" //chapmanganelo.com; + readonly chapURL = "https://manganelo.tv" //chapmanganelo.com //mangakakalot.tv; readonly name = "manganato"; private readonly manager = ManganatoManagerUtils.Instance; From fe1ce4cc95c001df493db3f7c131234334367651 Mon Sep 17 00:00:00 2001 From: barrientosvctor Date: Mon, 15 Jan 2024 16:16:46 -0500 Subject: [PATCH 13/16] Provider name changed --- src/index.ts | 4 ++-- .../ManganeloRoutes.ts} | 16 ++++++++-------- .../ManganatoManagerUtils.ts | 0 .../{manganato => manganelo}/ManganatoTypes.ts | 12 ++++++++---- .../Manganato.ts => manganelo/Manganelo.ts} | 13 ++++++------- .../managers/ManganatoManager.ts | 0 .../managers/ManganatoURLManager.ts | 16 ++++++++-------- 7 files changed, 32 insertions(+), 29 deletions(-) rename src/routes/v1/manga/{manganato/ManganatoRoutes.ts => manganelo/ManganeloRoutes.ts} (51%) rename src/scraper/sites/manga/{manganato => manganelo}/ManganatoManagerUtils.ts (100%) rename src/scraper/sites/manga/{manganato => manganelo}/ManganatoTypes.ts (71%) rename src/scraper/sites/manga/{manganato/Manganato.ts => manganelo/Manganelo.ts} (91%) rename src/scraper/sites/manga/{manganato => manganelo}/managers/ManganatoManager.ts (100%) rename src/scraper/sites/manga/{manganato => manganelo}/managers/ManganatoURLManager.ts (76%) diff --git a/src/index.ts b/src/index.ts index 8f86d46..4c6ac51 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,7 +13,7 @@ import comick from "./routes/v1/manga/comick/ComickRoutes"; import inmanga from "./routes/v1/manga/inmanga/InmangaRoutes"; import nhentai from "./routes/v1/manga/nhentai/NhentaiRoutes" import mangareader from "./routes/v1/manga/mangareader/MangaReaderRoutes"; -import manganato from "./routes/v1/manga/manganato/ManganatoRoutes"; +import manganelo from "./routes/v1/manga/manganelo/ManganeloRoutes"; import helmet from "helmet"; import cors from 'cors' import WcoStream from "./routes/v1/anime/wcostream/wcostreamRoutes"; @@ -46,7 +46,7 @@ app.use(comick); app.use(inmanga); app.use(nhentai) app.use(mangareader); -app.use(manganato); +app.use(manganelo); /*Manga*/ diff --git a/src/routes/v1/manga/manganato/ManganatoRoutes.ts b/src/routes/v1/manga/manganelo/ManganeloRoutes.ts similarity index 51% rename from src/routes/v1/manga/manganato/ManganatoRoutes.ts rename to src/routes/v1/manga/manganelo/ManganeloRoutes.ts index fb50991..3e52d0d 100644 --- a/src/routes/v1/manga/manganato/ManganatoRoutes.ts +++ b/src/routes/v1/manga/manganelo/ManganeloRoutes.ts @@ -1,19 +1,19 @@ import { Router } from "express"; -import { Manganato } from "../../../../scraper/sites/manga/manganato/Manganato"; +import { Manganelo } from "../../../../scraper/sites/manga/manganelo/Manganelo"; const router = Router(); -const manganato = new Manganato(); +const manganelo = new Manganelo(); -router.get("/manga/manganato/title/:id", async (req, res) => { - const result = await manganato.GetMangaInfo( +router.get(`/manga/${manganelo.name}/title/:id`, async (req, res) => { + const result = await manganelo.GetMangaInfo( req.params.id as unknown as string, ); return res.status(200).send(result); }); -router.get("/manga/manganato/filter", async (req, res) => { - const result = await manganato.Filter({ +router.get(`/manga/${manganelo.name}/filter`, async (req, res) => { + const result = await manganelo.Filter({ sts: req.query.status as unknown as string, genres: req.query.genres as unknown as string, orby: req.query.order as unknown as string, @@ -23,8 +23,8 @@ router.get("/manga/manganato/filter", async (req, res) => { return res.status(200).send(result); }) -router.get("/manga/manganato/chapter/:id", async (req, res) => { - const result = await manganato.GetMangaChapters(req.params.id as unknown as string, req.query.num as unknown as number); +router.get(`/manga/${manganelo.name}/chapter/:id`, async (req, res) => { + const result = await manganelo.GetMangaChapters(req.params.id as unknown as string, req.query.num as unknown as number); return res.status(200).send(result); }); diff --git a/src/scraper/sites/manga/manganato/ManganatoManagerUtils.ts b/src/scraper/sites/manga/manganelo/ManganatoManagerUtils.ts similarity index 100% rename from src/scraper/sites/manga/manganato/ManganatoManagerUtils.ts rename to src/scraper/sites/manga/manganelo/ManganatoManagerUtils.ts diff --git a/src/scraper/sites/manga/manganato/ManganatoTypes.ts b/src/scraper/sites/manga/manganelo/ManganatoTypes.ts similarity index 71% rename from src/scraper/sites/manga/manganato/ManganatoTypes.ts rename to src/scraper/sites/manga/manganelo/ManganatoTypes.ts index 3eb1d21..9110ebf 100644 --- a/src/scraper/sites/manga/manganato/ManganatoTypes.ts +++ b/src/scraper/sites/manga/manganelo/ManganatoTypes.ts @@ -1,5 +1,9 @@ export interface IManganatoFilterParams { - /** Status */ + /** + * Manga status + * + * Available status: "ongoing", "completed", empty string | null (for both) + */ sts: string; /** Order by */ orby: string; @@ -8,10 +12,10 @@ export interface IManganatoFilterParams { page: number; }; -export const orderByOptionsList = ["topview", "newest", "az"] as const; -export type orderByOptions = typeof orderByOptionsList[number]; +export const manganatoOrderByOptionsList = ["topview", "newest", "az"] as const; +export type manganatoOrderByOptions = typeof manganatoOrderByOptionsList[number]; -export const genreList = { +export const manganatoGenreList = { action: 2, adult: 3, adventure: 4, diff --git a/src/scraper/sites/manga/manganato/Manganato.ts b/src/scraper/sites/manga/manganelo/Manganelo.ts similarity index 91% rename from src/scraper/sites/manga/manganato/Manganato.ts rename to src/scraper/sites/manga/manganelo/Manganelo.ts index d85da89..4d1cd3b 100644 --- a/src/scraper/sites/manga/manganato/Manganato.ts +++ b/src/scraper/sites/manga/manganelo/Manganelo.ts @@ -6,10 +6,9 @@ import { ManganatoManagerUtils } from "./ManganatoManagerUtils"; import { IManganatoFilterParams } from "./ManganatoTypes"; import { ResultSearch } from "../../../../types/search"; -export class Manganato { - readonly url = "https://manganato.com"; - readonly chapURL = "https://manganelo.tv" //chapmanganelo.com //mangakakalot.tv; - readonly name = "manganato"; +export class Manganelo { + private readonly url = "https://manganelo.tv"; //chapmanganelo.com //mangakakalot.tv; + readonly name = "manganelo"; private readonly manager = ManganatoManagerUtils.Instance; private GetMangaDescription(data: cheerio.Root) { @@ -89,14 +88,14 @@ export class Manganato { } async GetMangaInfo(mangaId: string) { - const { data } = await axios.get(`${this.chapURL}/manga/manga-${mangaId}`); + const { data } = await axios.get(`${this.url}/manga/manga-${mangaId}`); const $ = load(data); const manga = new Manga; const title = $("div.panel-story-info > div.story-info-right > h1").text().trim(); const description = this.GetMangaDescription($); - const thumbnail = this.chapURL + $("div.panel-story-info > div.story-info-left > span.info-image > img").attr("src"); + const thumbnail = this.url + $("div.panel-story-info > div.story-info-left > span.info-image > img").attr("src"); const altTitle = $("table > tbody > tr:nth-child(1) > td.table-value > h2").text().trim(); const status = this.GetMangaStatus($); const authors = this.GetMangaAuthors($); @@ -146,7 +145,7 @@ export class Manganato { } async GetMangaChapters(mangaId: string, chapterNumber: number) { - const { data } = await axios.get(`${this.chapURL}/chapter/manga-${mangaId}/chapter-${chapterNumber}`); + const { data } = await axios.get(`${this.url}/chapter/manga-${mangaId}/chapter-${chapterNumber}`); const $ = load(data); const images = this.GetMangaPages($); diff --git a/src/scraper/sites/manga/manganato/managers/ManganatoManager.ts b/src/scraper/sites/manga/manganelo/managers/ManganatoManager.ts similarity index 100% rename from src/scraper/sites/manga/manganato/managers/ManganatoManager.ts rename to src/scraper/sites/manga/manganelo/managers/ManganatoManager.ts diff --git a/src/scraper/sites/manga/manganato/managers/ManganatoURLManager.ts b/src/scraper/sites/manga/manganelo/managers/ManganatoURLManager.ts similarity index 76% rename from src/scraper/sites/manga/manganato/managers/ManganatoURLManager.ts rename to src/scraper/sites/manga/manganelo/managers/ManganatoURLManager.ts index 9f6c625..db9655c 100644 --- a/src/scraper/sites/manga/manganato/managers/ManganatoURLManager.ts +++ b/src/scraper/sites/manga/manganelo/managers/ManganatoURLManager.ts @@ -1,5 +1,5 @@ import { URLSearchParams } from "url"; -import { IManganatoFilterParams, genreList, orderByOptions, orderByOptionsList } from "../ManganatoTypes"; +import { IManganatoFilterParams, manganatoGenreList, manganatoOrderByOptions, manganatoOrderByOptionsList } from "../ManganatoTypes"; import { ManganatoManager } from "./ManganatoManager"; export class ManganatoAdvancedSearchURLManager extends ManganatoManager { @@ -14,8 +14,8 @@ export class ManganatoAdvancedSearchURLManager extends ManganatoManager { let arrGenerated: number[] = []; for (let genre of genresArray) { - if (genreList[genre.toLowerCase()]) - arrGenerated.push(genreList[genre.toLowerCase()]); + if (manganatoGenreList[genre.toLowerCase()]) + arrGenerated.push(manganatoGenreList[genre.toLowerCase()]); else continue; } @@ -28,14 +28,14 @@ export class ManganatoAdvancedSearchURLManager extends ManganatoManager { private processStatus(status: unknown) { return (typeof status === "string" && (status.toLowerCase() === "ongoing" || status.toLowerCase() === "completed")) - ? status - : ""; + ? status + : ""; } private processOrderBy(order: unknown) { - return (typeof order === "string" && orderByOptionsList.includes(order.toLowerCase() as orderByOptions)) - ? order - : ""; + return (typeof order === "string" && manganatoOrderByOptionsList.includes(order.toLowerCase() as manganatoOrderByOptions)) + ? order + : ""; } generate(params: IManganatoFilterParams) { From 436f8b5d675024b9013c31d5e7173950365d0a35 Mon Sep 17 00:00:00 2001 From: barrientosvctor Date: Mon, 15 Jan 2024 16:21:13 -0500 Subject: [PATCH 14/16] style(manganelo): indentation --- src/scraper/sites/manga/manganelo/Manganelo.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/scraper/sites/manga/manganelo/Manganelo.ts b/src/scraper/sites/manga/manganelo/Manganelo.ts index 4d1cd3b..99c6b91 100644 --- a/src/scraper/sites/manga/manganelo/Manganelo.ts +++ b/src/scraper/sites/manga/manganelo/Manganelo.ts @@ -90,7 +90,7 @@ export class Manganelo { async GetMangaInfo(mangaId: string) { const { data } = await axios.get(`${this.url}/manga/manga-${mangaId}`); const $ = load(data); - + const manga = new Manga; const title = $("div.panel-story-info > div.story-info-right > h1").text().trim(); @@ -101,11 +101,11 @@ export class Manganelo { const authors = this.GetMangaAuthors($); const genres = this.GetMangaGenres($); const chapters = $("div.panel-story-chapter-list").find("ul > li.a-h").map((_, element) => { - const chapter = new MangaChapter; - const url = $(element).find("a.chapter-name").attr("href"); - - const chapterId = url.substring(url.lastIndexOf("-") + 1); - + const chapter = new MangaChapter; + const url = $(element).find("a.chapter-name").attr("href"); + + const chapterId = url.substring(url.lastIndexOf("-") + 1); + chapter.id = Number(chapterId); chapter.title = $(element).find("a.chapter-name").text().trim(); chapter.url = `/manga/${this.name}/chapter/${mangaId}?num=${chapterId}`; @@ -151,7 +151,7 @@ export class Manganelo { const images = this.GetMangaPages($); const name = $("body > div.body-site > div:nth-child(1) > div.panel-breadcrumb > a").eq(-1).attr("title") || null; const chapter = new MangaChapter; - + chapter.id = Number(chapterNumber); chapter.title = name; chapter.url = `/manga/${this.name}/chapter/${mangaId}?num=${chapterNumber}`; From ed7647b31f9c76f79a76d8797cccf90fdbe5e767 Mon Sep 17 00:00:00 2001 From: barrientosvctor Date: Mon, 15 Jan 2024 18:06:43 -0500 Subject: [PATCH 15/16] fix(types): manganelo --- src/routes/v1/manga/manganelo/ManganeloRoutes.ts | 5 +++-- src/scraper/sites/manga/manganelo/ManganatoTypes.ts | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/routes/v1/manga/manganelo/ManganeloRoutes.ts b/src/routes/v1/manga/manganelo/ManganeloRoutes.ts index 3e52d0d..d494d99 100644 --- a/src/routes/v1/manga/manganelo/ManganeloRoutes.ts +++ b/src/routes/v1/manga/manganelo/ManganeloRoutes.ts @@ -1,3 +1,4 @@ +import { manganatoOrderByOptionsList } from "@providers/manganelo/ManganatoTypes"; import { Router } from "express"; import { Manganelo } from "../../../../scraper/sites/manga/manganelo/Manganelo"; @@ -14,9 +15,9 @@ router.get(`/manga/${manganelo.name}/title/:id`, async (req, res) => { router.get(`/manga/${manganelo.name}/filter`, async (req, res) => { const result = await manganelo.Filter({ - sts: req.query.status as unknown as string, + sts: req.query.status as unknown as "ongoing" | "completed", genres: req.query.genres as unknown as string, - orby: req.query.order as unknown as string, + orby: req.query.order as unknown as typeof manganatoOrderByOptionsList[number], page: req.query.page as unknown as number }); diff --git a/src/scraper/sites/manga/manganelo/ManganatoTypes.ts b/src/scraper/sites/manga/manganelo/ManganatoTypes.ts index 9110ebf..925d79e 100644 --- a/src/scraper/sites/manga/manganelo/ManganatoTypes.ts +++ b/src/scraper/sites/manga/manganelo/ManganatoTypes.ts @@ -1,20 +1,20 @@ +export const manganatoOrderByOptionsList = ["topview", "newest", "az"] as const; +export type manganatoOrderByOptions = typeof manganatoOrderByOptionsList[number]; + export interface IManganatoFilterParams { /** * Manga status * * Available status: "ongoing", "completed", empty string | null (for both) */ - sts: string; + sts: "ongoing" | "completed"; /** Order by */ - orby: string; + orby: manganatoOrderByOptions; genres: string; /** Results page */ page: number; }; -export const manganatoOrderByOptionsList = ["topview", "newest", "az"] as const; -export type manganatoOrderByOptions = typeof manganatoOrderByOptionsList[number]; - export const manganatoGenreList = { action: 2, adult: 3, From f44bb3a622d5cf0088b619a757bc0907e95bcff7 Mon Sep 17 00:00:00 2001 From: barrientosvctor Date: Mon, 15 Jan 2024 18:07:07 -0500 Subject: [PATCH 16/16] test: manganelo provider --- src/test/Manganelo.spec.ts | 133 +++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 src/test/Manganelo.spec.ts diff --git a/src/test/Manganelo.spec.ts b/src/test/Manganelo.spec.ts new file mode 100644 index 0000000..6353a9f --- /dev/null +++ b/src/test/Manganelo.spec.ts @@ -0,0 +1,133 @@ +import { Manganelo } from '../scraper/sites/manga/manganelo/Manganelo'; +import { IManganatoFilterParams, manganatoGenreList } from '../scraper/sites/manga/manganelo/ManganatoTypes'; + +type ManganeloTestTemplate = { + id: string; + title: string; + status: "ongoing" | "completed"; + nsfw: boolean; +}; + +type ManganeloGenresOptions = keyof typeof manganatoGenreList; + +interface ManganeloFilterTestTemplate extends Omit { + genres: ManganeloGenresOptions[]; +}; + +type ManganeloChapterTestTemplate = { + id: string; + num: number; +}; + +describe('Manganelo', () => { + let manganelo: Manganelo; + + beforeEach(() => { + manganelo = new Manganelo(); + }); + + it('should get manga info successfully', async () => { + + const testsSuites: ManganeloTestTemplate[] = [ + { + id: 'md990312', + nsfw: false, + status: 'ongoing', + title: 'Your Eternal Lies' + }, + { + id: 'he984887', + nsfw: false, + status: 'ongoing', + title: 'The Peerless Sword God' + }, + { + id: 'go983949', + nsfw: false, + status: 'ongoing', + title: 'Bite Into Me' + }, + { + id: 'oj992266', + nsfw: true, + status: 'ongoing', + title: 'Dekiai Osananajimi Ha Watashi No Otto De Stalker!?' + } + ]; + + testsSuites.forEach(async (options) => { + const mangaInfo = await manganelo.GetMangaInfo(options.id); + expect(mangaInfo.title).toStrictEqual(options.title); + + if (mangaInfo.altTitles) + expect(mangaInfo.altTitles.length).toBeGreaterThanOrEqual(1); + + if (mangaInfo.thumbnail && mangaInfo.thumbnail.url) + expect(mangaInfo.thumbnail.url).toContain('.jpg'); + + expect(mangaInfo.status).toStrictEqual(options.status); + expect(mangaInfo.isNSFW).toStrictEqual(options.nsfw); + + if (mangaInfo.genres) + expect(mangaInfo.genres.length).toBeGreaterThanOrEqual(1); + + if (mangaInfo.chapters) + expect(mangaInfo.chapters.length).toBeGreaterThanOrEqual(1); + }); + }); + + it('should filter manga successfully', async () => { + const filterTestsSuites: ManganeloFilterTestTemplate[] = [ + { + genres: ['action'], + orby: 'az', + page: 3, + sts: 'completed' + }, + { + genres: ['drama', 'romance'], + orby: 'newest', + page: 1, + sts: 'ongoing' + } + ]; + + filterTestsSuites.forEach(async (options) => { + const result = await manganelo.Filter({ + genres: options.genres.join(' '), + orby: options.orby, + page: options.page, + sts: options.sts + }); + + expect(result.results.length).toBeGreaterThanOrEqual(1); + }); + }); + + it('should return manga chapters successfully', async () => { + const chapterTestsSuites: ManganeloChapterTestTemplate[] = [ + { + id: 'he984887', + num: 221 + }, + { + id: 'oj992266', + num: 1 + }, + { + id: 'md990312', + num: 79 + }, + { + id: 'go983949', + num: 2 + } + ]; + + chapterTestsSuites.forEach(async (options) => { + const result = await manganelo.GetMangaChapters(options.id, options.num); + + expect(result.images.length).toBeGreaterThanOrEqual(1); + }); + }); +});