Beta phase (#801)

* beta host

* Refactor: url -> path (#757)

* refactor: url -> path

* clean up

* fix: json branch

* PagePlugin (#769)

* paginated novel support

* fix: truyenfull

* fix: tunovel, remove pageList

* parsePage support for webtest (#773)

* add parsePage api

* add parsePage UI

* add totalPages

* (FR src) Beta plugin & Added filters (#775)

* noveldeglace:
 - added filters
 - added latest
 - defaultCover fallback
 - change id to noveldeglace.com
 - added complementary infos in summary
 - added cover artist
 - chapters are now parts
 - show volume number for multi volume novels
 - better search
 - fix search page number
 - refactored for plugin beta
 - releaseDate as Date.toString()
 - separted parseNovels
 - separated getCheerio
 - loadedCheerio => $ for shorter lines & simpler test on web

* chireads:
 - fix different parsing for mobile version
 - added filters
 - added author
 - added status
 - added released date
 - refactored for plugin beta
 - separted parseNovels
 - separated getCheerio
 - loadedCheerio => $ for shorter lines & simpler test on web

* wpmangastream => lightnovelwp rework & new sources (#776)

* Added sources:
 - Knoxt (EN)
 - Baca Light Novel (ID)
 - AllNovelRead (ES)
 - DaoTranslate (EN)
 - جنة الروايات (AR)
 - Universal Novel (EN)
 - NovelTR (TR)
old sources auto-generated:
 - GenesisTls (EN)
 - NobleMTL (EN)
wpmangastream => lightnovelwp:
 - added doc
 - added filters in a separte file
 - semi-automatic filter generator
 - made it compatible with version 1.0.X and 1.1.X versions of the theme
 - loadedCheerio => $
 - icons in "/icons/lightnovel/id.png"
 - code cleanup

* removed auto-implemented sources

* Added README.md to lightnovelwp

* removed ai generated text for kolnovel

* added novelsknight

* added centralNovel

* pandamtl accual name

* adding folder plugin/arabic

* Fix: Vietnamese plugins (#777)

* fix: lightnovelvn

* add nettruyen

* add truyenchu

* fix: truyenconect

* Fix: Migrate plugins (#771)

* Add novel.ovh (#756)

* fix

* test

* fix

* date fix + master merge (#761)

* date fix

* Added Brazilian Portuguese E-Novel (#758)

* fixed single digit dates (#767)

Co-authored-by: NaddH1 <naderhorani@hotmail.com>

* fix

* test

* fix order

* Update renovels.ts

* require_chapters

* Delete plugins/english/genesistl.broken.ts

* Update novelTL.ts

---------

Co-authored-by: NaddH1 <naderhorani@hotmail.com>

* (src update) ENG (#772)

* allnovelfull

* bestlightnovel

* divinedaolibrary

* earlynovel

* lightnovelpub

* L

* M

* novelbin

* novelfull

* novelbuddy

* N S

* novel updates

* async sleep

* rename file

* pagePlugin

* Feat: minify js dir (#781)

* Multisrc: HotNovelPub (#784)

* HotNovelPub

* fix search

* Squashed commit of the following:

commit 3b759c68eddc06c0adfb959f775de069a85adca7
Author: nyagami <hoangquan05112002@gmail.com>
Date:   Sat Feb 17 20:33:47 2024 +0700

    Feat: minify js dir (#781)

* jsmin changed to terser

* test

* why I did it.

* test

* Update ranobelib.ts

* fix page

* Update ranobelib.ts

* updated modules

* Update terser.json

Co-authored-by: nyagami <hoangquan05112002@gmail.com>

* Update scripts/multisrc/hotnovelpub/template.ts

Co-authored-by: nyagami <hoangquan05112002@gmail.com>

* terser api

---------

Co-authored-by: nyagami <hoangquan05112002@gmail.com>

* Added sources & fix sources (#787)

* lightnovelwp:
 - fix author name
 - added more localisation of strings
 - README typo
 - error on !r.ok new message

* noveldeglace:
 - fix chapterNumber for parts
 - better dates
 - better search

* Added sources:
 - White Moonlight Novels (EN)
 - System Translation (EN)

* Resized centralnovel.png

* Add Faqwiki.us source (#792)

* Add Faqwiki.us source (beta branch)

* Fix Path, Fix Chapter Parsing, Fix Novel Title

* search novel path fix

* fix chapter parsing. some chapter in this site store their whole text in 1-4 <p>,

* Add WTR-LAB Source (#797)

* Add WTR-LAB Source (Beta Branch)

* Add other language from WTR-LAB source

* change lang to sourceLang

* remove id es tr lang from WTR-LAB

* Update plugins/english/wtrlab.ts

agree

Co-authored-by: nyagami <hoangquan05112002@gmail.com>

* Update plugins/english/wtrlab.ts

agree

Co-authored-by: nyagami <hoangquan05112002@gmail.com>

---------

Co-authored-by: nyagami <hoangquan05112002@gmail.com>

* revert: scripts to master

---------

Co-authored-by: Justin COLLON <48163201+error7404@users.noreply.github.com>
Co-authored-by: Rider21 <58046032+Rider21@users.noreply.github.com>
Co-authored-by: NaddH1 <naderhorani@hotmail.com>
Co-authored-by: K1ngfish3r <trulyihu@gmail.com>
Co-authored-by: bregassa <bregas.sat1@gmail.com>
This commit is contained in:
nyagami
2024-02-25 14:52:37 +07:00
committed by GitHub
parent 2b9c8f7d2a
commit d818d1758f
147 changed files with 10278 additions and 5758 deletions
+1 -2
View File
@@ -50,14 +50,13 @@ const run = async () => {
const sources = fs
.readdirSync(__dirname)
.filter((name) =>
fs.lstatSync(path.join(__dirname, name)).isDirectory()
fs.lstatSync(path.join(__dirname, name)).isDirectory() && !name.endsWith('.broken')
);
for (let name of sources) {
const success = await generate(name);
if (success) console.log(`[${name}] OK`);
}
console.log('Generating: .js files');
};
run();
@@ -0,0 +1,83 @@
import * as fs from "fs";
import * as cheerio from "cheerio";
import * as path from "path";
import list from "./sources.json";
import { HotNovelPubMetadata } from "./template";
async function getFilters(sources: HotNovelPubMetadata) {
const filters: any = {
sort: {
type: "Picker",
label: "Order",
value: "hot",
options: [],
},
category: {
type: "Picker",
label: "category",
value: "",
options: [{ label: "NONE", value: "" }],
},
};
const body = await fetch(sources.sourceSite).then((res) => res.text());
const $: cheerio.CheerioAPI = cheerio.load(body);
$(".new-update").remove();
$("section > div").each((i, el) => {
const id = $(el).find('a[class="see-all"]').attr("href");
if (id) {
filters.sort.options.push({
label: $(el).find('[class="section-title"]').text().trim(),
value: id.split("/").pop(),
});
}
});
filters.category.label = $(".category-title").text().trim();
const apiSite = sources.sourceSite.replace("://", "://api.");
const jsonRaw = await fetch(apiSite + "/categories", {
headers: {
lang: sources.options?.lang || "en",
},
});
const json = (await jsonRaw.json()) as response;
if (json.data?.length) {
json.data
.sort((a, b) => a.name.localeCompare(b.name))
.forEach((category) =>
filters.category.options.push({
label: category.name,
value: category.slug,
}),
);
}
return filters;
}
interface response {
status: number;
message: string;
data?: DataEntity[];
}
interface DataEntity {
id: number;
name: string;
slug: string;
}
async function start() {
const result = [];
for (const sources of list) {
console.log("updating the filters in", sources.sourceName);
const NewFilters = await getFilters(sources as any);
sources.filters = NewFilters;
result.push(sources);
}
fs.writeFileSync(
path.join(__dirname, "sources.json"),
JSON.stringify(result, null, 2),
);
}
start();
+58
View File
@@ -0,0 +1,58 @@
import { FilterTypes, Filters } from "../../../libs/filterInputs";
import { ScrpitGeneratorFunction } from "../generate";
import list from "./sources.json";
import { HotNovelPubMetadata } from "./template";
import { readFileSync } from "fs";
import path from "path";
const lang: { [key: string]: string } = {
en: "english",
ru: "russian",
es: "spanish",
pt: "portuguese",
th: "turkish",
};
export const generateAll: ScrpitGeneratorFunction = function (name) {
return list
.map<HotNovelPubMetadata>((p) => {
const filters: Filters = {};
for (const k in p.filters) {
const f = p.filters[k as keyof typeof p.filters];
if (f) {
filters[k] = {
...f,
type: FilterTypes.Picker,
};
}
}
return { ...p, filters };
})
.map((metadata: HotNovelPubMetadata) => {
console.log(`[${name}]: Generating`, metadata.id);
return generator(metadata);
});
};
const generator = function generator(metadata: HotNovelPubMetadata) {
const HotNovelPubTemplate = readFileSync(
path.join(__dirname, "template.ts"), {
encoding: "utf-8",
},
);
const pluginScript = `
${HotNovelPubTemplate}
const plugin = new HotNovelPubPlugin(${JSON.stringify(metadata).replace(
/"type":"([^"]+)"/g,
'"type":FilterTypes.$1',
)});
export default plugin;
`.trim();
return {
lang: lang[metadata?.options?.lang || "en"] || "english",
filename: metadata.sourceName,
pluginScript,
};
};
+387
View File
@@ -0,0 +1,387 @@
[
{
"id": "hotnovelpub",
"sourceSite": "https://hotnovelpub.com",
"sourceName": "HotNovelPub",
"filters": {
"sort": {
"type": "Picker",
"label": "Order",
"value": "hot",
"options": [
{
"label": "Hot story",
"value": "hot"
},
{
"label": "New story",
"value": "new"
},
{
"label": "Full story",
"value": "full"
}
]
},
"category": {
"type": "Picker",
"label": "Category",
"value": "",
"options": [
{
"label": "NONE",
"value": ""
},
{
"label": "Adult Romance",
"value": "adult-romance"
},
{
"label": "Fantasy",
"value": "fantasy"
},
{
"label": "LGBTQ+",
"value": "lgbtq"
},
{
"label": "Mystery",
"value": "mystery"
},
{
"label": "New Adult",
"value": "new-adult"
},
{
"label": "Paranormal",
"value": "paranormal"
},
{
"label": "Romance",
"value": "romance"
},
{
"label": "Steamy Stories",
"value": "steamy-stories"
},
{
"label": "Urban",
"value": "urban"
},
{
"label": "Werewolf/Vampire",
"value": "werewolfvampire"
}
]
}
}
},
{
"id": "eznovels",
"sourceSite": "https://eznovels.com",
"sourceName": "EzNovels",
"filters": {
"sort": {
"type": "Picker",
"label": "Order",
"value": "hot",
"options": [
{
"label": "Горячая история",
"value": "hot"
},
{
"label": "Новая история",
"value": "new"
},
{
"label": "Полный рассказ",
"value": "full"
}
]
},
"category": {
"type": "Picker",
"label": "Категория",
"value": "",
"options": [
{
"label": "NONE",
"value": ""
},
{
"label": "Ведьмы",
"value": "vedmy"
},
{
"label": "Городское фэнтези",
"value": "gorodskoe-fentezi"
},
{
"label": "Городской роман",
"value": "gorodskoj-roman"
},
{
"label": "Любовное фэнтези",
"value": "lyubovnoe-fentezi"
},
{
"label": "Оборотни",
"value": "oborotni"
},
{
"label": "Сверхъестественное",
"value": "sverhuestestvennoe"
},
{
"label": "Современный любовный роман",
"value": "sovremennyj-lyubovnyj-roman"
},
{
"label": "Триллеры",
"value": "trillery"
},
{
"label": "Фэнтези",
"value": "fentezi"
},
{
"label": "Эротика",
"value": "erotika"
}
]
}
},
"options": {
"lang": "ru"
}
},
{
"id": "lightnoveldaily",
"sourceSite": "https://lightnoveldaily.com",
"sourceName": "LightNovelDaily",
"filters": {
"sort": {
"type": "Picker",
"label": "Order",
"value": "hot",
"options": [
{
"label": "Historia caliente",
"value": "hot"
},
{
"label": "Nuevas cuentos",
"value": "new"
},
{
"label": "Historia completa",
"value": "full"
}
]
},
"category": {
"type": "Picker",
"label": "Categoría",
"value": "",
"options": [
{
"label": "NONE",
"value": ""
},
{
"label": "Acción/Aventura",
"value": "accinaventura"
},
{
"label": "Fantasía",
"value": "fantasa"
},
{
"label": "Ficción adolescente",
"value": "ficcin-adolescente"
},
{
"label": "Hombre lobo/Vampiro",
"value": "hombre-lobovampiro"
},
{
"label": "LGBTQ+",
"value": "lgbtq"
},
{
"label": "Mafia",
"value": "mafia"
},
{
"label": "Nuevo Adulto",
"value": "nuevo-adulto"
},
{
"label": "Pornografía",
"value": "pornografa"
},
{
"label": "Romántico",
"value": "romntico"
},
{
"label": "Urbano",
"value": "urbano"
}
]
}
},
"options": {
"lang": "es"
}
},
{
"id": "lanovels",
"sourceSite": "https://lanovels.com",
"sourceName": "LaNovels",
"filters": {
"sort": {
"type": "Picker",
"label": "Order",
"value": "hot",
"options": [
{
"label": "História quente",
"value": "hot"
},
{
"label": "Nova estória",
"value": "new"
},
{
"label": "História completa",
"value": "full"
}
]
},
"category": {
"type": "Picker",
"label": "Categoria",
"value": "",
"options": [
{
"label": "NONE",
"value": ""
},
{
"label": "Aventura",
"value": "aventura"
},
{
"label": "Erótico",
"value": "erotico"
},
{
"label": "Fantasia",
"value": "fantasia"
},
{
"label": "Ficção adolescente",
"value": "ficcao-adolescente"
},
{
"label": "Ficção científica",
"value": "ficcao-cientifica"
},
{
"label": "Lobisomens/Vampiros",
"value": "lobisomens-vampiros"
},
{
"label": "Mistério",
"value": "misterio"
},
{
"label": "Paranormal",
"value": "paranormal"
},
{
"label": "Romance",
"value": "romance"
},
{
"label": "Suspense/Terror",
"value": "suspense-terror"
}
]
}
},
"options": {
"lang": "pt"
}
},
{
"id": "thnovels",
"sourceSite": "https://thnovels.com",
"sourceName": "ThNovels",
"filters": {
"sort": {
"type": "Picker",
"label": "Order",
"value": "hot",
"options": [
{
"label": "เรื่องร้อน",
"value": "hot"
},
{
"label": "เรื่องใหม่",
"value": "new"
},
{
"label": "เรื่องเต็ม",
"value": "full"
}
]
},
"category": {
"type": "Picker",
"label": "หมวดหมู่",
"value": "",
"options": [
{
"label": "NONE",
"value": ""
},
{
"label": "ใช้ชีวิต",
"value": "ใช้ชีวิต"
},
{
"label": "นิยายY",
"value": "นิยายy"
},
{
"label": "นิยายสำหรับผู้ใหญ่",
"value": "นิยายสำหรับผู้ใหญ่"
},
{
"label": "ประวัติศาสตร์",
"value": "ประวัติศาสตร์"
},
{
"label": "แฟนตาซี",
"value": "แฟนตาซี"
},
{
"label": "มนุษย์หมาป่าแวมไพร์",
"value": "มนุษย์หมาป่าแวมไพร์"
},
{
"label": "โรแมนซ์",
"value": "โรแมนซ์"
}
]
}
},
"options": {
"lang": "th"
}
}
]
+279
View File
@@ -0,0 +1,279 @@
import { fetchFile, fetchApi } from "@libs/fetch";
import { Filters, FilterTypes } from "@libs/filterInputs";
import { Plugin } from "@typings/plugin";
import { NovelStatus } from "@libs/novelStatus";
import { load as parseHTML } from "cheerio";
import dayjs from "dayjs";
export interface HotNovelPubMetadata {
id: string;
sourceSite: string;
sourceName: string;
filters?: Filters;
options?: HotNovelPubOptions;
}
interface HotNovelPubOptions {
lang?: string;
}
class HotNovelPubPlugin implements Plugin.PluginBase {
id: string;
name: string;
icon: string;
site: string;
apiSite: string;
version: string;
filters?: Filters;
lang: string;
constructor(metadata: HotNovelPubMetadata) {
this.id = metadata.id;
this.name = metadata.sourceName;
this.icon = `multisrc/hotnovelpub/icons/${metadata.id}.png`;
this.site = metadata.sourceSite;
this.apiSite = metadata.sourceSite.replace("://", "://api.");
this.version = "1.0.0";
this.filters = metadata.filters;
this.lang = metadata.options?.lang || "en";
}
async popularNovels(
pageNo: number,
{ filters, showLatestNovels }: Plugin.PopularNovelsOptions,
): Promise<Plugin.NovelItem[]> {
let url = this.apiSite + "/books/";
url += showLatestNovels ? "new" : filters?.sort?.value || "hot";
if (filters?.category?.value) {
url = this.apiSite + "/category/" + filters.category.value;
}
url += "/?page=" + (pageNo - 1) + "&limit=20";
const result = await fetchApi(url, {
headers: {
lang: this.lang,
},
});
const json = (await result.json()) as responseNovels;
const novels: Plugin.NovelItem[] = [];
if (json.status && json.data.books.data?.length) {
json.data.books.data.forEach((novel) =>
novels.push({
name: novel.name,
cover: this.site + novel.image,
path: "/" + novel.slug,
}),
);
}
return novels;
}
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
const result = await fetchApi(this.apiSite + "/book" + novelPath, {
headers: {
lang: this.lang,
},
});
const json = (await result.json()) as responseNovel;
const novel: Plugin.SourceNovel = {
name: json.data.book.name,
path: novelPath,
cover: this.site + json.data.book.image,
summary: json.data.book.authorize.description,
author: json.data.book.authorize.name,
status:
json.data.book.status === "updating"
? NovelStatus.Ongoing
: NovelStatus.Completed,
};
if (json.data.tags.tags_name?.length) {
novel.genres = json.data.tags.tags_name.join(",");
}
if (json.data.chapters?.length) {
const chapters: Plugin.ChapterItem[] = [];
json.data.chapters.forEach((chapter, chapterIndex) =>
chapters.push({
name: chapter.title,
path: "/" + chapter.slug,
releaseTime: undefined,
chapterNumber: (chapter.index || chapterIndex) + 1,
}),
);
novel.chapters = chapters;
}
return novel;
}
async parseChapter(chapterPath: string): Promise<string> {
const body = await fetchApi(this.site + chapterPath).then((res) => res.text());
const loadedCheerio = parseHTML(body);
let chapterText = loadedCheerio("#content-item").html() || "";
if (chapterText) {
const result = await fetchApi(
this.site + "/server/getContent?slug=" + chapterPath,
);
const json = (await result.json()) as ChapterType;
if (json.data) {
chapterText += json.data
.map((item) => "<p>" + item + "</p>")
.join("")
.replace(/\n/g, "</p><p>")
.replace(/\s/g, " ");
}
}
return chapterText.replace(/\.copy right hot novel pub/g, "");
}
async searchNovels(
searchTerm: string,
pageNo: number,
): Promise<Plugin.NovelItem[]> {
const result = await fetchApi(this.apiSite + "/search", {
headers: {
"Content-Type": "application/json;charset=utf-8",
Referer: this.site,
Origin: this.site,
lang: this.lang,
},
method: "POST",
body: JSON.stringify({ key_search: searchTerm }),
});
const json = (await result.json()) as responseSearch;
const novels: Plugin.NovelItem[] = [];
if (json.status && json.data.books?.length) {
json.data.books.forEach((novel) =>
novels.push({
name: novel.name,
cover: undefined,
path: "/" + novel.slug,
}),
);
}
return novels;
}
fetchImage = fetchFile;
}
interface responseNovels {
status: number;
message: string;
data: Data;
}
interface Data {
category?: Category;
books: Books;
}
interface Category {
id: number;
name: string;
description: string;
slug: string;
createdAt: string;
updatedAt: string;
countryId: number;
}
interface Books {
total: number;
pages_count: number;
data?: DataEntity[];
}
interface DataEntity {
id: number;
name: string;
view: number;
status: string;
image: string;
slug: string;
categories?: CategoriesEntity[] | null;
source?: null;
}
interface CategoriesEntity {
id: number;
name: string;
slug?: string;
}
interface responseNovel {
status: number;
message: string;
data: Data1;
}
interface Data1 {
book: Book1;
tags: Tags;
chapters?: ChaptersEntity[];
}
interface Book1 {
id: number;
name: string;
description: string;
view: number;
rate: number;
status: string;
image: string;
createdAt: string;
chapterCount: number;
crawlStt: string;
rateCount: number;
slug: string;
categories?: CategoriesEntity[] | null;
authorize: Authorize;
updated_at: string;
source?: null;
idSource?: null;
prize?: null;
}
interface Authorize {
id: number;
name: string;
description: string;
avatar: string;
slug: string;
}
interface Tags {
tags_name?: string[] | null;
tags?: CategoriesEntity[] | null;
}
interface ChaptersEntity {
id: number;
title: string;
slug: string;
index: number;
}
interface ChapterType {
type: string;
data?: string[];
}
interface responseSearch {
status: number;
message: string;
data: Data2;
}
interface Data2 {
books?: BooksEntity[] | null;
authorizes?: AuthorizesEntityOrCategoriesEntity[] | null;
categories?: AuthorizesEntityOrCategoriesEntity[] | null;
}
interface BooksEntity {
id: number;
name: string;
slug: string;
}
interface AuthorizesEntityOrCategoriesEntity {
id: number;
name: string;
slug: string;
}
+22 -14
View File
@@ -31,7 +31,10 @@ class IfreedomPlugin implements Plugin.PluginBase {
async popularNovels(
page: number,
{ filters, showLatestNovels }: Plugin.PopularNovelsOptions<typeof this.filters>,
{
filters,
showLatestNovels,
}: Plugin.PopularNovelsOptions<typeof this.filters>,
): Promise<Plugin.NovelItem[]> {
let url = this.site + "/vse-knigi/?sort=" +
(showLatestNovels ? "По дате обновления" : filters?.sort?.value || "По рейтингу");
@@ -53,20 +56,21 @@ class IfreedomPlugin implements Plugin.PluginBase {
.map((index, element) => ({
name: loadedCheerio(element).attr("title") || "",
cover: loadedCheerio(element).find("img").attr("src"),
url: loadedCheerio(element).attr("href") || "",
path:
loadedCheerio(element).attr("href")?.replace?.(this.site, "") || "",
}))
.get()
.filter((novel) => novel.name && novel.url);
.filter((novel) => novel.name && novel.path);
return novels;
}
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
const body = await fetchApi(novelUrl).then((res) => res.text());
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
const body = await fetchApi(this.site + novelPath).then((res) => res.text());
const loadedCheerio = parseHTML(body);
const novel: Plugin.SourceNovel = {
url: novelUrl,
path: novelPath,
name: loadedCheerio(".entry-title").text(),
cover: loadedCheerio(".img-ranobe > img").attr("src"),
summary: loadedCheerio('meta[name="description"]').attr("content"),
@@ -105,12 +109,16 @@ class IfreedomPlugin implements Plugin.PluginBase {
const name = loadedCheerio(element).find("a").text();
const url = loadedCheerio(element).find("a").attr("href");
if (!loadedCheerio(element).find("label.buy-ranobe").length && name && url) {
const releaseDate = loadedCheerio(element).find("div.li-col2-ranobe").text().trim();
chapters.push({
const releaseDate = loadedCheerio(element)
.find("div.li-col2-ranobe")
.text()
.trim();
chapters.push({
name,
url,
path: url.replace(this.site, ""),
releaseTime: this.parseDate(releaseDate),
chapterNumber: totalChapters - chapterIndex
chapterNumber: totalChapters - chapterIndex,
});
}
});
@@ -119,8 +127,8 @@ class IfreedomPlugin implements Plugin.PluginBase {
return novel;
}
async parseChapter(chapterUrl: string): Promise<string> {
const body = await fetchApi(chapterUrl).then((res) => res.text());
async parseChapter(chapterPath: string): Promise<string> {
const body = await fetchApi(this.site + chapterPath).then((res) => res.text());
const loadedCheerio = parseHTML(body);
loadedCheerio(".entry-content img").each((index, element) => {
@@ -153,10 +161,10 @@ class IfreedomPlugin implements Plugin.PluginBase {
.map((index, element) => ({
name: loadedCheerio(element).attr("title") || "",
cover: loadedCheerio(element).find("img").attr("src"),
url: loadedCheerio(element).attr("href") || "",
path: loadedCheerio(element).attr("href")?.replace?.(this.site, "") || "",
}))
.get()
.filter((novel) => novel.name && novel.url);
.filter((novel) => novel.name && novel.path);
return novels;
}
+32
View File
@@ -0,0 +1,32 @@
# Compatiblity
### This generator is for most sites that uses the LightNovel WordPress Theme: https://themesia.com/lightnovel-wordpress-theme/
it should work for all sites that uses this theme (highest version tested: 1.1.5)
to know the name and version of the theme you can enter the url in : [WP Theme Detector](https://www.wpthemedetector.com)
(it sometimes hallucinates for version 1.0.X as 1.0) \
or you can check the version by adding "/wp-content/themes/lightnovel/style.css"
to the site url and check the version in the file
## ADD A NEW SOURCE
To add a new source you need to add it to sources.json:
- id: the id of the source (something unique)
- sourceName: the name of the source (the name that will be displayed in the app)
- sourceSite: the site url
- options: the options of the source
- lang: the language of the source (default: "English") (check that the language
exists in the languages (check folder names in "plugins/"))
- reverseChapter: if the chapters are in reverse order
(if the first chapter of the list is 1 set it to false)
To add an icon to the source you need to find the icon of the site \
(try the favicon of the site (https://site.com/favicon.ico) most of the times it redirects you to an image named something like "cropped-site-32x32.png" try to access "cropped-site.png" or "site.png" if that did not work you can try to access "https://site.com/wp-json/" at the end of this very long file there should be a "site_icon_url" value
) (don't forget to convert it to png)
and add it to the folder "icons/multisrc/lightnovelwp" named as the id of the source
To add filters to a source you need to run the script "get_filters.ts" \
(./node_modules/.bin/ts-node scripts/multisrc/lightnovelwp/get_filters.ts
(if you are in the root of the project) (and you have run "npm install" before))
and follow the instructions (url is easier and faster but sometimes it doesn't work)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,78 @@
{
"filters": {
"genre[]": {
"type": "Checkbox",
"label": "Genre",
"value": [],
"options": [
{ "label": "Action", "value": "action" },
{ "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": "Historical", "value": "historical" },
{ "label": "Horror", "value": "horror" },
{ "label": "Isekai", "value": "isekai" },
{ "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": "Shoujo Ai", "value": "shoujo-ai" },
{ "label": "Shounen", "value": "shounen" },
{ "label": "Shounen Ai", "value": "shounen-ai" },
{ "label": "Slice of Life", "value": "slice-of-life" },
{ "label": "Sports", "value": "sports" },
{ "label": "Supernatural", "value": "supernatural" },
{ "label": "Tragedy", "value": "tragedy" },
{ "label": "Wuxia", "value": "wuxia" },
{ "label": "Xianxia", "value": "xianxia" },
{ "label": "Xuanhuan", "value": "xuanhuan" },
{ "label": "Yaoi", "value": "yaoi" },
{ "label": "Yuri", "value": "yuri" }
]
},
"type[]": {
"type": "Checkbox",
"label": "Tipe",
"value": [],
"options": [
{ "label": "", "value": "" },
{ "label": "Novel", "value": "novel" }
]
},
"status": {
"type": "Picker",
"label": "Status",
"value": "",
"options": [
{ "label": "All", "value": "" },
{ "label": "Ongoing", "value": "ongoing" },
{ "label": "Hiatus", "value": "hiatus" },
{ "label": "Completed", "value": "completed" }
]
},
"order": {
"type": "Urutkan",
"label": "Order",
"value": "",
"options": [
{ "label": "Default", "value": "" },
{ "label": "A-Z", "value": "title" },
{ "label": "Z-A", "value": "titlereverse" },
{ "label": "Baru diperbarui", "value": "update" },
{ "label": "Baru ditambahkan", "value": "latest" },
{ "label": "Terpopuler", "value": "popular" }
]
}
}
}
@@ -0,0 +1,300 @@
{
"filters": {
"genre[]": {
"type": "Checkbox",
"label": "Gênero",
"value": [],
"options": [
{
"label": "Ação",
"value": "acao"
},
{
"label": "Action",
"value": "action"
},
{
"label": "Adulto",
"value": "adulto"
},
{
"label": "Adventure",
"value": "adventure"
},
{
"label": "Artes Marciais",
"value": "artes-marciais"
},
{
"label": "Aventura",
"value": "aventura"
},
{
"label": "Comédia",
"value": "comedia"
},
{
"label": "Cotidiano",
"value": "cotidiano"
},
{
"label": "Cultivo",
"value": "cultivo"
},
{
"label": "Drama",
"value": "drama"
},
{
"label": "Ecchi",
"value": "ecchi"
},
{
"label": "Escolar",
"value": "escolar"
},
{
"label": "Esportes",
"value": "esportes"
},
{
"label": "Evolução",
"value": "evolucao"
},
{
"label": "Fantasia",
"value": "fantasia"
},
{
"label": "Fantasy",
"value": "fantasy"
},
{
"label": "Ficção Científica",
"value": "ficcao-cientifica"
},
{
"label": "Gender Bender",
"value": "gender-bender"
},
{
"label": "Harém",
"value": "harem"
},
{
"label": "Histórico",
"value": "historico"
},
{
"label": "Isekai",
"value": "isekai"
},
{
"label": "Josei",
"value": "josei"
},
{
"label": "Magia",
"value": "magia"
},
{
"label": "Mecha",
"value": "mecha"
},
{
"label": "Medieval",
"value": "medieval"
},
{
"label": "Mistério",
"value": "misterio"
},
{
"label": "Mitologia",
"value": "mitologia"
},
{
"label": "Monstros",
"value": "monstros"
},
{
"label": "Pet",
"value": "pet"
},
{
"label": "Protagonista Feminina",
"value": "protagonista-feminina"
},
{
"label": "Protagonista Maligno",
"value": "protagonista-maligno"
},
{
"label": "Psicológico",
"value": "psicologico"
},
{
"label": "Psychological",
"value": "psychological"
},
{
"label": "Reencarnação",
"value": "reencarnacao"
},
{
"label": "Romance",
"value": "romance"
},
{
"label": "Seinen",
"value": "seinen"
},
{
"label": "Shounen",
"value": "shounen"
},
{
"label": "Shounen BL",
"value": "shounen-bl"
},
{
"label": "Sistema",
"value": "sistema"
},
{
"label": "Sistema de Jogo",
"value": "sistema-de-jogo"
},
{
"label": "Slice of Life",
"value": "slice-of-life"
},
{
"label": "Sobrenatural",
"value": "sobrenatural"
},
{
"label": "Supernatural",
"value": "supernatural"
},
{
"label": "Terror",
"value": "terror"
},
{
"label": "Tragédia",
"value": "tragedia"
},
{
"label": "Transmigração",
"value": "transmigracao"
},
{
"label": "Vida Escolar",
"value": "vida-escolar"
},
{
"label": "VRMMO",
"value": "vrmmo"
},
{
"label": "Wuxia",
"value": "wuxia"
},
{
"label": "Xianxia",
"value": "xianxia"
},
{
"label": "Xuanhuan",
"value": "xuanhuan"
}
]
},
"type[]": {
"type": "Checkbox",
"label": "Tipo",
"value": [],
"options": [
{
"label": "Light Novel",
"value": "light-novel"
},
{
"label": "Novel Chinesa",
"value": "novel-chinesa"
},
{
"label": "Novel Coreana",
"value": "novel-coreana"
},
{
"label": "Novel Japonesa",
"value": "novel-japonesa"
},
{
"label": "Novel Ocidental",
"value": "novel-ocidental"
},
{
"label": "Webnovel",
"value": "webnovel"
}
]
},
"status": {
"type": "Picker",
"label": "Status",
"value": "",
"options": [
{
"label": "Todos",
"value": ""
},
{
"label": "Em andamento",
"value": "em andamento"
},
{
"label": "Hiato",
"value": "hiato"
},
{
"label": "Completo",
"value": "completo"
}
]
},
"order": {
"type": "Picker",
"label": "Ordenar por",
"value": "",
"options": [
{
"label": "Padrão",
"value": ""
},
{
"label": "A-Z",
"value": "title"
},
{
"label": "Z-A",
"value": "titlereverse"
},
{
"label": "Últ. Att",
"value": "update"
},
{
"label": "Últ. Add",
"value": "latest"
},
{
"label": "Populares",
"value": "popular"
}
]
}
}
}
@@ -0,0 +1,308 @@
{
"filters": {
"genre[]": {
"type": "Checkbox",
"label": "Genre",
"value": [],
"options": [
{
"label": "Action",
"value": "action"
},
{
"label": "Adult",
"value": "adult"
},
{
"label": "Adventure",
"value": "adventure"
},
{
"label": "Billionaire",
"value": "billionaire"
},
{
"label": "Comedy",
"value": "comedy"
},
{
"label": "Contemporary",
"value": "contemporary"
},
{
"label": "Drama",
"value": "drama"
},
{
"label": "Family",
"value": "family"
},
{
"label": "Fantasy",
"value": "fantasy"
},
{
"label": "Growth",
"value": "growth"
},
{
"label": "Harem",
"value": "harem"
},
{
"label": "Hidden Identity",
"value": "hidden-identity"
},
{
"label": "Historical",
"value": "historical"
},
{
"label": "Horror",
"value": "horror"
},
{
"label": "Isekai",
"value": "isekai"
},
{
"label": "Korean",
"value": "korean"
},
{
"label": "Magic",
"value": "magic"
},
{
"label": "Manhwa",
"value": "manhwa"
},
{
"label": "Marriage",
"value": "marriage"
},
{
"label": "Martial Arts",
"value": "martial-arts"
},
{
"label": "Mature",
"value": "mature"
},
{
"label": "Mecha",
"value": "mecha"
},
{
"label": "Murim",
"value": "murim"
},
{
"label": "Mystery",
"value": "mystery"
},
{
"label": "Overpowered",
"value": "overpowered"
},
{
"label": "Psychological",
"value": "psychological"
},
{
"label": "Regression",
"value": "regression"
},
{
"label": "Reincarnation",
"value": "reincarnation"
},
{
"label": "Revenge",
"value": "revenge"
},
{
"label": "Rich",
"value": "rich"
},
{
"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": "Son-in-Law",
"value": "son-in-law"
},
{
"label": "Supernatural",
"value": "supernatural"
},
{
"label": "Superpower",
"value": "superpower"
},
{
"label": "Tragedy",
"value": "tragedy"
},
{
"label": "Urban",
"value": "urban"
},
{
"label": "Virtual Game",
"value": "virtual-game"
},
{
"label": "Warrior",
"value": "warrior"
},
{
"label": "Webtoons",
"value": "webtoons"
},
{
"label": "Wuxia",
"value": "wuxia"
},
{
"label": "Xuanhuan",
"value": "xuanhuan"
}
]
},
"type[]": {
"type": "Checkbox",
"label": "Type",
"value": [],
"options": [
{
"label": "Action",
"value": "action"
},
{
"label": "Adventure",
"value": "adventure"
},
{
"label": "Chinese Novel",
"value": "chinese-novel"
},
{
"label": "Fantasy",
"value": "fantasy"
},
{
"label": "Korean Novel",
"value": "korean-novel"
},
{
"label": "Light Novel (KR)",
"value": "light-novel-kr"
},
{
"label": "Martial Arts",
"value": "martial-arts"
},
{
"label": "Published Novel (KR)",
"value": "published-novel-kr"
},
{
"label": "Romance",
"value": "romance"
},
{
"label": "Supernatural",
"value": "supernatural"
},
{
"label": "Web Novel",
"value": "web-novel"
}
]
},
"status": {
"type": "Picker",
"label": "Status",
"value": "",
"options": [
{
"label": "All",
"value": ""
},
{
"label": "Ongoing",
"value": "ongoing"
},
{
"label": "Hiatus",
"value": "hiatus"
},
{
"label": "Completed",
"value": "completed"
}
]
},
"order": {
"type": "Picker",
"label": "Order by",
"value": "",
"options": [
{
"label": "Default",
"value": ""
},
{
"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"
}
]
}
}
}
@@ -0,0 +1,240 @@
{
"filters": {
"genre[]": {
"type": "Checkbox",
"label": "Genre",
"value": [],
"options": [
{
"label": "Academy",
"value": "academy"
},
{
"label": "Action",
"value": "action"
},
{
"label": "Adult",
"value": "adult"
},
{
"label": "Adventure",
"value": "adventure"
},
{
"label": "Another World",
"value": "another-world"
},
{
"label": "Comdey",
"value": "comdey"
},
{
"label": "Comedy",
"value": "comedy"
},
{
"label": "Dark Fantasy",
"value": "dark-fantasy"
},
{
"label": "Drama",
"value": "drama"
},
{
"label": "Fantasy",
"value": "fantasy"
},
{
"label": "Fantasy Fusion",
"value": "fantasy-fusion"
},
{
"label": "Harem",
"value": "harem"
},
{
"label": "Historical",
"value": "historical"
},
{
"label": "Horror",
"value": "horror"
},
{
"label": "Hunter",
"value": "hunter"
},
{
"label": "Light Novel",
"value": "light-novel"
},
{
"label": "Martial Arts",
"value": "martial-arts"
},
{
"label": "Mature",
"value": "mature"
},
{
"label": "Misunderstanding",
"value": "misunderstanding"
},
{
"label": "Modern",
"value": "modern"
},
{
"label": "Modern Fantasy",
"value": "modern-fantasy"
},
{
"label": "Munchkin",
"value": "munchkin"
},
{
"label": "Murim",
"value": "murim"
},
{
"label": "mystery",
"value": "mystery"
},
{
"label": "No Harem",
"value": "no-harem"
},
{
"label": "NO NTR",
"value": "no-ntr"
},
{
"label": "obsession",
"value": "obsession"
},
{
"label": "Possession",
"value": "possession"
},
{
"label": "Psychological",
"value": "psychological"
},
{
"label": "Regression",
"value": "regression"
},
{
"label": "Regret",
"value": "regret"
},
{
"label": "reincarnation",
"value": "reincarnation"
},
{
"label": "Romance",
"value": "romance"
},
{
"label": "School Life",
"value": "school-life"
},
{
"label": "Seinen",
"value": "seinen"
},
{
"label": "Slice of life",
"value": "slice-of-life"
},
{
"label": "Supernatural",
"value": "supernatural"
},
{
"label": "Tragedy",
"value": "tragedy"
},
{
"label": "Transmigrated to Game",
"value": "transmigrated-to-game"
},
{
"label": "Transmigration",
"value": "transmigration"
}
]
},
"type[]": {
"type": "Checkbox",
"label": "Type",
"value": [],
"options": [
{
"label": "korean novel",
"value": "korean-novel"
},
{
"label": "Web Novel",
"value": "web-novel"
}
]
},
"status": {
"type": "Picker",
"label": "Status",
"value": "",
"options": [
{
"label": "All",
"value": ""
},
{
"label": "Ongoing",
"value": "ongoing"
},
{
"label": "Hiatus",
"value": "hiatus"
},
{
"label": "Completed",
"value": "completed"
}
]
},
"order": {
"type": "Picker",
"label": "Order by",
"value": "",
"options": [
{
"label": "Default",
"value": ""
},
{
"label": "A-Z",
"value": "title"
},
{
"label": "Z-A",
"value": "titlereverse"
},
{
"label": "Latest Update",
"value": "update"
},
{
"label": "Latest Added",
"value": "latest"
},
{
"label": "Popular",
"value": "popular"
}
]
}
}
}
@@ -0,0 +1,424 @@
{
"filters": {
"genre[]": {
"type": "Checkbox",
"label": "Genre",
"value": [],
"options": [
{
"label": "Action",
"value": "action"
},
{
"label": "Adult",
"value": "adult"
},
{
"label": "Adventure",
"value": "adventure"
},
{
"label": "Ancient times",
"value": "ancient-times"
},
{
"label": "BL",
"value": "bl"
},
{
"label": "Campus",
"value": "campus"
},
{
"label": "carefree protagonist",
"value": "carefree-protagonist"
},
{
"label": "celebrity",
"value": "celebrity"
},
{
"label": "Chinese novel",
"value": "chinese-novel"
},
{
"label": "Comedy",
"value": "comedy"
},
{
"label": "Cooking",
"value": "cooking"
},
{
"label": "Drama",
"value": "drama"
},
{
"label": "Entertaiment circle",
"value": "entertaiment-circle"
},
{
"label": "Entertainment circle",
"value": "entertainment-circle"
},
{
"label": "Fantasy",
"value": "fantasy"
},
{
"label": "Fiction",
"value": "fiction"
},
{
"label": "futuristic setting",
"value": "futuristic-setting"
},
{
"label": "Gaming",
"value": "gaming"
},
{
"label": "Gender Bender",
"value": "gender-bender"
},
{
"label": "General",
"value": "general"
},
{
"label": "GL",
"value": "gl"
},
{
"label": "Harem",
"value": "harem"
},
{
"label": "historic love",
"value": "historic-love"
},
{
"label": "Historical",
"value": "historical"
},
{
"label": "Horror",
"value": "horror"
},
{
"label": "humor",
"value": "humor"
},
{
"label": "Idol",
"value": "idol"
},
{
"label": "infrastructure",
"value": "infrastructure"
},
{
"label": "interstellar",
"value": "interstellar"
},
{
"label": "Josei",
"value": "josei"
},
{
"label": "love at first sight",
"value": "love-at-first-sight"
},
{
"label": "love romance",
"value": "love-romance"
},
{
"label": "Male protagonist",
"value": "male-protagonist"
},
{
"label": "Marriage",
"value": "marriage"
},
{
"label": "Martial Arts",
"value": "martial-arts"
},
{
"label": "Mature",
"value": "mature"
},
{
"label": "Mecha",
"value": "mecha"
},
{
"label": "Modern",
"value": "modern"
},
{
"label": "Mpreg",
"value": "mpreg"
},
{
"label": "Mystery",
"value": "mystery"
},
{
"label": "Omegaverse",
"value": "omegaverse"
},
{
"label": "Otherworld fantasy",
"value": "otherworld-fantasy"
},
{
"label": "Psychological",
"value": "psychological"
},
{
"label": "Quick transmigration",
"value": "quick-transmigration"
},
{
"label": "REBIRTH",
"value": "rebirth"
},
{
"label": "Regression",
"value": "regression"
},
{
"label": "Reverse Harem",
"value": "reverse-harem"
},
{
"label": "Romance",
"value": "romance"
},
{
"label": "School Life",
"value": "school-life"
},
{
"label": "Sci-fi",
"value": "sci-fi"
},
{
"label": "Seinen",
"value": "seinen"
},
{
"label": "shonen ai",
"value": "shonen-ai"
},
{
"label": "Shoujo",
"value": "shoujo"
},
{
"label": "Shoujo ai",
"value": "shoujo-ai"
},
{
"label": "Shounen",
"value": "shounen"
},
{
"label": "Shounen ai",
"value": "shounen-ai"
},
{
"label": "Showbi",
"value": "showbi"
},
{
"label": "showbiz",
"value": "showbiz"
},
{
"label": "Slice of Life",
"value": "slice-of-life"
},
{
"label": "Smut",
"value": "smut"
},
{
"label": "Sports",
"value": "sports"
},
{
"label": "Supernatural",
"value": "supernatural"
},
{
"label": "suspense",
"value": "suspense"
},
{
"label": "Tragedy",
"value": "tragedy"
},
{
"label": "Transmigration",
"value": "transmigration"
},
{
"label": "Unlimited flow",
"value": "unlimited-flow"
},
{
"label": "Urban Life",
"value": "urban-life"
},
{
"label": "Western",
"value": "western"
},
{
"label": "Wu xia",
"value": "wu-xia"
},
{
"label": "Wuxia",
"value": "wuxia"
},
{
"label": "Xianxia",
"value": "xianxia"
},
{
"label": "Xuanhuan",
"value": "xuanhuan"
},
{
"label": "Yaoi",
"value": "yaoi"
},
{
"label": "Yuri",
"value": "yuri"
}
]
},
"type[]": {
"type": "Checkbox",
"label": "Type",
"value": [],
"options": [
{
"label": "chinese",
"value": "chinese"
},
{
"label": "Chinese Novel",
"value": "chinese-novel"
},
{
"label": "historic love",
"value": "historic-love"
},
{
"label": "Japanese Novel",
"value": "japanese-novel"
},
{
"label": "Kō Randō (藍銅 紅)",
"value": "ko-rando-藍銅-紅"
},
{
"label": "Korean Novel",
"value": "korean-novel"
},
{
"label": "Light Novel (CN)",
"value": "light-novel-cn"
},
{
"label": "Original Novel",
"value": "original-novel"
},
{
"label": "Published Novel",
"value": "published-novel"
},
{
"label": "Published Novel (KR)",
"value": "published-novel-kr"
},
{
"label": "Quick Transmigration",
"value": "quick-transmigration"
},
{
"label": "romance",
"value": "romance"
},
{
"label": "Short Story",
"value": "short-story"
},
{
"label": "Web Novel",
"value": "web-novel"
}
]
},
"status": {
"type": "Picker",
"label": "Status",
"value": "",
"options": [
{
"label": "All",
"value": ""
},
{
"label": "Ongoing",
"value": "ongoing"
},
{
"label": "Hiatus",
"value": "hiatus"
},
{
"label": "Completed",
"value": "completed"
}
]
},
"order": {
"type": "Picker",
"label": "Order by",
"value": "",
"options": [
{
"label": "Default",
"value": ""
},
{
"label": "A-Z",
"value": "title"
},
{
"label": "Z-A",
"value": "titlereverse"
},
{
"label": "Latest Update",
"value": "update"
},
{
"label": "Latest Added",
"value": "latest"
},
{
"label": "Popular",
"value": "popular"
}
]
}
}
}
@@ -0,0 +1,268 @@
{
"filters": {
"genre[]": {
"type": "Checkbox",
"label": "تصنيف",
"value": [],
"options": [
{
"label": "Wuxia",
"value": "wuxia"
},
{
"label": "Xianxia",
"value": "xianxia"
},
{
"label": "XUANHUAN",
"value": "xuanhuan"
},
{
"label": "أكشن",
"value": "action"
},
{
"label": "إثارة",
"value": "excitement"
},
{
"label": "إنتقال الى عالم أخر",
"value": "isekai"
},
{
"label": "إيتشي",
"value": "etchi"
},
{
"label": "الخيال العلمي",
"value": "sci-fi"
},
{
"label": "بوليسي",
"value": "policy"
},
{
"label": "تاريخي",
"value": "historical"
},
{
"label": "تحقيقات",
"value": "تحقيق"
},
{
"label": "تقمص شخصيات",
"value": "rpg"
},
{
"label": "جريمة",
"value": "crime"
},
{
"label": "جوسى",
"value": "josei"
},
{
"label": "جوسي",
"value": "جوسي"
},
{
"label": "حريم",
"value": "harem"
},
{
"label": "حياة مدرسية",
"value": "school-life"
},
{
"label": "خيالي(فانتازيا)",
"value": "fantasy"
},
{
"label": "دراما",
"value": "drama"
},
{
"label": "رعب",
"value": "horror"
},
{
"label": "رومانسي",
"value": "romantic"
},
{
"label": "سحر",
"value": "magic"
},
{
"label": "سينن",
"value": "senen"
},
{
"label": "شريحة من الحياة",
"value": "slice-of-life"
},
{
"label": "شوجو",
"value": "shojo"
},
{
"label": "شونين",
"value": "shonen"
},
{
"label": "طبي",
"value": "medical"
},
{
"label": "ظواهر خارقة للطبيعة",
"value": "supernatural"
},
{
"label": "غموض",
"value": "mysteries"
},
{
"label": "فانتازيا",
"value": "فانتازيا"
},
{
"label": "فنون القتال",
"value": "martial-arts"
},
{
"label": "قصة قصيرة",
"value": "قصة-قصيرة"
},
{
"label": "قوى خارقة",
"value": "superpower"
},
{
"label": "كوميدي",
"value": "comedy"
},
{
"label": "مأساوي",
"value": "tragedy"
},
{
"label": "ما بعد الكارثة",
"value": "after-the-disaster"
},
{
"label": "مغامرة",
"value": "adventure"
},
{
"label": "ميكا",
"value": "mechanical"
},
{
"label": "ناضج",
"value": "mature"
},
{
"label": "نفسي",
"value": "psychological"
},
{
"label": "ون شوت",
"value": "ون-شوت"
}
]
},
"type[]": {
"type": "Checkbox",
"label": "النوع",
"value": [],
"options": [
{
"label": "إنجليزية",
"value": "english"
},
{
"label": "رواية لايت",
"value": "light-novel"
},
{
"label": "رواية ويب",
"value": "web-novel"
},
{
"label": "صينية",
"value": "chinese"
},
{
"label": "عربية",
"value": "arabic"
},
{
"label": "كورية",
"value": "korean"
},
{
"label": "ون شوت",
"value": "ون-شوت"
},
{
"label": "يابانية",
"value": "japanese"
}
]
},
"status": {
"type": "Picker",
"label": "الحالة",
"value": "",
"options": [
{
"label": "الكل",
"value": ""
},
{
"label": "Ongoing",
"value": "ongoing"
},
{
"label": "Hiatus",
"value": "hiatus"
},
{
"label": "Completed",
"value": "completed"
}
]
},
"order": {
"type": "Picker",
"label": "ترتيب حسب",
"value": "",
"options": [
{
"label": "الإعداد الأولي",
"value": ""
},
{
"label": "A-Z",
"value": "title"
},
{
"label": "Z-A",
"value": "titlereverse"
},
{
"label": "أخر التحديثات",
"value": "update"
},
{
"label": "أخر ما تم إضافته",
"value": "latest"
},
{
"label": "الرائجة",
"value": "popular"
}
]
}
}
}
@@ -0,0 +1,248 @@
{
"filters": {
"genre[]": {
"type": "Checkbox",
"label": "Genre",
"value": [],
"options": [
{
"label": "action",
"value": "action"
},
{
"label": "alchimie",
"value": "alchimie"
},
{
"label": "Arts martiaux",
"value": "arts-martiaux"
},
{
"label": "Aventure",
"value": "aventure"
},
{
"label": "Célébrités",
"value": "celebrites"
},
{
"label": "chinese",
"value": "chinese"
},
{
"label": "Combat",
"value": "combat"
},
{
"label": "comedie",
"value": "comedie"
},
{
"label": "Cultivation",
"value": "cultivation"
},
{
"label": "dark fantasy",
"value": "dark-fantasy"
},
{
"label": "Demon",
"value": "demon"
},
{
"label": "Drame",
"value": "drame"
},
{
"label": "Fantasie",
"value": "fantasie"
},
{
"label": "Fantastique",
"value": "fantastique"
},
{
"label": "Guildes",
"value": "guildes"
},
{
"label": "harem",
"value": "harem"
},
{
"label": "Idole",
"value": "idole"
},
{
"label": "japan",
"value": "japan"
},
{
"label": "jeux vidéos",
"value": "jeux-videos"
},
{
"label": "Josei",
"value": "josei"
},
{
"label": "korean",
"value": "korean"
},
{
"label": "light Novel",
"value": "light-novel"
},
{
"label": "Magic",
"value": "magic"
},
{
"label": "mature",
"value": "mature"
},
{
"label": "Mystère",
"value": "mystere"
},
{
"label": "novel",
"value": "novel"
},
{
"label": "Renaissance",
"value": "renaissance"
},
{
"label": "Roman",
"value": "roman"
},
{
"label": "romance",
"value": "romance"
},
{
"label": "School life",
"value": "school-life"
},
{
"label": "Science-fiction",
"value": "science-fiction"
},
{
"label": "Seinen",
"value": "seinen"
},
{
"label": "Shonen",
"value": "shonen"
},
{
"label": "showbiz",
"value": "showbiz"
},
{
"label": "Surnaturel",
"value": "surnaturel"
},
{
"label": "système",
"value": "systeme"
},
{
"label": "Système de niveau",
"value": "systeme-de-niveau"
},
{
"label": "tranche de vie",
"value": "tranche-de-vie"
},
{
"label": "webnovel",
"value": "webnovel"
},
{
"label": "xp",
"value": "xp"
},
{
"label": "Xuanhuan",
"value": "xuanhuan"
}
]
},
"type[]": {
"type": "Checkbox",
"label": "Type",
"value": [],
"options": [
{
"label": "Korean",
"value": "korean"
},
{
"label": "Light novel",
"value": "light-novel"
},
{
"label": "novel chinois",
"value": "novel-chinois"
}
]
},
"status": {
"type": "Picker",
"label": "Statut",
"value": "",
"options": [
{
"label": "Tout",
"value": ""
},
{
"label": "Ongoing",
"value": "ongoing"
},
{
"label": "Hiatus",
"value": "hiatus"
},
{
"label": "Completed",
"value": "completed"
}
]
},
"order": {
"type": "Picker",
"label": "Par ordre",
"value": "",
"options": [
{
"label": "Par défaut",
"value": ""
},
{
"label": "A-Z",
"value": "title"
},
{
"label": "Z-A",
"value": "titlereverse"
},
{
"label": "Dernier mise à jour",
"value": "update"
},
{
"label": "Derniers Ajouts",
"value": "latest"
},
{
"label": "Populaire",
"value": "popular"
}
]
}
}
}
@@ -0,0 +1,492 @@
{
"filters": {
"genre[]": {
"type": "Checkbox",
"label": "Genre",
"value": [],
"options": [
{
"label": "A.I",
"value": "a-i"
},
{
"label": "Academy",
"value": "academy"
},
{
"label": "Action",
"value": "action"
},
{
"label": "Adult",
"value": "adult"
},
{
"label": "Adventure",
"value": "adventure"
},
{
"label": "Alternative History",
"value": "alternative-history"
},
{
"label": "Another World",
"value": "another-world"
},
{
"label": "Apocalypse",
"value": "apocalypse"
},
{
"label": "Bromance",
"value": "bromance"
},
{
"label": "Comedy",
"value": "comedy"
},
{
"label": "Cthulhu",
"value": "cthulhu"
},
{
"label": "Dark fantasy",
"value": "dark-fantasy"
},
{
"label": "Demons",
"value": "demons"
},
{
"label": "Drama",
"value": "drama"
},
{
"label": "Dystopia",
"value": "dystopia"
},
{
"label": "Ecchi",
"value": "ecchi"
},
{
"label": "Entertainment",
"value": "entertainment"
},
{
"label": "Exhaustion",
"value": "exhaustion"
},
{
"label": "Fanfiction",
"value": "fanfiction"
},
{
"label": "fantasy",
"value": "fantasy"
},
{
"label": "finance",
"value": "finance"
},
{
"label": "For men",
"value": "for-men"
},
{
"label": "Full color",
"value": "full-color"
},
{
"label": "fusion",
"value": "fusion"
},
{
"label": "gacha",
"value": "gacha"
},
{
"label": "Gallery",
"value": "gallery"
},
{
"label": "Game",
"value": "game"
},
{
"label": "Gender Bender",
"value": "gender-bender"
},
{
"label": "Genius",
"value": "genius"
},
{
"label": "Harem",
"value": "harem"
},
{
"label": "Healing",
"value": "healing"
},
{
"label": "Hero",
"value": "hero"
},
{
"label": "Historical",
"value": "historical"
},
{
"label": "Hunter",
"value": "hunter"
},
{
"label": "korean novel",
"value": "korean-novel"
},
{
"label": "Light Novel",
"value": "light-novel"
},
{
"label": "List Adventure Manga Genres",
"value": "list-adventure-manga-genres"
},
{
"label": "Long Strip",
"value": "long-strip"
},
{
"label": "Love comedy",
"value": "love-comedy"
},
{
"label": "magic",
"value": "magic"
},
{
"label": "Manhua",
"value": "manhua"
},
{
"label": "Martial Arts",
"value": "martial-arts"
},
{
"label": "Mature",
"value": "mature"
},
{
"label": "Medieval",
"value": "medieval"
},
{
"label": "Middle Ages",
"value": "middle-ages"
},
{
"label": "Misunderstanding",
"value": "misunderstanding"
},
{
"label": "Modern",
"value": "modern"
},
{
"label": "modern fantasy",
"value": "modern-fantasy"
},
{
"label": "Munchkin",
"value": "munchkin"
},
{
"label": "music",
"value": "music"
},
{
"label": "Mystery",
"value": "mystery"
},
{
"label": "Necromancy",
"value": "necromancy"
},
{
"label": "No Romance",
"value": "no-romance"
},
{
"label": "NTL",
"value": "ntl"
},
{
"label": "o",
"value": "o"
},
{
"label": "Obsession",
"value": "obsession"
},
{
"label": "Politics",
"value": "politics"
},
{
"label": "Possession",
"value": "possession"
},
{
"label": "Programming",
"value": "programming"
},
{
"label": "Psychological",
"value": "psychological"
},
{
"label": "Pure Love",
"value": "pure-love"
},
{
"label": "reasoning",
"value": "reasoning"
},
{
"label": "Redemption",
"value": "redemption"
},
{
"label": "Regression",
"value": "regression"
},
{
"label": "Regret",
"value": "regret"
},
{
"label": "Reincarnation",
"value": "reincarnation"
},
{
"label": "Return",
"value": "return"
},
{
"label": "Revenge",
"value": "revenge"
},
{
"label": "Reversal",
"value": "reversal"
},
{
"label": "Romance",
"value": "romance"
},
{
"label": "Romance Fanrasy",
"value": "romance-fanrasy"
},
{
"label": "Salvation",
"value": "salvation"
},
{
"label": "School Life",
"value": "school-life"
},
{
"label": "Sci-fi",
"value": "sci-fi"
},
{
"label": "Science fiction",
"value": "science-fiction"
},
{
"label": "Seinen",
"value": "seinen"
},
{
"label": "Shounen",
"value": "shounen"
},
{
"label": "Slice of Life",
"value": "slice-of-life"
},
{
"label": "Soft yandere",
"value": "soft-yandere"
},
{
"label": "Space opera",
"value": "space-opera"
},
{
"label": "Sports",
"value": "sports"
},
{
"label": "Supernatural",
"value": "supernatural"
},
{
"label": "Survival",
"value": "survival"
},
{
"label": "system",
"value": "system"
},
{
"label": "Time limit",
"value": "time-limit"
},
{
"label": "Tragedy",
"value": "tragedy"
},
{
"label": "Transmigration",
"value": "transmigration"
},
{
"label": "TRPG",
"value": "trpg"
},
{
"label": "TS",
"value": "ts"
},
{
"label": "Tsundere",
"value": "tsundere"
},
{
"label": "Unique",
"value": "unique"
},
{
"label": "Urban",
"value": "urban"
},
{
"label": "Villain",
"value": "villain"
},
{
"label": "Wholesome",
"value": "wholesome"
},
{
"label": "Wisdom",
"value": "wisdom"
},
{
"label": "Wuxia",
"value": "wuxia"
},
{
"label": "Xuanhuan",
"value": "xuanhuan"
},
{
"label": "Yandere",
"value": "yandere"
},
{
"label": "Yuri",
"value": "yuri"
}
]
},
"type[]": {
"type": "Checkbox",
"label": "Type",
"value": [],
"options": [
{
"label": "Chinese novel",
"value": "chinese-novel"
},
{
"label": "habyeol",
"value": "habyeol"
},
{
"label": "korean novel",
"value": "korean-novel"
},
{
"label": "Web Novel",
"value": "web-novel"
},
{
"label": "삼심",
"value": "삼심"
},
{
"label": "호곡",
"value": "호곡"
}
]
},
"status": {
"type": "Picker",
"label": "Status",
"value": "",
"options": [
{
"label": "All",
"value": ""
},
{
"label": "Ongoing",
"value": "ongoing"
},
{
"label": "Hiatus",
"value": "hiatus"
},
{
"label": "Completed",
"value": "completed"
}
]
},
"order": {
"type": "Picker",
"label": "Order by",
"value": "",
"options": [
{
"label": "Default",
"value": ""
},
{
"label": "A-Z",
"value": "title"
},
{
"label": "Z-A",
"value": "titlereverse"
},
{
"label": "Latest Update",
"value": "update"
},
{
"label": "Latest Added",
"value": "latest"
},
{
"label": "Popular",
"value": "popular"
}
]
}
}
}
@@ -0,0 +1,344 @@
{
"filters": {
"genre[]": {
"type": "Checkbox",
"label": "Genre",
"value": [],
"options": [
{
"label": "Fan fiction",
"value": "fan-fiction"
},
{
"label": "fanqienovel",
"value": "fanqienovel"
},
{
"label": "fanstay",
"value": "fanstay"
},
{
"label": "fantasy",
"value": "fantasy"
},
{
"label": "football",
"value": "football"
},
{
"label": "horror",
"value": "horror"
},
{
"label": "Huyen Huyen",
"value": "huyen-huyen"
},
{
"label": "light novel",
"value": "light-novel"
},
{
"label": "living",
"value": "living"
},
{
"label": "martial arts",
"value": "martial-arts"
},
{
"label": "Mature",
"value": "mature"
},
{
"label": "military",
"value": "military"
},
{
"label": "military history",
"value": "military-history"
},
{
"label": "Mystical Demons",
"value": "mystical-demons"
},
{
"label": "nba",
"value": "nba"
},
{
"label": "roamnce",
"value": "roamnce"
},
{
"label": "romance",
"value": "romance"
},
{
"label": "sc-fi online game",
"value": "sc-fi-online-game"
},
{
"label": "Sci-fi online games",
"value": "sci-fi-online-games"
},
{
"label": "Science fiction",
"value": "science-fiction"
},
{
"label": "Science fiction online game",
"value": "science-fiction-online-game"
},
{
"label": "speech",
"value": "speech"
},
{
"label": "Sports",
"value": "sports"
},
{
"label": "tanmi doujin",
"value": "tanmi-doujin"
},
{
"label": "Terrifying supernatural",
"value": "terrifying-supernatural"
},
{
"label": "urabn",
"value": "urabn"
},
{
"label": "urban",
"value": "urban"
},
{
"label": "Urban Fiction",
"value": "urban-fiction"
},
{
"label": "urban;romance",
"value": "urbanromance"
},
{
"label": "wuxia",
"value": "wuxia"
},
{
"label": "wuxia fairy",
"value": "wuxia-fairy"
},
{
"label": "Xuan Feng weak initiation",
"value": "xuan-feng-weak-initiation"
},
{
"label": "Xuanhuan",
"value": "xuanhuan"
},
{
"label": "Youth Campus",
"value": "youth-campus"
}
]
},
"type[]": {
"type": "Checkbox",
"label": "Type",
"value": [],
"options": [
{
"label": "Black Basket: Platinum Star",
"value": "black-basket-platinum-star"
},
{
"label": "ciweimao",
"value": "ciweimao"
},
{
"label": "Doomsday",
"value": "doomsday"
},
{
"label": "dragon ball",
"value": "dragon-ball"
},
{
"label": "Entertainment",
"value": "entertainment"
},
{
"label": "falo",
"value": "falo"
},
{
"label": "faloo",
"value": "faloo"
},
{
"label": "fan fiction",
"value": "fan-fiction"
},
{
"label": "fanqienovel",
"value": "fanqienovel"
},
{
"label": "Fantasy",
"value": "fantasy"
},
{
"label": "Football",
"value": "football"
},
{
"label": "Harry Potter",
"value": "harry-potter"
},
{
"label": "horror",
"value": "horror"
},
{
"label": "Hunter X Hunter",
"value": "hunter-x-hunter"
},
{
"label": "Infinite Evolution",
"value": "infinite-evolution"
},
{
"label": "Lords: Opens with a Bloody Nest",
"value": "lords-opens-with-a-bloody-nest"
},
{
"label": "martial arts",
"value": "martial-arts"
},
{
"label": "marvel",
"value": "marvel"
},
{
"label": "naruto",
"value": "naruto"
},
{
"label": "nba",
"value": "nba"
},
{
"label": "nove",
"value": "nove"
},
{
"label": "novel",
"value": "novel"
},
{
"label": "one piece",
"value": "one-piece"
},
{
"label": "online game",
"value": "online-game"
},
{
"label": "Please",
"value": "please"
},
{
"label": "qidian",
"value": "qidian"
},
{
"label": "qimao",
"value": "qimao"
},
{
"label": "romance",
"value": "romance"
},
{
"label": "Sci-Fi",
"value": "sci-fi"
},
{
"label": "Science-fiction",
"value": "science-fiction"
},
{
"label": "this is a world of horror",
"value": "this-is-a-world-of-horror"
},
{
"label": "Urban",
"value": "urban"
},
{
"label": "Web Novel",
"value": "web-novel"
}
]
},
"status": {
"type": "Picker",
"label": "Status",
"value": "",
"options": [
{
"label": "All",
"value": ""
},
{
"label": "Ongoing",
"value": "ongoing"
},
{
"label": "Hiatus",
"value": "hiatus"
},
{
"label": "Completed",
"value": "completed"
}
]
},
"order": {
"type": "Picker",
"label": "Order by",
"value": "",
"options": [
{
"label": "Default",
"value": ""
},
{
"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"
}
]
}
}
}
@@ -0,0 +1,156 @@
{
"filters": {
"genre[]": {
"type": "Checkbox",
"label": "النوع",
"value": [],
"options": [
{
"label": "+18",
"value": "18"
},
{
"label": "آليه",
"value": "آليه"
},
{
"label": "اكشن",
"value": "اكشن"
},
{
"label": "حريم",
"value": "حريم"
},
{
"label": "خيال",
"value": "خيال"
},
{
"label": "خيال علمي",
"value": "خيال-علمي"
},
{
"label": "دراما",
"value": "دراما"
},
{
"label": "رعب",
"value": "رعب"
},
{
"label": "رومانسي",
"value": "رومانسي"
},
{
"label": "سحر",
"value": "سحر"
},
{
"label": "شبه بشريه",
"value": "شبه-بشريه"
},
{
"label": "صيني",
"value": "صيني"
},
{
"label": "غموض",
"value": "غموض"
},
{
"label": "فان فيكشن",
"value": "فان-فيكشن"
},
{
"label": "فنون قتاليه",
"value": "فنون-قتاليه"
},
{
"label": "كوميديا",
"value": "كوميديا"
},
{
"label": "مغامره",
"value": "مغامره"
}
]
},
"type[]": {
"type": "Checkbox",
"label": "النوع",
"value": [],
"options": [
{
"label": "روايات صينيه",
"value": "روايات-صينيه"
},
{
"label": "روايات كوريه",
"value": "روايات-كوريه"
},
{
"label": "روايات ويب",
"value": "روايات-ويب"
}
]
},
"status": {
"type": "Picker",
"label": "الحالة",
"value": "",
"options": [
{
"label": "الكل",
"value": ""
},
{
"label": "Ongoing",
"value": "ongoing"
},
{
"label": "Hiatus",
"value": "hiatus"
},
{
"label": "Completed",
"value": "completed"
}
]
},
"order": {
"type": "Picker",
"label": "ترتيب بواسطة",
"value": "",
"options": [
{
"label": "Default",
"value": ""
},
{
"label": "A-Z",
"value": "title"
},
{
"label": "Z-A",
"value": "titlereverse"
},
{
"label": "أخر التحديثات",
"value": "update"
},
{
"label": "المضاف حديثاً",
"value": "latest"
},
{
"label": "شائع",
"value": "popular"
},
{
"label": "{{advanced_search_series_order_by_rating_label}}",
"value": "rating"
}
]
}
}
}
@@ -0,0 +1,252 @@
{
"filters": {
"genre[]": {
"type": "Checkbox",
"label": "Genre",
"value": [],
"options": [
{
"label": "Aksiyon",
"value": "aksiyon"
},
{
"label": "Bilim Kurgu",
"value": "bilim-kurgu"
},
{
"label": "Büyü",
"value": "buyu"
},
{
"label": "Comedy",
"value": "comedy"
},
{
"label": "Doğaüstü",
"value": "dogaustu"
},
{
"label": "dövüş sanatları",
"value": "dovus-sanatlari"
},
{
"label": "Dram",
"value": "dram"
},
{
"label": "Drama",
"value": "drama"
},
{
"label": "ecchi",
"value": "ecchi"
},
{
"label": "fantastik",
"value": "fantastik"
},
{
"label": "Fantasy",
"value": "fantasy"
},
{
"label": "gizem",
"value": "gizem"
},
{
"label": "Harem",
"value": "harem"
},
{
"label": "isekai",
"value": "isekai"
},
{
"label": "Josei",
"value": "josei"
},
{
"label": "Komedi",
"value": "komedi"
},
{
"label": "korku",
"value": "korku"
},
{
"label": "macera",
"value": "macera"
},
{
"label": "Mecha",
"value": "mecha"
},
{
"label": "okul",
"value": "okul"
},
{
"label": "oyun",
"value": "oyun"
},
{
"label": "psikoloji",
"value": "psikoloji"
},
{
"label": "Psychological",
"value": "psychological"
},
{
"label": "reenkarnasyon",
"value": "reenkarnasyon"
},
{
"label": "Romance",
"value": "romance"
},
{
"label": "Romantik",
"value": "romantik"
},
{
"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": "Shounen Ai",
"value": "shounen-ai"
},
{
"label": "Slice of Life",
"value": "slice-of-life"
},
{
"label": "Smut",
"value": "smut"
},
{
"label": "süper kahraman",
"value": "super-kahraman"
},
{
"label": "Supernatural",
"value": "supernatural"
},
{
"label": "tarih",
"value": "tarih"
},
{
"label": "trajedi",
"value": "trajedi"
},
{
"label": "Wuxia",
"value": "wuxia"
},
{
"label": "Xianxia",
"value": "xianxia"
},
{
"label": "Xuanhuan",
"value": "xuanhuan"
},
{
"label": "Yaoi",
"value": "yaoi"
},
{
"label": "yetişkin",
"value": "yetiskin"
},
{
"label": "Yuri",
"value": "yuri"
}
]
},
"type[]": {
"type": "Checkbox",
"label": "Tür",
"value": [],
"options": [
{
"label": "Web Novel",
"value": "web-novel"
}
]
},
"status": {
"type": "Picker",
"label": "Durum",
"value": "",
"options": [
{
"label": "Hepsi",
"value": ""
},
{
"label": "Devam Ediyor",
"value": "ongoing"
},
{
"label": "Askıda",
"value": "hiatus"
},
{
"label": "Tamamlanmış",
"value": "completed"
}
]
},
"order": {
"type": "Picker",
"label": "Sıralama",
"value": "",
"options": [
{
"label": "Varsayılan",
"value": ""
},
{
"label": "A-Z",
"value": "title"
},
{
"label": "Z-A",
"value": "titlereverse"
},
{
"label": "Latest Update",
"value": "update"
},
{
"label": "Latest Added",
"value": "latest"
},
{
"label": "Popular",
"value": "popular"
}
]
}
}
}
@@ -0,0 +1,156 @@
{
"filters": {
"genre[]": {
"type": "Checkbox",
"label": "Genre",
"value": [],
"options": [
{
"label": "Action",
"value": "action"
},
{
"label": "Adult",
"value": "adult"
},
{
"label": "Adventure",
"value": "adventure"
},
{
"label": "Comedy",
"value": "comedy"
},
{
"label": "Ecchi",
"value": "ecchi"
},
{
"label": "Fantasy",
"value": "fantasy"
},
{
"label": "Harem",
"value": "harem"
},
{
"label": "Josei",
"value": "josei"
},
{
"label": "Martial Arts",
"value": "martial-arts"
},
{
"label": "Mature",
"value": "mature"
},
{
"label": "Romance",
"value": "romance"
},
{
"label": "School Life",
"value": "school-life"
},
{
"label": "Sci-fi",
"value": "sci-fi"
},
{
"label": "Seinen",
"value": "seinen"
},
{
"label": "Slice of Life",
"value": "slice-of-life"
},
{
"label": "Smut",
"value": "smut"
},
{
"label": "Sports",
"value": "sports"
},
{
"label": "Supernatural",
"value": "supernatural"
},
{
"label": "Tragedy",
"value": "tragedy"
}
]
},
"type[]": {
"type": "Checkbox",
"label": "Type",
"value": [],
"options": [
{
"label": "Light Novel (KR)",
"value": "light-novel-kr"
},
{
"label": "Web Novel",
"value": "web-novel"
}
]
},
"status": {
"type": "Picker",
"label": "Status",
"value": "",
"options": [
{
"label": "All",
"value": ""
},
{
"label": "Ongoing",
"value": "ongoing"
},
{
"label": "Hiatus",
"value": "hiatus"
},
{
"label": "Completed",
"value": "completed"
}
]
},
"order": {
"type": "Picker",
"label": "Order by",
"value": "",
"options": [
{
"label": "Default",
"value": ""
},
{
"label": "A-Z",
"value": "title"
},
{
"label": "Z-A",
"value": "titlereverse"
},
{
"label": "Latest Update",
"value": "update"
},
{
"label": "Latest Added",
"value": "latest"
},
{
"label": "Popular",
"value": "popular"
}
]
}
}
}
@@ -0,0 +1,248 @@
{
"filters": {
"genre[]": {
"type": "Checkbox",
"label": "Genre",
"value": [],
"options": [
{
"label": "Acting",
"value": "acting"
},
{
"label": "Action",
"value": "action"
},
{
"label": "Adult",
"value": "adult"
},
{
"label": "Adventure",
"value": "adventure"
},
{
"label": "Bl",
"value": "bl"
},
{
"label": "Comedy",
"value": "comedy"
},
{
"label": "Drama",
"value": "drama"
},
{
"label": "entertainment",
"value": "entertainment"
},
{
"label": "Fantasy",
"value": "fantasy"
},
{
"label": "Gender Bender",
"value": "gender-bender"
},
{
"label": "Ghost",
"value": "ghost"
},
{
"label": "Harem",
"value": "harem"
},
{
"label": "Historical",
"value": "historical"
},
{
"label": "Horror",
"value": "horror"
},
{
"label": "Isekai",
"value": "isekai"
},
{
"label": "Josei",
"value": "josei"
},
{
"label": "Male protagonist",
"value": "male-protagonist"
},
{
"label": "Martial Arts",
"value": "martial-arts"
},
{
"label": "Mature",
"value": "mature"
},
{
"label": "Mystery",
"value": "mystery"
},
{
"label": "Psychological",
"value": "psychological"
},
{
"label": "Reincarnation",
"value": "reincarnation"
},
{
"label": "Romance",
"value": "romance"
},
{
"label": "School Life",
"value": "school-life"
},
{
"label": "Sci-Fi",
"value": "sci-fi"
},
{
"label": "Shoujo",
"value": "shoujo"
},
{
"label": "Shounen",
"value": "shounen"
},
{
"label": "Shounen Ai",
"value": "shounen-ai"
},
{
"label": "Slice of Life",
"value": "slice-of-life"
},
{
"label": "Smut",
"value": "smut"
},
{
"label": "Sprited animal",
"value": "sprited-animal"
},
{
"label": "Supernatural",
"value": "supernatural"
},
{
"label": "Survival",
"value": "survival"
},
{
"label": "Transmigration",
"value": "transmigration"
},
{
"label": "Unlimited flow",
"value": "unlimited-flow"
},
{
"label": "Wuxia",
"value": "wuxia"
},
{
"label": "Xianxia",
"value": "xianxia"
},
{
"label": "Yaoi",
"value": "yaoi"
},
{
"label": "Yuri",
"value": "yuri"
}
]
},
"type[]": {
"type": "Checkbox",
"label": "Type",
"value": [],
"options": [
{
"label": "Chinese",
"value": "chinese"
},
{
"label": "Chinese Novel",
"value": "chinese-novel"
},
{
"label": "Korean Novel",
"value": "korean-novel"
},
{
"label": "Published Novel (KR)",
"value": "published-novel-kr"
},
{
"label": "Web Novel",
"value": "web-novel"
}
]
},
"status": {
"type": "Picker",
"label": "Status",
"value": "",
"options": [
{
"label": "All",
"value": ""
},
{
"label": "Ongoing",
"value": "ongoing"
},
{
"label": "Hiatus",
"value": "hiatus"
},
{
"label": "Completed",
"value": "completed"
}
]
},
"order": {
"type": "Picker",
"label": "Order by",
"value": "",
"options": [
{
"label": "Default",
"value": ""
},
{
"label": "A-Z",
"value": "title"
},
{
"label": "Z-A",
"value": "titlereverse"
},
{
"label": "Latest Update",
"value": "update"
},
{
"label": "Latest Added",
"value": "latest"
},
{
"label": "Popular",
"value": "popular"
}
]
}
}
}
@@ -0,0 +1,35 @@
import { ScrpitGeneratorFunction } from "../generate";
import { LightNovelWPMetadata } from "./template";
import list from "./sources.json";
import { readFileSync } from "fs";
import path from "path";
export const generateAll: ScrpitGeneratorFunction = function (name) {
return list.map((source: LightNovelWPMetadata) => {
let filters: any = {};
try {
filters = require(`./filters/${source.id}`);
source.filters = filters.filters;
} catch (e) {}
console.log(`[${name}] Generating:`, source.id, source.filters? "\t🔎with filters🔍" : "\t🚫no filters🚫");
return generator(source);
});
};
const generator = function generator(source: LightNovelWPMetadata) {
const LightNovelWPTemplate = readFileSync(path.join(__dirname, "template.ts"),{
encoding: "utf-8",
});
const pluginScript = `
${LightNovelWPTemplate}
const plugin = new LightNovelWPPlugin(${JSON.stringify(source)});
export default plugin;
`.trim();
return {
lang: source.options?.lang || "English",
filename: source.sourceName,
pluginScript,
};
};
@@ -0,0 +1,103 @@
import * as fs from 'fs';
import * as cheerio from 'cheerio';
import * as path from 'path';
function getFilters(name: string, html: string) {
const $:cheerio.CheerioAPI = cheerio.load(html);
const filters: any = {filters: {
"genre[]": {
type: "Checkbox",
label: "Genre",
value: [],
options: []
},
"type[]": {
type: "Checkbox",
label: "Type",
value: [],
options: []
},
"status": {
type: "Picker",
label: "Status",
value: "",
options: []
},
"order": {
type: "Picker",
label: "Order",
value: "",
options: []
},
}};
const filtersContainer = $('div.quickfilter').find("ul");
filtersContainer.each((i, el) => {
const filterName = Object.keys(filters.filters)[i];
filters.filters[filterName].label = $(el).prev().contents().first().text().trim();
$(el).find("li").each((j, li) => {
filters.filters[filterName].options.push({
label: $(li).text().trim(),
value: decodeURI($(li).find("input").attr("value") || "")
});
});
});
fs.writeFileSync(
path.join(__dirname, 'filters', name + ".json"),
JSON.stringify(filters, null, 2)
);
}
async function getFiltersFromURL(name: string, url: string) {
const response = await fetch(url + "/series/");
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}, while fetching ${response.url}`);
}
const html = await response.text();
try {
getFilters(name, html);
} catch (e) {
console.error("Error while getting filters from", url);
}
}
async function askGetFilter(){
const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout
});
const EREASE_PREV_LINE = '\x1b[1A\r\x1b[2K';
await readline.question('Enter the id of the site (same one as in sources.json): ', async (name: string) => {
await readline.question(EREASE_PREV_LINE + 'Do you want to get the filters from a URL or the html text? (if url dosen\'t work try html) (url/html): ', async (method: string) => {
if (method.toLowerCase() === "url") {
await readline.question(EREASE_PREV_LINE + 'Enter the URL (same one as in sources.json): ', async (url: string) => {
try {
await getFiltersFromURL(name, url);
} catch (e: any) {
console.error("Error while getting filters from", url);
console.log(e.message || e);
}
readline.close();
});
} else {
process.stdout.write(EREASE_PREV_LINE + `Enter the html text from the page at {sourceSite}/series (at the end press ENTER then press CTRL+C)
(to make it faster you can run \`$(\"div.quickfilter\").parent().html()\` in the console to get only the important html part): `)
let html = "";
readline.on('SIGINT', () => {
console.log("Creating filters file");
getFilters(name, html);
readline.close();
});
readline.on('line', (line: string) => {
html += line + "\n";
});
}
});
});
}
askGetFilter();
export { getFiltersFromURL };
+146
View File
@@ -0,0 +1,146 @@
[
{
"id": "lightnovelfr",
"sourceSite": "https://lightnovelfr.com/",
"sourceName": "Ligh Novel FR",
"options": {
"lang": "French",
"reverseChapters": true
}
},
{
"id": "kolnovel",
"sourceSite": "https://kolnovel.me/",
"sourceName": "ملوك الروايات",
"options": {
"lang": "Arabic",
"reverseChapters": true
}
},
{
"id": "knoxt",
"sourceSite": "https://knoxt.space/",
"sourceName": "KnoxT",
"options": {
"lang": "English",
"reverseChapters": true
}
},
{
"id": "bacalightnovel",
"sourceSite": "https://bacalightnovel.co/",
"sourceName": "Baca Light Novel",
"options": {
"lang": "Indonesian",
"reverseChapters": true
}
},
{
"id": "allnovelread",
"sourceSite": "https://allnovelread.com/",
"sourceName": "AllNovelRead",
"options": {
"lang": "Spanish",
"reverseChapters": true
}
},
{
"id": "daotranslate",
"sourceSite": "https://daotranslate.com/",
"sourceName": "DaoTranslate",
"options": {
"lang": "English",
"reverseChapters": true
}
},
{
"id": "novelsparadise",
"sourceSite": "https://novelsparadise.net/",
"sourceName": "جنة الروايات",
"options": {
"lang": "Arabic",
"reverseChapters": true
}
},
{
"id": "universalnovel",
"sourceSite": "https://universalnovel.com/",
"sourceName": "Universal Novel",
"options": {
"lang": "English",
"reverseChapters": false
}
},
{
"id": "noveltr",
"sourceSite": "https://noveltr.com/",
"sourceName": "NovelTR",
"options": {
"lang": "Turkish",
"reverseChapters": true
}
},
{
"id": "noblemtl",
"sourceSite": "https://noblemtl.com/",
"sourceName": "NobleMTL",
"options": {
"lang": "English",
"reverseChapters": true
}
},
{
"id": "genesistls",
"sourceSite": "https://genesistls.com/",
"sourceName": "GenesisTls",
"options": {
"lang": "English",
"reverseChapters": false
}
},
{
"id": "pandamtl",
"sourceSite": "https://pandamtl.com/",
"sourceName": "Panda Machine Translations",
"options": {
"lang": "English",
"reverseChapters": true
}
},
{
"id": "novelsknight",
"sourceSite": "https://novelsknight.com/",
"sourceName": "NovelsKnight",
"options": {
"lang": "English",
"reverseChapters": true
}
},
{
"id": "centralnovel",
"sourceSite": "https://centralnovel.com/",
"sourceName": "Central Novel",
"options": {
"lang": "Portuguese",
"reverseChapters": true
}
},
{
"id": "whitemoonlightnovels",
"sourceSite": "https://whitemoonlightnovels.com/",
"sourceName": "White Moonlight Novels",
"options": {
"lang": "English",
"reverseChapters": false
}
},
{
"id": "systemtranslation",
"sourceSite": "https://systemtranslation.com/",
"sourceName": "System Translation",
"options": {
"lang": "English",
"reverseChapters": true
}
}
]
+234
View File
@@ -0,0 +1,234 @@
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.3";
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...")
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;
}
+31 -30
View File
@@ -6,7 +6,6 @@ import { defaultCover } from "@libs/defaultCover";
import { NovelStatus } from "@libs/novelStatus";
import dayjs from "dayjs";
const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));
const includesAny = (str: string, keywords: string[]) =>
new RegExp(keywords.join("|")).test(str);
@@ -38,6 +37,7 @@ export interface MadaraMetadata {
options?: MadaraOptions;
filters?: Filters;
}
class MadaraPlugin implements Plugin.PluginBase {
id: string;
name: string;
@@ -57,6 +57,7 @@ class MadaraPlugin implements Plugin.PluginBase {
this.options = metadata.options;
this.filters = metadata.filters;
}
async popularNovels(
pageNo: number,
{
@@ -69,8 +70,8 @@ class MadaraPlugin implements Plugin.PluginBase {
let url = this.site;
if (filters?.genres?.value) {
url +=
(this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" + filters.genres.value;
(this.options?.path?.genres || MadaraDefaultPath.genres) + "/" +
filters.genres.value;
} else {
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
@@ -83,18 +84,19 @@ class MadaraPlugin implements Plugin.PluginBase {
loadedCheerio(".manga-title-badges").remove();
loadedCheerio(".page-item-detail").each(function () {
const novelName = loadedCheerio(this).find(".post-title").text().trim();
const image = loadedCheerio(this).find("img");
const novelCover = image.attr("data-src") || image.attr("src");
loadedCheerio(".page-item-detail").each((index, element) => {
const novelName = loadedCheerio(element).find(".post-title").text().trim();
const novelUrl =
loadedCheerio(this).find(".post-title").find("a").attr("href") || "";
loadedCheerio(element).find(".post-title").find("a").attr("href") || "";
if (!novelName || !novelUrl) return;
const image = loadedCheerio(element).find("img");
const novelCover = image.attr("data-src") || image.attr("src");
const novel: Plugin.NovelItem = {
name: novelName,
cover: novelCover,
url: novelUrl,
path: novelUrl.replace(this.site, ""),
};
novels.push(novel);
@@ -102,19 +104,18 @@ class MadaraPlugin implements Plugin.PluginBase {
return novels;
}
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
const novel: Plugin.SourceNovel = {
url: novelUrl,
};
const body = await fetchApi(novelUrl).then((res) => res.text());
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
const body = await fetchApi(this.site + novelPath).then((res) => res.text());
let loadedCheerio = parseHTML(body);
loadedCheerio(".manga-title-badges, #manga-title span").remove();
novel.name =
loadedCheerio(".post-title h1").text().trim() ||
loadedCheerio("#manga-title h1").text();
const novel: Plugin.SourceNovel = {
path: novelPath,
name:
loadedCheerio(".post-title h1").text().trim() ||
loadedCheerio("#manga-title h1").text(),
};
novel.cover =
loadedCheerio(".summary_image > a > img").attr("data-lazy-src") ||
@@ -161,7 +162,7 @@ class MadaraPlugin implements Plugin.PluginBase {
let html = "";
if (this.options?.useNewChapterEndpoint) {
html = await fetchApi(novelUrl + "ajax/chapters/", {
html = await fetchApi(this.site + novelPath + "ajax/chapters/", {
method: "POST",
}).then((res) => res.text());
} else {
@@ -203,7 +204,7 @@ class MadaraPlugin implements Plugin.PluginBase {
chapters.push({
name: chapterName,
url: chapterUrl,
path: chapterUrl.replace(this.site, ""),
releaseTime: releaseDate || null,
chapterNumber: totalChapters - chapterIndex,
});
@@ -212,8 +213,9 @@ class MadaraPlugin implements Plugin.PluginBase {
novel.chapters = chapters.reverse();
return novel;
}
async parseChapter(chapterUrl: string): Promise<string> {
const body = await fetchApi(chapterUrl).then((res) => res.text());
async parseChapter(chapterPath: string): Promise<string> {
const body = await fetchApi(this.site + chapterPath).then((res) => res.text());
const loadedCheerio = parseHTML(body);
const chapterText =
@@ -225,6 +227,7 @@ class MadaraPlugin implements Plugin.PluginBase {
return chapterText;
}
async searchNovels(
searchTerm: string,
pageNo?: number | undefined,
@@ -233,21 +236,19 @@ class MadaraPlugin implements Plugin.PluginBase {
const url = this.site + "?s=" + searchTerm + "&post_type=wp-manga";
const body = await fetchApi(url).then((res) => res.text());
const loadedCheerio = parseHTML(body);
loadedCheerio(".c-tabs-item__content").each(function () {
const novelName = loadedCheerio(this).find(".post-title").text().trim();
let image = loadedCheerio(this).find("img");
loadedCheerio(".c-tabs-item__content").each((index, element) => {
const novelName = loadedCheerio(element).find(".post-title").text().trim();
const novelUrl = loadedCheerio(element).find(".post-title").find("a").attr("href") || "";
if (!novelName || !novelUrl) return;
let image = loadedCheerio(element).find("img");
const novelCover = image.attr("data-src") || image.attr("src");
let novelUrl =
loadedCheerio(this).find(".post-title").find("a").attr("href") || "";
const novel = {
name: novelName,
cover: novelCover,
url: novelUrl,
path: novelUrl.replace(this.site, ""),
};
novels.push(novel);
+34 -24
View File
@@ -39,7 +39,8 @@ class ReadwnPlugin implements Plugin.PluginBase {
url += showLatestNovels ? "lastdotime" : filters?.sort?.value || "newstime";
url += "-" + (pageNo - 1) + ".html";
if (filters?.tags?.value) { //only 1 page
if (filters?.tags?.value) {
//only 1 page
url = this.site + "/tags/" + filters.tags.value + "-0.html";
}
@@ -48,25 +49,27 @@ class ReadwnPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = loadedCheerio("li.novel-item")
.map((index, element) => ({
name: loadedCheerio(element).find("h4").text(),
cover: this.site +
name: loadedCheerio(element).find("h4").text() || "",
cover:
this.site +
loadedCheerio(element).find(".novel-cover > img").attr("data-src"),
url: this.site + loadedCheerio(element).find("a").attr("href"),
path: loadedCheerio(element).find("a").attr("href") || "",
}))
.get();
.get()
.filter((novel) => novel.name && novel.path);
return novels;
}
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
const body = await fetchApi(novelUrl).then((res) => res.text());
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
const body = await fetchApi(this.site + novelPath).then((res) => res.text());
const loadedCheerio = parseHTML(body);
const novel: Plugin.SourceNovel = {
url: novelUrl,
path: novelPath,
name: loadedCheerio("h1.novel-title").text() || "",
};
novel.name = loadedCheerio("h1.novel-title").text();
novel.author = loadedCheerio("span[itemprop=author]").text();
novel.cover =
this.site + loadedCheerio("figure.cover > img").attr("data-src");
@@ -100,11 +103,17 @@ class ReadwnPlugin implements Plugin.PluginBase {
const chapters: Plugin.ChapterItem[] = loadedCheerio(".chapter-list li")
.map((chapterIndex, element) => {
const name = loadedCheerio(element).find("a .chapter-title").text().trim();
const url = loadedCheerio(element).find("a").attr("href")?.trim();
if (!name || !url) return null;
const name = loadedCheerio(element)
.find("a .chapter-title")
.text()
.trim();
const path = loadedCheerio(element).find("a").attr("href")?.trim();
if (!name || !path) return null;
let releaseTime = loadedCheerio(element).find("a .chapter-update").text().trim();
let releaseTime = loadedCheerio(element)
.find("a .chapter-update")
.text()
.trim();
if (releaseTime?.includes?.("ago")) {
const timeAgo = releaseTime.match(/\d+/)?.[0] || "0";
const timeAgoInt = parseInt(timeAgo, 10);
@@ -112,14 +121,14 @@ class ReadwnPlugin implements Plugin.PluginBase {
if (timeAgoInt) {
const dayJSDate = dayjs(); // today
if (
releaseTime.includes("hours ago") ||
releaseTime.includes("hours ago") ||
releaseTime.includes("hour ago")
) {
dayJSDate.subtract(timeAgoInt, "hours"); // go back N hours
}
if (
releaseTime.includes("days ago") ||
releaseTime.includes("days ago") ||
releaseTime.includes("day ago")
) {
dayJSDate.subtract(timeAgoInt, "days"); // go back N days
@@ -138,7 +147,7 @@ class ReadwnPlugin implements Plugin.PluginBase {
return {
name,
url: this.site + url,
path,
releaseTime,
chapterNumber: chapterIndex + 1,
};
@@ -148,14 +157,14 @@ class ReadwnPlugin implements Plugin.PluginBase {
if (latestChapterNo > chapters.length) {
const lastChapterNo = parseInt(
chapters[chapters.length - 1].url.match(/_(\d+)\.html/)?.[1] || "",
chapters[chapters.length - 1].path.match(/_(\d+)\.html/)?.[1] || "",
10,
);
for (let i = (lastChapterNo || chapters.length) + 1; i <= latestChapterNo; i++) {
chapters.push({
name: "Chapter " + i,
url: novelUrl.replace(".html", "_" + i + ".html"),
path: novelPath.replace(".html", "_" + i + ".html"),
releaseTime: null,
chapterNumber: i,
});
@@ -166,8 +175,8 @@ class ReadwnPlugin implements Plugin.PluginBase {
return novel;
}
async parseChapter(chapterUrl: string): Promise<string> {
const body = await fetchApi(chapterUrl).then((res) => res.text());
async parseChapter(chapterPath: string): Promise<string> {
const body = await fetchApi(this.site + chapterPath).then((res) => res.text());
const loadedCheerio = parseHTML(body);
const chapterText = loadedCheerio(".chapter-content").html() || "";
@@ -190,11 +199,12 @@ class ReadwnPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = loadedCheerio("li.novel-item")
.map((index, element) => ({
name: loadedCheerio(element).find("h4").text(),
name: loadedCheerio(element).find("h4").text() || "",
cover: this.site + loadedCheerio(element).find("img").attr("data-src"),
url: this.site + loadedCheerio(element).find("a").attr("href"),
path: loadedCheerio(element).find("a").attr("href") || "",
}))
.get();
.get()
.filter((novel) => novel.name && novel.path);
return novels;
}
+49 -34
View File
@@ -62,23 +62,20 @@ class RulatePlugin implements Plugin.PluginBase {
loadedCheerio(element).find("p > a").text();
const name = loadedCheerio(element).find("p > a").text();
const cover = loadedCheerio(element).find("img").attr("src");
const url = loadedCheerio(element).find("p > a").attr("href");
if (!name || !url) return;
const path = loadedCheerio(element).find("p > a").attr("href");
if (!name || !path) return;
novels.push({ name, cover: this.site + cover, url: this.site + url });
novels.push({ name, cover: this.site + cover, path });
});
return novels;
}
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
const novel: Plugin.SourceNovel = {
url: novelUrl,
};
let result = await fetchApi(novelUrl);
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
let result = await fetchApi(this.site + novelPath);
if (result.url.includes("mature?path=")) {
const formData = new FormData();
formData.append("path", novelUrl);
formData.append("path", novelPath);
formData.append("ok", "Да");
await fetchApi(result.url, {
@@ -86,23 +83,27 @@ class RulatePlugin implements Plugin.PluginBase {
body: formData,
});
result = await fetchApi(novelUrl);
result = await fetchApi(this.site + novelPath);
}
const body = await result.text();
const loadedCheerio = parseHTML(body);
novel.name = loadedCheerio(
'div[class="container"] > div > div > h1, div.span8:nth-child(1) > h1:nth-child(2)',
)
.text()
.trim();
const novel: Plugin.SourceNovel = {
path: novelPath,
name: loadedCheerio(
'div[class="container"] > div > div > h1, div.span8:nth-child(1) > h1:nth-child(2)',
)
.text()
.trim(),
};
if (novel.name?.includes?.("[")) {
novel.name = novel.name.split("[")[0].trim();
}
novel.cover =
this.site + loadedCheerio('div[class="images"] > div img').attr("src");
novel.summary =
loadedCheerio("#Info > div:nth-child(3), .book-description").text().trim();
novel.summary = loadedCheerio("#Info > div:nth-child(3), .book-description")
.text()
.trim();
novel.author = loadedCheerio(
".book-stats-icons_author > span:nth-child(2) > a:nth-child(1)",
).text();
@@ -143,27 +144,41 @@ class RulatePlugin implements Plugin.PluginBase {
}
const chapters: Plugin.ChapterItem[] = [];
loadedCheerio("table > tbody > tr.chapter_row").each((chapterIndex, element) => {
const chapterName = loadedCheerio(element).find('td[class="t"] > a').text().trim();
const releaseDate = loadedCheerio(element).find("td > span").attr("title")?.trim();
const chapterUrl = loadedCheerio(element).find('td[class="t"] > a').attr("href");
loadedCheerio("table > tbody > tr.chapter_row").each(
(chapterIndex, element) => {
const chapterName = loadedCheerio(element)
.find('td[class="t"] > a')
.text()
.trim();
const releaseDate = loadedCheerio(element)
.find("td > span")
.attr("title")
?.trim();
const chapterUrl = loadedCheerio(element)
.find('td[class="t"] > a')
.attr("href");
if (!loadedCheerio(element).find('td > span[class="disabled"]').length && releaseDate) {
chapters.push({
name: chapterName,
url: this.site + chapterUrl,
releaseTime: this.parseDate(releaseDate),
chapterNumber: chapterIndex + 1,
});
}
});
if (
!loadedCheerio(element).find('td > span[class="disabled"]').length &&
releaseDate &&
chapterUrl
) {
chapters.push({
name: chapterName,
path: chapterUrl,
releaseTime: this.parseDate(releaseDate),
chapterNumber: chapterIndex + 1,
});
}
},
);
novel.chapters = chapters;
return novel;
}
async parseChapter(chapterUrl: string): Promise<string> {
let result = await fetchApi(chapterUrl);
async parseChapter(chapterPath: string): Promise<string> {
let result = await fetchApi(this.site + chapterPath);
if (result.url.includes("mature?path=")) {
const formData = new FormData();
formData.append("ok", "Да");
@@ -173,7 +188,7 @@ class RulatePlugin implements Plugin.PluginBase {
body: formData,
});
result = await fetchApi(chapterUrl);
result = await fetchApi(this.site + chapterPath);
}
const body = await result.text();
const loadedCheerio = parseHTML(body);
@@ -203,7 +218,7 @@ class RulatePlugin implements Plugin.PluginBase {
novels.push({
name,
cover: this.site + novel.img,
url: this.site + novel.url,
path: novel.url,
});
});
@@ -1,30 +0,0 @@
import { ScrpitGeneratorFunction } from "../generate";
import { WPmangaStreamMetadata } from "./template";
import list from "./sources.json";
import { readFileSync } from "fs";
import path from "path";
export const generateAll: ScrpitGeneratorFunction = function (name) {
return list.map((source) => {
console.log(`[${name}] Generating:`, source.id);
return generator(source);
});
};
const generator = function generator(source: WPmangaStreamMetadata) {
const WPmangaStreamTemplate = readFileSync(path.join(__dirname, "template.ts"),{
encoding: "utf-8",
});
const pluginScript = `
${WPmangaStreamTemplate}
const plugin = new WPmangaStreamPlugin(${JSON.stringify(source)});
export default plugin;
`.trim();
return {
lang: source.options?.lang || "English",
filename: source.sourceName,
pluginScript,
};
};
@@ -1,20 +0,0 @@
[
{
"id": "lightnovelfr",
"sourceSite": "https://lightnovelfr.com/",
"sourceName": "Ligh Novel FR",
"options": {
"lang": "French",
"reverseChapters": true
}
},
{
"id": "kol-novel",
"sourceSite": "https://kolnovel.com/",
"sourceName": "Kol Novel",
"options": {
"lang": "arabic",
"reverseChapters": true
}
}
]
-149
View File
@@ -1,149 +0,0 @@
import { CheerioAPI, load } from "cheerio";
import { fetchApi, fetchFile } from "@libs/fetch";
import { Plugin } from "@typings/plugin";
import { NovelStatus } from "@libs/novelStatus";
interface WPmangaStreamOptions {
reverseChapters?: boolean;
lang?: string;
}
export interface WPmangaStreamMetadata {
id: string;
sourceSite: string;
sourceName: string;
options?: WPmangaStreamOptions;
}
class WPmangaStreamPlugin implements Plugin.PluginBase {
id: string;
name: string;
icon: string;
site: string;
version: string;
options?: WPmangaStreamOptions;
constructor(metadata: WPmangaStreamMetadata) {
this.id = metadata.id;
this.name = metadata.sourceName;
this.icon = `multisrc/wpmangastream/icons/${metadata.id}.png`;
this.site = metadata.sourceSite;
this.version = "1.0.0";
this.options = metadata.options;
}
async popularNovels(page: number): Promise<Plugin.NovelItem[]> {
const url = this.site + "series/?page=" + page + "&status=&order=popular";
const body = await fetchApi(url).then((res) => res.text());
const loadedCheerio = load(body);
const novels: Plugin.NovelItem[] = [];
loadedCheerio("article.maindet").each(function () {
const name = loadedCheerio(this).find("h2").text();
const image = loadedCheerio(this).find("img");
const url = loadedCheerio(this).find("h2 a").attr("href");
if (!url) return;
novels.push({
name,
cover: image.attr("data-src") || image.attr("src"),
url,
});
});
return novels;
}
async parseNovelAndChapters(url: string): Promise<Plugin.SourceNovel> {
const body = await fetchApi(url).then((res) => res.text());
const loadedCheerio = load(body);
const novel: Plugin.SourceNovel = { url };
// novel.url = url;
novel.name = loadedCheerio("h1.entry-title").text();
novel.cover =
loadedCheerio("img.wp-post-image").attr("data-src") ||
loadedCheerio("img.wp-post-image").attr("src");
novel.status = loadedCheerio("div.sertostat > span").attr("class");
loadedCheerio("div.serl > span").each(function () {
const detailName = loadedCheerio(this).text().trim();
const detail = loadedCheerio(this).next().text().trim();
switch (detailName) {
case "الكاتب":
case "Author":
case "Auteur":
novel.author = detail;
break;
}
});
novel.genres = loadedCheerio(".sertogenre")
.children("a")
.map((i, el) => loadedCheerio(el).text())
.toArray()
.join(",");
let summary = loadedCheerio(".sersys > p").siblings().remove("div").end();
novel.summary = "";
for (let i = 0; i < summary.length; i++) {
const p = summary[i];
novel.summary += loadedCheerio(p).text().trim() + "\\n\\n";
}
const totalChapters = loadedCheerio(".eplister li").length;
const chapters: Plugin.ChapterItem[] = loadedCheerio(".eplister li")
.map((chapterIndex, element) => ({
name:
loadedCheerio(element).find(".epl-num").text() +
loadedCheerio(element).find(".epl-title").text(),
url: loadedCheerio(element).find("a").attr("href") || "",
releaseTime: loadedCheerio(element).find(".epl-date").text().trim(),
chapterNumber: this.options?.reverseChapters
? totalChapters - chapterIndex
: chapterIndex + 1,
}))
.get()
.filter((chapter) => chapter.name && chapter.url);
if (this.options?.reverseChapters && chapters.length) chapters.reverse();
novel.chapters = chapters;
return novel;
}
async parseChapter(chapterUrl: string): Promise<string> {
const body = await fetchApi(chapterUrl).then((res) => res.text());
const loadedCheerio = load(body);
const chapterText = loadedCheerio(".epcontent").html() || "";
return chapterText;
}
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
const url = this.site + "?s=" + searchTerm;
const body = await fetchApi(url).then((res) => res.text());
const loadedCheerio = load(body);
const novels: Plugin.NovelItem[] = [];
loadedCheerio("article.maindet").each(function () {
const name = loadedCheerio(this).find("h2").text();
const image = loadedCheerio(this).find("img");
const url = loadedCheerio(this).find("h2 a").attr("href");
if (!url) return;
novels.push({
name,
cover: image.attr("data-src") || image.attr("src"),
url,
});
});
return novels;
}
fetchImage = fetchFile;
}