import { CheerioAPI, load as parseHTML } from 'cheerio'; import { fetchApi, fetchFile } from '@libs/fetch'; import { FilterTypes, Filters } from '@libs/filterInputs'; import { Plugin } from '@typings/plugin'; interface ChapterEntry { id: number; title: string; date: string; link: string; } class RanobesPlugin implements Plugin.PagePlugin { id = 'ranobes'; name = 'Ranobes'; icon = 'src/en/ranobes/icon.png'; site = 'https://ranobes.top'; filters?: Filters | undefined; version = '1.1.0'; async sleep(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); } parseNovels(loadedCheerio: CheerioAPI) { const novels: Plugin.NovelItem[] = []; loadedCheerio('.short-cont').each((i, el) => { const novelName = loadedCheerio(el).find('h2.title').text(); const novelUrl = loadedCheerio(el).find('h2.title a').attr('href'); const novelCover = loadedCheerio(el) .find('figure') .attr('style') ?.replace(/.*url\((.*?)\)./g, '$1'); if (!novelUrl) return; const novel = { name: novelName, cover: novelCover, path: novelUrl?.replace(this.site, ''), }; novels.push(novel); }); return novels; } parseChapters(data: { chapters: ChapterEntry[] }) { const chapter: Plugin.ChapterItem[] = []; data.chapters.map((item: ChapterEntry) => { chapter.push({ name: item.title, releaseTime: new Date(item.date).toISOString(), path: item.link.replace(this.site, ''), }); }); return chapter.reverse(); } async popularNovels( page: number, { showLatestNovels, filters, }: Plugin.PopularNovelsOptions, ): Promise { let link = `${this.site}/novels/page/${page}/`; const body = await fetchApi(link).then(result => result.text()); const loadedCheerio = parseHTML(body); return this.parseNovels(loadedCheerio); } async parseNovel( novelPath: string, ): Promise { const url = this.site + novelPath; const result = await fetchApi(url); const body = await result.text(); let loadedCheerio = parseHTML(body); const novel: Plugin.SourceNovel & { totalPages: number } = { path: novelPath, name: loadedCheerio('.poster img').attr('alt') || 'Untitled', cover: this.site + loadedCheerio('.poster img').attr('src'), author: loadedCheerio('[itemprop="creator"]').text(), chapters: [], totalPages: 1, }; novel.summary = loadedCheerio('.moreless') .find('br') .replaceWith('\n') .end() .text() .trim(); novel.status = loadedCheerio('li:contains("Status") span').text() === 'Ongoing' ? 'Ongoing' : 'Completed'; novel.genres = loadedCheerio('div#mc-fs-genre').text().trim(); let chapterListUrl = loadedCheerio('.read-continue:last').attr('href'); if (!chapterListUrl?.startsWith('http')) { chapterListUrl = this.site + chapterListUrl; } const chaptersHtmlToString = await fetchApi(chapterListUrl).then(r => r.text(), ); const pageCheerio = parseHTML(chaptersHtmlToString); const json = pageCheerio('#dle-content main').next().html()!; const data = JSON.parse(json.replace('window.__DATA__ =', '')); novel.totalPages = Number(data.pages_count); novel.chapters = this.parseChapters(data); const latestChapterUrl = data.chapters[0].link; const latestChapterName = data.chapters[0].title; const latestChapterDate = data.chapters[0].date; novel.latestChapter = latestChapterUrl ? { path: latestChapterUrl.replace(this.site, ''), name: latestChapterName, releaseTime: new Date(latestChapterDate).toISOString(), } : undefined; return novel; } async parsePage(novelPath: string, page: string): Promise { const pagePath = novelPath.split('-')[0]; const firstUrl = this.site + '/chapters' + pagePath.replace('novels/', ''); const pageUrl = firstUrl + '/page/' + page; const pageBody = await fetchApi(pageUrl).then(r => r.text()); const loadedCheerio = parseHTML(pageBody); const json = loadedCheerio('#dle-content main').next().html()!; const data = JSON.parse(json.replace('window.__DATA__ =', '')); const chapters = this.parseChapters(data); return { chapters, }; } async parseChapter(chapterPath: string): Promise { const result = await fetchApi(this.site + chapterPath); const body = await result.text(); const loadedCheerio = parseHTML(body); const chapterText = loadedCheerio('#arrticle').html() || ''; return chapterText; } async searchNovels( searchTerm: string, page: number, ): Promise { let link = `${this.site}/search/${searchTerm}/page/${page}`; const body = await fetchApi(link).then(r => r.text()); const loadedCheerio = parseHTML(body); return this.parseNovels(loadedCheerio); } async fetchImage(url: string): Promise { return await fetchFile(url); } } export default new RanobesPlugin();