Skip to content

Commit

Permalink
Merge pull request #120 from koikiss-dev/dev
Browse files Browse the repository at this point in the history
Move to main
  • Loading branch information
koikiss-dev authored Jan 15, 2024
2 parents fc0ceaa + 04c9fcb commit 5589a11
Show file tree
Hide file tree
Showing 8 changed files with 462 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 manganelo from "./routes/v1/manga/manganelo/ManganeloRoutes";
import helmet from "helmet";
import cors from 'cors'
import WcoStream from "./routes/v1/anime/wcostream/wcostreamRoutes";
Expand Down Expand Up @@ -45,6 +46,7 @@ app.use(comick);
app.use(inmanga);
app.use(nhentai)
app.use(mangareader);
app.use(manganelo);
/*Manga*/


Expand Down
33 changes: 33 additions & 0 deletions src/routes/v1/manga/manganelo/ManganeloRoutes.ts
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 src/scraper/sites/manga/manganelo/ManganatoManagerUtils.ts
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;
}
}
61 changes: 61 additions & 0 deletions src/scraper/sites/manga/manganelo/ManganatoTypes.ts
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;
163 changes: 163 additions & 0 deletions src/scraper/sites/manga/manganelo/Manganelo.ts
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;
}
}
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 src/scraper/sites/manga/manganelo/managers/ManganatoURLManager.ts
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()}`;
}
}
Loading

2 comments on commit 5589a11

@vercel
Copy link

@vercel vercel bot commented on 5589a11 Jan 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 5589a11 Jan 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.