-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #120 from koikiss-dev/dev
Move to main
- Loading branch information
Showing
8 changed files
with
462 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { manganatoOrderByOptionsList } from "@providers/manganelo/ManganatoTypes"; | ||
import { Router } from "express"; | ||
import { Manganelo } from "../../../../scraper/sites/manga/manganelo/Manganelo"; | ||
|
||
const router = Router(); | ||
const manganelo = new Manganelo(); | ||
|
||
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/${manganelo.name}/filter`, async (req, res) => { | ||
const result = await manganelo.Filter({ | ||
sts: req.query.status as unknown as "ongoing" | "completed", | ||
genres: req.query.genres as unknown as string, | ||
orby: req.query.order as unknown as typeof manganatoOrderByOptionsList[number], | ||
page: req.query.page as unknown as number | ||
}); | ||
|
||
return res.status(200).send(result); | ||
}) | ||
|
||
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); | ||
}); | ||
|
||
export default router; |
12 changes: 12 additions & 0 deletions
12
src/scraper/sites/manga/manganelo/ManganatoManagerUtils.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
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: "ongoing" | "completed"; | ||
/** Order by */ | ||
orby: manganatoOrderByOptions; | ||
genres: string; | ||
/** Results page */ | ||
page: number; | ||
}; | ||
|
||
export const manganatoGenreList = { | ||
action: 2, | ||
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
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 Manganelo { | ||
private readonly url = "https://manganelo.tv"; //chapmanganelo.com //mangakakalot.tv; | ||
readonly name = "manganelo"; | ||
private readonly manager = ManganatoManagerUtils.Instance; | ||
|
||
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(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; | ||
|
||
return data("div.container-chapter-reader > img").map((_, element) => data(element).attr("data-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.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.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($); | ||
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); | ||
|
||
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(genres); | ||
|
||
return manga; | ||
} | ||
|
||
async Filter(params: IManganatoFilterParams) { | ||
const url = this.manager.url.generate(params); | ||
|
||
const { data } = await axios.get(url); | ||
const $ = load(data); | ||
|
||
const mangaResultSearch = new ResultSearch<IMangaResult>(); | ||
mangaResultSearch.results = this.GetMangaSearchResults($); | ||
|
||
return mangaResultSearch; | ||
} | ||
|
||
async GetMangaChapters(mangaId: string, chapterNumber: number) { | ||
const { data } = await axios.get(`${this.url}/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}`; | ||
chapter.number = Number(chapterNumber); | ||
chapter.images = images; | ||
|
||
return chapter; | ||
} | ||
} |
3 changes: 3 additions & 0 deletions
3
src/scraper/sites/manga/manganelo/managers/ManganatoManager.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export abstract class ManganatoManager { | ||
abstract generate(item: unknown, ...args: unknown[]): unknown; | ||
} |
55 changes: 55 additions & 0 deletions
55
src/scraper/sites/manga/manganelo/managers/ManganatoURLManager.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { URLSearchParams } from "url"; | ||
import { IManganatoFilterParams, manganatoGenreList, manganatoOrderByOptions, manganatoOrderByOptionsList } 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 (manganatoGenreList[genre.toLowerCase()]) | ||
arrGenerated.push(manganatoGenreList[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" && manganatoOrderByOptionsList.includes(order.toLowerCase() as manganatoOrderByOptions)) | ||
? 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()}`; | ||
} | ||
} |
Oops, something went wrong.
5589a11
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
jimov-api-hf2p – ./
jimov-api-hf2p-git-main-posadasjorge71-gmailcom.vercel.app
jimov-api-hf2p-posadasjorge71-gmailcom.vercel.app
jimov-api-hf2p.vercel.app
5589a11
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
jimov-api – ./
jimov-api.vercel.app
jimov-api-posadasjorge71-gmailcom.vercel.app
jimov-api-git-main-posadasjorge71-gmailcom.vercel.app