From aee3126e6c43fd493a9c45040019e034c5f2062b Mon Sep 17 00:00:00 2001 From: Rajarshee Chatterjee Date: Tue, 3 May 2022 12:55:58 +0530 Subject: [PATCH] feat(source): Add MTNovel (Closes LNReader/lnreader-sources#197) --- src/sources/en/mtnovel.ts | 185 +++++++++++++++++++++++++++++++++++ src/sources/sourceManager.ts | 2 + src/sources/sources.json | 7 ++ 3 files changed, 194 insertions(+) create mode 100644 src/sources/en/mtnovel.ts diff --git a/src/sources/en/mtnovel.ts b/src/sources/en/mtnovel.ts new file mode 100644 index 00000000..2a2355cf --- /dev/null +++ b/src/sources/en/mtnovel.ts @@ -0,0 +1,185 @@ +import * as cheerio from 'cheerio'; +import { + SourceChapter, + SourceChapterItem, + SourceNovel, + SourceNovelItem, +} from '../types'; + +const sourceId = 120; +const sourceName = 'MT Novel'; +const baseUrl = 'https://mtnovel.com'; + +const popularNovels = async (page: number) => { + let totalPages = 22; + const url = `${baseUrl}/most-popular?page=${page}`; + + const result = await fetch(url); + const body = await result.text(); + + const loadedCheerio = cheerio.load(body); + + let novels: SourceNovelItem[] = []; + + loadedCheerio('.list-truyen .row').each(function () { + const novelName = loadedCheerio(this).find('h3.truyen-title > a').text(); + const novelCover = + baseUrl + loadedCheerio(this).find('img.cover').attr('src'); + const novelUrl = + baseUrl + loadedCheerio(this).find('h3.truyen-title > a').attr('href'); + + const novel = { + sourceId, + novelUrl, + novelName, + novelCover, + }; + + novels.push(novel); + }); + + return { totalPages, novels }; +}; + +const parseNovelAndChapters = async (novelUrl: string) => { + const url = novelUrl; + + const result = await fetch(url); + const body = await result.text(); + + let loadedCheerio = cheerio.load(body); + + let novel: SourceNovel = { + sourceId, + sourceName, + url: novelUrl, + novelUrl, + }; + + novel.novelName = loadedCheerio('div.book > img').attr('alt'); + + novel.novelCover = baseUrl + loadedCheerio('div.book > img').attr('src'); + + novel.summary = loadedCheerio('div.desc-text').text().trim(); + + novel.author = loadedCheerio('div.info > div > h3') + .filter(function () { + return loadedCheerio(this).text().trim() === 'Author:'; + }) + .siblings() + .text(); + + novel.genre = loadedCheerio('div.info > div') + .filter(function () { + return loadedCheerio(this).find('h3').text().trim() === 'Genre:'; + }) + .text() + .replace('Genre:', ''); + + novel.status = loadedCheerio('div.info > div > h3') + .filter(function () { + return loadedCheerio(this).text().trim() === 'Status:'; + }) + .next() + .text(); + + const novelId = loadedCheerio('#rating').attr('data-novel-id'); + + const getChapters = async (id: string) => { + const chapterListUrl = baseUrl + '/ajax/chapter-option?novelId=' + id; + + const data = await fetch(chapterListUrl); + const chapters = await data.text(); + + loadedCheerio = cheerio.load(chapters); + + let novelChapters: SourceChapterItem[] = []; + + loadedCheerio('select > option').each(function () { + let chapterName = loadedCheerio(this).text(); + let releaseDate = null; + let chapterUrl = loadedCheerio(this) + .attr('value') + ?.replace(`/${novelUrl}`, ''); + + if (chapterUrl) { + novelChapters.push({ + chapterName, + releaseDate, + chapterUrl, + }); + } + }); + + return novelChapters; + }; + + if (novelId) { + novel.chapters = await getChapters(novelId); + } + + return novel; +}; + +const parseChapter = async (novelUrl: string, chapterUrl: string) => { + const url = baseUrl + chapterUrl; + + const result = await fetch(url); + const body = await result.text(); + + const loadedCheerio = cheerio.load(body); + + const chapterName = loadedCheerio('.chapter-title').attr('title'); + const chapterText = loadedCheerio('#chapter-content').html() || ''; + + const chapter: SourceChapter = { + sourceId, + novelUrl, + chapterUrl, + chapterName, + chapterText, + }; + + return chapter; +}; + +const searchNovels = async (searchTerm: string) => { + const url = `${baseUrl}/search?keyword=${searchTerm}`; + + const result = await fetch(url); + const body = await result.text(); + + const loadedCheerio = cheerio.load(body); + + let novels: SourceNovelItem[] = []; + + loadedCheerio('div.col-truyen-main > div.list-truyen > .row').each( + function () { + const novelUrl = + baseUrl + loadedCheerio(this).find('h3.truyen-title > a').attr('href'); + + const novelName = loadedCheerio(this).find('h3.truyen-title > a').text(); + const novelCover = baseUrl + loadedCheerio(this).find('img').attr('src'); + + if (novelUrl) { + novels.push({ + sourceId, + novelUrl, + novelName, + novelCover, + }); + } + }, + ); + + return novels; +}; + +const MTNovelScraper = { + popularNovels, + parseNovelAndChapters, + parseChapter, + searchNovels, +}; + +export default MTNovelScraper; diff --git a/src/sources/sourceManager.ts b/src/sources/sourceManager.ts index 9c15693f..3de2a281 100644 --- a/src/sources/sourceManager.ts +++ b/src/sources/sourceManager.ts @@ -119,6 +119,7 @@ import FreeNovelUpdatesScraper from './en/freenovelupdates'; import NovelsCafeScraper from './en/NovelsCafe'; import LightNovelReaderScraper from './en/LightNovelReader'; import HakoLightNovelScraper from './vi/HakoLightNovel'; +import MTNovelScraper from './en/mtnovel'; interface PopularNovelsResponse { totalPages: number; @@ -252,6 +253,7 @@ export const sourceManager = (sourceId: number): Scraper => { 117: JaomixScraper, 118: RulateScraper, 119: RanobeRFScraper, + 120: MTNovelScraper, }; return scrapers[sourceId]; diff --git a/src/sources/sources.json b/src/sources/sources.json index da9c4221..e2f5cbbf 100644 --- a/src/sources/sources.json +++ b/src/sources/sources.json @@ -803,5 +803,12 @@ "url": "https://ранобэ.рф", "lang": "Russian", "icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/ru/ranoberf/icon.png?raw=true" + }, + { + "sourceId": 120, + "url": "https://mtnovel.com/", + "sourceName": "MT Novel", + "icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/allnovelfull/icon.png?raw=true", + "lang": "English" } ]