import { CheerioAPI, load } from "cheerio"; import { fetchApi, fetchFile } from "@libs/fetch"; import { Plugin } from "@typings/plugin"; import { NovelStatus } from "@libs/novelStatus"; import { defaultCover } from "@libs/defaultCover"; import { Filters } from "@libs/filterInputs"; interface LightNovelWPOptions { reverseChapters?: boolean; lang?: string; } export interface LightNovelWPMetadata { id: string; sourceSite: string; sourceName: string; options?: LightNovelWPOptions; filters?: any; } class LightNovelWPPlugin implements Plugin.PluginBase { id: string; name: string; icon: string; site: string; version: string; options?: LightNovelWPOptions; filters?: Filters; constructor(metadata: LightNovelWPMetadata) { this.id = metadata.id; this.name = metadata.sourceName; this.icon = `multisrc/lightnovelwp/${metadata.id}.png`; this.site = metadata.sourceSite; this.version = "1.0.4"; this.options = metadata.options; this.filters = metadata.filters satisfies Filters; } getHostname(url: string): string { return url.split("/")[2]; } async getCheerio(url: string, search: boolean): Promise { const r = await fetchApi(url); if (!r.ok && search != true) throw new Error("Could not reach site (" + r.status + ") try to open in webview."); const $ = load(await r.text()); const title = $("title").text().trim(); if (this.getHostname(url) != this.getHostname(r.url) || title == "Bot Verification" || title == "You are being redirected..." || title == "Un instant..." || title == "Just a moment..." || title == "Redirecting...") throw new Error("Captcha error, please open in webview"); return ($); } parseNovels($: CheerioAPI): Plugin.NovelItem[] { const novels: Plugin.NovelItem[] = []; $("div.listupd > article").each((i, elem) => { const novelName = $(elem).find("h2").text(); const image = $(elem).find("img"); const novelUrl = $(elem).find("a").attr("href"); if (novelUrl) { novels.push({ name: novelName, cover: image.attr("data-src") || image.attr("src") || defaultCover, path: novelUrl.replace(this.site, ""), }); } }); return novels; } async popularNovels(pageNo: number, { filters, showLatestNovels }: Plugin.PopularNovelsOptions ): Promise { let url = this.site + "/series/?page=" + pageNo; if (!filters) filters = {}; if (showLatestNovels) url += "&order=latest"; for (const key in filters) { if (typeof filters[key].value === "object") for (const value of filters[key].value as string[]) url += `&${key}=${value}`; else if (filters[key].value) url += `&${key}=${filters[key].value}`; } const $ = await this.getCheerio(url, false); return this.parseNovels($); } async parseNovel(novelPath: string): Promise { const $ = await this.getCheerio(this.site + novelPath, false); const novel: Plugin.SourceNovel = { path: novelPath.replace(this.site, ""), name: "Untitled" }; novel.name = $("h1.entry-title").text().trim(); const image = $("img.wp-post-image"); novel.cover = image.attr("data-src") || image.attr("src") || defaultCover; switch ($("div.sertostat > span").attr("class")?.toLowerCase() || "") { case "completed": novel.status = NovelStatus.Completed; break; case "ongoing": novel.status = NovelStatus.Ongoing; break; case "hiatus": novel.status = NovelStatus.OnHiatus; break; default: novel.status = NovelStatus.Unknown; break; } let details = $("div.serl"); if (!details.length) details = $("div.spe > span"); details.each(function () { const detailName = $(this).contents().first().text().replace(":", "").trim().toLowerCase(); const detail = $(this).contents().last().text().trim().toLowerCase(); switch (detailName) { case "الكاتب": case "author": case "auteur": case "autor": case "yazar": novel.author = detail; break; case "الحالة": case "status": case "statut": case "estado": case "durum": switch (detail) { case "مكتملة": case "completed": case "complété": case "completo": case "completado": case "tamamlandı": novel.status = NovelStatus.Completed; break; case "مستمرة": case "ongoing": case "en cours": case "em andamento": case "en progreso": case "devam ediyor": novel.status = NovelStatus.Ongoing; break; case "متوقفة": case "hiatus": case "en pause": case "hiato": case "pausa": case "pausado": case "duraklatıldı": novel.status = NovelStatus.OnHiatus; break; default: novel.status = NovelStatus.Unknown; break; } break; case "الفنان": case "artist": case "artiste": case "artista": case "çizer": novel.artist = detail; break; } }); let genres = $(".sertogenre") if (!genres.length) genres = $(".genxed") novel.genres = genres .children("a") .map((i, el) => $(el).text()) .toArray() .join(","); let summary = $(".sersys > p").map((i, el) => $(el).text().trim()).toArray(); if (!summary.length) summary = $(".entry-content > p").map((i, el) => $(el).text().trim()).toArray(); novel.summary = summary.join("\n"); const chapters: Plugin.ChapterItem[] = []; $(".eplister li").each((i, elem) => { const chapterName = $(elem).find(".epl-num").text() + " " + $(elem).find(".epl-title").text(); const chapterUrl = $(elem).find("a").attr("href") || ""; const releaseTime = $(elem).find(".epl-date").text().trim(); let isFreeChapter: boolean; switch ($(elem).find(".epl-price").text().trim().toLowerCase()) { case "free": case "gratuit": case "مجاني": case "livre": case "": isFreeChapter = true; break; default: isFreeChapter = false; break; } if (isFreeChapter) chapters.push({ name: chapterName, path: chapterUrl.replace(this.site, ""), releaseTime: releaseTime, }); }); if (this.options?.reverseChapters && chapters.length) chapters.reverse(); novel.chapters = chapters; return novel; } async parseChapter(chapterPath: string): Promise { const $ = await this.getCheerio(this.site + chapterPath, false); if (this.id == "kolnovel") { let ignore = $("article > style").text().trim().split(","); ignore.push(...(ignore.pop()?.match(/^\.\w+/) || [])); ignore.map((tag) => $(`p${tag}`).remove()); } return $(".epcontent p").map((i, el) => $(el)).toArray().join("\n") || ""; } async searchNovels(searchTerm: string, page: number): Promise { const url = this.site + "page/" + page + "/?s=" + searchTerm; const $ = await this.getCheerio(url, true); return this.parseNovels($); } fetchImage = fetchFile; }