diff --git a/plugins/indonesian/indowebnovel.ts b/plugins/indonesian/indowebnovel.ts index 2f5e838..788f828 100644 --- a/plugins/indonesian/indowebnovel.ts +++ b/plugins/indonesian/indowebnovel.ts @@ -1,47 +1,55 @@ -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 { defaultCover } from "@libs/defaultCover"; -import { Filters } from "@libs/filterInputs"; +import { Filters, FilterTypes } 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[] = []; + version = "1.1.0"; - 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"); + parseNovels(loadedCheerio:CheerioAPI){ + const novels: Plugin.NovelItem[] = []; - if (!novelUrl || !novelName) return; + loadedCheerio(".flexbox2-item").each((i,el) => { + const novelName = loadedCheerio(el) + .find(".flexbox2-title span") + .first() + .text(); + const novelCover = loadedCheerio(el).find("img").attr("src"); + const novelUrl = loadedCheerio(el) + .find(".flexbox2-content > a") + .attr("href"); - const novel = { + if (!novelUrl) return; + + novels.push({ 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/`; + async popularNovels( + page: number, + { filters }: Plugin.PopularNovelsOptions + ): Promise { + let link = `${this.site}advanced-search/page/${page}/?title=&author=&yearx=`; + link += `&status=${filters.status.value}` + link += `&type=${filters.type.value}`; + link += `&order=${filters.sort.value}`; - const result = await fetchApi(url); + if (filters.lang.value.length) + link += filters.lang.value.map((i) => `&country[]=${i}`).join(""); + if (filters.genre.value.length) + link += filters.genre.value.map((i) => `&genre[]=${i}`).join(""); + + const result = await fetchApi(link); const body = await result.text(); const loadedCheerio = parseHTML(body); @@ -107,13 +115,96 @@ class IndoWebNovel implements Plugin.PluginBase { const body = await result.text(); const loadedCheerio = parseHTML(body); - return this.parseNovels(loadedCheerio, searchTerm) + return this.parseNovels(loadedCheerio) } async fetchImage(url: string): Promise { return fetchFile(url); } + filters = { + status: { + value: "", + label: "Status", + options: [ + { label: "All", value: "" }, + { label: "Ongoing", value: "ongoing" }, + { label: "Completed", value: "completed" }, + ], + type: FilterTypes.Picker, + }, + type: { + value: "", + label: "Type", + options: [ + { label: "All", value: "" }, + { label: "Web Novel", value: "Web+Novel" }, + { label: "Light Novel", value: "Light+Novel" }, + ], + type: FilterTypes.Picker, + }, + sort: { + value: "rating", + label: "Order By", + options: [ + { label: "A-Z", value: "title" }, + { label: "Z-A", value: "titlereverse" }, + { label: "Latest Update", value: "update" }, + { label: "Latest Added", value: "latest" }, + { label: "Popular", value: "popular" }, + { label: "Rating", value: "rating" }, + ], + type: FilterTypes.Picker, + }, + lang: { + value: ['china','jepang','korea','unknown'], + label: "Country", + options: [ + { label: "China", value: "china" }, + { label: "Jepang", value: "jepang" }, + { label: "Korea", value: "korea" }, + { label: "Unknown", value: "unknown" }, + ], + type: FilterTypes.CheckboxGroup, + }, + genre: { + value: [], + label: "Genres", + options: [ + { label: "Action", value: "action" }, + { label: "Adult", value: "adult" }, + { label: "Adventure", value: "adventure" }, + { label: "Comedy", value: "comedy" }, + { label: "Drama", value: "drama" }, + { label: "Ecchi", value: "ecchi" }, + { label: "Fantasy", value: "fantasy" }, + { label: "Gender Bender", value: "gender-bender" }, + { label: "Harem", value: "harem" }, + { label: "Horror", value: "horror" }, + { label: "Josei", value: "josei" }, + { label: "Josei", value: "josei" }, + { label: "Martial Arts", value: "martial-arts" }, + { label: "Mature", value: "mature" }, + { label: "Mecha", value: "mecha" }, + { label: "Mystery", value: "mystery" }, + { label: "Psychological", value: "psychological" }, + { label: "Romance", value: "romance" }, + { label: "School Life", value: "school-life" }, + { label: "Sci-fi", value: "sci-fi" }, + { label: "Seinen", value: "seinen" }, + { label: "Shoujo", value: "shoujo" }, + { label: "Shounen", value: "shounen" }, + { label: "Slice of Life", value: "slice-of-life" }, + { label: "Smut", value: "smut" }, + { label: "Supernatural", value: "supernatural" }, + { label: "Tragedy", value: "tragedy" }, + { label: "Wuxia", value: "wuxia" }, + { label: "Xianxia", value: "xianxia" }, + { label: "Xuanhuan", value: "xuanhuan" } + ], + type: FilterTypes.CheckboxGroup, + }, + } satisfies Filters; } export default new IndoWebNovel(); diff --git a/plugins/indonesian/novelringan.broken.ts b/plugins/indonesian/novelringan.broken.ts deleted file mode 100644 index b86115e..0000000 --- a/plugins/indonesian/novelringan.broken.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { load as parseHTML } from "cheerio"; -import { fetchApi, fetchFile } from "@libs/fetch"; -import { NovelStatus } from "@libs/novelStatus"; -import { Plugin } from "@typings/plugin"; -import { Filters } from "@libs/filterInputs"; - -class NovelRingan implements Plugin.PluginBase { - id = "novelringan.com"; - name = "NovelRingan"; - icon = "src/id/novelringan/icon.png"; - site = "https://novelringan.com/"; - filters?: Filters | undefined; - version = "1.0.0"; - baseUrl = this.site; - coverUriPrefix = "https://i0.wp.com/novelringan.com/wp-content/uploads/"; - - async popularNovels(pageNo: number, options: Plugin.PopularNovelsOptions): Promise { - const url = `${this.baseUrl}/top-novel/page/${pageNo}`; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = parseHTML(body); - - const novels: Plugin.NovelItem[] = []; - - loadedCheerio("article.post").each((idx, ele) => { - const novelName = loadedCheerio(ele) - .find(".entry-title") - .text() - ?.trim(); - const novelCover = - this.coverUriPrefix + loadedCheerio(ele).find("img").attr("data-sxrx"); - const novelUrl = loadedCheerio(ele).find("h2 > a").attr("href"); - - if (!novelUrl) 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(); - - const loadedCheerio = parseHTML(body); - - let novel: Plugin.SourceNovel = { - url, - name: "", - cover: "", - genres: "", - author: "", - status: NovelStatus.Unknown, - summary: "", - chapters: [], - }; - - novel.name = loadedCheerio(".entry-title").text()?.trim(); - novel.cover = - this.coverUriPrefix + - loadedCheerio("img.ts-post-image").attr("data-sxrx"); - novel.summary = loadedCheerio( - "body > div.site-container > div > main > article > div > div.maininfo > span > p" - ).text(); - - let genreSelector = loadedCheerio( - "body > div.site-container > div > main > article > div > div.maininfo > span > ul > li:nth-child(4)" - ).text(); - - novel.genres = genreSelector.includes("Genre") - ? genreSelector.replace("Genre:", "").trim() - : ""; - - let statusSelector = loadedCheerio( - "body > div.site-container > div > main > article > div > div.maininfo > span > ul > li:nth-child(3)" - ).text(); - - novel.status = statusSelector.includes("Status") - ? statusSelector.replace("Status:", "").trim() - : NovelStatus.Unknown; - - let chapters: Plugin.ChapterItem[] = []; - - loadedCheerio(".bxcl > ul > li").each(function () { - const chapterName = loadedCheerio(this).find("a").text(); - const releaseDate = null; - const chapterUrl = loadedCheerio(this).find("a").attr("href"); - - if (!chapterUrl) return; - - const chapter = { - name: chapterName, - releaseTime: releaseDate, - url: chapterUrl, - }; - - chapters.push(chapter); - }); - - novel.chapters = chapters.reverse(); - - return novel; - } - async parseChapter(chapterUrl: string): Promise { - const url = chapterUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = parseHTML(body); - - loadedCheerio('.entry-content div[style="display:none"]').remove(); - - const chapterText = loadedCheerio(".entry-content").html() || ''; - - return chapterText; - } - async searchNovels(searchTerm: string, pageNo: number): Promise { - const url = this.baseUrl + "?s=" + searchTerm; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = parseHTML(body); - - let novels: Plugin.NovelItem[] = []; - - loadedCheerio("article.post").each((idx, ele) => { - const novelName = loadedCheerio(ele).find(".entry-title").text(); - const novelCover = - this.coverUriPrefix + loadedCheerio(ele).find("img").attr("data-sxrx"); - - const novelUrl = loadedCheerio(ele).find("h2 > a").attr("href"); - - if (!novelUrl) return; - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - }); - return novels; - } - async fetchImage(url: string): Promise { - return fetchFile(url); - } - -} - -export default new NovelRingan(); diff --git a/plugins/indonesian/novelringan.ts b/plugins/indonesian/novelringan.ts new file mode 100644 index 0000000..027aa25 --- /dev/null +++ b/plugins/indonesian/novelringan.ts @@ -0,0 +1,261 @@ +import { CheerioAPI, load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Plugin } from "@typings/plugin"; +import { Filters, FilterTypes } from "@libs/filterInputs"; + +class NovelRingan implements Plugin.PluginBase { + id = "novelringan.com"; + name = "NovelRingan"; + icon = "src/id/novelringan/icon.png"; + site = "https://novelringan.com/"; + version = "1.0.0"; + coverUriPrefix = "https://i0.wp.com/novelringan.com/wp-content/uploads/"; + + parseNovels(loadedCheerio:CheerioAPI) { + const novels: Plugin.NovelItem[] = []; + + loadedCheerio("article.post").each((idx, ele) => { + const novelName = loadedCheerio(ele) + .find(".entry-title") + .text() + ?.trim(); + const novelCover = + this.coverUriPrefix + loadedCheerio(ele).find("img").attr("data-sxrx"); + const novelUrl = loadedCheerio(ele).find("h2 > a").attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + path: novelUrl.replace(this.site, ''), + }; + + novels.push(novel); + }); + + return novels; + } + + async popularNovels( + page: number, + { filters }: Plugin.PopularNovelsOptions + ): Promise { + let link = `${this.site}advanced-search/page/${page}/?title`; + link += `&status=${filters.status.value}` + link += `&order=${filters.sort.value}`; + + if (filters.type.value.length) + link += filters.type.value.map((i) => `&type[]=${i}`).join(""); + if (filters.genre.value.length) + link += filters.genre.value.map((i) => `&genre[]=${i}`).join(""); + + + const result = await fetchApi(link); + 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); + + const styletag = Array.from( + ( + loadedCheerio('meta[name=msapplication-TileImage] + style') + .html() + ?.matchAll(/"(.*?)"/g) + ) || [], + m => m[1] + ); + + const novel: Plugin.SourceNovel = { + path: novelPath, + name: styletag[0] || loadedCheerio('.entry-title').text() || "Untitled", + author: styletag[1], + summary: loadedCheerio('.maininfo span p').text(), + chapters: [], + }; + + novel.cover = + this.coverUriPrefix + + loadedCheerio("img.ts-post-image").attr("data-sxrx"); + + loadedCheerio('.maininfo li').each(function() { + let detailName = loadedCheerio(this).find('b').text().trim(); + let detail = loadedCheerio(this).find('b').remove().end().text().trim() + + switch(detailName) { + case "Status:": + novel.status = detail; + break; + case "Genre:": + novel.genres = detail; + break; + } + }) + + const chapter: Plugin.ChapterItem[] = []; + + loadedCheerio(".bxcl > ul > li").each((i,el) => { + const chapterName = loadedCheerio(el).find("a").text(); + const chapterUrl = loadedCheerio(el).find("a").attr("href"); + + if (!chapterUrl) return; + + chapter.push({ + name: chapterName, + path: chapterUrl.replace(this.site, ''), + }); + }); + + novel.chapters = chapter.reverse(); + + return novel; + } + + async parseChapter(chapterPath: string): Promise { + const url = this.site + chapterPath; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + loadedCheerio('.entry-content div[style="display:none"]').remove(); + + const chapterText = loadedCheerio(".entry-content").html() || ''; + + return chapterText; + } + + async searchNovels(searchTerm: string, page: number): Promise { + const url = this.site + "page/" + page + "/?s=" + searchTerm; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + return this.parseNovels(loadedCheerio) + } + + async fetchImage(url: string): Promise { + return fetchFile(url); + } + + filters = { + status: { + value: "", + label: "Status", + options: [ + { label: "All", value: "" }, + { label: "Ongoing", value: "Ongoing" }, + { label: "Completed", value: "Completed" }, + ], + type: FilterTypes.Picker, + }, + sort: { + value: "popular", + label: "Urutkan", + options: [ + { label: "A-Z", value: "title" }, + { label: "Z-A", value: "titlereverse" }, + { label: "Terbarui", value: "update" }, + { label: "Ditambahkan", value: "latest" }, + { label: "Terpopuler", value: "popular" }, + ], + type: FilterTypes.Picker, + }, + type: { + value: [], + label: "Tipe", + options: [ + { label: "Chinese Novel", value: "chinese-novel" }, + { label: "Chinese Web Novel", value: "chinese-web-novel" }, + { label: "Filipino Novel", value: "filipino-novel" }, + { label: "Indonesia Novel", value: "indonesia-novel" }, + { label: "Korean Novel", value: "korean-novel" }, + { label: "Light Novel", value: "light-novel" }, + { label: "Light Novel (CN)", value: "light-novel-cn" }, + { label: "Light Novel (JP)", value: "light-novel-jp" }, + { label: "Light Novel (KR)", value: "light-novel-kr" }, + { label: "Malaysian Novel", value: "malaysian-novel" }, + { label: "Published Novel (CN)", value: "published-novel-cn" }, + { label: "Published Novel (JP)", value: "published-novel-jp" }, + { label: "Published Novel (KR)", value: "published-novel-kr" }, + { label: "Published Novel (TH)", value: "published-novel-th" }, + { label: "Thai Novel", value: "thai-novel" }, + { label: "Vietnamese Novel", value: "vietnamese-novel" }, + { label: "Web Novel", value: "web-novel" }, + { label: "Webnovel", value: "webnovel" }, + ], + type: FilterTypes.CheckboxGroup, + }, + genre: { + value: [], + label: "Genres", + options: [ + { label: "Action", value: "action" }, + { label: "Adult", value: "adult" }, + { label: "Adventure", value: "adventure" }, + { label: "Celebrity", value: "celebrity" }, + { label: "Comedy", value: "comedy" }, + { label: "ction", value: "ction" }, + { label: "Drama", value: "drama" }, + { label: "Eastern", value: "eastern" }, + { label: "Ecchi", value: "ecchi" }, + { label: "Fantasy", value: "fantasy" }, + { label: "Game", value: "game" }, + { label: "Games", value: "games" }, + { label: "Gender Bender", value: "gender-bender" }, + { label: "Harem", value: "harem" }, + { label: "Historical", value: "historical" }, + { label: "Horror", value: "horror" }, + { label: "Isekai", value: "isekai" }, + { label: "Josei", value: "josei" }, + { label: "Life", value: "life" }, + { label: "LitRPG", value: "litrpg" }, + { label: "Magical Realism", value: "magical-realism" }, + { label: "Martial Arts", value: "martial-arts" }, + { label: "Mature", value: "mature" }, + { label: "Mecha", value: "mecha" }, + { label: "Mystery", value: "mystery" }, + { label: "Psychologic", value: "psychologic" }, + { label: "Psychological", value: "psychological" }, + { label: "Recarnation", value: "recarnation" }, + { label: "Reincarnation", value: "reincarnation" }, + { label: "Romance", value: "romance" }, + { label: "School", value: "school" }, + { label: "School Life", value: "school-life" }, + { label: "Sci-fi", value: "sci-fi" }, + { label: "Seinen", value: "seinen" }, + { label: "Shotacon", value: "shotacon" }, + { label: "Shoujo", value: "shoujo" }, + { label: "Shoujo Ai", value: "shoujo-ai" }, + { label: "Shounen", value: "shounen" }, + { label: "Shounen Ai", value: "shounen-ai" }, + { label: "Slice of Life", value: "slice-of-life" }, + { label: "Smut", value: "smut" }, + { label: "Sports", value: "sports" }, + { label: "Supernatural", value: "supernatural" }, + { label: "Tragedy", value: "tragedy" }, + { label: "Urban", value: "urban" }, + { label: "Urban Life", value: "urban-life" }, + { label: "ve names:N/A Genre:Romance", value: "ve-namesn-a-genreromance" }, + { label: "Wuxia", value: "wuxia" }, + { label: "Xianxia", value: "xianxia" }, + { label: "Xuanhuan", value: "xuanhuan" }, + { label: "Yaoi", value: "yaoi" }, + { label: "Yuri", value: "yuri" }, + ], + type: FilterTypes.CheckboxGroup, + }, + } satisfies Filters; +} + +export default new NovelRingan(); diff --git a/plugins/indonesian/sakuranovel.broken.ts b/plugins/indonesian/sakuranovel.broken.ts deleted file mode 100644 index 9d6afff..0000000 --- a/plugins/indonesian/sakuranovel.broken.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { load as parseHTML } from "cheerio"; -import { fetchApi, fetchFile } from "@libs/fetch"; -import { Plugin } from "@typings/plugin"; -import { Filters } from "@libs/filterInputs"; - -class SakuraNovel implements Plugin.PluginBase { - id = "sakura.id"; - name = "SakuraNovel"; - icon = "src/id/sakuranovel/icon.png"; - site = "https://sakuranovel.id/"; - filters?: Filters | undefined; - version = "1.0.0"; - baseUrl = this.site; - - async popularNovels(pageNo: number, options: Plugin.PopularNovelsOptions): Promise { - const url = `${this.baseUrl}advanced-search/page/${pageNo}/?title&author&yearx&status&type&order=rating&country%5B0%5D=china&country%5B1%5D=jepang&country%5B2%5D=unknown`; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = parseHTML(body); - - let novels: Plugin.NovelItem[] = []; - - loadedCheerio(".flexbox2-item").each(function () { - const novelName = loadedCheerio(this) - .find(".flexbox2-title span") - .first() - .text(); - const novelCover = loadedCheerio(this).find("img").attr("src"); - const novelUrl = loadedCheerio(this) - .find(".flexbox2-content > a") - .attr("href"); - - if (!novelUrl) return; - - const novel = { name: novelName, cover: novelCover, url: novelUrl }; - - novels.push(novel); - }); - - return novels; - } - async parseNovelAndChapters(novelUrl: string): Promise { - const result = await fetchApi(novelUrl); - const body = await result.text(); - - let loadedCheerio = parseHTML(body); - - let novel: Plugin.SourceNovel = { - url: novelUrl, - }; - - novel.name = loadedCheerio(".series-title h2").text().trim(); - - novel.cover = loadedCheerio(".series-thumb img").attr("src"); - - loadedCheerio(".series-infolist > li").each(function () { - const detailName = loadedCheerio(this).find("b").text().trim(); - const detail = loadedCheerio(this).find("b").next().text().trim(); - - switch (detailName) { - case "Author": - novel.author = detail; - break; - } - }); - - novel.status = loadedCheerio(".status").text().trim(); - - novel.genres = loadedCheerio(".series-genres") - .children("a") - .map((i, el) => loadedCheerio(el).text()) - .toArray() - .join(","); - - loadedCheerio(".series-synops div").remove(); - novel.summary = loadedCheerio(".series-synops").text().trim(); - - let chapters: Plugin.ChapterItem[] = []; - - loadedCheerio(".series-chapterlist li").each(function () { - const chapterName = loadedCheerio(this) - .find("a span") - .first() - .text() - .replace(/.*?(Chapter.|[0-9])/g, "$1") - .replace(/Bahasa Indonesia/g, "") - .replace(/\s+/g, " ") - .trim(); - - const releaseDate = loadedCheerio(this) - .find("a span") - .first() - .next() - .text(); - 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 result = await fetchApi(chapterUrl); - 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}?s=${searchTerm}`; - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = parseHTML(body); - - let novels: Plugin.NovelItem[] = []; - - loadedCheerio(".flexbox2-item").each(function () { - const novelName = loadedCheerio(this) - .find(".flexbox2-title span") - .first() - .text(); - const novelCover = loadedCheerio(this).find("img").attr("src"); - const novelUrl = loadedCheerio(this) - .find(".flexbox2-content > a") - .attr("href"); - - if (!novelUrl) return; - - const novel = { name: novelName, cover: novelCover, url: novelUrl }; - - novels.push(novel); - }); - - return novels; - } - async fetchImage(url: string): Promise { - return fetchFile(url); - } - -} - -export default new SakuraNovel(); diff --git a/plugins/indonesian/sakuranovel.ts b/plugins/indonesian/sakuranovel.ts new file mode 100644 index 0000000..8957cff --- /dev/null +++ b/plugins/indonesian/sakuranovel.ts @@ -0,0 +1,228 @@ +import { CheerioAPI, load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Plugin } from "@typings/plugin"; +import { Filters, FilterTypes } from "@libs/filterInputs"; + +class SakuraNovel implements Plugin.PluginBase { + id = "sakura.id"; + name = "SakuraNovel"; + icon = "src/id/sakuranovel/icon.png"; + site = "https://sakuranovel.id/"; + version = "1.0.0"; + + parseNovels(loadedCheerio:CheerioAPI){ + const novels: Plugin.NovelItem[] = []; + + loadedCheerio(".flexbox2-item").each((i,el) => { + const novelName = loadedCheerio(el) + .find(".flexbox2-title span") + .first() + .text(); + const novelCover = loadedCheerio(el).find("img").attr("src"); + const novelUrl = loadedCheerio(el) + .find(".flexbox2-content > a") + .attr("href"); + + if (!novelUrl) return; + + novels.push({ + name: novelName, + cover: novelCover, + path: novelUrl.replace(this.site, ''), + }); + }); + + return novels; + } + + async popularNovels( + page: number, + { filters }: Plugin.PopularNovelsOptions + ): Promise { + let link = `${this.site}advanced-search/page/${page}/?title&author&yearx`; + link += `&status=${filters.status.value}` + link += `&type=${filters.type.value}`; + link += `&order=${filters.sort.value}`; + + if (filters.lang.value.length) + link += filters.lang.value.map((i) => `&country[]=${i}`).join(""); + if (filters.genre.value.length) + link += filters.genre.value.map((i) => `&genre[]=${i}`).join(""); + + const result = await fetchApi(link); + 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 h2").text().trim() || "Untitled", + cover: loadedCheerio(".series-thumb img").attr("src"), + author: loadedCheerio(".series-infolist > li b:contains('Author') +").text().trim(), + status: loadedCheerio(".status").text().trim(), + summary: loadedCheerio(".series-synops").text().trim(), + chapters: [] + }; + + novel.genres = loadedCheerio(".series-genres") + .children("a") + .map((i, el) => loadedCheerio(el).text()) + .toArray() + .join(","); + + const chapter: Plugin.ChapterItem[] = []; + + loadedCheerio(".series-chapterlist li").each((i,el) => { + const chapterName = loadedCheerio(el) + .find("a span") + .first() + .text() + .replace(/.*?(Chapter.|[0-9])/g, "$1") + .replace(/Bahasa Indonesia/g, "") + .replace(/\s+/g, " ") + .trim(); + + const releaseDate = loadedCheerio(el) + .find("a span") + .first() + .next() + .text(); + const chapterUrl = loadedCheerio(el).find("a").attr("href"); + + if (!chapterUrl) return; + + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + path: chapterUrl.replace(this.site, ''), + }); + }); + + novel.chapters = chapter.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 divi = loadedCheerio("div:contains('Daftar Isi') +") + .find("div:first") + .attr("class"); + loadedCheerio(`.${divi}`).remove() + const chapterText = loadedCheerio("div:contains('Daftar Isi') +").html() || ''; + + return chapterText; + } + + async searchNovels(searchTerm: string, page: number): Promise { + const url = `${this.site}page/${page}/?s=${searchTerm}`; + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + return this.parseNovels(loadedCheerio); + } + + async fetchImage(url: string): Promise { + return fetchFile(url); + } + + filters = { + status: { + value: "", + label: "Status", + options: [ + { label: "All", value: "" }, + { label: "Ongoing", value: "ongoing" }, + { label: "Completed", value: "completed" }, + ], + type: FilterTypes.Picker, + }, + type: { + value: "", + label: "Type", + options: [ + { label: "All", value: "" }, + { label: "Web Novel", value: "Web+Novel" }, + { label: "Light Novel", value: "Light+Novel" }, + ], + type: FilterTypes.Picker, + }, + sort: { + value: "rating", + label: "Order By", + options: [ + { label: "A-Z", value: "title" }, + { label: "Z-A", value: "titlereverse" }, + { label: "Latest Update", value: "update" }, + { label: "Latest Added", value: "latest" }, + { label: "Popular", value: "popular" }, + { label: "Rating", value: "rating" }, + ], + type: FilterTypes.Picker, + }, + lang: { + value: ['china','jepang','korea','unknown'], + label: "Country", + options: [ + { label: "China", value: "china" }, + { label: "Jepang", value: "jepang" }, + { label: "Korea", value: "korea" }, + { label: "Unknown", value: "unknown" }, + ], + type: FilterTypes.CheckboxGroup, + }, + genre: { + value: [], + label: "Genres", + options: [ + { label: "Action", value: "action" }, + { label: "Adult", value: "adult" }, + { label: "Adventure", value: "adventure" }, + { label: "Comedy", value: "comedy" }, + { label: "Drama", value: "drama" }, + { label: "Ecchi", value: "ecchi" }, + { label: "Fantasy", value: "fantasy" }, + { label: "Gender Bender", value: "gender-bender" }, + { label: "Harem", value: "harem" }, + { label: "Horror", value: "horror" }, + { label: "Josei", value: "josei" }, + { label: "Josei", value: "josei" }, + { label: "Martial Arts", value: "martial-arts" }, + { label: "Mature", value: "mature" }, + { label: "Mecha", value: "mecha" }, + { label: "Mystery", value: "mystery" }, + { label: "Psychological", value: "psychological" }, + { label: "Romance", value: "romance" }, + { label: "School Life", value: "school-life" }, + { label: "Sci-fi", value: "sci-fi" }, + { label: "Seinen", value: "seinen" }, + { label: "Shoujo", value: "shoujo" }, + { label: "Shounen", value: "shounen" }, + { label: "Slice of Life", value: "slice-of-life" }, + { label: "Smut", value: "smut" }, + { label: "Supernatural", value: "supernatural" }, + { label: "Tragedy", value: "tragedy" }, + { label: "Wuxia", value: "wuxia" }, + { label: "Xianxia", value: "xianxia" }, + { label: "Xuanhuan", value: "xuanhuan" } + ], + type: FilterTypes.CheckboxGroup, + }, + } satisfies Filters; +} + +export default new SakuraNovel();