diff --git a/icons/src/en/ranobes/icon.png b/icons/src/en/ranobes/icon.png new file mode 100644 index 0000000..55f4b9f Binary files /dev/null and b/icons/src/en/ranobes/icon.png differ diff --git a/plugins/english/novelupdates.ts b/plugins/english/novelupdates.ts index 88ba4fa..5be0dfc 100644 --- a/plugins/english/novelupdates.ts +++ b/plugins/english/novelupdates.ts @@ -6,7 +6,7 @@ import { Plugin } from "@typings/plugin"; class NovelUpdates implements Plugin.PluginBase { id = "novelupdates"; name = "Novel Updates"; - version = "0.5.0"; + version = "0.5.1"; icon = "src/en/novelupdates/icon.png"; site = "https://www.novelupdates.com/"; @@ -90,11 +90,15 @@ class NovelUpdates implements Plugin.PluginBase { const novel: Plugin.SourceNovel = { path: novelPath, name: loadedCheerio(".seriestitlenu").text() || "Untitled", - cover: loadedCheerio(".seriesimg > img").attr("src"), - author: loadedCheerio("#showauthors").text().trim(), + cover: loadedCheerio('.wpb_wrapper img').attr('src'), chapters: [], }; + novel.author = loadedCheerio('#authtag') + .map((i, el) => loadedCheerio(el).text().trim()) + .toArray() + .join(', '); + novel.genres = loadedCheerio("#seriesgenre") .children("a") .map((i, el) => loadedCheerio(el).text()) @@ -180,6 +184,8 @@ class NovelUpdates implements Plugin.PluginBase { let isAnomalously = result.url.toLowerCase().includes('anotivereads'); + let kofi = result.url.toLowerCase().includes('ko-fi'); + let isBlossomTranslation = result.url .toLowerCase() .includes('blossomtranslation'); @@ -248,6 +254,10 @@ class NovelUpdates implements Plugin.PluginBase { chapterText = loadedCheerio('.manga-child-content').html()!; } else if (isAnomalously) { chapterText = loadedCheerio('#comic').html()!; + } else if (kofi) { + chapterText = loadedCheerio('script:contains("shadowDom.innerHTML")') + .html() + ?.match(/shadowDom\.innerHTML \+= '( r.json()); @@ -320,10 +330,9 @@ class NovelUpdates implements Plugin.PluginBase { } async searchNovels(searchTerm: string, page: number): Promise { - const url =`${this.site}page/${page}/?s=${searchTerm}&post_type=seriesplans`; + const url = this.site + '?s=' + searchTerm.replace(/\s+/g, '+') + '&post_type=seriesplans'; const result = await fetchApi(url); const body = await result.text(); - const loadedCheerio = parseHTML(body); return this.parseNovels(loadedCheerio); } diff --git a/plugins/english/pawread.ts b/plugins/english/pawread.ts index a890523..6f6c75f 100644 --- a/plugins/english/pawread.ts +++ b/plugins/english/pawread.ts @@ -1,4 +1,4 @@ -import { CheerioAPI, load, load as parseHTML } from "cheerio"; +import { CheerioAPI, load as parseHTML } from "cheerio"; import { fetchApi, fetchFile } from "@libs/fetch"; import { Plugin } from "@typings/plugin"; import { Filters, FilterTypes } from "@libs/filterInputs"; @@ -6,7 +6,7 @@ import { Filters, FilterTypes } from "@libs/filterInputs"; class PawRead implements Plugin.PluginBase { id = "pawread"; name = "PawRead"; - version = "1.1.0"; + version = "2.0.0"; icon = "src/en/pawread/icon.png"; site = "https://m.pawread.com/"; @@ -15,7 +15,7 @@ class PawRead implements Plugin.PluginBase { loadedCheerio(".list-comic, .itemBox").each((idx, ele) => { loadedCheerio(ele).find('.serialise').remove(); - const novelName = loadedCheerio(ele).find('a').text(); + const novelName = loadedCheerio(ele).find('a').text().trim(); const novelCover = loadedCheerio(ele).find('img').attr('src'); const novelUrl = loadedCheerio(ele).find('a').attr('href')?.slice(1); @@ -55,7 +55,7 @@ class PawRead implements Plugin.PluginBase { link += filters.sort.value; link += `/?page=${page}`; - const body = await fetchApi(link).then((r) => r.text()); + const body = await fetchApi(link).then((r) => r.text()); const loadedCheerio = parseHTML(body); return this.parseNovels(loadedCheerio); @@ -91,16 +91,17 @@ class PawRead implements Plugin.PluginBase { const chapter: Plugin.ChapterItem[] = []; loadedCheerio(".item-box").each((idx, ele) => { - const releaseDate = loadedCheerio(ele).find('span:last').text().trim(); - if (releaseDate === "Advanced Chapter") return; + const smallText = loadedCheerio(ele).find('span:last').text().trim(); + if (smallText === "Advanced Chapter") return; + const releaseDate = smallText.split('.').map(x => Number(x)); const chapterName = loadedCheerio(ele).find("span:first").text().trim(); - const chapterUrl = loadedCheerio(ele).attr("onclick")?.match(/\d+/); + const chapterUrl = loadedCheerio(ele).attr("onclick")?.match(/\d+/)![0]; if (!chapterUrl) return; chapter.push({ name: chapterName, - path: `${novelPath}${chapterUrl[0]}.html`, - releaseTime: new Date(releaseDate).toISOString(), + path: `${novelPath}${chapterUrl}.html`, + releaseTime: new Date(releaseDate[0], releaseDate[1], releaseDate[2]).toISOString(), }); }); diff --git a/plugins/english/ranobes.ts b/plugins/english/ranobes.ts new file mode 100644 index 0000000..13e82e9 --- /dev/null +++ b/plugins/english/ranobes.ts @@ -0,0 +1,184 @@ +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.0.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); + + 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 body = await fetchApi(firstUrl).then(r => r.text()); + + let loadedCheerio = parseHTML(body); + let json = loadedCheerio('#dle-content main').next().html()!; + let data = JSON.parse(json.replace('window.__DATA__ =', '')); + + const latestChapterUrl = data.chapters[0].link; + const latestChapterName = data.chapters[0].title; + const latestChapterDate = data.chapters[0].date; + + const latestChapter: Plugin.ChapterItem | undefined = latestChapterUrl ? { + path: latestChapterUrl.replace(this.site, ''), + name: latestChapterName, + releaseTime: new Date(latestChapterDate).toISOString(), + } : undefined + + await this.sleep(1000); + + const pageBody = await fetchApi(pageUrl).then(r => r.text()); + loadedCheerio = parseHTML(pageBody); + json = loadedCheerio('#dle-content main').next().html()!; + data = JSON.parse(json.replace('window.__DATA__ =', '')); + const chapters = this.parseChapters(data); + return { + chapters, + latestChapter + } + } + + 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(); diff --git a/plugins/indonesian/indowebnovel.broken.ts b/plugins/indonesian/indowebnovel.broken.ts deleted file mode 100644 index 0126e06..0000000 --- a/plugins/indonesian/indowebnovel.broken.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { load as parseHTML } from "cheerio"; -import { fetchApi, fetchFile } from "@libs/fetch"; -import { Plugin } from "@typings/plugin"; -import { defaultCover } from "@libs/defaultCover"; -import { Filters } from "@libs/filterInputs"; - -class IndoWebNovel implements Plugin.PluginBase { - id = "IDWN.id"; - name = "IndoWebNovel"; - icon = "src/id/indowebnovel/icon.png"; - site = "https://indowebnovel.id/id/"; - filters?: Filters | undefined; - version = "1.0.0"; - baseUrl = this.site - async popularNovels(pageNo: number, options: Plugin.PopularNovelsOptions): Promise { - const url = `${this.baseUrl}daftar-novel/`; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = parseHTML(body); - - let novels: Plugin.NovelItem[] = []; - - loadedCheerio(".novellist-blc li").each(function () { - const novelName = loadedCheerio(this).find("a").text(); - const novelCover = defaultCover; - const novelUrl = loadedCheerio(this).find("a").attr("href"); - - if (!novelUrl || !novelName) return; - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - }); - - return novels; - } - async parseNovelAndChapters(novelUrl: string): Promise { - const url = novelUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = parseHTML(body); - - let novel: Plugin.SourceNovel = { - url, - name: "", - cover: "", - author: "", - status: "", - genres: "", - summary: "", - chapters: [], - }; - - novel.name = loadedCheerio(".series-title").text().trim(); - - novel.cover = loadedCheerio(".series-thumb > img").attr("src"); - - novel.summary = loadedCheerio(".series-synops").text().trim(); - - novel.status = loadedCheerio(".status").text().trim(); - - const genres: string[] = []; - - loadedCheerio(".series-genres").each(function () { - genres.push(loadedCheerio(this).find("a").text().trim()); - }); - - novel.genres = genres.join(","); - - let chapters: Plugin.ChapterItem[] = []; - - loadedCheerio(".series-chapterlist li").each(function () { - const chapterName = loadedCheerio(this).find("a").text().trim(); - const releaseDate = null; - const chapterUrl = loadedCheerio(this).find("a").attr("href"); - - if (!chapterUrl) return; - - chapters.push({ - name: chapterName, - releaseTime: releaseDate, - url: chapterUrl, - }); - }); - - novel.chapters = chapters.reverse(); - - return novel; - } - async parseChapter(chapterUrl: string): Promise { - const url = chapterUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = parseHTML(body); - - const chapterText = loadedCheerio(".readerss").html() || ''; - - return chapterText; - } - async searchNovels(searchTerm: string, pageNo: number): Promise { - const url = `${this.baseUrl}daftar-novel/`; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = parseHTML(body); - - let novels: Plugin.NovelItem[] = []; - - loadedCheerio(".novellist-blc li").each(function () { - const novelName = loadedCheerio(this).find("a").text(); - const novelCover = defaultCover; - const novelUrl = loadedCheerio(this).find("a").attr("href"); - - if (!novelUrl) return; - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - if (novelName.toLowerCase().includes(searchTerm.toLowerCase())) { - novels.push(novel); - } - }); - - return novels; - } - async fetchImage(url: string): Promise { - return fetchFile(url); - } - -} - -export default new IndoWebNovel(); diff --git a/plugins/indonesian/indowebnovel.ts b/plugins/indonesian/indowebnovel.ts new file mode 100644 index 0000000..2f5e838 --- /dev/null +++ b/plugins/indonesian/indowebnovel.ts @@ -0,0 +1,119 @@ +import { CheerioAPI, load, load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Plugin } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { Filters } from "@libs/filterInputs"; + +class IndoWebNovel implements Plugin.PluginBase { + id = "IDWN.id"; + name = "IndoWebNovel"; + icon = "src/id/indowebnovel/icon.png"; + site = "https://indowebnovel.id/id/"; + filters?: Filters | undefined; + version = "1.0.0"; + parseNovels(loadedCheerio:CheerioAPI, searchTerm?: string) { + let novels: Plugin.NovelItem[] = []; + + loadedCheerio(".novellist-blc li").each((i,el) => { + const novelName = loadedCheerio(el).find("a").text(); + const novelCover = defaultCover; + const novelUrl = loadedCheerio(el).find("a").attr("href"); + + if (!novelUrl || !novelName) return; + + const novel = { + name: novelName, + cover: novelCover, + path: novelUrl.replace(this.site, ''), + }; + + novels.push(novel); + }); + + if (searchTerm) { + novels = novels.filter((novel) => + novel.name.toLowerCase().includes(searchTerm.toLowerCase()) + ); + } + return novels; + }; + + async popularNovels(pageNo: number, options: Plugin.PopularNovelsOptions): Promise { + const url = `${this.site}daftar-novel/`; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + return this.parseNovels(loadedCheerio) + } + + async parseNovel(novelPath: string): Promise { + const result = await fetchApi(this.site + novelPath); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + loadedCheerio(".series-synops div").remove(); + + const novel: Plugin.SourceNovel = { + path: novelPath, + name: loadedCheerio(".series-title").text().trim() || "Untitled", + cover: loadedCheerio(".series-thumb img").attr("src"), + author: loadedCheerio("ul.series-infolist b:contains('Author') +").text().trim(), + status: loadedCheerio(".status").text().trim(), + summary: loadedCheerio(".series-synops").text().trim(), + chapters: [], + }; + + novel.genres = loadedCheerio(".series-genres a") + .map((i,el) => loadedCheerio(el).text().trim()) + .toArray() + .join(','); + + const chapters: Plugin.ChapterItem[] = []; + + loadedCheerio(".series-chapterlist li").each((i,el) => { + const chapterName = loadedCheerio(el).find("a").text().trim(); + const chapterUrl = loadedCheerio(el).find("a").attr("href"); + + if (!chapterUrl) return; + + chapters.push({ + name: chapterName, + path: chapterUrl.replace(this.site, ''), + }); + }); + + novel.chapters = chapters.reverse(); + + return novel; + } + + async parseChapter(chapterPath: string): Promise { + const result = await fetchApi(this.site + chapterPath); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + const chapterText = loadedCheerio(".readerss").html() || ''; + + return chapterText; + } + + async searchNovels(searchTerm: string, pageNo: number): Promise { + const url = `${this.site}daftar-novel/`; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + return this.parseNovels(loadedCheerio, searchTerm) + } + + async fetchImage(url: string): Promise { + return fetchFile(url); + } + +} + +export default new IndoWebNovel();