Files
lnreader-plugins/scripts/multisrc/lightnovelwp/template.ts
T
Justin COLLON a45b859182 lightnovelwp & novelDeGlace update (#851)
* lightnovelwp:
 - template accepte version increments
 - daotranslate update url

* novel de glace: - fix date parsing failing for single digit day value
2024-03-23 10:44:21 +07:00

237 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
versionIncrements?: number;
}
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;
const versionIncrements = metadata.options?.versionIncrements || 0;
this.version = `1.0.${4 + versionIncrements}`;
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<CheerioAPI> {
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 <typeof this.filters>): Promise<Plugin.NovelItem[]> {
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<Plugin.SourceNovel> {
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<string> {
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<Plugin.NovelItem[]> {
const url = this.site + "page/" + page + "/?s=" + searchTerm;
const $ = await this.getCheerio(url, true);
return this.parseNovels($);
}
fetchImage = fetchFile;
}