fix: Update Plugins (#677)

* typo

* update

* fix

* fix

* update

* ifreedom

* fix

* fix ranobelib

* ...
This commit is contained in:
Rider21
2024-01-07 14:25:02 +03:00
committed by GitHub
parent 8a267561cb
commit f36e93d0a6
154 changed files with 68923 additions and 58660 deletions
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+145 -159
View File
@@ -1,166 +1,152 @@
import { CheerioAPI, load } from "cheerio";
import { fetchApi, fetchFile } from "@libs/fetch";
import { Plugin } from "@typings/plugin";
import { NovelStatus } from "@libs/novelStatus";
class LightNovelFRPlugin implements Plugin.PluginBase {
id: string;
name: string;
icon: string;
site: string;
version: string;
userAgent: string;
cookieString: string;
constructor() {
this.id = "kol-novel";
this.name = "Kol Novel[wpmangastream]";
this.icon = "multisrc/wpmangastream/icons/kol-novel.png";
this.site = "https://kolnovel.com/";
this.version = "1.0.0";
this.userAgent = "''";
this.cookieString = "''";
}
async popularNovels(page: number): Promise<Plugin.NovelItem[]> {
let url = this.site + 'series/?page=' + page + '&status=&order=popular';
const body = await(await fetchApi(url, {})).text();
const loadedCheerio = load(body);
let novels: Plugin.NovelItem[] = [];
loadedCheerio('article.maindet').each(function () {
const novelName = loadedCheerio(this).find('h2').text();
let image = loadedCheerio(this).find('img');
const novelCover = image.attr('data-src') || image.attr('src');
const novelUrl = loadedCheerio(this).find('h2 a').attr('href');
if (!novelUrl) return;
const novel = {
name: novelName,
cover: novelCover,
url: novelUrl,
};
novels.push(novel);
});
return novels;
}
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
const url = novelUrl;
const body = await(await fetchApi(url, {})).text();
let 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";
}
let novelChapters:Plugin.ChapterItem[] = [];
loadedCheerio('.eplister')
.find('li')
.each(function () {
const chapterName =
loadedCheerio(this).find('.epl-num').text() +
' - ' +
loadedCheerio(this).find('.epl-title').text();
const releaseDate = loadedCheerio(this).find('.epl-date').text().trim();
const chapterUrl = loadedCheerio(this).find('a').attr('href');
if (!chapterUrl) return;
const chapter = {
name: chapterName,
url: chapterUrl,
releaseDate,
};
novelChapters.push(chapter);
});
novel.chapters = novelChapters;
if (novel.chapters) novel.chapters.reverse()
return novel;
}
async parseChapter(chapterUrl: string): Promise<string> {
const url = chapterUrl;
const body = await(await fetchApi(url, {})).text();
const loadedCheerio = load(body);
let chapterText = loadedCheerio('.epcontent').html() || '';
return chapterText;
};
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
const url = this.site + "?s=" + searchTerm;
const body = await(await fetchApi(url, {})).text();
const loadedCheerio = load(body);
let novels: Plugin.NovelItem[] = [];
loadedCheerio('article.maindet').each(function () {
const novelName = loadedCheerio(this).find('h2').text();
let image = loadedCheerio(this).find('img');
const novelCover = image.attr('data-src') || image.attr('src');
const novelUrl = loadedCheerio(this).find('h2 a').attr('href');
if (!novelUrl) return;
const novel = {
name: novelName,
cover: novelCover,
url: novelUrl,
};
novels.push(novel);
});
return novels;
};
async fetchImage(url: string): Promise<string | undefined>{
return await fetchFile(url);
}
interface WPmangaStreamOptions {
reverseChapters?: boolean;
lang?: string;
}
export default new LightNovelFRPlugin();
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;
userAgent: string;
cookieString: string;
options?: WPmangaStreamOptions;
constructor(metadata: WPmangaStreamMetadata) {
this.id = metadata.id;
this.name = metadata.sourceName + "[wpmangastream]";
this.icon = `multisrc/wpmangastream/icons/${metadata.id}.png`;
this.site = metadata.sourceSite;
this.version = "1.0.0";
this.userAgent = "";
this.cookieString = "";
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 chapters: Plugin.ChapterItem[] = loadedCheerio(".eplister li")
.map((index, element) => ({
name:
loadedCheerio(element).find(".epl-num").text() +
loadedCheerio(element).find(".epl-title").text(),
releaseTime: loadedCheerio(element).find(".epl-date").text().trim(),
url: loadedCheerio(element).find("a").attr("href") || "",
}))
.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;
}
const plugin = new WPmangaStreamPlugin({"id":"kol-novel","sourceSite":"https://kolnovel.com/","sourceName":"Kol Novel","options":{"lang":"arabic","reverseChapters":true}});
export default plugin;
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
File diff suppressed because one or more lines are too long
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
-166
View File
@@ -1,166 +0,0 @@
import { load as parseHTML } from "cheerio";
import { fetchApi, fetchFile } from "@libs/fetch";
import { Novel, Plugin } from "@typings/plugin";
export const id = "FWN.com";
export const name = "Web NOVEL";
export const site = "https://freewebnovel.com/";
export const version = "1.0.0";
export const icon = "src/en/freewebnovel/icon.png";
const baseUrl = site;
export const popularNovels: Plugin.popularNovels = async function (page) {
let url = baseUrl + "completed-novel/" + page;
const result = await fetchApi(url);
const body = await result.text();
const loadedCheerio = parseHTML(body);
let novels: Novel.Item[] = [];
loadedCheerio(".li-row").each(function () {
const novelName = loadedCheerio(this).find(".tit").text();
const novelCover = loadedCheerio(this).find("img").attr("src");
let novelUrl =
"https://freewebnovel.com" +
loadedCheerio(this).find("h3 > a").attr("href");
const novel = {
name: novelName,
cover: novelCover,
url: novelUrl,
};
novels.push(novel);
});
return novels;
};
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
async function (novelUrl) {
const url = novelUrl;
const result = await fetchApi(url);
const body = await result.text();
const loadedCheerio = parseHTML(body);
let novel: Novel.instance = {
url,
};
novel.name = loadedCheerio("h1.tit").text();
novel.cover = loadedCheerio(".pic > img").attr("src");
novel.genres = loadedCheerio("[title=Genre]")
.next()
.text()
.replace(/[\t\n]/g, "");
novel.author = loadedCheerio("[title=Author]")
.next()
.text()
.replace(/[\t\n]/g, "");
novel.status = loadedCheerio("[title=Status]")
.next()
.text()
.replace(/[\t\n]/g, "");
let novelSummary = loadedCheerio(".inner").text().trim();
novel.summary = novelSummary;
let novelChapters = [];
let latestChapter: string | undefined;
loadedCheerio("h3.tit").each(function (res) {
if (loadedCheerio(this).find("a").text() === novel.name) {
latestChapter = loadedCheerio(this)
.next()
.find("span.s3")
.text()
.match(/(\d+)/)?.[0];
}
});
let prefixUrl = novelUrl.replace(".html", "/");
for (let i = 1; i <= parseInt(latestChapter || "0", 10); i++) {
const chapterName = "Chapter " + i;
const releaseDate = null;
let chapterUrl = "chapter-" + i;
chapterUrl = `${prefixUrl}${chapterUrl}.html`;
const chapter = {
name: chapterName,
releaseTime: releaseDate,
url: chapterUrl,
};
novelChapters.push(chapter);
}
novel.chapters = novelChapters;
return novel;
};
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
const url = chapterUrl;
const result = await fetchApi(url);
const body = await result.text();
const loadedCheerio = parseHTML(body);
let chapterText = loadedCheerio("div.txt").html();
return chapterText;
};
export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
const url = baseUrl + "search/";
const formData = {
searchkey: searchTerm,
};
const result = await fetchApi(url, {
method: "POST",
body: JSON.stringify(formData),
});
const body = await result.text();
const loadedCheerio = parseHTML(body);
let novels: Novel.Item[] = [];
loadedCheerio(".li-row > .li > .con").each(function () {
const novelName = loadedCheerio(this).find(".tit").text();
const novelCover = loadedCheerio(this)
.find(".pic > a > img")
.attr("data-cfsrc");
let novelUrl =
"https://freewebnovel.com" +
loadedCheerio(this).find("h3 > a").attr("href");
const novel = {
name: novelName,
cover: novelCover,
url: novelUrl,
};
novels.push(novel);
});
return novels;
};
export const fetchImage: Plugin.fetchImage = fetchFile;
+104
View File
@@ -0,0 +1,104 @@
import { Plugin } from "@typings/plugin";
import { fetchApi, fetchFile } from "@libs/fetch";
import { NovelStatus } from "@libs/novelStatus";
import { load as parseHTML } from "cheerio";
class FreeWebNovel implements Plugin.PluginBase {
id = "FWN.com";
name = "Web NOVEL";
site = "https://freewebnovel.com";
version = "1.0.0";
icon = "src/en/freewebnovel/icon.png";
userAgent = "";
cookieString = "";
async popularNovels(
page: number,
{ showLatestNovels }: Plugin.PopularNovelsOptions,
): Promise<Plugin.NovelItem[]> {
const sort = showLatestNovels
? "/latest-release-novels/"
: "/completed-novels/";
const body = await fetchApi(this.site + sort + page).then((res) => res.text());
const loadedCheerio = parseHTML(body);
const novels: Plugin.NovelItem[] = loadedCheerio(".li-row")
.map((index, element) => ({
name: loadedCheerio(element).find(".tit").text(),
cover: loadedCheerio(element).find("img").attr("src"),
url: this.site + loadedCheerio(element).find("h3 > a").attr("href"),
}))
.get();
return novels;
}
async parseNovelAndChapters(url: string): Promise<Plugin.SourceNovel> {
const body = await fetchApi(url).then((res) => res.text());
const loadedCheerio = parseHTML(body);
const novel: Plugin.SourceNovel = { url };
novel.name = loadedCheerio("h1.tit").text();
novel.cover = loadedCheerio(".pic > img").attr("src");
novel.genres = loadedCheerio("[title=Genre]")
.next()
.text()
.replace(/[\t\n]/g, "");
novel.author = loadedCheerio("[title=Author]")
.next()
.text()
.replace(/[\t\n]/g, "");
novel.status = loadedCheerio("[title=Status]")
.next()
.text()
.replace(/[\t\n]/g, "");
novel.summary = loadedCheerio(".inner").text().trim();
const chapters: Plugin.ChapterItem[] = loadedCheerio("#idData > li > a")
.map((index, element) => ({
name: loadedCheerio(element).attr("title") || "Chapter " + index,
releaseTime: null,
url: this.site + loadedCheerio(element).attr("href"),
}))
.get();
novel.chapters = chapters;
return novel;
}
async parseChapter(chapterUrl: string): Promise<string> {
const body = await fetchApi(chapterUrl).then((res) => res.text());
const loadedCheerio = parseHTML(body);
const chapterText = loadedCheerio("div.txt").html() || "";
return chapterText;
}
async searchNovels(
searchkey: string,
): Promise<Plugin.NovelItem[]> {
const body = await fetchApi(this.site + "/search/", {
method: "POST",
body: JSON.stringify({ searchkey }),
}).then((res) => res.text());
const loadedCheerio = parseHTML(body);
const novels: Plugin.NovelItem[] = loadedCheerio(".li-row > .li > .con")
.map((index, element) => ({
name: loadedCheerio(element).find(".tit").text(),
cover: loadedCheerio(element).find(".pic > a > img").attr("data-cfsrc"),
url: this.site + loadedCheerio(element).find("h3 > a").attr("href"),
}))
.get();
return novels;
}
fetchImage = fetchFile;
}
export default new FreeWebNovel();
+145 -159
View File
@@ -1,166 +1,152 @@
import { CheerioAPI, load } from "cheerio";
import { fetchApi, fetchFile } from "@libs/fetch";
import { Plugin } from "@typings/plugin";
import { NovelStatus } from "@libs/novelStatus";
class LightNovelFRPlugin implements Plugin.PluginBase {
id: string;
name: string;
icon: string;
site: string;
version: string;
userAgent: string;
cookieString: string;
constructor() {
this.id = "lightnovelfr";
this.name = "Ligh Novel FR[wpmangastream]";
this.icon = "multisrc/wpmangastream/icons/lightnovelfr.png";
this.site = "https://lightnovelfr.com/";
this.version = "1.0.0";
this.userAgent = "''";
this.cookieString = "''";
}
async popularNovels(page: number): Promise<Plugin.NovelItem[]> {
let url = this.site + 'series/?page=' + page + '&status=&order=popular';
const body = await(await fetchApi(url, {})).text();
const loadedCheerio = load(body);
let novels: Plugin.NovelItem[] = [];
loadedCheerio('article.maindet').each(function () {
const novelName = loadedCheerio(this).find('h2').text();
let image = loadedCheerio(this).find('img');
const novelCover = image.attr('data-src') || image.attr('src');
const novelUrl = loadedCheerio(this).find('h2 a').attr('href');
if (!novelUrl) return;
const novel = {
name: novelName,
cover: novelCover,
url: novelUrl,
};
novels.push(novel);
});
return novels;
}
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
const url = novelUrl;
const body = await(await fetchApi(url, {})).text();
let 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";
}
let novelChapters:Plugin.ChapterItem[] = [];
loadedCheerio('.eplister')
.find('li')
.each(function () {
const chapterName =
loadedCheerio(this).find('.epl-num').text() +
' - ' +
loadedCheerio(this).find('.epl-title').text();
const releaseDate = loadedCheerio(this).find('.epl-date').text().trim();
const chapterUrl = loadedCheerio(this).find('a').attr('href');
if (!chapterUrl) return;
const chapter = {
name: chapterName,
url: chapterUrl,
releaseDate,
};
novelChapters.push(chapter);
});
novel.chapters = novelChapters;
if (novel.chapters) novel.chapters.reverse()
return novel;
}
async parseChapter(chapterUrl: string): Promise<string> {
const url = chapterUrl;
const body = await(await fetchApi(url, {})).text();
const loadedCheerio = load(body);
let chapterText = loadedCheerio('.epcontent').html() || '';
return chapterText;
};
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
const url = this.site + "?s=" + searchTerm;
const body = await(await fetchApi(url, {})).text();
const loadedCheerio = load(body);
let novels: Plugin.NovelItem[] = [];
loadedCheerio('article.maindet').each(function () {
const novelName = loadedCheerio(this).find('h2').text();
let image = loadedCheerio(this).find('img');
const novelCover = image.attr('data-src') || image.attr('src');
const novelUrl = loadedCheerio(this).find('h2 a').attr('href');
if (!novelUrl) return;
const novel = {
name: novelName,
cover: novelCover,
url: novelUrl,
};
novels.push(novel);
});
return novels;
};
async fetchImage(url: string): Promise<string | undefined>{
return await fetchFile(url);
}
interface WPmangaStreamOptions {
reverseChapters?: boolean;
lang?: string;
}
export default new LightNovelFRPlugin();
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;
userAgent: string;
cookieString: string;
options?: WPmangaStreamOptions;
constructor(metadata: WPmangaStreamMetadata) {
this.id = metadata.id;
this.name = metadata.sourceName + "[wpmangastream]";
this.icon = `multisrc/wpmangastream/icons/${metadata.id}.png`;
this.site = metadata.sourceSite;
this.version = "1.0.0";
this.userAgent = "";
this.cookieString = "";
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 chapters: Plugin.ChapterItem[] = loadedCheerio(".eplister li")
.map((index, element) => ({
name:
loadedCheerio(element).find(".epl-num").text() +
loadedCheerio(element).find(".epl-title").text(),
releaseTime: loadedCheerio(element).find(".epl-date").text().trim(),
url: loadedCheerio(element).find("a").attr("href") || "",
}))
.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;
}
const plugin = new WPmangaStreamPlugin({"id":"lightnovelfr","sourceSite":"https://lightnovelfr.com/","sourceName":"Ligh Novel FR","options":{"lang":"French","reverseChapters":true}});
export default plugin;
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+167
View File
@@ -0,0 +1,167 @@
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 { defaultCover } from "@libs/defaultCover";
export interface IfreedomMetadata {
id: string;
sourceSite: string;
sourceName: string;
filters?: Filters;
}
class IfreedomPlugin implements Plugin.PluginBase {
id: string;
name: string;
icon: string;
site: string;
version: string;
userAgent: string;
cookieString: string;
filters?: Filters;
constructor(metadata: IfreedomMetadata) {
this.id = metadata.id;
this.name = metadata.sourceName + "[ifreedom]";
this.icon = `multisrc/ifreedom/icons/${metadata.id}.png`;
this.site = metadata.sourceSite;
this.version = "1.0.0";
this.userAgent = "";
this.cookieString = "";
this.filters = metadata.filters;
}
async popularNovels(
page: number,
{ filters, showLatestNovels }: Plugin.PopularNovelsOptions,
): Promise<Plugin.NovelItem[]> {
let url = this.site + "/vse-knigi/?sort=" +
(showLatestNovels ? "По дате обновления" : filters?.sort?.value || "По рейтингу");
if (filters?.status?.value instanceof Array) {
url += filters.status.value.map((i) => "&status[]=" + i).join("");
}
if (filters?.lang?.value instanceof Array) {
url += filters.lang.value.map((i) => "&lang[]=" + i).join("");
}
if (filters?.genre?.value instanceof Array) {
url += filters.genre.value.map((i) => "&genre[]=" + i).join("");
}
url += "&bpage=" + page;
const body = await fetchApi(url).then((res) => res.text());
const loadedCheerio = parseHTML(body);
const novels: Plugin.NovelItem[] = loadedCheerio(
"div.one-book-home > div.img-home a",
)
.map((index, element) => ({
name: loadedCheerio(element).attr("title") || "",
cover: loadedCheerio(element).find("img").attr("src"),
url: loadedCheerio(element).attr("href") || "",
}))
.get()
.filter((novel) => novel.name && novel.url);
return novels;
}
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
const body = await fetchApi(novelUrl).then((res) => res.text());
const loadedCheerio = parseHTML(body);
const novel: Plugin.SourceNovel = {
url: novelUrl,
name: loadedCheerio(".entry-title").text(),
cover: loadedCheerio(".img-ranobe > img").attr("src"),
summary: loadedCheerio('meta[name="description"]').attr("content"),
};
loadedCheerio("div.data-ranobe").each(function () {
switch (loadedCheerio(this).find("b").text()) {
case "Автор":
novel.author = loadedCheerio(this)
.find("div.data-value")
.text()
.trim();
break;
case "Жанры":
novel.genres = loadedCheerio("div.data-value > a")
.map((index, element) => loadedCheerio(element).text()?.trim())
.get()
.join(",");
break;
case "Статус книги":
novel.status = loadedCheerio("div.data-value")
.text()
.includes("активен")
? NovelStatus.Ongoing
: NovelStatus.Completed;
break;
}
});
if (novel.author == "Не указан") delete novel.author;
const chapters: Plugin.ChapterItem[] = [];
loadedCheerio("div.li-ranobe").each(function () {
const name = loadedCheerio(this).find("a").text();
const url = loadedCheerio(this).find("a").attr("href");
if (!loadedCheerio(this).find("label.buy-ranobe").length && name && url) {
const releaseTime = loadedCheerio(this)
.find("div.li-col2-ranobe").text().trim();
chapters.push({ name, releaseTime, url });
}
});
novel.chapters = chapters.reverse();
return novel;
}
async parseChapter(chapterUrl: string): Promise<string> {
const body = await fetchApi(chapterUrl).then((res) => res.text());
const loadedCheerio = parseHTML(body);
loadedCheerio(".entry-content img").each(function () {
const srcset = loadedCheerio(this).attr("srcset")?.split?.(" ");
if (srcset?.length) {
const bestlink = srcset.filter((url) => url.startsWith("http")).unshift();
if (typeof bestlink == 'string') loadedCheerio(this).attr("src", bestlink);
}
});
const chapterText = loadedCheerio(".entry-content").html() || "";
return chapterText;
}
async searchNovels(
searchTerm: string,
page: number | undefined = 1,
): Promise<Plugin.NovelItem[]> {
const url =
this.site + "/vse-knigi/?searchname=" + searchTerm + "&bpage=" + page;
const result = await fetchApi(url).then((res) => res.text());
const loadedCheerio = parseHTML(result);
const novels: Plugin.NovelItem[] = loadedCheerio(
"div.one-book-home > div.img-home a",
)
.map((index, element) => ({
name: loadedCheerio(element).attr("title") || "",
cover: loadedCheerio(element).find("img").attr("src"),
url: loadedCheerio(element).attr("href") || "",
}))
.get()
.filter((novel) => novel.name && novel.url);
return novels;
}
fetchImage = fetchFile;
}
const plugin = new IfreedomPlugin({"id":"bookhamster","sourceSite":"https://bookhamster.ru","sourceName":"Bookhamster","filters":{"sort":{"type":FilterTypes.Picker,"label":"Сортировка:","options":[{"label":"По дате добавления","value":"По дате добавления"},{"label":"По дате обновления","value":"По дате обновления"},{"label":"По количеству глав","value":"По количеству глав"},{"label":"По названию","value":"По названию"},{"label":"По просмотрам","value":"По просмотрам"},{"label":"По рейтингу","value":"По рейтингу"}],"value":"По рейтингу"},"status":{"type":FilterTypes.CheckboxGroup,"label":"Статус:","options":[{"label":"Перевод активен","value":"Перевод активен"},{"label":"Перевод приостановлен","value":"Перевод приостановлен"},{"label":"Произведение завершено","value":"Произведение завершено"}],"value":[]},"lang":{"type":FilterTypes.CheckboxGroup,"label":"Язык:","options":[{"label":"Английский","value":"Английский"},{"label":"Китайский","value":"Китайский"},{"label":"Корейский","value":"Корейский"},{"label":"Японский","value":"Японский"}],"value":[]},"genre":{"type":FilterTypes.CheckboxGroup,"label":"Жанры:","options":[{"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":"Милитари"},{"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":"Триллер"},{"label":"Ужасы","value":"Ужасы"},{"label":"Фантастика","value":"Фантастика"},{"label":"Фэнтези","value":"Фэнтези"},{"label":"Школьная жизнь","value":"Школьная жизнь"},{"label":"Экшн","value":"Экшн"},{"label":"Эротика","value":"Эротика"},{"label":"Этти","value":"Этти"},{"label":"Яой","value":"Яой"},{"label":"Adult","value":"Adult"},{"label":"Mature","value":"Mature"},{"label":"Xianxia","value":"Xianxia"},{"label":"Xuanhuan","value":"Xuanhuan"}],"value":[]}}});
export default plugin;
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
import { Plugin } from "@typings/plugin";
import { FilterInputs } from "@libs/filterInputs";
import { FilterTypes, Filters } from "@libs/filterInputs";
import { fetchApi, fetchFile } from "@libs/fetch";
import { NovelStatus } from "@libs/novelStatus";
import { load as parseHTML } from "cheerio";
@@ -14,19 +14,26 @@ class freedlit implements Plugin.PluginBase {
cookieString = "";
async popularNovels(
pageNo: number,
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
page: number,
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
): Promise<Plugin.NovelItem[]> {
let url = this.site + "/books/";
url += (filters?.genre || "all") + "?sort=";
url += showLatestNovels ? "recent" : filters?.sort || "popular";
url += "&status=" + (filters?.status || "all");
url += "&access=" + (filters?.access || "all");
url += "&adult=" + (filters?.adult || "hide");
url += "&page=" + pageNo;
let url = this.site + "/get-books/all/list/" + page + "?sort=";
url += showLatestNovels ? "recent" : filters?.sort?.value || "popular";
url += "&status=" + (filters?.status?.value || "all");
url += "&access=" + (filters?.access?.value || "all");
url += "&adult=" + (filters?.adult?.value || "hide");
const result = await fetchApi(url);
const body = await result.text();
if (filters?.genre?.value?.include?.length) {
url += filters.genre.value.include
.map(id => '&genres_included[]=' + id).join("");
}
if (filters?.genre?.value?.exclude?.length) {
url += filters.genre.value.exclude
.map(id => '&genres_excluded[]=' + id).join("");
}
const body = await fetchApi(url).then((res) => res.text());
const loadedCheerio = parseHTML(body);
const novels: Plugin.NovelItem[] = [];
@@ -46,16 +53,15 @@ class freedlit implements Plugin.PluginBase {
}
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
const result = await fetchApi(novelUrl);
const body = await result.text();
const body = await fetchApi(novelUrl).then((res) => res.text());
const loadedCheerio = parseHTML(body);
const novel: Plugin.SourceNovel = {
url: novelUrl,
name: loadedCheerio(".book-info > h4").text(),
name: loadedCheerio(".book-info h4").text(),
cover: loadedCheerio(".book-cover > div > img").attr("src")?.trim(),
summary: loadedCheerio("#nav-home").text()?.trim(),
author: loadedCheerio(".book-info > h5 > a").text(),
author: loadedCheerio(".book-info h5 > a").text(),
genres: loadedCheerio(".genre-list > a")
.map((index, element) => loadedCheerio(element).text())
.get()
@@ -64,10 +70,10 @@ class freedlit implements Plugin.PluginBase {
const chapters: Plugin.ChapterItem[] = [];
loadedCheerio("#nav-contents > div").each(function () {
const name = loadedCheerio(this).find("a").text();
loadedCheerio("a.chapter-line").each(function () {
const name = loadedCheerio(this).find("h6").text();
const releaseTime = loadedCheerio(this).find('span[class="date"]').text();
const url = loadedCheerio(this).find("a").attr("href");
const url = loadedCheerio(this).attr("href");
if (!name || !url) return;
chapters.push({ name, releaseTime, url });
@@ -78,24 +84,19 @@ class freedlit implements Plugin.PluginBase {
}
async parseChapter(chapterUrl: string): Promise<string> {
const result = await fetchApi(chapterUrl);
const body = await result.text();
const body = await fetchApi(chapterUrl).then((res) => res.text());
const loadedCheerio = parseHTML(body);
loadedCheerio('div[class="standart-block"]').remove();
loadedCheerio('div[class="mobile-block"]').remove();
const chapterText = loadedCheerio('div[class="chapter"]').html();
return chapterText || "";
loadedCheerio("div.mobile-block").remove();
loadedCheerio("div.standart-block").remove();
const chapterText = loadedCheerio('div[class="chapter"]').html() || "";
return chapterText;
}
async searchNovels(
searchTerm: string,
//page: number | undefined = 1,
): Promise<Plugin.NovelItem[]> {
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
const url = `${this.site}/search?query=${searchTerm}&type=all`;
const result = await fetchApi(url);
const body = await result.text();
const body = await fetchApi(url).then((res) => res.text());
const loadedCheerio = parseHTML(body);
const novels: Plugin.NovelItem[] = [];
@@ -112,23 +113,23 @@ class freedlit implements Plugin.PluginBase {
}
fetchImage = fetchFile;
filters = [
{
key: "sort",
filters = {
sort: {
label: "Сортировка:",
values: [
value: "",
options: [
{ label: "По популярности", value: "popular" },
{ label: "По количеству комментариев", value: "comments" },
{ label: "По количеству лайков", value: "likes" },
{ label: "По новизне", value: "recent" },
{ label: "По просмотрам", value: "views" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
{
key: "genre",
genre: {
label: "Жанры:",
values: [
value: { include: [], exclude: [] },
options: [
{ label: "Любой жанр", value: "all" },
{ label: "Альтернативная история", value: "alternative-history" },
{ label: "Антиутопия", value: "dystopia" },
@@ -146,10 +147,7 @@ class freedlit implements Plugin.PluginBase {
{ label: "Документальная проза", value: "biography" },
{ label: "Историческая проза", value: "historical-fiction" },
{ label: "Исторический детектив", value: "historical-mystery" },
{
label: "Исторический любовный роман",
value: "historical-romantic-novel",
},
{ label: "Исторический любовный роман", value: "historical-romantic-novel" },
{ label: "Историческое фэнтези", value: "historical-fantasy" },
{ label: "Киберпанк", value: "cyberpunk" },
{ label: "Космическая фантастика", value: "cosmic-fiction" },
@@ -207,38 +205,38 @@ class freedlit implements Plugin.PluginBase {
{ label: "Юмористическое фэнтези", value: "humor-fantasy" },
{ label: "RPS", value: "rps" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.ExcludableCheckboxGroup,
},
{
key: "status",
status: {
label: "Статус:",
values: [
value: "",
options: [
{ label: "Любой статус", value: "all" },
{ label: "В процессе", value: "in-process" },
{ label: "Завершено", value: "finished" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
{
key: "access",
access: {
label: "Доступ:",
values: [
value: "",
options: [
{ label: "Любой доступ", value: "all" },
{ label: "Бесплатные", value: "free" },
{ label: "Платные", value: "paid" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
{
key: "adult",
adult: {
label: "Возрастные ограничения:",
values: [
value: "",
options: [
{ label: "Скрыть 18+", value: "hide" },
{ label: "Показать +18", value: "show" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
];
} satisfies Filters;
}
export default new freedlit();
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
import { Plugin } from "@typings/plugin";
import { FilterInputs } from "@libs/filterInputs";
import { FilterTypes, Filters } from "@libs/filterInputs";
import { defaultCover } from "@libs/defaultCover";
import { fetchApi, fetchFile } from "@libs/fetch";
import { NovelStatus } from "@libs/novelStatus";
@@ -23,18 +23,18 @@ class AuthorToday implements Plugin.PluginBase {
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
): Promise<Plugin.NovelItem[]> {
let url = apiUrl + "v1/catalog/search?page=" + pageNo;
if (filters?.genre) {
url += "&genre=" + filters.genre;
if (filters?.genre?.value) {
url += "&genre=" + filters.genre.value;
}
url +=
"&sorting=" + (showLatestNovels ? "recent" : filters?.sort || "popular");
"&sorting=" + (showLatestNovels ? "recent" : filters?.sort?.value || "popular");
url += "&form=" + (filters?.form || "any");
url += "&state=" + (filters?.state || "any");
url += "&series=" + (filters?.series || "any");
url += "&access=" + (filters?.access || "any");
url += "&promo=" + (filters?.promo || "hide");
url += "&form=" + (filters?.form?.value || "any");
url += "&state=" + (filters?.state?.value || "any");
url += "&series=" + (filters?.series?.value || "any");
url += "&access=" + (filters?.access?.value || "any");
url += "&promo=" + (filters?.promo?.value || "hide");
const result = await fetchApi(url, {
headers: {
@@ -183,13 +183,14 @@ class AuthorToday implements Plugin.PluginBase {
return novels;
}
fetchImage = fetchFile;
filters = [
{
key: "sort",
filters = {
sort: {
label: "Сортировка",
values: [
value: "",
options: [
{ label: "По популярности", value: "popular" },
{ label: "По количеству лайков", value: "likes" },
{ label: "По комментариям", value: "comments" },
@@ -197,12 +198,12 @@ class AuthorToday implements Plugin.PluginBase {
{ label: "По просмотрам", value: "views" },
{ label: "Набирающие популярность", value: "trending" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
{
key: "genre",
genre: {
label: "Жанры",
values: [
value: "",
options: [
{ label: "Альтернативная история", value: "sf-history" },
{ label: "Антиутопия", value: "dystopia" },
{ label: "Бизнес-литература", value: "biznes-literatura" },
@@ -217,15 +218,9 @@ class AuthorToday implements Plugin.PluginBase {
{ label: "Детская литература", value: "detskaya-literatura" },
{ label: "Документальная проза", value: "non-fiction" },
{ label: "Историческая проза", value: "historical-fiction" },
{
label: "Исторические приключения",
value: "historical-adventure",
},
{ label: "Исторические приключения", value: "historical-adventure" },
{ label: "Исторический детектив", value: "historical-mystery" },
{
label: "Исторический любовный роман",
value: "historical-romance",
},
{ label: "Исторический любовный роман", value: "historical-romance" },
{ label: "Историческое фэнтези", value: "historical-fantasy" },
{ label: "Киберпанк", value: "cyberpunk" },
{ label: "Короткий любовный роман", value: "short-romance" },
@@ -240,10 +235,7 @@ class AuthorToday implements Plugin.PluginBase {
{ label: "Политический роман", value: "political-fiction" },
{ label: "Попаданцы", value: "popadantsy" },
{ label: "Попаданцы в космос", value: "popadantsy-v-kosmos" },
{
label: "Попаданцы в магические миры",
value: "popadantsy-v-magicheskie-miry",
},
{ label: "Попаданцы в магические миры", value: "popadantsy-v-magicheskie-miry" },
{ label: "Попаданцы во времени", value: "popadantsy-vo-vremeni" },
{ label: "Постапокалипсис", value: "postapocalyptic" },
{ label: "Поэзия", value: "poetry" },
@@ -255,20 +247,14 @@ class AuthorToday implements Plugin.PluginBase {
{ label: "Романтическая эротика", value: "romantic-erotika" },
{ label: "Сказка", value: "fairy-tale" },
{ label: "Современная проза", value: "modern-prose" },
{
label: "Современный любовный роман",
value: "contemporary-romance",
},
{ label: "Современный любовный роман", value: "contemporary-romance" },
{ label: "Социальная фантастика", value: "sf-social" },
{ label: "Стимпанк", value: "steampunk" },
{ label: "Темное фэнтези", value: "dark-fantasy" },
{ label: "Триллер", value: "thriller" },
{ label: "Ужасы", value: "horror" },
{ label: "Фантастика", value: "sci-fi" },
{
label: "Фантастический детектив",
value: "detective-science-fiction",
},
{ label: "Фантастический детектив", value: "detective-science-fiction" },
{ label: "Фанфик", value: "fanfiction" },
{ label: "Фэнтези", value: "fantasy" },
{ label: "Шпионский детектив", value: "spy-mystery" },
@@ -281,12 +267,12 @@ class AuthorToday implements Plugin.PluginBase {
{ label: "Юмористическая фантастика", value: "sf-humor" },
{ label: "Юмористическое фэнтези", value: "ironical-fantasy" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
{
key: "form",
form: {
label: "Форма произведения",
values: [
value: "",
options: [
{ label: "Любой", value: "any" },
{ label: "Перевод", value: "translation" },
{ label: "Повесть", value: "tale" },
@@ -295,50 +281,51 @@ class AuthorToday implements Plugin.PluginBase {
{ label: "Сборник поэзии", value: "poetry" },
{ label: "Сборник рассказов", value: "story-book" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
{
key: "state",
state: {
label: "Статус произведения",
values: [
value: "",
options: [
{ label: "Любой статус", value: "any" },
{ label: "В процессе", value: "in-progress" },
{ label: "Завершено", value: "finished" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
{
key: "series",
series: {
label: "Статус цикла",
values: [
value: "",
options: [
{ label: "Не важно", value: "any" },
{ label: "Вне цикла", value: "out" },
{ label: "Цикл завершен", value: "finished" },
{ label: "Цикл не завершен", value: "unfinished" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
{
key: "access",
access: {
label: "Тип доступа",
values: [
value: "",
options: [
{ label: "Любой", value: "any" },
{ label: "Платный", value: "paid" },
{ label: "Бесплатный", value: "free" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
{
key: "promo",
promo: {
label: "Промо-фрагмент",
values: [
value: "",
options: [
{ label: "Скрывать", value: "hide" },
{ label: "Показывать", value: "show" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
];
} satisfies Filters;
}
export default new AuthorToday();
interface response {
@@ -1,5 +1,5 @@
import { Plugin } from "@typings/plugin";
import { FilterInputs } from "@libs/filterInputs";
import { FilterTypes, Filters } from "@libs/filterInputs";
import { fetchApi, fetchFile } from "@libs/fetch";
import { NovelStatus } from "@libs/novelStatus";
import { load as parseHTML } from "cheerio";
@@ -19,12 +19,12 @@ class Bookriver implements Plugin.PluginBase {
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
): Promise<Plugin.NovelItem[]> {
let url = this.site + `/genre?page=${pageNo}&perPage=24&sortingType=`;
url += showLatestNovels ? "last-update" : filters?.sort || "bestseller";
url += showLatestNovels
? "last-update"
: filters?.sort?.value || "bestseller";
if (filters) {
if (Array.isArray(filters.genres) && filters.genres.length) {
url += "&g=" + filters.genres.join(",");
}
if (filters?.genres?.value instanceof Array && filters?.genres?.value.length) {
url += "&g=" + filters.genres.value.join(",");
}
const result = await fetchApi(url);
@@ -118,56 +118,38 @@ class Bookriver implements Plugin.PluginBase {
}
fetchImage = fetchFile;
filters = [
{
key: "sort",
filters = {
sort: {
label: "Сортировка",
values: [
value: "",
options: [
{ label: "Бестселлеры", value: "bestseller" },
{ label: "Дате добавления", value: "newest" },
{ label: "Дате обновления", value: "last-update" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
{
key: "genres",
genres: {
label: "жанры",
values: [
{
label: "Альтернативная история",
value: "alternativnaya-istoriya",
},
value: [],
options: [
{ label: "Альтернативная история", value: "alternativnaya-istoriya" },
{ label: "Боевая фантастика", value: "boevaya-fantastika" },
{ label: "Боевое фэнтези", value: "boevoe-fentezi" },
{ label: "Бытовое фэнтези", value: "bytovoe-fentezi" },
{
label: "Героическая фантастика",
value: "geroicheskaya-fantastika",
},
{ label: "Героическая фантастика", value: "geroicheskaya-fantastika" },
{ label: "Героическое фэнтези", value: "geroicheskoe-fentezi" },
{ label: "Городское фэнтези", value: "gorodskoe-fentezi" },
{ label: "Детектив", value: "detektiv" },
{
label: "Детективная фантастика",
value: "detektivnaya-fantastika",
},
{ label: "Детективная фантастика", value: "detektivnaya-fantastika" },
{ label: "Жёсткая эротика", value: "zhyostkaya-erotika" },
{ label: "Исторический детектив", value: "istoricheskii-detektiv" },
{
label: "Исторический любовный роман",
value: "istoricheskii-lyubovnyi-roman",
},
{ label: "Исторический любовный роман", value: "istoricheskii-lyubovnyi-roman" },
{ label: "Историческое фэнтези", value: "istoricheskoe-fentezi" },
{ label: "Киберпанк", value: "kiberpank" },
{ label: "Классический детектив", value: "klassicheskii-detektiv" },
{
label: "Короткий любовный роман",
value: "korotkii-lyubovnyi-roman",
},
{
label: "Космическая фантастика",
value: "kosmicheskaya-fantastika",
},
{ label: "Короткий любовный роман", value: "korotkii-lyubovnyi-roman" },
{ label: "Космическая фантастика", value: "kosmicheskaya-fantastika" },
{ label: "Криминальный детектив", value: "kriminalnyi-detektiv" },
{ label: "ЛитРПГ", value: "litrpg" },
{ label: "Любовная фантастика", value: "lyubovnaya-fantastika" },
@@ -176,27 +158,15 @@ class Bookriver implements Plugin.PluginBase {
{ label: "Мистика", value: "mistika" },
{ label: "Молодежная проза", value: "molodezhnaya-proza" },
{ label: "Научная фантастика", value: "nauchnaya-fantastika" },
{
label: "Остросюжетный любовный роман",
value: "ostrosyuzhetnyi-lyubovnyi-roman",
},
{ label: "Остросюжетный любовный роман", value: "ostrosyuzhetnyi-lyubovnyi-roman" },
{ label: "Политический детектив", value: "politicheskii-detektiv" },
{ label: "Попаданцы", value: "popadantsy" },
{ label: "Постапокалипсис", value: "postapokalipsis" },
{
label: "Приключенческое фэнтези",
value: "priklyuchencheskoe-fentezi",
},
{
label: "Романтическая эротика",
value: "romanticheskaya-erotika",
},
{ label: "Приключенческое фэнтези", value: "priklyuchencheskoe-fentezi" },
{ label: "Романтическая эротика", value: "romanticheskaya-erotika" },
{ label: "С элементами эротики", value: "s-elementami-erotiki" },
{ label: "Славянское фэнтези", value: "slavyanskoe-fentezi" },
{
label: "Современный любовный роман",
value: "sovremennyi-lyubovnyi-roman",
},
{ label: "Современный любовный роман", value: "sovremennyi-lyubovnyi-roman" },
{ label: "Социальная фантастика", value: "sotsialnaya-fantastika" },
{ label: "Тёмное фэнтези", value: "temnoe-fentezi" },
{ label: "Фантастика", value: "fantastika" },
@@ -204,24 +174,15 @@ class Bookriver implements Plugin.PluginBase {
{ label: "Шпионский детектив", value: "shpionskii-detektiv" },
{ label: "Эпическое фэнтези", value: "epicheskoe-fentezi" },
{ label: "Эротика", value: "erotika" },
{
label: "Эротическая фантастика",
value: "eroticheskaya-fantastika",
},
{ label: "Эротическая фантастика", value: "eroticheskaya-fantastika" },
{ label: "Эротический фанфик", value: "eroticheskii-fanfik" },
{ label: "Эротическое фэнтези", value: "eroticheskoe-fentezi" },
{
label: "Юмористический детектив",
value: "yumoristicheskii-detektiv",
},
{
label: "Юмористическое фэнтези",
value: "yumoristicheskoe-fentezi",
},
{ label: "Юмористический детектив", value: "yumoristicheskii-detektiv" },
{ label: "Юмористическое фэнтези", value: "yumoristicheskoe-fentezi" },
],
inputType: FilterInputs.Checkbox,
type: FilterTypes.CheckboxGroup,
},
];
} satisfies Filters;
}
export default new Bookriver();
@@ -1,5 +1,5 @@
import { Plugin } from "@typings/plugin";
import { FilterInputs } from "@libs/filterInputs";
import { FilterTypes, Filters } from "@libs/filterInputs";
import { fetchApi, fetchFile } from "@libs/fetch";
import { NovelStatus } from "@libs/novelStatus";
import { load as parseHTML } from "cheerio";
@@ -21,12 +21,12 @@ class ficbook implements Plugin.PluginBase {
const baseUrl = this.site;
let url = baseUrl;
if (filters?.directions) {
url += "/popular-fanfics/" + filters.directions;
} else if (filters?.tags) {
url += "/tags/" + filters.tags + "?p=" + pageNo;
if (filters?.directions?.value) {
url += "/popular-fanfics/" + filters.directions.value;
} else if (filters?.tags?.value) {
url += "/tags/" + filters.tags.value + "?p=" + pageNo;
} else {
url += "/" + (filters?.sort || "fanfiction") + "?p=" + pageNo;
url += "/" + (filters?.sort?.value || "fanfiction") + "?p=" + pageNo;
}
const result = await fetchApi(url);
@@ -170,20 +170,21 @@ class ficbook implements Plugin.PluginBase {
}
fetchImage = fetchFile;
filters = [
{
key: "sort",
filters = {
sort: {
label: "Сортировка:",
values: [
value: "",
options: [
{ label: "Горячие работы", value: "fanfiction" },
{ label: "Популярные ", value: "popular-fanfics" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
{
key: "directions",
directions: {
label: "Направление:",
values: [
value: "",
options: [
{ label: "Все", value: "" },
{ label: "Джен", value: "gen" },
{ label: "Гет", value: "het" },
{ label: "Слэш", value: "slash-fics-3712917" },
@@ -192,12 +193,13 @@ class ficbook implements Plugin.PluginBase {
{ label: "Смешанный", value: "mixed" },
{ label: "Другой", value: "other" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
{
key: "tags",
tags: {
label: "Тэги:",
values: [
value: "",
options: [
{ label: "Все", value: "" },
{ label: "#1fic1week", value: "884" },
{ label: "#7daystowrite", value: "2961" },
{ label: "#Бинго_ТФ", value: "1458" },
@@ -1310,10 +1312,7 @@ class ficbook implements Plugin.PluginBase {
{ label: "Нетаймори", value: "2323" },
{ label: "Неторопливое повествование", value: "1815" },
{ label: "Нетрадиционное использование Силы", value: "2520" },
{
label: "Неумышленное употребление наркотических веществ",
value: "1192",
},
{ label: "Неумышленное употребление наркотических веществ", value: "1192" },
{ label: "Неуставные отношения", value: "1396" },
{ label: "Нефилимы", value: "1086" },
{ label: "Неформальный брак", value: "1355" },
@@ -1921,10 +1920,7 @@ class ficbook implements Plugin.PluginBase {
{ label: "Секс при посторонних", value: "2514" },
{ label: "Секс с воображаемыми партнерами", value: "2276" },
{ label: "Секс с использованием магии", value: "1563" },
{
label: "Секс с использованием одурманивающих веществ",
value: "2635",
},
{ label: "Секс с использованием одурманивающих веществ", value: "2635" },
{ label: "Секс с использованием посторонних предметов", value: "1661" },
{ label: "Секс с использованием сверхспособностей", value: "2610" },
{ label: "Секс со смертельным исходом", value: "1171" },
@@ -2615,9 +2611,9 @@ class ficbook implements Plugin.PluginBase {
{ label: "XVIII век", value: "248" },
{ label: "XX век", value: "1004" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
];
} satisfies Filters;
}
export default new ficbook();
@@ -1,5 +1,5 @@
import { Plugin } from "@typings/plugin";
import { FilterInputs } from "@libs/filterInputs";
import { FilterTypes, Filters } from "@libs/filterInputs";
import { fetchApi, fetchFile } from "@libs/fetch";
import { NovelStatus } from "@libs/novelStatus";
import { load as parseHTML } from "cheerio";
@@ -15,33 +15,35 @@ class Jaomix implements Plugin.PluginBase {
async popularNovels(
pageNo: number,
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
{
showLatestNovels,
filters,
}: Plugin.PopularNovelsOptions<typeof this.filters>,
): Promise<Plugin.NovelItem[]> {
let url = this.site + "/?searchrn";
if (filters?.lang instanceof Array) {
url += filters.lang.map((lang, idx) => `&lang[${idx}]=${lang}`).join("");
}
if (filters?.genre instanceof Array) {
url += filters.genre
.map((genre, idx) => `&genre[${idx}]=${genre}`)
.join("");
}
if (filters?.delgenre instanceof Array) {
url += filters.delgenre
.map((genre, idx) => `&delgenre[${idx}]=del ${genre}`)
.join("");
if (filters?.lang?.value?.length) {
url += filters.lang.value
.map((lang, idx) => `&lang[${idx}]=${lang}`).join("");
}
url += "&sortcountchapt=" + (filters?.sortcountchapt || "1");
url += "&sortdaycreate=" + (filters?.sortdaycreate || "1");
if (filters?.genre?.value?.include?.length) {
url += filters.genre.value.include
.map((genre, idx) => `&genre[${idx}]=${genre}`).join("");
}
if (filters?.genre?.value?.exclude?.length) {
url += filters.genre.value.exclude
.map((genre, idx) => `&delgenre[${idx}]=del ${genre}`).join("");
}
url += "&sortcountchapt=" + (filters?.sortcountchapt?.value || "1");
url += "&sortdaycreate=" + (filters?.sortdaycreate?.value || "1");
url +=
"&sortby=" + (showLatestNovels ? "upd" : filters?.sortby || "topweek");
"&sortby=" + (showLatestNovels ? "upd" : filters?.sortby?.value || "topweek");
url += "&gpage=" + pageNo;
const result = await fetchApi(url);
const body = await result.text();
const body = await fetchApi(url).then((res) => res.text());
const loadedCheerio = parseHTML(body);
const novels: Plugin.NovelItem[] = [];
@@ -68,18 +70,16 @@ class Jaomix implements Plugin.PluginBase {
}
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
const result = await fetchApi(novelUrl);
const body = await result.text();
const body = await fetchApi(novelUrl).then((res) => res.text());
const loadedCheerio = parseHTML(body);
const novel: Plugin.SourceNovel = {
url: novelUrl,
name: loadedCheerio('div[class="desc-book"] > h1').text().trim(),
cover: loadedCheerio('div[class="img-book"] > img').attr("src"),
summary: loadedCheerio('div[id="desc-tab"]').text().trim()
};
novel.name = loadedCheerio('div[class="desc-book"] > h1').text().trim();
novel.cover = loadedCheerio('div[class="img-book"] > img').attr("src");
novel.summary = loadedCheerio('div[id="desc-tab"]').text().trim();
loadedCheerio("#info-book > p").each(function () {
let text = loadedCheerio(this).text().replace(/,/g, "").split(" ");
if (text[0] === "Автор:") {
@@ -109,8 +109,7 @@ class Jaomix implements Plugin.PluginBase {
}
async parseChapter(chapterUrl: string): Promise<string> {
const result = await fetchApi(chapterUrl);
const body = await result.text();
const body = await fetchApi(chapterUrl).then((res) => res.text());
const loadedCheerio = parseHTML(body);
loadedCheerio('div[class="adblock-service"]').remove();
@@ -124,8 +123,7 @@ class Jaomix implements Plugin.PluginBase {
pageNo: number | undefined = 1,
): Promise<Plugin.NovelItem[]> {
const url = `${this.site}/?searchrn=${searchTerm}&but=Поиск по названию&sortby=upd&gpage=${pageNo}`;
const result = await fetchApi(url);
const body = await result.text();
const body = await fetchApi(url).then((res) => res.text());
const loadedCheerio = parseHTML(body);
const novels: Plugin.NovelItem[] = [];
@@ -152,11 +150,11 @@ class Jaomix implements Plugin.PluginBase {
}
fetchImage = fetchFile;
filters = [
{
key: "sortby",
filters = {
sortby: {
label: "Сортировка:",
values: [
value: "topweek",
options: [
{ label: "Топ недели", value: "topweek" },
{ label: "По алфавиту", value: "alphabet" },
{ label: "По дате обновления", value: "upd" },
@@ -167,12 +165,12 @@ class Jaomix implements Plugin.PluginBase {
{ label: "Топ за все время", value: "alltime" },
{ label: "Топ месяца", value: "topmonth" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
{
key: "sortdaycreate",
sortdaycreate: {
label: "Дата добавления:",
values: [
value: "1",
options: [
{ label: "Любое", value: "1" },
{ label: "От 120 до 180 дней", value: "1218" },
{ label: "От 180 до 365 дней", value: "1836" },
@@ -182,12 +180,12 @@ class Jaomix implements Plugin.PluginBase {
{ label: "От 90 до 120 дней", value: "9012" },
{ label: "Послед. 30 дней", value: "30" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
{
key: "sortcountchapt",
sortcountchapt: {
label: "Количество глав:",
values: [
value: "1",
options: [
{ label: "Любое кол-во глав", value: "1" },
{ label: "До 500", value: "500" },
{ label: "От 1000 до 2000", value: "1020" },
@@ -196,12 +194,12 @@ class Jaomix implements Plugin.PluginBase {
{ label: "От 4000", value: "400" },
{ label: "От 500 до 1000", value: "510" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
{
key: "genre",
genre: {
label: "Жанры:",
values: [
value: { include: [], exclude: [] },
options: [
{ label: "Боевые Искусства", value: "Боевые Искусства" },
{ label: "Виртуальный Мир", value: "Виртуальный Мир" },
{ label: "Гарем", value: "Гарем" },
@@ -248,72 +246,20 @@ class Jaomix implements Plugin.PluginBase {
{ label: "Xuanhuan", value: "Xuanhuan" },
{ label: "Yaoi", value: "Yaoi" },
],
inputType: FilterInputs.Checkbox,
type: FilterTypes.ExcludableCheckboxGroup,
},
{
key: "delgenre",
label: "Исключить жанры:",
values: [
{ 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: "Постапокалипсис" },
{ 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: "Шоунен" },
{ label: "Экшн", value: "Экшн" },
{ label: "Этти", value: "Этти" },
{ label: "Юри", value: "Юри" },
{ label: "Adult", value: "Adult" },
{ label: "Ecchi", value: "Ecchi" },
{ label: "Josei", value: "Josei" },
{ label: "Lolicon", value: "Lolicon" },
{ label: "Mature", value: "Mature" },
{ label: "Shoujo", value: "Shoujo" },
{ label: "Wuxia", value: "Wuxia" },
{ label: "Xianxia", value: "Xianxia" },
{ label: "Xuanhuan", value: "Xuanhuan" },
{ label: "Yaoi", value: "Yaoi" },
],
inputType: FilterInputs.Checkbox,
},
{
key: "lang",
lang: {
label: "Выбрать языки:",
values: [
value: [],
options: [
{ label: "Английский", value: "Английский" },
{ label: "Китайский", value: "Китайский" },
{ label: "Корейский", value: "Корейский" },
{ label: "Японский", value: "Японский" },
],
inputType: FilterInputs.Checkbox,
type: FilterTypes.CheckboxGroup,
},
];
} satisfies Filters;
}
export default new Jaomix();
-958
View File
@@ -1,958 +0,0 @@
import { Plugin } from "@typings/plugin";
import { FilterInputs } from "@libs/filterInputs";
import { defaultCover } from "@libs/defaultCover";
import { fetchApi, fetchFile } from "@libs/fetch";
import { NovelStatus } from "@libs/novelStatus";
import { load as parseHTML } from "cheerio";
import dayjs from "dayjs";
class TL implements Plugin.PluginBase {
id = "TL";
name = "NovelTL";
site = "https://novel.tl";
version = "1.0.0";
icon = "src/ru/noveltl/icon.png";
userAgent = "";
cookieString = "";
async popularNovels(
page: number,
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
): Promise<Plugin.NovelItem[]> {
const result = await fetchApi(this.site + "/api/site/v2/graphql", {
method: "post",
headers: {
Accept: "application/json, text/plain, */*",
"Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3",
"Content-Type": "application/json",
},
referrer: this.site,
body: JSON.stringify({
operationName: "Projects",
query:
"query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title fullUrl covers{url}}}}",
variables: {
filter: {
tags: filters?.tags || [],
genres: filters?.genres || [],
},
hostname: "novel.tl",
limit: 40,
page,
},
}),
});
const json = (await result.json()) as response;
const novels: Plugin.NovelItem[] = [];
json.data?.projects?.content?.forEach((novel) =>
novels.push({
name: novel.title,
url: "https://" + novel.fullUrl,
cover: novel?.covers?.[0]?.url
? this.site + novel.covers[0].url
: defaultCover,
}),
);
return novels;
}
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
const result = await fetchApi(this.site + "/api/site/v2/graphql", {
method: "post",
headers: {
Accept: "application/json, text/plain, */*",
"Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3",
"Content-Type": "application/json",
},
referrer: this.site,
body: JSON.stringify({
operationName: "Book",
query:
'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus fullUrl covers{url}persons(langs:["ru","en","*"],roles:["author","illustrator"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{shortName chapters{title publishDate fullUrl published}}}}}}}',
variables: {
url: novelUrl,
},
}),
});
const json = (await result.json()) as response;
const novel: Plugin.SourceNovel = {
url: novelUrl,
name: json.data.project?.title,
cover: json.data.project?.covers?.[0]?.url
? this.site + json.data.project.covers[0].url
: defaultCover,
summary: json.data.project?.annotation?.text,
status:
json.data.project?.translationStatus === "active"
? NovelStatus.Ongoing
: NovelStatus.Completed,
};
const genres = [json.data.project?.tags, json.data.project?.genres]
.flat()
.map((item) => item?.nameRu || item?.nameEng)
.filter((item) => item);
if (genres?.length) {
novel.genres = genres.join(", ");
}
json.data.project?.persons?.forEach((person) => {
if (person.role == "author" && person.name.firstName) {
novel.author =
person.name.firstName + " " + (person.name?.lastName || "");
}
});
const chapters: Plugin.ChapterItem[] = [];
json.data.project?.subprojects?.content?.forEach((work) =>
work.volumes.content.forEach((volume) =>
volume.chapters.forEach(
(chapter) =>
chapter?.published &&
chapters.push({
name: volume.shortName + " " + chapter.title,
url: "https://" + chapter.fullUrl,
releaseTime: dayjs(chapter.publishDate).format("LLL"),
}),
),
),
);
novel.chapters = chapters;
return novel;
}
async parseChapter(chapterUrl: string): Promise<string> {
const result = await fetchApi(this.site + "/api/site/v2/graphql", {
method: "post",
headers: {
Accept: "application/json, text/plain, */*",
"Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3",
"Content-Type": "application/json",
},
referrer: this.site,
body: JSON.stringify({
query:
"query($url:String){chapter(chapter:{fullUrl:$url}){text{text}}}",
variables: {
url: chapterUrl,
},
}),
});
const json = (await result.json()) as response;
const baseUrl = this.site;
const loadedCheerio = parseHTML(json.data.chapter?.text?.text || "");
loadedCheerio("p > a[href]").each(function () {
let src = baseUrl + loadedCheerio(this).attr("href");
if (!src.startsWith("http")) {
src = baseUrl + src;
}
loadedCheerio(this).find("picture").remove();
loadedCheerio(this).removeAttr("href");
loadedCheerio(`<img src="${src}">`).appendTo(this);
});
const chapterText = loadedCheerio.html();
return chapterText;
}
async searchNovels(
searchTerm: string,
page: number | undefined = 1,
): Promise<Plugin.NovelItem[]> {
const result = await fetchApi(this.site + "/api/site/v2/graphql", {
method: "post",
headers: {
Accept: "application/json, text/plain, */*",
"Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3",
"Content-Type": "application/json",
},
referrer: this.site,
body: JSON.stringify({
operationName: "Projects",
query:
"query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title fullUrl covers{url}}}}",
variables: {
filter: {
query: searchTerm,
},
hostname: "novel.tl",
limit: 40,
page,
},
}),
});
const json = (await result.json()) as response;
const novels: Plugin.NovelItem[] = [];
json?.data?.projects?.content?.forEach((novel) =>
novels.push({
name: novel.title,
url: "https://" + novel.fullUrl,
cover: novel?.covers?.[0]?.url
? this.site + novel.covers[0].url
: defaultCover,
}),
);
return novels;
}
fetchImage = fetchFile;
filters = [
{
key: "tags",
label: "Тэги",
values: [
{ label: "Автоматы", value: 64 },
{ label: "Агрессивные персонажи", value: 26 },
{ label: "Ад", value: 338 },
{ label: "Адаптация манги", value: 8 },
{ label: "Академия", value: 5 },
{ label: "Актёрское искусство", value: 7 },
{ label: "Актеры озвучки", value: 746 },
{ label: "Алхимия", value: 27 },
{ label: "Альтернативная реальность", value: 30 },
{ label: "Амнезия", value: 31 },
{ label: "Анабиоз", value: 170 },
{ label: "Анал", value: 33 },
{ label: "Ангелы", value: 38 },
{ label: "Андроиды", value: 37 },
{ label: "Анти-рост персонажа", value: 25 },
{ label: "Антигерой", value: 43 },
{ label: "Антикварный магазин", value: 44 },
{ label: "Антимагия", value: 41 },
{ label: "Антиутопия", value: 225 },
{ label: "Апатичный главный герой", value: 46 },
{ label: "Апокалипсис", value: 47 },
{ label: "Аристократия", value: 51 },
{ label: "Армия", value: 53 },
{ label: "Артефакты", value: 58 },
{ label: "Асоциальный главный герой", value: 42 },
{ label: "Ассасины", value: 61 },
{ label: "Атмосфера Европы", value: 245 },
{ label: "Банды", value: 302 },
{ label: "БДСМ", value: 77 },
{ label: "Бедный главный герой", value: 541 },
{ label: "Бездушный главный герой", value: 143 },
{ label: "Беззаботный герой", value: 115 },
{ label: "Безответная любовь", value: 213 },
{ label: "Безответная любовь", value: 740 },
{ label: "Безработные", value: 377 },
{ label: "Беременность", value: 551 },
{ label: "Бескорыстная любовь", value: 733 },
{ label: "Бессмертные", value: 359 },
{ label: "Бесстрашный главный герой", value: 274 },
{ label: "Библиотека", value: 393 },
{ label: "Биография", value: 86 },
{ label: "Близнецы", value: 729 },
{ label: "Богачи", value: 753 },
{ label: "Боги", value: 318 },
{ label: "Богини", value: 316 },
{ label: "Богоподобный протагонист", value: 314 },
{ label: "Боевая академия", value: 75 },
{ label: "Боевая парочка", value: 547 },
{ label: "Божественная защита", value: 208 },
{ label: "Божественная сила", value: 317 },
{ label: "Болеющие персонажи", value: 648 },
{ label: "Борьба за власть", value: 548 },
{ label: "Брак по расчету", value: 55 },
{ label: "Братство", value: 106 },
{ label: "Братья или сёстры", value: 646 },
{ label: "Брошенные дети", value: 1 },
{ label: "Бывший герой", value: 290 },
{ label: "Вампиры", value: 742 },
{ label: "Ваншот", value: 496 },
{ label: "Вдали от родителей", value: 3 },
{ label: "Ведьмы", value: 756 },
{ label: "Везучий главный герой", value: 411 },
{ label: "Верные подданные", value: 410 },
{ label: "Вероломные герои/плетение интриг", value: 424 },
{ label: "Взросление персонажа", value: 24 },
{ label: "Видит то, чего остальной люд не в силах", value: 617 },
{ label: "Виртуальная реальность", value: 744 },
{ label: "Владелец магазина", value: 677 },
{ label: "Владелец уникального оружия", value: 736 },
{ label: "Властные персонажи", value: 545 },
{ label: "Влияние прошлого", value: 517 },
{ label: "Внезапное обогащение", value: 691 },
{ label: "Внезапное обретение силы", value: 690 },
{ label: "Военные хроники", value: 749 },
{ label: "Возвращение в родной мир", value: 587 },
{ label: "Война", value: 750 },
{ label: "Вокалоид", value: 745 },
{ label: "Волшебные твари", value: 413 },
{ label: "Воскрешение", value: 586 },
{ label: "Воспитанный главный герой", value: 537 },
{ label: "Воспоминания прошлого", value: 283 },
{ label: "Враги становятся союзниками", value: 238 },
{ label: "Врата в иной мир", value: 303 },
{ label: "Временной парадокс", value: 711 },
{ label: "Вторжение на Землю", value: 228 },
{ label: "Второй шанс", value: 608 },
{ label: "Выживание", value: 695 },
{ label: "Вынужденные условия проживания", value: 287 },
{ label: "Высокомерный герой", value: 56 },
{ label: "Гадание", value: 207 },
{ label: "Галлюцинации", value: 189 },
{ label: "Гарем рабов", value: 656 },
{ label: "Геймеры", value: 301 },
{ label: "Генетические модификации", value: 306 },
{ label: "Герои", value: 341 },
{ label: "Герои непонятного пола", value: 36 },
{ label: "Героиня девушка-сорванец", value: 715 },
{ label: "Героиня красавица", value: 81 },
{ label: "Герой влюбляется первым", value: 561 },
{ label: "Герой использует щит", value: 636 },
{ label: "Герой красавец", value: 329 },
{ label: "Герой со множеством тел", value: 564 },
{ label: "Герой-бесстыдник", value: 632 },
{ label: "Герой-извращенец", value: 521 },
{ label: "Герой-трудяга", value: 330 },
{ label: "Герой-яндере", value: 421 },
{ label: "Гетерохромия", value: 342 },
{ label: "Гильдии", value: 324 },
{ label: "Гиперопека", value: 509 },
{ label: "Главная героиня — женщина", value: 277 },
{ label: "Главный герой - гений", value: 308 },
{ label: "Главный герой - Моб", value: 446 },
{ label: "Главный герой — знаменитость", value: 263 },
{ label: "Главный герой — мужчина", value: 419 },
{ label: "Главный герой в поисках гарема", value: 331 },
{ label: "Главный герой заранее готовится", value: 557 },
{ label: "Главный герой не человек", value: 488 },
{ label: "Главный герой приёмный", value: 20 },
{ label: "Главный герой сразу силён", value: 563 },
{ label: "Гладиаторы", value: 310 },
{ label: "Глухой к любви герой", value: 194 },
{ label: "Гоблины", value: 313 },
{ label: "Големы", value: 319 },
{ label: "Гомункул", value: 347 },
{ label: "Горничные", value: 418 },
{ label: "Государственные интриги", value: 212 },
{ label: "Гринд", value: 322 },
{ label: "Давняя травма", value: 518 },
{ label: "Двойник", value: 97 },
{ label: "Дворецкие", value: 111 },
{ label: "Дворфы", value: 224 },
{ label: "Дворяне", value: 487 },
{ label: "Двоюродные братья или сёстры", value: 162 },
{ label: "Девочки-волшебницы", value: 415 },
{ label: "Демоны", value: 193 },
{ label: "Депрессия", value: 196 },
{ label: "Детектив", value: 198 },
{ label: "Детективное расследование", value: 473 },
{ label: "Детское насилие", value: 125 },
{ label: "Детское обещание", value: 130 },
{ label: "Дискриминация", value: 203 },
{ label: "Длительный разрыв отношений", value: 400 },
{ label: "Домашняя жизнь", value: 45 },
{ label: "Драконы", value: 218 },
{ label: "Древний Китай", value: 34 },
{ label: "Дружба", value: 293 },
{ label: "Друзья детства", value: 128 },
{ label: "Друзья становятся врагами", value: 292 },
{ label: "Духи", value: 673 },
{ label: "Духовная сила", value: 665 },
{ label: "Духовный наставник", value: 671 },
{ label: "Душещипательная история", value: 335 },
{ label: "Души", value: 666 },
{ label: "Дьявольский путь культивации", value: 192 },
{ label: "Ёкай", value: 763 },
{ label: "Есть аниме-адаптация", value: 10 },
{ label: "Есть видеоигра", value: 13 },
{ label: "Есть дорама-адаптация", value: 11 },
{ label: "Есть манга-адаптация", value: 14 },
{ label: "Есть манхва-адаптация", value: 16 },
{ label: "Есть маньхуа-адаптация", value: 15 },
{ label: "Есть фильм", value: 17 },
{ label: "Есть CD дорама-адаптация", value: 12 },
{ label: "Жёсткий главный герой", value: 597 },
{ label: "Жестокие персонажи", value: 4 },
{ label: "Жёстокие персонажи", value: 169 },
{ label: "Животное-спутник", value: 78 },
{ label: "Жизнь в одиночку", value: 396 },
{ label: "Жрецы", value: 555 },
{ label: "Жрицы", value: 554 },
{ label: "Забота о детях", value: 127 },
{ label: "Заботливый главный герой", value: 116 },
{ label: "Загадочное заболевание", value: 471 },
{ label: "Загадочное прошлое родителей", value: 470 },
{ label: "Заговоры", value: 154 },
{ label: "Закалка тела", value: 96 },
{ label: "Закон джунглей", value: 682 },
{ label: "Закулисная борба", value: 603 },
{ label: "Замкнутый главный герой", value: 371 },
{ label: "Запечатанная сила", value: 607 },
{ label: "Затворник", value: 346 },
{ label: "Звери", value: 80 },
{ label: "Зверолюди", value: 39 },
{ label: "Звероподобные", value: 79 },
{ label: "Земледелие", value: 268 },
{ label: "Злобные благородные девы", value: 743 },
{ label: "Зловещие организации", value: 247 },
{ label: "Злой главный герой", value: 248 },
{ label: "Злые боги", value: 246 },
{ label: "Злые религии", value: 249 },
{ label: "Знаменитости", value: 118 },
{ label: "Знания из прошлой жизни", value: 553 },
{ label: "Зомби", value: 767 },
{ label: "Игра на выживание", value: 696 },
{ label: "Игровая рейтинговая система", value: 300 },
{ label: "Из грязи в князи", value: 542 },
{ label: "Известные родители", value: 262 },
{ label: "Изменение внешнего вида", value: 48 },
{ label: "Изнасилование", value: 574 },
{ label: "Инвалидность", value: 202 },
{ label: "Индустриализация", value: 364 },
{ label: "Инженер", value: 241 },
{ label: "Инцест", value: 361 },
{ label: "Искажённая личность", value: 730 },
{ label: "Искусственный интеллект", value: 59 },
{ label: "Исполнение желаний", value: 755 },
{ label: "Каннибализм", value: 113 },
{ label: "Карточные игры", value: 114 },
{ label: "Кладбищенский смотритель", value: 321 },
{ label: "Классика", value: 134 },
{ label: "Книги", value: 99 },
{ label: "Книги навыков", value: 654 },
{ label: "Книжный червь", value: 100 },
{ label: "Коллеги", value: 140 },
{ label: "Колледж/Университет", value: 145 },
{ label: "Командная работа", value: 701 },
{ label: "Комедийный подтекст", value: 147 },
{ label: "Комплекс брата", value: 105 },
{ label: "Комплекс неполноценности", value: 365 },
{ label: "Контракты", value: 155 },
{ label: "Контроль разума", value: 441 },
{ label: "Конфликт в семье", value: 261 },
{ label: "Конфликт верности", value: 153 },
{ label: "Копейщик", value: 668 },
{ label: "Королевская особа", value: 596 },
{ label: "Королевства", value: 382 },
{ label: "Короткий рассказ", value: 638 },
{ label: "Коррупция", value: 157 },
{ label: "Космические Войны", value: 158 },
{ label: "Космос", value: 507 },
{ label: "Косплей", value: 159 },
{ label: "Кража способностей", value: 2 },
{ label: "Крафтинг", value: 164 },
{ label: "Кризис личности", value: 357 },
{ label: "Кровь", value: 320 },
{ label: "Кружки, клубы", value: 138 },
{ label: "Кудэрэ", value: 384 },
{ label: "Кузнец", value: 90 },
{ label: "Кукловоды", value: 567 },
{ label: "Куклы/Марионетки", value: 211 },
{ label: "Кулинария", value: 156 },
{ label: "Культивация", value: 171 },
{ label: "Куннилингус", value: 172 },
{ label: "Легенды", value: 391 },
{ label: "Легкая жизнь", value: 229 },
{ label: "Лекари", value: 334 },
{ label: "Ленивый главный герой", value: 389 },
{ label: "Лидерство", value: 390 },
{ label: "Любит персонажа младше себя", value: 765 },
{ label: "Любит персонажа старше себя", value: 494 },
{ label: "Любовное соперничество", value: 406 },
{ label: "Любовные треугольники", value: 407 },
{ label: "Любовь детства", value: 129 },
{ label: "Любовь с первого взгляда", value: 404 },
{ label: "Любящие родители", value: 215 },
{ label: "Маги", value: 757 },
{ label: "Магические технологии", value: 417 },
{ label: "Магия", value: 412 },
{ label: "Магия призыва", value: 694 },
{ label: "Маленькие девочки", value: 397 },
{ label: "Мангака", value: 423 },
{ label: "Маниакальная любовь", value: 492 },
{ label: "Марти Сью", value: 508 },
{ label: "Мастер подземелий", value: 222 },
{ label: "Мастурбация", value: 432 },
{ label: "Матриархат", value: 433 },
{ label: "Межпространственные путешествия", value: 370 },
{ label: "Менеджмент", value: 422 },
{ label: "Месть", value: 588 },
{ label: "Мечи и магия", value: 697 },
{ label: "Мечники", value: 698 },
{ label: "Миленькие дети", value: 176 },
{ label: "Милитаризм", value: 439 },
{ label: "Миловидная история", value: 178 },
{ label: "Милый главный герой", value: 177 },
{ label: "Минет", value: 275 },
{ label: "Мировое древо", value: 760 },
{ label: "Миролюбивый главный герой", value: 510 },
{ label: "Мифология", value: 475 },
{ label: "Младшие сёстры", value: 766 },
{ label: "ММОРПГ", value: 445 },
{ label: "Множество временных линий", value: 463 },
{ label: "Модели", value: 447 },
{ label: "Молод не по годам", value: 49 },
{ label: "Монстры", value: 454 },
{ label: "Мрачность", value: 183 },
{ label: "Музыка", value: 466 },
{ label: "Музыкальные группы", value: 68 },
{ label: "Мутации", value: 468 },
{ label: "На волосок от смерти", value: 479 },
{ label: "На все руки мастер", value: 374 },
{ label: "Навыки имеют стоимость", value: 150 },
{ label: "Нагота", value: 490 },
{ label: "Наездники на драконах", value: 216 },
{ label: "Наёмники", value: 437 },
{ label: "Наивный главный герой", value: 476 },
{ label: "Наследие", value: 366 },
{ label: "Настоящая любовь", value: 200 },
{ label: "Настоящий гарем", value: 540 },
{ label: "Небесное испытание", value: 337 },
{ label: "Невезучий главный герой", value: 738 },
{ label: "Недоверчивый протагонист", value: 206 },
{ label: "Недооцененный главный герой", value: 734 },
{ label: "Недопонимание", value: 444 },
{ label: "Некромант", value: 480 },
{ label: "Нелинейное повествование", value: 489 },
{ label: "Неловкий главный герой", value: 67 },
{ label: "Нелюбимый протагонист", value: 333 },
{ label: "Немой персонаж", value: 469 },
{ label: "Ненадежный рассказчик", value: 739 },
{ label: "Непримечательный герой", value: 65 },
{ label: "Нерешительный протагонист", value: 363 },
{ label: "Несгибаемый главный герой", value: 676 },
{ label: "Несколько главных героев", value: 460 },
{ label: "Несколько миров", value: 461 },
{ label: "Несколько перерожденцев", value: 462 },
{ label: "Несколько попаданцев", value: 464 },
{ label: "Несравненная скорость культивации", value: 269 },
{ label: "Нехватка/отсутствие здравого смысла", value: 385 },
{ label: "Нечестный протагонист", value: 205 },
{ label: "НИИТ", value: 481 },
{ label: "Ниндзя", value: 486 },
{ label: "Обмен телами", value: 95 },
{ label: "Оборотни", value: 754 },
{ label: "Объект любви влюбился первым", value: 405 },
{ label: "Объект любви популярен", value: 543 },
{ label: "Огнестрельное оружие", value: 280 },
{ label: "Ограничение на время жизни", value: 394 },
{ label: "Одержимость", value: 544 },
{ label: "Один родитель", value: 651 },
{ label: "Одинокий главный герой", value: 399 },
{ label: "Одиночество", value: 398 },
{ label: "Одно тело на двоих", value: 634 },
{ label: "Око силы", value: 253 },
{ label: "Оммёдзи", value: 498 },
{ label: "Организованная преступность", value: 500 },
{ label: "Оргия", value: 501 },
{ label: "Орки", value: 499 },
{ label: "Основано на аниме", value: 74 },
{ label: "Основано на видеоигре", value: 72 },
{ label: "Основано на визуальной новелле", value: 73 },
{ label: "Основано на песне", value: 70 },
{ label: "Основано на фильме", value: 69 },
{ label: "Осторожный главный герой", value: 117 },
{ label: "От ненависти до любви один шаг", value: 239 },
{ label: "От сильного с сильнейшему", value: 684 },
{ label: "От слабого к сильному", value: 752 },
{ label: "Отаку", value: 503 },
{ label: "Отзывчивая любовь", value: 380 },
{ label: "Отзывчивый главный герой", value: 339 },
{ label: "Отношение учитель-ученик", value: 430 },
{ label: "Отношения бог-человек", value: 315 },
{ label: "Отношения между учителем и учеником", value: 687 },
{ label: "Отношения начальник-подчинённый", value: 101 },
{ label: "Отношения против воли", value: 286 },
{ label: "Отношения с нечеловеком", value: 353 },
{ label: "Отношения сэмпай-кохай", value: 621 },
{ label: "Отношения хозяин-слуга", value: 431 },
{ label: "Отомэ-игры", value: 504 },
{ label: "ОТП", value: 562 },
{ label: "Отшельники", value: 505 },
{ label: "Офисный работник", value: 600 },
{ label: "Официанты", value: 748 },
{ label: "Охотники", value: 355 },
{ label: "Падшие ангелы", value: 255 },
{ label: "Падшие дворяне", value: 256 },
{ label: "Пайзури", value: 511 },
{ label: "Паразиты", value: 513 },
{ label: "Параллельные миры", value: 512 },
{ label: "Парк развлечений", value: 32 },
{ label: "Пародия", value: 515 },
{ label: "Перемещение между мирами", value: 758 },
{ label: "Переодевание", value: 167 },
{ label: "Переплавка пилюль", value: 529 },
{ label: "Перерождение в девушку", value: 420 },
{ label: "Перерождение в другом мире", value: 580 },
{ label: "Перерождение в другом мире", value: 720 },
{ label: "Перерождение в игровом мире", value: 579 },
{ label: "Перерождение в монстра", value: 577 },
{ label: "Персонажи с мазохистскими наклонностями", value: 429 },
{ label: "Персонажи с садистскими наклонностями", value: 598 },
{ label: "Петля времени", value: 709 },
{ label: "Петтинг", value: 328 },
{ label: "Писатели", value: 761 },
{ label: "Питомцы", value: 522 },
{ label: "Племенное общество", value: 726 },
{ label: "Повелитель демонов", value: 191 },
{ label: "Повествование от нескольких лиц", value: 459 },
{ label: "Поглощение навыков", value: 653 },
{ label: "Подземелья", value: 223 },
{ label: "Поздно начинающаяся романтическая линия", value: 387 },
{ label: "Политика", value: 538 },
{ label: "Полулюди", value: 190 },
{ label: "Помолвка", value: 240 },
{ label: "Попаданец в другой мир", value: 723 },
{ label: "Попаданец в игровой мир", value: 722 },
{ label: "Постапокалипсис", value: 546 },
{ label: "Постижение Дао", value: 181 },
{ label: "Потеря девственности", value: 282 },
{ label: "Потустороннее пространство", value: 416 },
{ label: "Похищения", value: 379 },
{ label: "Прагматичный главный герой", value: 549 },
{ label: "Предательство", value: 83 },
{ label: "Предвидение", value: 550 },
{ label: "Преследователи", value: 674 },
{ label: "Преступление", value: 165 },
{ label: "Преступность", value: 166 },
{ label: "Приёмные дети", value: 19 },
{ label: "Призванный герой", value: 693 },
{ label: "Призраки", value: 309 },
{ label: "Приключения", value: 22 },
{ label: "Принуждение", value: 108 },
{ label: "Приручатель", value: 453 },
{ label: "Прислуга", value: 625 },
{ label: "Приставучий любовник", value: 136 },
{ label: "Притворная любовь", value: 552 },
{ label: "Пришельцы", value: 28 },
{ label: "Программист", value: 558 },
{ label: "Произведение - обладатель наград", value: 66 },
{ label: "Проклятия", value: 175 },
{ label: "Промывка мозгов", value: 102 },
{ label: "Пропуск времени", value: 712 },
{ label: "Пророчества", value: 559 },
{ label: "Протагонист в очках", value: 312 },
{ label: "Протагонист раб", value: 657 },
{ label: "Психокинез", value: 565 },
{ label: "Психопаты", value: 566 },
{ label: "Путешествия", value: 759 },
{ label: "Путешествия во времени", value: 713 },
{ label: "Пытки", value: 716 },
{ label: "Рабы", value: 658 },
{ label: "Развитие отношений", value: 160 },
{ label: "Развитие персонажа", value: 119 },
{ label: "Раздвоение личности", value: 458 },
{ label: "Размеренная романтика", value: 661 },
{ label: "Разница в статусе", value: 201 },
{ label: "Разорванная помолвка", value: 104 },
{ label: "Разумные предметы", value: 622 },
{ label: "Рай", value: 336 },
{ label: "Раняя романтика", value: 227 },
{ label: "Расизм", value: 573 },
{ label: "Расследования", value: 372 },
{ label: "Ребенок — главный герой", value: 126 },
{ label: "Реверс-гарем", value: 589 },
{ label: "Реверс-изнасилование", value: 590 },
{ label: "Резкая смена характера", value: 520 },
{ label: "Реинкарнация", value: 581 },
{ label: "Религии", value: 582 },
{ label: "Ресторан", value: 585 },
{ label: "Решительный главный герой", value: 199 },
{ label: "Робкий протагонист", value: 714 },
{ label: "Родительсткий комплекс", value: 514 },
{ label: "Родные не связанные кровью", value: 647 },
{ label: "Родословные", value: 94 },
{ label: "РПГ-система", value: 392 },
{ label: "Рыцари", value: 383 },
{ label: "Самовлюбленный главный герой", value: 477 },
{ label: "Самоубийства", value: 692 },
{ label: "Самураи", value: 601 },
{ label: "Сборник коротких историй", value: 144 },
{ label: "Свадьба", value: 426 },
{ label: "Связанные сюжетные линии", value: 369 },
{ label: "Святые", value: 599 },
{ label: "Секреты", value: 614 },
{ label: "Секс втроём", value: 707 },
{ label: "Секс-рабыня", value: 629 },
{ label: "Семейная любовь", value: 257 },
{ label: "Семь добродетелей", value: 627 },
{ label: "Семь смертных грехов", value: 626 },
{ label: "Серийные убийцы", value: 624 },
{ label: "Сикигами", value: 637 },
{ label: "Сильная любовь от старших", value: 214 },
{ label: "Синдром восьмиклассника", value: 132 },
{ label: "Синдром сестры", value: 652 },
{ label: "Сироты", value: 502 },
{ label: "Системный администратор", value: 699 },
{ label: "Скромный главный герой", value: 409 },
{ label: "Скрытые романтические отношения", value: 23 },
{ label: "Скрытые способности", value: 343 },
{ label: "Скряга", value: 450 },
{ label: "Скульпторы", value: 606 },
{ label: "Слабая романтика", value: 688 },
{ label: "Слабый главный герой", value: 751 },
{ label: "Сложные семейные отношения", value: 149 },
{ label: "Смена расы", value: 572 },
{ label: "Смертельное заболевание", value: 704 },
{ label: "Смерть", value: 185 },
{ label: "Смерть любимых", value: 186 },
{ label: "Современность", value: 448 },
{ label: "Современные знания в слаборазвитых мирах", value: 449 },
{ label: "Сожительство", value: 141 },
{ label: "Создание артефактов", value: 57 },
{ label: "Создание клана", value: 133 },
{ label: "Создание королевства", value: 381 },
{ label: "Создание навыков", value: 655 },
{ label: "Сокрытие истинных способностей", value: 344 },
{ label: "Сокрытие личности", value: 345 },
{ label: "Солдаты", value: 664 },
{ label: "Сон", value: 659 },
{ label: "Соседи по комнате", value: 595 },
{ label: "Социальные изгои", value: 663 },
{ label: "Спасение мира", value: 602 },
{ label: "Спорящая пара", value: 84 },
{ label: "Способность перевоплощения", value: 719 },
{ label: "Средневековье", value: 436 },
{ label: "Стеснительные персонажи", value: 643 },
{ label: "Стокгольмский синдром", value: 575 },
{ label: "Стокгольмский синдром", value: 675 },
{ label: "Стратег", value: 681 },
{ label: "Стратегические битвы", value: 680 },
{ label: "Стрелки", value: 325 },
{ label: "Стрельба из лука", value: 50 },
{ label: "Студенческий совет", value: 686 },
{ label: "Судьба", value: 197 },
{ label: "Суккубы", value: 689 },
{ label: "Суперспособности", value: 669 },
{ label: "Суровая тренировка", value: 332 },
{ label: "Сцены насилия", value: 195 },
{ label: "Таинственное прошлое", value: 472 },
{ label: "Тайная личность", value: 610 },
{ label: "Тайные организации", value: 611 },
{ label: "Телохранители", value: 98 },
{ label: "Тентакли", value: 703 },
{ label: "Террористы", value: 705 },
{ label: "Технологический разрыв", value: 702 },
{ label: "Тихие герои", value: 568 },
{ label: "Толстый протагонист", value: 271 },
{ label: "Торговцы", value: 438 },
{ label: "Травник", value: 340 },
{ label: "Трагичное прошлое", value: 718 },
{ label: "Трап", value: 725 },
{ label: "Триллер", value: 708 },
{ label: "Турнир", value: 76 },
{ label: "Тюрьма", value: 556 },
{ label: "Убийства", value: 465 },
{ label: "Уверенный в себе главный герой", value: 151 },
{ label: "Умная пара", value: 662 },
{ label: "Умный главный герой", value: 135 },
{ label: "Уникальная техника культивации", value: 735 },
{ label: "Уникальные оружия", value: 737 },
{ label: "Управление бизнесом", value: 109 },
{ label: "Управление временем", value: 710 },
{ label: "Управление пространством", value: 667 },
{ label: "Ускоренный рост", value: 6 },
{ label: "Учёные", value: 605 },
{ label: "Учителя", value: 700 },
{ label: "Фамильяры", value: 258 },
{ label: "Фанатизм", value: 264 },
{ label: "Фармацевт", value: 523 },
{ label: "Феи", value: 254 },
{ label: "Фениксы", value: 526 },
{ label: "Фетиш на грудь", value: 103 },
{ label: "Фетиш на очки", value: 311 },
{ label: "Философия", value: 524 },
{ label: "Фобии/Страхи", value: 525 },
{ label: "Фольклор", value: 285 },
{ label: "Формирование армии", value: 54 },
{ label: "Футанари", value: 295 },
{ label: "Футуристический мир", value: 296 },
{ label: "Фэнтези", value: 267 },
{ label: "Фэнтезийные существа", value: 266 },
{ label: "Хакеры", value: 326 },
{ label: "Харизматичный протагонист", value: 120 },
{ label: "Хитрый главный герой", value: 173 },
{ label: "Хладнокровный главный герой", value: 112 },
{ label: "Художники", value: 60 },
{ label: "Хулиганы", value: 188 },
{ label: "Цундере", value: 728 },
{ label: "Чат", value: 122 },
{ label: "Человек-оружие", value: 352 },
{ label: "Человекоподобный главный герой", value: 354 },
{ label: "Честный главный герой", value: 348 },
{ label: "Четкие любовные убеждения", value: 683 },
{ label: "Читы", value: 123 },
{ label: "Чудаковатые герои", value: 569 },
{ label: "Чужие воспоминания", value: 721 },
{ label: "Шантаж", value: 89 },
{ label: "Шеф-повар", value: 124 },
{ label: "Школа для девочек", value: 29 },
{ label: "Шоу-бизнес", value: 642 },
{ label: "Шпионы", value: 670 },
{ label: "Эволюция", value: 250 },
{ label: "Эгоистичный главный герой", value: 618 },
{ label: "Экзорцизм", value: 252 },
{ label: "Экономика", value: 230 },
{ label: "Эксгибиционизм", value: 251 },
{ label: "Эксперименты на людях", value: 351 },
{ label: "Элементальная магия", value: 234 },
{ label: "Элементы игрового мира", value: 299 },
{ label: "Элементы романтики", value: 594 },
{ label: "Элементы юри", value: 640 },
{ label: "Элементы яоя", value: 641 },
{ label: "Эльфы", value: 235 },
{ label: "Эпизодичность", value: 243 },
{ label: "Язвительные персонажи", value: 635 },
{ label: "Языковой барьер", value: 386 },
{ label: "Яндере", value: 762 },
{ label: "Яойщица", value: 294 },
{ label: "Японские силы самообороны", value: 378 },
{ label: "R-15", value: 570 },
{ label: "R-18", value: 571 },
],
inputType: FilterInputs.Checkbox,
},
{
key: "genres",
label: "Жанры",
values: [
{ label: "16+", value: 1 },
{ label: "18+", value: 2 },
{ label: "Боевик", value: 3 },
{ label: "Боевые искусства", value: 4 },
{ label: "Вампиры", value: 47 },
{ label: "Военное", value: 44 },
{ label: "Гарем", value: 5 },
{ label: "Демоны", value: 48 },
{ label: "Детектив", value: 6 },
{ label: "Дзёсей", value: 50 },
{ label: "Драма", value: 7 },
{ label: "Игры", value: 45 },
{ label: "Историческое", value: 8 },
{ label: "Киберпанк", value: 9 },
{ label: "Комедия", value: 10 },
{ label: "Космос", value: 46 },
{ label: "Магия", value: 42 },
{ label: "Махо-сёдзё", value: 12 },
{ label: "Меха", value: 13 },
{ label: "Мистика", value: 14 },
{ label: "Музыка", value: 49 },
{ label: "Научная фантастика", value: 15 },
{ label: "Пародия", value: 16 },
{ label: "Повседневность", value: 17 },
{ label: "Приключения", value: 18 },
{ label: "Психологическое", value: 19 },
{ label: "Романтика", value: 20 },
{ label: "Сверхъестественное", value: 21 },
{ label: "Сёдзё", value: 22 },
{ label: "Сёдзё-ай", value: 23 },
{ label: "Сёнэн", value: 24 },
{ label: "Сёнэн-ай", value: 25 },
{ label: "Смена пола", value: 26 },
{ label: "Современность", value: 27 },
{ label: "Спорт", value: 28 },
{ label: "Супер сила", value: 43 },
{ label: "Сэйнэн", value: 29 },
{ label: "Трагедия", value: 32 },
{ label: "Триллер", value: 33 },
{ label: "Ужасы", value: 34 },
{ label: "Уся", value: 35 },
{ label: "Фэнтези", value: 36 },
{ label: "Школьная жизнь", value: 37 },
{ label: "Этти", value: 38 },
{ label: "Юри", value: 39 },
{ label: "Яой", value: 40 },
{ label: "LitRPG", value: 41 },
],
inputType: FilterInputs.Checkbox,
},
];
}
export default new TL();
interface response {
data: Data;
}
interface Data {
projects?: Projects;
project?: Project;
chapter?: DataChapter;
}
interface DataChapter {
text: Text;
}
interface Text {
text: string;
}
interface Project {
title: string;
translationStatus: string;
fullUrl: string;
covers: Cover[];
persons: Person[];
genres: Genre[];
tags: Genre[];
annotation: Text;
subprojects: Subprojects;
}
interface Cover {
url: string;
}
interface Genre {
nameRu: string;
nameEng: string;
}
interface Person {
role: string;
name: Name;
}
interface Name {
firstName: string;
lastName: string;
}
interface Subprojects {
content: SubprojectsContent[];
}
interface SubprojectsContent {
title: null | string;
volumes: Volumes;
}
interface Volumes {
content: VolumesContent[];
}
interface VolumesContent {
shortName: string;
chapters: ChapterElement[];
}
interface ChapterElement {
title: string;
publishDate: Date;
fullUrl: string;
published: boolean;
}
interface Projects {
content: ProjectsContent[];
}
interface ProjectsContent {
title: string;
fullUrl: string;
covers: Cover[];
}
+958
View File
@@ -0,0 +1,958 @@
import { Plugin } from "@typings/plugin";
import { FilterTypes, Filters } from "@libs/filterInputs";
import { defaultCover } from "@libs/defaultCover";
import { fetchApi, fetchFile } from "@libs/fetch";
import { NovelStatus } from "@libs/novelStatus";
import { load as parseHTML } from "cheerio";
import dayjs from "dayjs";
class TL implements Plugin.PluginBase {
id = "TL";
name = "NovelTL";
site = "https://novel.tl";
version = "1.0.0";
icon = "src/ru/noveltl/icon.png";
userAgent = "";
cookieString = "";
async popularNovels(
page: number,
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
): Promise<Plugin.NovelItem[]> {
const result = await fetchApi(this.site + "/api/site/v2/graphql", {
method: "post",
headers: {
Accept: "application/json, text/plain, */*",
"Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3",
"Content-Type": "application/json",
},
referrer: this.site,
body: JSON.stringify({
operationName: "Projects",
query:
"query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title fullUrl covers{url}}}}",
variables: {
filter: {
tags: filters?.tags?.value || [],
genres: filters?.genres?.value || [],
},
hostname: "novel.tl",
limit: 40,
page,
},
}),
});
const json = (await result.json()) as response;
const novels: Plugin.NovelItem[] = [];
json.data?.projects?.content?.forEach((novel) =>
novels.push({
name: novel.title,
url: "https://" + novel.fullUrl,
cover: novel?.covers?.[0]?.url
? this.site + novel.covers[0].url
: defaultCover,
}),
);
return novels;
}
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
const result = await fetchApi(this.site + "/api/site/v2/graphql", {
method: "post",
headers: {
Accept: "application/json, text/plain, */*",
"Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3",
"Content-Type": "application/json",
},
referrer: this.site,
body: JSON.stringify({
operationName: "Book",
query:
'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus fullUrl covers{url}persons(langs:["ru","en","*"],roles:["author","illustrator"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{shortName chapters{title publishDate fullUrl published}}}}}}}',
variables: {
url: novelUrl,
},
}),
});
const json = (await result.json()) as response;
const novel: Plugin.SourceNovel = {
url: novelUrl,
name: json.data.project?.title,
cover: json.data.project?.covers?.[0]?.url
? this.site + json.data.project.covers[0].url
: defaultCover,
summary: json.data.project?.annotation?.text,
status:
json.data.project?.translationStatus === "active"
? NovelStatus.Ongoing
: NovelStatus.Completed,
};
const genres = [json.data.project?.tags, json.data.project?.genres]
.flat()
.map((item) => item?.nameRu || item?.nameEng)
.filter((item) => item);
if (genres?.length) {
novel.genres = genres.join(", ");
}
json.data.project?.persons?.forEach((person) => {
if (person.role == "author" && person.name.firstName) {
novel.author =
person.name.firstName + " " + (person.name?.lastName || "");
}
});
const chapters: Plugin.ChapterItem[] = [];
json.data.project?.subprojects?.content?.forEach((work) =>
work.volumes.content.forEach((volume) =>
volume.chapters.forEach(
(chapter) =>
chapter?.published &&
chapters.push({
name: volume.shortName + " " + chapter.title,
url: "https://" + chapter.fullUrl,
releaseTime: dayjs(chapter.publishDate).format("LLL"),
}),
),
),
);
novel.chapters = chapters;
return novel;
}
async parseChapter(chapterUrl: string): Promise<string> {
const result = await fetchApi(this.site + "/api/site/v2/graphql", {
method: "post",
headers: {
Accept: "application/json, text/plain, */*",
"Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3",
"Content-Type": "application/json",
},
referrer: this.site,
body: JSON.stringify({
query:
"query($url:String){chapter(chapter:{fullUrl:$url}){text{text}}}",
variables: {
url: chapterUrl,
},
}),
});
const json = (await result.json()) as response;
const baseUrl = this.site;
const loadedCheerio = parseHTML(json.data.chapter?.text?.text || "");
loadedCheerio("p > a[href]").each(function () {
let src = baseUrl + loadedCheerio(this).attr("href");
if (!src.startsWith("http")) {
src = baseUrl + src;
}
loadedCheerio(this).find("picture").remove();
loadedCheerio(this).removeAttr("href");
loadedCheerio(`<img src="${src}">`).appendTo(this);
});
const chapterText = loadedCheerio.html();
return chapterText;
}
async searchNovels(
searchTerm: string,
page: number | undefined = 1,
): Promise<Plugin.NovelItem[]> {
const result = await fetchApi(this.site + "/api/site/v2/graphql", {
method: "post",
headers: {
Accept: "application/json, text/plain, */*",
"Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3",
"Content-Type": "application/json",
},
referrer: this.site,
body: JSON.stringify({
operationName: "Projects",
query:
"query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title fullUrl covers{url}}}}",
variables: {
filter: {
query: searchTerm,
},
hostname: "novel.tl",
limit: 40,
page,
},
}),
});
const json = (await result.json()) as response;
const novels: Plugin.NovelItem[] = [];
json?.data?.projects?.content?.forEach((novel) =>
novels.push({
name: novel.title,
url: "https://" + novel.fullUrl,
cover: novel?.covers?.[0]?.url
? this.site + novel.covers[0].url
: defaultCover,
}),
);
return novels;
}
fetchImage = fetchFile;
filters = {
tags: {
label: "Тэги",
value: [],
options: [
{ label: "Автоматы", value: "64" },
{ label: "Агрессивные персонажи", value: "26" },
{ label: "Ад", value: "338" },
{ label: "Адаптация манги", value: "8" },
{ label: "Академия", value: "5" },
{ label: "Актёрское искусство", value: "7" },
{ label: "Актеры озвучки", value: "746" },
{ label: "Алхимия", value: "27" },
{ label: "Альтернативная реальность", value: "30" },
{ label: "Амнезия", value: "31" },
{ label: "Анабиоз", value: "170" },
{ label: "Анал", value: "33" },
{ label: "Ангелы", value: "38" },
{ label: "Андроиды", value: "37" },
{ label: "Анти-рост персонажа", value: "25" },
{ label: "Антигерой", value: "43" },
{ label: "Антикварный магазин", value: "44" },
{ label: "Антимагия", value: "41" },
{ label: "Антиутопия", value: "225" },
{ label: "Апатичный главный герой", value: "46" },
{ label: "Апокалипсис", value: "47" },
{ label: "Аристократия", value: "51" },
{ label: "Армия", value: "53" },
{ label: "Артефакты", value: "58" },
{ label: "Асоциальный главный герой", value: "42" },
{ label: "Ассасины", value: "61" },
{ label: "Атмосфера Европы", value: "245" },
{ label: "Банды", value: "302" },
{ label: "БДСМ", value: "77" },
{ label: "Бедный главный герой", value: "541" },
{ label: "Бездушный главный герой", value: "143" },
{ label: "Беззаботный герой", value: "115" },
{ label: "Безответная любовь", value: "213" },
{ label: "Безответная любовь", value: "740" },
{ label: "Безработные", value: "377" },
{ label: "Беременность", value: "551" },
{ label: "Бескорыстная любовь", value: "733" },
{ label: "Бессмертные", value: "359" },
{ label: "Бесстрашный главный герой", value: "274" },
{ label: "Библиотека", value: "393" },
{ label: "Биография", value: "86" },
{ label: "Близнецы", value: "729" },
{ label: "Богачи", value: "753" },
{ label: "Боги", value: "318" },
{ label: "Богини", value: "316" },
{ label: "Богоподобный протагонист", value: "314" },
{ label: "Боевая академия", value: "75" },
{ label: "Боевая парочка", value: "547" },
{ label: "Божественная защита", value: "208" },
{ label: "Божественная сила", value: "317" },
{ label: "Болеющие персонажи", value: "648" },
{ label: "Борьба за власть", value: "548" },
{ label: "Брак по расчету", value: "55" },
{ label: "Братство", value: "106" },
{ label: "Братья или сёстры", value: "646" },
{ label: "Брошенные дети", value: "1" },
{ label: "Бывший герой", value: "290" },
{ label: "Вампиры", value: "742" },
{ label: "Ваншот", value: "496" },
{ label: "Вдали от родителей", value: "3" },
{ label: "Ведьмы", value: "756" },
{ label: "Везучий главный герой", value: "411" },
{ label: "Верные подданные", value: "410" },
{ label: "Вероломные герои/плетение интриг", value: "424" },
{ label: "Взросление персонажа", value: "24" },
{ label: "Видит то, чего остальной люд не в силах", value: "617" },
{ label: "Виртуальная реальность", value: "744" },
{ label: "Владелец магазина", value: "677" },
{ label: "Владелец уникального оружия", value: "736" },
{ label: "Властные персонажи", value: "545" },
{ label: "Влияние прошлого", value: "517" },
{ label: "Внезапное обогащение", value: "691" },
{ label: "Внезапное обретение силы", value: "690" },
{ label: "Военные хроники", value: "749" },
{ label: "Возвращение в родной мир", value: "587" },
{ label: "Война", value: "750" },
{ label: "Вокалоид", value: "745" },
{ label: "Волшебные твари", value: "413" },
{ label: "Воскрешение", value: "586" },
{ label: "Воспитанный главный герой", value: "537" },
{ label: "Воспоминания прошлого", value: "283" },
{ label: "Враги становятся союзниками", value: "238" },
{ label: "Врата в иной мир", value: "303" },
{ label: "Временной парадокс", value: "711" },
{ label: "Вторжение на Землю", value: "228" },
{ label: "Второй шанс", value: "608" },
{ label: "Выживание", value: "695" },
{ label: "Вынужденные условия проживания", value: "287" },
{ label: "Высокомерный герой", value: "56" },
{ label: "Гадание", value: "207" },
{ label: "Галлюцинации", value: "189" },
{ label: "Гарем рабов", value: "656" },
{ label: "Геймеры", value: "301" },
{ label: "Генетические модификации", value: "306" },
{ label: "Герои", value: "341" },
{ label: "Герои непонятного пола", value: "36" },
{ label: "Героиня девушка-сорванец", value: "715" },
{ label: "Героиня красавица", value: "81" },
{ label: "Герой влюбляется первым", value: "561" },
{ label: "Герой использует щит", value: "636" },
{ label: "Герой красавец", value: "329" },
{ label: "Герой со множеством тел", value: "564" },
{ label: "Герой-бесстыдник", value: "632" },
{ label: "Герой-извращенец", value: "521" },
{ label: "Герой-трудяга", value: "330" },
{ label: "Герой-яндере", value: "421" },
{ label: "Гетерохромия", value: "342" },
{ label: "Гильдии", value: "324" },
{ label: "Гиперопека", value: "509" },
{ label: "Главная героиня — женщина", value: "277" },
{ label: "Главный герой - гений", value: "308" },
{ label: "Главный герой - Моб", value: "446" },
{ label: "Главный герой — знаменитость", value: "263" },
{ label: "Главный герой — мужчина", value: "419" },
{ label: "Главный герой в поисках гарема", value: "331" },
{ label: "Главный герой заранее готовится", value: "557" },
{ label: "Главный герой не человек", value: "488" },
{ label: "Главный герой приёмный", value: "20" },
{ label: "Главный герой сразу силён", value: "563" },
{ label: "Гладиаторы", value: "310" },
{ label: "Глухой к любви герой", value: "194" },
{ label: "Гоблины", value: "313" },
{ label: "Големы", value: "319" },
{ label: "Гомункул", value: "347" },
{ label: "Горничные", value: "418" },
{ label: "Государственные интриги", value: "212" },
{ label: "Гринд", value: "322" },
{ label: "Давняя травма", value: "518" },
{ label: "Двойник", value: "97" },
{ label: "Дворецкие", value: "111" },
{ label: "Дворфы", value: "224" },
{ label: "Дворяне", value: "487" },
{ label: "Двоюродные братья или сёстры", value: "162" },
{ label: "Девочки-волшебницы", value: "415" },
{ label: "Демоны", value: "193" },
{ label: "Депрессия", value: "196" },
{ label: "Детектив", value: "198" },
{ label: "Детективное расследование", value: "473" },
{ label: "Детское насилие", value: "125" },
{ label: "Детское обещание", value: "130" },
{ label: "Дискриминация", value: "203" },
{ label: "Длительный разрыв отношений", value: "400" },
{ label: "Домашняя жизнь", value: "45" },
{ label: "Драконы", value: "218" },
{ label: "Древний Китай", value: "34" },
{ label: "Дружба", value: "293" },
{ label: "Друзья детства", value: "128" },
{ label: "Друзья становятся врагами", value: "292" },
{ label: "Духи", value: "673" },
{ label: "Духовная сила", value: "665" },
{ label: "Духовный наставник", value: "671" },
{ label: "Душещипательная история", value: "335" },
{ label: "Души", value: "666" },
{ label: "Дьявольский путь культивации", value: "192" },
{ label: "Ёкай", value: "763" },
{ label: "Есть аниме-адаптация", value: "10" },
{ label: "Есть видеоигра", value: "13" },
{ label: "Есть дорама-адаптация", value: "11" },
{ label: "Есть манга-адаптация", value: "14" },
{ label: "Есть манхва-адаптация", value: "16" },
{ label: "Есть маньхуа-адаптация", value: "15" },
{ label: "Есть фильм", value: "17" },
{ label: "Есть CD дорама-адаптация", value: "12" },
{ label: "Жёсткий главный герой", value: "597" },
{ label: "Жестокие персонажи", value: "4" },
{ label: "Жёстокие персонажи", value: "169" },
{ label: "Животное-спутник", value: "78" },
{ label: "Жизнь в одиночку", value: "396" },
{ label: "Жрецы", value: "555" },
{ label: "Жрицы", value: "554" },
{ label: "Забота о детях", value: "127" },
{ label: "Заботливый главный герой", value: "116" },
{ label: "Загадочное заболевание", value: "471" },
{ label: "Загадочное прошлое родителей", value: "470" },
{ label: "Заговоры", value: "154" },
{ label: "Закалка тела", value: "96" },
{ label: "Закон джунглей", value: "682" },
{ label: "Закулисная борба", value: "603" },
{ label: "Замкнутый главный герой", value: "371" },
{ label: "Запечатанная сила", value: "607" },
{ label: "Затворник", value: "346" },
{ label: "Звери", value: "80" },
{ label: "Зверолюди", value: "39" },
{ label: "Звероподобные", value: "79" },
{ label: "Земледелие", value: "268" },
{ label: "Злобные благородные девы", value: "743" },
{ label: "Зловещие организации", value: "247" },
{ label: "Злой главный герой", value: "248" },
{ label: "Злые боги", value: "246" },
{ label: "Злые религии", value: "249" },
{ label: "Знаменитости", value: "118" },
{ label: "Знания из прошлой жизни", value: "553" },
{ label: "Зомби", value: "767" },
{ label: "Игра на выживание", value: "696" },
{ label: "Игровая рейтинговая система", value: "300" },
{ label: "Из грязи в князи", value: "542" },
{ label: "Известные родители", value: "262" },
{ label: "Изменение внешнего вида", value: "48" },
{ label: "Изнасилование", value: "574" },
{ label: "Инвалидность", value: "202" },
{ label: "Индустриализация", value: "364" },
{ label: "Инженер", value: "241" },
{ label: "Инцест", value: "361" },
{ label: "Искажённая личность", value: "730" },
{ label: "Искусственный интеллект", value: "59" },
{ label: "Исполнение желаний", value: "755" },
{ label: "Каннибализм", value: "113" },
{ label: "Карточные игры", value: "114" },
{ label: "Кладбищенский смотритель", value: "321" },
{ label: "Классика", value: "134" },
{ label: "Книги", value: "99" },
{ label: "Книги навыков", value: "654" },
{ label: "Книжный червь", value: "100" },
{ label: "Коллеги", value: "140" },
{ label: "Колледж/Университет", value: "145" },
{ label: "Командная работа", value: "701" },
{ label: "Комедийный подтекст", value: "147" },
{ label: "Комплекс брата", value: "105" },
{ label: "Комплекс неполноценности", value: "365" },
{ label: "Контракты", value: "155" },
{ label: "Контроль разума", value: "441" },
{ label: "Конфликт в семье", value: "261" },
{ label: "Конфликт верности", value: "153" },
{ label: "Копейщик", value: "668" },
{ label: "Королевская особа", value: "596" },
{ label: "Королевства", value: "382" },
{ label: "Короткий рассказ", value: "638" },
{ label: "Коррупция", value: "157" },
{ label: "Космические Войны", value: "158" },
{ label: "Космос", value: "507" },
{ label: "Косплей", value: "159" },
{ label: "Кража способностей", value: "2" },
{ label: "Крафтинг", value: "164" },
{ label: "Кризис личности", value: "357" },
{ label: "Кровь", value: "320" },
{ label: "Кружки, клубы", value: "138" },
{ label: "Кудэрэ", value: "384" },
{ label: "Кузнец", value: "90" },
{ label: "Кукловоды", value: "567" },
{ label: "Куклы/Марионетки", value: "211" },
{ label: "Кулинария", value: "156" },
{ label: "Культивация", value: "171" },
{ label: "Куннилингус", value: "172" },
{ label: "Легенды", value: "391" },
{ label: "Легкая жизнь", value: "229" },
{ label: "Лекари", value: "334" },
{ label: "Ленивый главный герой", value: "389" },
{ label: "Лидерство", value: "390" },
{ label: "Любит персонажа младше себя", value: "765" },
{ label: "Любит персонажа старше себя", value: "494" },
{ label: "Любовное соперничество", value: "406" },
{ label: "Любовные треугольники", value: "407" },
{ label: "Любовь детства", value: "129" },
{ label: "Любовь с первого взгляда", value: "404" },
{ label: "Любящие родители", value: "215" },
{ label: "Маги", value: "757" },
{ label: "Магические технологии", value: "417" },
{ label: "Магия", value: "412" },
{ label: "Магия призыва", value: "694" },
{ label: "Маленькие девочки", value: "397" },
{ label: "Мангака", value: "423" },
{ label: "Маниакальная любовь", value: "492" },
{ label: "Марти Сью", value: "508" },
{ label: "Мастер подземелий", value: "222" },
{ label: "Мастурбация", value: "432" },
{ label: "Матриархат", value: "433" },
{ label: "Межпространственные путешествия", value: "370" },
{ label: "Менеджмент", value: "422" },
{ label: "Месть", value: "588" },
{ label: "Мечи и магия", value: "697" },
{ label: "Мечники", value: "698" },
{ label: "Миленькие дети", value: "176" },
{ label: "Милитаризм", value: "439" },
{ label: "Миловидная история", value: "178" },
{ label: "Милый главный герой", value: "177" },
{ label: "Минет", value: "275" },
{ label: "Мировое древо", value: "760" },
{ label: "Миролюбивый главный герой", value: "510" },
{ label: "Мифология", value: "475" },
{ label: "Младшие сёстры", value: "766" },
{ label: "ММОРПГ", value: "445" },
{ label: "Множество временных линий", value: "463" },
{ label: "Модели", value: "447" },
{ label: "Молод не по годам", value: "49" },
{ label: "Монстры", value: "454" },
{ label: "Мрачность", value: "183" },
{ label: "Музыка", value: "466" },
{ label: "Музыкальные группы", value: "68" },
{ label: "Мутации", value: "468" },
{ label: "На волосок от смерти", value: "479" },
{ label: "На все руки мастер", value: "374" },
{ label: "Навыки имеют стоимость", value: "150" },
{ label: "Нагота", value: "490" },
{ label: "Наездники на драконах", value: "216" },
{ label: "Наёмники", value: "437" },
{ label: "Наивный главный герой", value: "476" },
{ label: "Наследие", value: "366" },
{ label: "Настоящая любовь", value: "200" },
{ label: "Настоящий гарем", value: "540" },
{ label: "Небесное испытание", value: "337" },
{ label: "Невезучий главный герой", value: "738" },
{ label: "Недоверчивый протагонист", value: "206" },
{ label: "Недооцененный главный герой", value: "734" },
{ label: "Недопонимание", value: "444" },
{ label: "Некромант", value: "480" },
{ label: "Нелинейное повествование", value: "489" },
{ label: "Неловкий главный герой", value: "67" },
{ label: "Нелюбимый протагонист", value: "333" },
{ label: "Немой персонаж", value: "469" },
{ label: "Ненадежный рассказчик", value: "739" },
{ label: "Непримечательный герой", value: "65" },
{ label: "Нерешительный протагонист", value: "363" },
{ label: "Несгибаемый главный герой", value: "676" },
{ label: "Несколько главных героев", value: "460" },
{ label: "Несколько миров", value: "461" },
{ label: "Несколько перерожденцев", value: "462" },
{ label: "Несколько попаданцев", value: "464" },
{ label: "Несравненная скорость культивации", value: "269" },
{ label: "Нехватка/отсутствие здравого смысла", value: "385" },
{ label: "Нечестный протагонист", value: "205" },
{ label: "НИИТ", value: "481" },
{ label: "Ниндзя", value: "486" },
{ label: "Обмен телами", value: "95" },
{ label: "Оборотни", value: "754" },
{ label: "Объект любви влюбился первым", value: "405" },
{ label: "Объект любви популярен", value: "543" },
{ label: "Огнестрельное оружие", value: "280" },
{ label: "Ограничение на время жизни", value: "394" },
{ label: "Одержимость", value: "544" },
{ label: "Один родитель", value: "651" },
{ label: "Одинокий главный герой", value: "399" },
{ label: "Одиночество", value: "398" },
{ label: "Одно тело на двоих", value: "634" },
{ label: "Око силы", value: "253" },
{ label: "Оммёдзи", value: "498" },
{ label: "Организованная преступность", value: "500" },
{ label: "Оргия", value: "501" },
{ label: "Орки", value: "499" },
{ label: "Основано на аниме", value: "74" },
{ label: "Основано на видеоигре", value: "72" },
{ label: "Основано на визуальной новелле", value: "73" },
{ label: "Основано на песне", value: "70" },
{ label: "Основано на фильме", value: "69" },
{ label: "Осторожный главный герой", value: "117" },
{ label: "От ненависти до любви один шаг", value: "239" },
{ label: "От сильного с сильнейшему", value: "684" },
{ label: "От слабого к сильному", value: "752" },
{ label: "Отаку", value: "503" },
{ label: "Отзывчивая любовь", value: "380" },
{ label: "Отзывчивый главный герой", value: "339" },
{ label: "Отношение учитель-ученик", value: "430" },
{ label: "Отношения бог-человек", value: "315" },
{ label: "Отношения между учителем и учеником", value: "687" },
{ label: "Отношения начальник-подчинённый", value: "101" },
{ label: "Отношения против воли", value: "286" },
{ label: "Отношения с нечеловеком", value: "353" },
{ label: "Отношения сэмпай-кохай", value: "621" },
{ label: "Отношения хозяин-слуга", value: "431" },
{ label: "Отомэ-игры", value: "504" },
{ label: "ОТП", value: "562" },
{ label: "Отшельники", value: "505" },
{ label: "Офисный работник", value: "600" },
{ label: "Официанты", value: "748" },
{ label: "Охотники", value: "355" },
{ label: "Падшие ангелы", value: "255" },
{ label: "Падшие дворяне", value: "256" },
{ label: "Пайзури", value: "511" },
{ label: "Паразиты", value: "513" },
{ label: "Параллельные миры", value: "512" },
{ label: "Парк развлечений", value: "32" },
{ label: "Пародия", value: "515" },
{ label: "Перемещение между мирами", value: "758" },
{ label: "Переодевание", value: "167" },
{ label: "Переплавка пилюль", value: "529" },
{ label: "Перерождение в девушку", value: "420" },
{ label: "Перерождение в другом мире", value: "580" },
{ label: "Перерождение в другом мире", value: "720" },
{ label: "Перерождение в игровом мире", value: "579" },
{ label: "Перерождение в монстра", value: "577" },
{ label: "Персонажи с мазохистскими наклонностями", value: "429" },
{ label: "Персонажи с садистскими наклонностями", value: "598" },
{ label: "Петля времени", value: "709" },
{ label: "Петтинг", value: "328" },
{ label: "Писатели", value: "761" },
{ label: "Питомцы", value: "522" },
{ label: "Племенное общество", value: "726" },
{ label: "Повелитель демонов", value: "191" },
{ label: "Повествование от нескольких лиц", value: "459" },
{ label: "Поглощение навыков", value: "653" },
{ label: "Подземелья", value: "223" },
{ label: "Поздно начинающаяся романтическая линия", value: "387" },
{ label: "Политика", value: "538" },
{ label: "Полулюди", value: "190" },
{ label: "Помолвка", value: "240" },
{ label: "Попаданец в другой мир", value: "723" },
{ label: "Попаданец в игровой мир", value: "722" },
{ label: "Постапокалипсис", value: "546" },
{ label: "Постижение Дао", value: "181" },
{ label: "Потеря девственности", value: "282" },
{ label: "Потустороннее пространство", value: "416" },
{ label: "Похищения", value: "379" },
{ label: "Прагматичный главный герой", value: "549" },
{ label: "Предательство", value: "83" },
{ label: "Предвидение", value: "550" },
{ label: "Преследователи", value: "674" },
{ label: "Преступление", value: "165" },
{ label: "Преступность", value: "166" },
{ label: "Приёмные дети", value: "19" },
{ label: "Призванный герой", value: "693" },
{ label: "Призраки", value: "309" },
{ label: "Приключения", value: "22" },
{ label: "Принуждение", value: "108" },
{ label: "Приручатель", value: "453" },
{ label: "Прислуга", value: "625" },
{ label: "Приставучий любовник", value: "136" },
{ label: "Притворная любовь", value: "552" },
{ label: "Пришельцы", value: "28" },
{ label: "Программист", value: "558" },
{ label: "Произведение - обладатель наград", value: "66" },
{ label: "Проклятия", value: "175" },
{ label: "Промывка мозгов", value: "102" },
{ label: "Пропуск времени", value: "712" },
{ label: "Пророчества", value: "559" },
{ label: "Протагонист в очках", value: "312" },
{ label: "Протагонист раб", value: "657" },
{ label: "Психокинез", value: "565" },
{ label: "Психопаты", value: "566" },
{ label: "Путешествия", value: "759" },
{ label: "Путешествия во времени", value: "713" },
{ label: "Пытки", value: "716" },
{ label: "Рабы", value: "658" },
{ label: "Развитие отношений", value: "160" },
{ label: "Развитие персонажа", value: "119" },
{ label: "Раздвоение личности", value: "458" },
{ label: "Размеренная романтика", value: "661" },
{ label: "Разница в статусе", value: "201" },
{ label: "Разорванная помолвка", value: "104" },
{ label: "Разумные предметы", value: "622" },
{ label: "Рай", value: "336" },
{ label: "Раняя романтика", value: "227" },
{ label: "Расизм", value: "573" },
{ label: "Расследования", value: "372" },
{ label: "Ребенок — главный герой", value: "126" },
{ label: "Реверс-гарем", value: "589" },
{ label: "Реверс-изнасилование", value: "590" },
{ label: "Резкая смена характера", value: "520" },
{ label: "Реинкарнация", value: "581" },
{ label: "Религии", value: "582" },
{ label: "Ресторан", value: "585" },
{ label: "Решительный главный герой", value: "199" },
{ label: "Робкий протагонист", value: "714" },
{ label: "Родительсткий комплекс", value: "514" },
{ label: "Родные не связанные кровью", value: "647" },
{ label: "Родословные", value: "94" },
{ label: "РПГ-система", value: "392" },
{ label: "Рыцари", value: "383" },
{ label: "Самовлюбленный главный герой", value: "477" },
{ label: "Самоубийства", value: "692" },
{ label: "Самураи", value: "601" },
{ label: "Сборник коротких историй", value: "144" },
{ label: "Свадьба", value: "426" },
{ label: "Связанные сюжетные линии", value: "369" },
{ label: "Святые", value: "599" },
{ label: "Секреты", value: "614" },
{ label: "Секс втроём", value: "707" },
{ label: "Секс-рабыня", value: "629" },
{ label: "Семейная любовь", value: "257" },
{ label: "Семь добродетелей", value: "627" },
{ label: "Семь смертных грехов", value: "626" },
{ label: "Серийные убийцы", value: "624" },
{ label: "Сикигами", value: "637" },
{ label: "Сильная любовь от старших", value: "214" },
{ label: "Синдром восьмиклассника", value: "132" },
{ label: "Синдром сестры", value: "652" },
{ label: "Сироты", value: "502" },
{ label: "Системный администратор", value: "699" },
{ label: "Скромный главный герой", value: "409" },
{ label: "Скрытые романтические отношения", value: "23" },
{ label: "Скрытые способности", value: "343" },
{ label: "Скряга", value: "450" },
{ label: "Скульпторы", value: "606" },
{ label: "Слабая романтика", value: "688" },
{ label: "Слабый главный герой", value: "751" },
{ label: "Сложные семейные отношения", value: "149" },
{ label: "Смена расы", value: "572" },
{ label: "Смертельное заболевание", value: "704" },
{ label: "Смерть", value: "185" },
{ label: "Смерть любимых", value: "186" },
{ label: "Современность", value: "448" },
{ label: "Современные знания в слаборазвитых мирах", value: "449" },
{ label: "Сожительство", value: "141" },
{ label: "Создание артефактов", value: "57" },
{ label: "Создание клана", value: "133" },
{ label: "Создание королевства", value: "381" },
{ label: "Создание навыков", value: "655" },
{ label: "Сокрытие истинных способностей", value: "344" },
{ label: "Сокрытие личности", value: "345" },
{ label: "Солдаты", value: "664" },
{ label: "Сон", value: "659" },
{ label: "Соседи по комнате", value: "595" },
{ label: "Социальные изгои", value: "663" },
{ label: "Спасение мира", value: "602" },
{ label: "Спорящая пара", value: "84" },
{ label: "Способность перевоплощения", value: "719" },
{ label: "Средневековье", value: "436" },
{ label: "Стеснительные персонажи", value: "643" },
{ label: "Стокгольмский синдром", value: "575" },
{ label: "Стокгольмский синдром", value: "675" },
{ label: "Стратег", value: "681" },
{ label: "Стратегические битвы", value: "680" },
{ label: "Стрелки", value: "325" },
{ label: "Стрельба из лука", value: "50" },
{ label: "Студенческий совет", value: "686" },
{ label: "Судьба", value: "197" },
{ label: "Суккубы", value: "689" },
{ label: "Суперспособности", value: "669" },
{ label: "Суровая тренировка", value: "332" },
{ label: "Сцены насилия", value: "195" },
{ label: "Таинственное прошлое", value: "472" },
{ label: "Тайная личность", value: "610" },
{ label: "Тайные организации", value: "611" },
{ label: "Телохранители", value: "98" },
{ label: "Тентакли", value: "703" },
{ label: "Террористы", value: "705" },
{ label: "Технологический разрыв", value: "702" },
{ label: "Тихие герои", value: "568" },
{ label: "Толстый протагонист", value: "271" },
{ label: "Торговцы", value: "438" },
{ label: "Травник", value: "340" },
{ label: "Трагичное прошлое", value: "718" },
{ label: "Трап", value: "725" },
{ label: "Триллер", value: "708" },
{ label: "Турнир", value: "76" },
{ label: "Тюрьма", value: "556" },
{ label: "Убийства", value: "465" },
{ label: "Уверенный в себе главный герой", value: "151" },
{ label: "Умная пара", value: "662" },
{ label: "Умный главный герой", value: "135" },
{ label: "Уникальная техника культивации", value: "735" },
{ label: "Уникальные оружия", value: "737" },
{ label: "Управление бизнесом", value: "109" },
{ label: "Управление временем", value: "710" },
{ label: "Управление пространством", value: "667" },
{ label: "Ускоренный рост", value: "6" },
{ label: "Учёные", value: "605" },
{ label: "Учителя", value: "700" },
{ label: "Фамильяры", value: "258" },
{ label: "Фанатизм", value: "264" },
{ label: "Фармацевт", value: "523" },
{ label: "Феи", value: "254" },
{ label: "Фениксы", value: "526" },
{ label: "Фетиш на грудь", value: "103" },
{ label: "Фетиш на очки", value: "311" },
{ label: "Философия", value: "524" },
{ label: "Фобии/Страхи", value: "525" },
{ label: "Фольклор", value: "285" },
{ label: "Формирование армии", value: "54" },
{ label: "Футанари", value: "295" },
{ label: "Футуристический мир", value: "296" },
{ label: "Фэнтези", value: "267" },
{ label: "Фэнтезийные существа", value: "266" },
{ label: "Хакеры", value: "326" },
{ label: "Харизматичный протагонист", value: "120" },
{ label: "Хитрый главный герой", value: "173" },
{ label: "Хладнокровный главный герой", value: "112" },
{ label: "Художники", value: "60" },
{ label: "Хулиганы", value: "188" },
{ label: "Цундере", value: "728" },
{ label: "Чат", value: "122" },
{ label: "Человек-оружие", value: "352" },
{ label: "Человекоподобный главный герой", value: "354" },
{ label: "Честный главный герой", value: "348" },
{ label: "Четкие любовные убеждения", value: "683" },
{ label: "Читы", value: "123" },
{ label: "Чудаковатые герои", value: "569" },
{ label: "Чужие воспоминания", value: "721" },
{ label: "Шантаж", value: "89" },
{ label: "Шеф-повар", value: "124" },
{ label: "Школа для девочек", value: "29" },
{ label: "Шоу-бизнес", value: "642" },
{ label: "Шпионы", value: "670" },
{ label: "Эволюция", value: "250" },
{ label: "Эгоистичный главный герой", value: "618" },
{ label: "Экзорцизм", value: "252" },
{ label: "Экономика", value: "230" },
{ label: "Эксгибиционизм", value: "251" },
{ label: "Эксперименты на людях", value: "351" },
{ label: "Элементальная магия", value: "234" },
{ label: "Элементы игрового мира", value: "299" },
{ label: "Элементы романтики", value: "594" },
{ label: "Элементы юри", value: "640" },
{ label: "Элементы яоя", value: "641" },
{ label: "Эльфы", value: "235" },
{ label: "Эпизодичность", value: "243" },
{ label: "Язвительные персонажи", value: "635" },
{ label: "Языковой барьер", value: "386" },
{ label: "Яндере", value: "762" },
{ label: "Яойщица", value: "294" },
{ label: "Японские силы самообороны", value: "378" },
{ label: "R-15", value: "570" },
{ label: "R-18", value: "571" },
],
type: FilterTypes.CheckboxGroup,
},
genres: {
label: "Жанры",
value: [],
options: [
{ label: "16+", value: "1" },
{ label: "18+", value: "2" },
{ label: "Боевик", value: "3" },
{ label: "Боевые искусства", value: "4" },
{ label: "Вампиры", value: "47" },
{ label: "Военное", value: "44" },
{ label: "Гарем", value: "5" },
{ label: "Демоны", value: "48" },
{ label: "Детектив", value: "6" },
{ label: "Дзёсей", value: "50" },
{ label: "Драма", value: "7" },
{ label: "Игры", value: "45" },
{ label: "Историческое", value: "8" },
{ label: "Киберпанк", value: "9" },
{ label: "Комедия", value: "10" },
{ label: "Космос", value: "46" },
{ label: "Магия", value: "42" },
{ label: "Махо-сёдзё", value: "12" },
{ label: "Меха", value: "13" },
{ label: "Мистика", value: "14" },
{ label: "Музыка", value: "49" },
{ label: "Научная фантастика", value: "15" },
{ label: "Пародия", value: "16" },
{ label: "Повседневность", value: "17" },
{ label: "Приключения", value: "18" },
{ label: "Психологическое", value: "19" },
{ label: "Романтика", value: "20" },
{ label: "Сверхъестественное", value: "21" },
{ label: "Сёдзё", value: "22" },
{ label: "Сёдзё-ай", value: "23" },
{ label: "Сёнэн", value: "24" },
{ label: "Сёнэн-ай", value: "25" },
{ label: "Смена пола", value: "26" },
{ label: "Современность", value: "27" },
{ label: "Спорт", value: "28" },
{ label: "Супер сила", value: "43" },
{ label: "Сэйнэн", value: "29" },
{ label: "Трагедия", value: "32" },
{ label: "Триллер", value: "33" },
{ label: "Ужасы", value: "34" },
{ label: "Уся", value: "35" },
{ label: "Фэнтези", value: "36" },
{ label: "Школьная жизнь", value: "37" },
{ label: "Этти", value: "38" },
{ label: "Юри", value: "39" },
{ label: "Яой", value: "40" },
{ label: "LitRPG", value: "41" },
],
type: FilterTypes.CheckboxGroup,
},
} satisfies Filters;
}
export default new TL();
interface response {
data: Data;
}
interface Data {
projects?: Projects;
project?: Project;
chapter?: DataChapter;
}
interface DataChapter {
text: Text;
}
interface Text {
text: string;
}
interface Project {
title: string;
translationStatus: string;
fullUrl: string;
covers: Cover[];
persons: Person[];
genres: Genre[];
tags: Genre[];
annotation: Text;
subprojects: Subprojects;
}
interface Cover {
url: string;
}
interface Genre {
nameRu: string;
nameEng: string;
}
interface Person {
role: string;
name: Name;
}
interface Name {
firstName: string;
lastName: string;
}
interface Subprojects {
content: SubprojectsContent[];
}
interface SubprojectsContent {
title: null | string;
volumes: Volumes;
}
interface Volumes {
content: VolumesContent[];
}
interface VolumesContent {
shortName: string;
chapters: ChapterElement[];
}
interface ChapterElement {
title: string;
publishDate: Date;
fullUrl: string;
published: boolean;
}
interface Projects {
content: ProjectsContent[];
}
interface ProjectsContent {
title: string;
fullUrl: string;
covers: Cover[];
}
@@ -1,5 +1,5 @@
import { Plugin } from "@typings/plugin";
import { FilterInputs } from "@libs/filterInputs";
import { FilterTypes, Filters } from "@libs/filterInputs";
import { fetchApi, fetchFile } from "@libs/fetch";
import { NovelStatus } from "@libs/novelStatus";
import { load as parseHTML } from "cheerio";
@@ -16,21 +16,44 @@ class RNBH implements Plugin.PluginBase {
async popularNovels(
pageNo: number,
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
{
showLatestNovels,
filters,
}: Plugin.PopularNovelsOptions<typeof this.filters>,
): Promise<Plugin.NovelItem[]> {
let url = this.site + `api/search?page=${pageNo}&sort=`;
url += showLatestNovels
? "last_chapter_at"
: filters?.sort || "computed_rating";
url += "&status=" + (filters?.status ? filters?.status : "0");
url += "&status=" + (filters?.status?.value ? filters?.status?.value : "0");
if (filters) {
if (Array.isArray(filters.country) && filters.country.length) {
url += "&country=" + filters.country.join(",");
if (filters.country?.value?.length) {
url += "&country=" + filters.country.value.join(",");
}
let tags = [filters?.tags, filters?.events].flat().filter((t) => t);
if (tags.length) {
url += "&tags:positive=" + tags.join(",");
if (
filters.tags?.value?.include?.length ||
filters.events?.value?.include?.length
) {
const tagsPositive = [
filters.tags.value?.include,
filters.events.value?.include,
]
.flat()
.filter((t) => t);
url += "&tags:positive=" + tagsPositive.join(",");
}
if (
filters?.tags?.value?.exclude?.length ||
filters?.events?.value?.exclude?.length
) {
const tagsNegative = [
filters.tags.value?.exclude,
filters.events.value?.exclude,
]
.flat()
.filter((t) => t);
url += "&tags:negative=" + tagsNegative.join(",");
}
}
@@ -73,7 +96,7 @@ class RNBH implements Plugin.PluginBase {
.map((tags) => tags?.names?.rus || tags?.names?.eng || tags?.title)
.filter((tags) => tags);
if (tags.length > 0) {
if (tags.length) {
novel.genres = tags.join(", ");
}
@@ -158,11 +181,11 @@ class RNBH implements Plugin.PluginBase {
fetchImage = fetchFile;
filters = [
{
key: "sort",
filters = {
sort: {
label: "Сортировка",
values: [
value: "",
options: [
{ label: "по рейтингу", value: "computed_rating" },
{ label: "по дате обновления", value: "last_chapter_at" },
{ label: "по дате добавления", value: "created_at" },
@@ -171,35 +194,35 @@ class RNBH implements Plugin.PluginBase {
{ label: "по количеству глав", value: "count_chapters" },
{ label: "по объему перевода", value: "count_of_symbols" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
{
key: "status",
status: {
label: "Статус перевода",
values: [
value: "",
options: [
{ label: "Любой", value: "" },
{ label: "В процессе", value: "1" },
{ label: "Завершено", value: "2" },
{ label: "Заморожено", value: "3" },
{ label: "Неизвестно", value: "4" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
{
key: "country",
country: {
label: "Тип",
values: [
value: [],
options: [
{ label: "Китай", value: "2" },
{ label: "Корея", value: "3" },
{ label: "США", value: "4" },
{ label: "Япония", value: "1" },
],
inputType: FilterInputs.Checkbox,
type: FilterTypes.CheckboxGroup,
},
{
key: "events",
events: {
label: "События",
values: [
value: { include: [], exclude: [] },
options: [
{ label: "[Награжденная работа]", value: "611" },
{ label: "18+", value: "338" },
{ label: "Авантюристы", value: "353" },
@@ -252,11 +275,7 @@ class RNBH implements Plugin.PluginBase {
{ label: "Боевые соревнования", value: "422" },
{ label: "Божественная защита", value: "336" },
{ label: "Божественные силы", value: "224" },
{
label:
"Большая разница в возрасте между героем и его любовным интересом",
value: "348",
},
{ label: "Большая разница в возрасте между героем и его любовным интересом", value: "348" },
{ label: "Борьба за власть", value: "544" },
{ label: "Брак", value: "363" },
{ label: "Брак по расчету", value: "65" },
@@ -278,10 +297,7 @@ class RNBH implements Plugin.PluginBase {
{ label: "Владелец магазина", value: "653" },
{ label: "Внезапная сила", value: "376" },
{ label: "Внезапное богатство", value: "802" },
{
label: "Внешний вид отличается от фактического возраста",
value: "334",
},
{ label: "Внешний вид отличается от фактического возраста", value: "334" },
{ label: "Военные Летописи", value: "740" },
{ label: "Возвращение из другого мира", value: "673" },
{ label: "Войны", value: "58" },
@@ -355,10 +371,7 @@ class RNBH implements Plugin.PluginBase {
{ label: "Депрессия", value: "494" },
{ label: "Детективы", value: "561" },
{ label: "Дискриминация", value: "34" },
{
label: "Добыча денег одно из основных стремлений главного героя",
value: "307",
},
{ label: "Добыча денег одно из основных стремлений главного героя", value: "307" },
{ label: "Долгая разлука", value: "200" },
{ label: "Домашние дела", value: "178" },
{ label: "Домогательство", value: "394" },
@@ -385,10 +398,7 @@ class RNBH implements Plugin.PluginBase {
{ label: "Есть сериал-адаптация", value: "421" },
{ label: "Есть фильм по мотивам", value: "47" },
{ label: "Женища-наставник", value: "438" },
{
label: "Жертва изнасилования влюбляется в насильника",
value: "414",
},
{ label: "Жертва изнасилования влюбляется в насильника", value: "414" },
{ label: "Жесткая, двуличная личность", value: "249" },
{ label: "Жестокие персонажи", value: "436" },
{ label: "Жестокое обращение с ребенком", value: "617" },
@@ -444,10 +454,7 @@ class RNBH implements Plugin.PluginBase {
{ label: "Карточные игры", value: "654" },
{ label: "Киберспорт", value: "658" },
{ label: "Кланы", value: "950" },
{
label: "Класс безработного [Игровой класс в игре]",
value: "727",
},
{ label: "Класс безработного [Игровой класс в игре]", value: "727" },
{ label: "Клоны", value: "365" },
{ label: "Клубы", value: "96" },
{ label: "Книги", value: "341" },
@@ -488,21 +495,14 @@ class RNBH implements Plugin.PluginBase {
{ label: "Лоли", value: "92" },
{ label: "Лотерея", value: "401" },
{ label: "Любовный интерес влюбляется первым", value: "647" },
{
label: "Любовный интерес главного героя носит очки",
value: "576",
},
{ label: "Любовный интерес главного героя носит очки", value: "576" },
{ label: "Любовный треугольник", value: "98" },
{ label: "Любовь детства", value: "500" },
{ label: "Любовь с первого взгляда", value: "730" },
{ label: "Магические надписи", value: "373" },
{ label: "Магические печати", value: "277" },
{ label: "Магические технологии", value: "357" },
{
label:
"Магическое пространство/измерение, доступное не всем персонажам",
value: "244",
},
{ label: "Магическое пространство/измерение, доступное не всем персонажам", value: "244" },
{ label: "Магия", value: "38" },
{ label: "Магия призыва", value: "333" },
{ label: "Мазохистские персонажи", value: "660" },
@@ -635,11 +635,7 @@ class RNBH implements Plugin.PluginBase {
{ label: "Первая любовь", value: "716" },
{ label: "Первоисточник новеллы — манга", value: "560" },
{ label: "Первый раз", value: "845" },
{
label:
"Перемещение в другой мир, имея при себе современные достижения",
value: "732",
},
{ label: "Перемещение в другой мир, имея при себе современные достижения", value: "732" },
{ label: "Перемещение в игровой мир", value: "532" },
{ label: "Перемещение в иной мир", value: "754" },
{ label: "Перерождение в ином мире", value: "755" },
@@ -651,10 +647,7 @@ class RNBH implements Plugin.PluginBase {
{ label: "Питомцы", value: "253" },
{ label: "Племенное общество", value: "291" },
{ label: "Повелитель демонов", value: "171" },
{
label: "Повествование от нескольких лиц/Несколько точек зрения",
value: "156",
},
{ label: "Повествование от нескольких лиц/Несколько точек зрения", value: "156" },
{ label: "Подземелья", value: "219" },
{ label: "Пожелания", value: "166" },
{ label: "Познание Дао", value: "360" },
@@ -1095,12 +1088,12 @@ class RNBH implements Plugin.PluginBase {
{ label: "Weak-lead", value: "885" },
{ label: "Web-novel", value: "1024" },
],
inputType: FilterInputs.Checkbox,
type: FilterTypes.ExcludableCheckboxGroup,
},
{
key: "tags",
tags: {
label: "Жанры",
values: [
value: { include: [], exclude: [] },
options: [
{ label: "Боевые искусства", value: "22" },
{ label: "Гарем", value: "114" },
{ label: "Гендер бендер", value: "246" },
@@ -1144,9 +1137,9 @@ class RNBH implements Plugin.PluginBase {
{ label: "Isekai", value: "999" },
{ label: "Video games", value: "993" },
],
inputType: FilterInputs.Checkbox,
type: FilterTypes.ExcludableCheckboxGroup,
},
];
} satisfies Filters;
}
export default new RNBH();
@@ -1,5 +1,5 @@
import { Plugin } from "@typings/plugin";
import { FilterInputs } from "@libs/filterInputs";
import { FilterTypes, Filters } from "@libs/filterInputs";
import { fetchApi, fetchFile } from "@libs/fetch";
import { NovelStatus } from "@libs/novelStatus";
import { load as parseHTML } from "cheerio";
@@ -17,36 +17,57 @@ class RLIB implements Plugin.PluginBase {
async popularNovels(
pageNo: number,
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
{
showLatestNovels,
filters,
}: Plugin.PopularNovelsOptions<typeof this.filters>,
): Promise<Plugin.NovelItem[]> {
let url = `${this.site}/manga-list?sort=`;
url += showLatestNovels ? "last_chapter_at" : filters?.sort || "rate";
url += "&dir=" + (filters?.order || "desc");
url += showLatestNovels
? "last_chapter_at"
: filters?.sort?.value || "rate";
url += "&dir=" + (filters?.order?.value || "desc");
if (filters) {
if (Array.isArray(filters.type) && filters.type.length) {
url += filters.type.map((i) => `&types[]=${i}`).join("");
}
if (filters.type?.value?.length) {
url += filters.type.value.map((i) => "&types[]=" + i).join("");
}
if (Array.isArray(filters.format) && filters.format?.length) {
url += filters.format.map((i) => `&format[include][]=${i}`).join("");
}
if (filters.format?.value?.include?.length) {
url += filters.format.value.include
.map((i) => "&format[include][]=" + i).join("");
}
if (Array.isArray(filters.status) && filters.status.length) {
url += filters.status.map((i) => `&status[]=${i}`).join("");
}
if (filters.format?.value?.exclude?.length) {
url += filters.format.value.exclude
.map((i) => "&format[exclude][]=" + i).join("");
}
if (Array.isArray(filters.statuss) && filters.statuss.length) {
url += filters.statuss.map((i) => `&manga_status[]=${i}`).join("");
}
if (filters.status?.value?.length) {
url += filters.status.value.map((i) => "&status[]=" + i).join("");
}
if (Array.isArray(filters.genres) && filters.genres.length) {
url += filters.genres.map((i) => `&genres[include][]=${i}`).join("");
}
if (filters.statuss?.value?.length) {
url += filters.statuss.value.map((i) => "&manga_status[]=" + i).join("");
}
if (Array.isArray(filters.tags) && filters.tags.length) {
url += filters.tags.map((i) => `&tags[include][]=${i}`).join("");
}
if (filters.genres?.value?.include?.length) {
url += filters.genres.value.include
.map((i) => "&genres[include][]=" + i).join("");
}
if (filters.genres?.value?.exclude?.length) {
url += filters.genres.value.exclude
.map((i) => "&genres[exclude][]=" + i).join("");
}
if (filters.tags?.value?.include?.length) {
url += filters.tags.value.include
.map((i) => "&tags[include][]=" + i).join("");
}
if (filters.tags?.value?.exclude?.length) {
url += filters.tags.value.exclude
.map((i) => "&tags[exclude][]=" + i).join("");
}
url += "&page=" + pageNo;
@@ -117,12 +138,12 @@ class RLIB implements Plugin.PluginBase {
const chapters: Plugin.ChapterItem[] = [];
let chaptersRaw: any = body.match(
/window.__DATA__ = [\s\S]*?window._this.SITE_COLOR_/gm,
/window.__DATA__ = [\s\S]*?window._SITE_COLOR_/gm,
);
chaptersRaw = chaptersRaw?.[0]
?.replace("window.__DATA__ = ", "")
?.replace("window._this.SITE_COLOR_", "")
?.replace("window._SITE_COLOR_", "")
?.trim()
?.slice(0, -1);
@@ -145,7 +166,7 @@ class RLIB implements Plugin.PluginBase {
async parseChapter(chapterUrl: string): Promise<string> {
const result = await fetchApi(
chapterUrl + (this.ui ? `&ui=${this.ui}` : ""),
chapterUrl + (this.ui ? "&ui=" + this.ui : ""),
);
const body = await result.text();
@@ -191,11 +212,11 @@ class RLIB implements Plugin.PluginBase {
fetchImage = fetchFile;
filters = [
{
key: "sort",
filters = {
sort: {
label: "Сортировка",
values: [
value: "",
options: [
{ label: "Рейтинг", value: "rate" },
{ label: "Имя", value: "name" },
{ label: "Просмотры", value: "views" },
@@ -203,21 +224,21 @@ class RLIB implements Plugin.PluginBase {
{ label: "Дате обновления", value: "last_chapter_at" },
{ label: "Количество глав", value: "chap_count" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
{
key: "order",
order: {
label: "Порядок",
values: [
value: "",
options: [
{ label: "По убыванию", value: "desc" },
{ label: "По возрастанию", value: "asc" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
{
key: "type",
type: {
label: "Тип",
values: [
value: [],
options: [
{ label: "Авторский", value: "14" },
{ label: "Английский", value: "13" },
{ label: "Китай", value: "12" },
@@ -225,12 +246,12 @@ class RLIB implements Plugin.PluginBase {
{ label: "Фанфик", value: "15" },
{ label: "Япония", value: "10" },
],
inputType: FilterInputs.Checkbox,
type: FilterTypes.CheckboxGroup,
},
{
key: "format",
format: {
label: "Формат выпуска",
values: [
value: { include: [], exclude: [] },
options: [
{ label: "4-кома (Ёнкома)", value: "1" },
{ label: "В цвете", value: "4" },
{ label: "Веб", value: "6" },
@@ -239,35 +260,35 @@ class RLIB implements Plugin.PluginBase {
{ label: "Сборник", value: "2" },
{ label: "Сингл", value: "5" },
],
inputType: FilterInputs.Checkbox,
type: FilterTypes.ExcludableCheckboxGroup,
},
{
key: "status",
status: {
label: "Статус перевода",
values: [
value: [],
options: [
{ label: "Продолжается", value: "1" },
{ label: "Завершен", value: "2" },
{ label: "Заморожен", value: "3" },
{ label: "Заброшен", value: "4" },
],
inputType: FilterInputs.Checkbox,
type: FilterTypes.CheckboxGroup,
},
{
key: "statuss",
statuss: {
label: "Статус тайтла",
values: [
value: [],
options: [
{ label: "Онгоинг", value: "1" },
{ label: "Завершён", value: "2" },
{ label: "Анонс", value: "3" },
{ label: "Приостановлен", value: "4" },
{ label: "Выпуск прекращён", value: "5" },
],
inputType: FilterInputs.Checkbox,
type: FilterTypes.CheckboxGroup,
},
{
key: "genres",
genres: {
label: "Жанры",
values: [
value: { include: [], exclude: [] },
options: [
{ label: "Арт", value: "32" },
{ label: "Безумие", value: "91" },
{ label: "Боевик", value: "34" },
@@ -324,12 +345,12 @@ class RLIB implements Plugin.PluginBase {
{ label: "Юри", value: "73" },
{ label: "Яой", value: "74" },
],
inputType: FilterInputs.Checkbox,
type: FilterTypes.ExcludableCheckboxGroup,
},
{
key: "tags",
tags: {
label: "Теги",
values: [
value: { include: [], exclude: [] },
options: [
{ label: "Авантюристы", value: "328" },
{ label: "Антигерой", value: "176" },
{ label: "Бессмертные", value: "333" },
@@ -402,9 +423,9 @@ class RLIB implements Plugin.PluginBase {
{ label: "Эльфы", value: "217" },
{ label: "Якудза", value: "165" },
],
inputType: FilterInputs.Checkbox,
type: FilterTypes.ExcludableCheckboxGroup,
},
];
} satisfies Filters;
}
export default new RLIB();
@@ -1,5 +1,5 @@
import { Plugin } from "@typings/plugin";
import { FilterInputs } from "@libs/filterInputs";
import { FilterTypes, Filters } from "@libs/filterInputs";
import { defaultCover } from "@libs/defaultCover";
import { fetchApi, fetchFile } from "@libs/fetch";
import { NovelStatus } from "@libs/novelStatus";
@@ -22,7 +22,7 @@ class RNRF implements Plugin.PluginBase {
let url = this.site + "/books?order=";
url += showLatestNovels
? "lastPublishedChapter"
: filters?.sort || "popular";
: filters?.sort?.value || "popular";
url += "&page=" + pageNo;
const result = await fetchApi(url);
@@ -71,7 +71,7 @@ class RNRF implements Plugin.PluginBase {
const chapters: Plugin.ChapterItem[] = [];
book?.chapters?.forEach((chapter) => {
if (!chapter?.isDonate || chapter?.isUserPaid) {
if (!chapter.isDonate || chapter.isUserPaid) {
chapters.push({
name: chapter.title,
releaseTime: dayjs(chapter.publishedAt).format("LLL"),
@@ -131,19 +131,19 @@ class RNRF implements Plugin.PluginBase {
}
fetchImage = fetchFile;
filters = [
{
key: "sort",
filters = {
sort: {
label: "Сортировка",
values: [
value: "",
options: [
{ label: "Рейтинг", value: "popular" },
{ label: "Дате добавления", value: "new" },
{ label: "Дате обновления", value: "lastPublishedChapter" },
{ label: "Законченные", value: "completed" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
];
} satisfies Filters;
}
export default new RNRF();
@@ -1,5 +1,5 @@
import { Plugin } from "@typings/plugin";
import { FilterInputs } from "@libs/filterInputs";
import { FilterTypes, Filters } from "@libs/filterInputs";
import { fetchApi, fetchFile } from "@libs/fetch";
import { NovelStatus } from "@libs/novelStatus";
import dayjs from "dayjs";
@@ -15,30 +15,46 @@ class ReN implements Plugin.PluginBase {
async popularNovels(
pageNo: number,
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
{
showLatestNovels,
filters,
}: Plugin.PopularNovelsOptions<typeof this.filters>,
): Promise<Plugin.NovelItem[]> {
let url = this.site + "/api/search/catalog/?count=30&ordering=";
url += filters?.order ? (filters.order as string).replace("+", "") : "-";
url += showLatestNovels ? "chapter_date" : filters?.sort || "rating";
url += filters?.order?.value
? (filters.order.value as string).replace("+", "")
: "-";
url += showLatestNovels ? "chapter_date" : filters?.sort?.value || "rating";
if (filters?.genres instanceof Array) {
url += filters.genres.map((i) => `&genres=${i}`).join("");
if (filters?.genres?.value?.include?.length) {
url += filters.genres.value.include.map((i) => "&genres=" + i).join("");
}
if (filters?.status instanceof Array) {
url += filters?.status.map((i) => `&status=${i}`).join("");
if (filters?.genres?.value?.exclude?.length) {
url += filters.genres.value.exclude
.map((i) => "&exclude_genres=" + i).join("");
}
if (filters?.types instanceof Array) {
url += filters.types.map((i) => `&types=${i}`).join("");
if (filters?.status?.value?.length) {
url += filters.status.value.map((i) => "&status=" + i).join("");
}
if (filters?.categories instanceof Array) {
url += filters.categories.map((i) => `&categories=${i}`).join("");
if (filters?.types?.value?.length) {
url += filters.types.value.map((i) => "&types=" + i).join("");
}
if (filters?.age_limit instanceof Array) {
url += filters.age_limit.map((i) => `&age_limit=${i}`).join("");
if (filters?.categories?.value?.include?.length) {
url += filters.categories.value.include
.map((i) => "&categories=" + i).join("");
}
if (filters?.categories?.value?.exclude?.length) {
url += filters.categories.value.exclude
.map((i) => "&exclude_categories=" + i).join("");
}
if (filters?.age_limit?.value?.length) {
url += filters.age_limit.value.map((i) => "&age_limit=" + i).join("");
}
url += "&page=" + pageNo;
@@ -84,7 +100,7 @@ class ReN implements Plugin.PluginBase {
.map((tags) => tags?.name)
.filter((tags) => tags);
if (tags.length > 0) {
if (tags.length) {
novel.genres = tags.join(",");
}
@@ -149,11 +165,11 @@ class ReN implements Plugin.PluginBase {
fetchImage = fetchFile;
filters = [
{
key: "sort",
filters = {
sort: {
label: "Сортировка",
values: [
value: "",
options: [
{ label: "Рейтинг", value: "rating" },
{ label: "Просмотры", value: "views" },
{ label: "Лайкам", value: "votes" },
@@ -161,21 +177,21 @@ class ReN implements Plugin.PluginBase {
{ label: "Дате обновления", value: "chapter_date" },
{ label: "Количество глав", value: "count_chapters" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
{
key: "order",
order: {
label: "Порядок",
values: [
value: "",
options: [
{ label: "По убыванию", value: "-" },
{ label: "По возрастанию", value: "+" },
],
inputType: FilterInputs.Picker,
type: FilterTypes.Picker,
},
{
key: "genres",
genres: {
label: "Жанры:",
values: [
value: { include: [], exclude: [] },
options: [
{ label: "Боевик", value: "112" },
{ label: "Война", value: "123" },
{ label: "Детектив", value: "114" },
@@ -207,12 +223,12 @@ class ReN implements Plugin.PluginBase {
{ label: "Эротика", value: "118" },
{ label: "Юмор", value: "104" },
],
inputType: FilterInputs.Checkbox,
type: FilterTypes.ExcludableCheckboxGroup,
},
{
key: "categories",
categories: {
label: "Тэги:",
values: [
value: { include: [], exclude: [] },
options: [
{ label: "[Награжденная работа]", value: "648" },
{ label: "18+", value: "423" },
{ label: "Абьюзеры", value: "663" },
@@ -911,12 +927,12 @@ class ReN implements Plugin.PluginBase {
{ label: "R-15 Японское возрастное огр.", value: "332" },
{ label: "R-18", value: "424" },
],
inputType: FilterInputs.Checkbox,
type: FilterTypes.ExcludableCheckboxGroup,
},
{
key: "types",
types: {
label: "Типы:",
values: [
value: [],
options: [
{ label: "Авторское", value: "1" },
{ label: "Другое", value: "7" },
{ label: "Запад", value: "5" },
@@ -925,12 +941,12 @@ class ReN implements Plugin.PluginBase {
{ label: "Фанфики", value: "6" },
{ label: "Япония", value: "2" },
],
inputType: FilterInputs.Checkbox,
type: FilterTypes.CheckboxGroup,
},
{
key: "status",
status: {
label: "Статус проекта:",
values: [
value: [],
options: [
{ label: "Анонс", value: "4" },
{ label: "Закончен", value: "0" },
{ label: "Заморожен", value: "2" },
@@ -938,19 +954,19 @@ class ReN implements Plugin.PluginBase {
{ label: "Нет переводчика", value: "3" },
{ label: "Продолжается", value: "1" },
],
inputType: FilterInputs.Checkbox,
type: FilterTypes.CheckboxGroup,
},
{
key: "age_limit",
age_limit: {
label: "Возрастной рейтинг:",
values: [
value: [],
options: [
{ label: "16+", value: "1" },
{ label: "18+", value: "2" },
{ label: "Для всех", value: "0" },
],
inputType: FilterInputs.Checkbox,
type: FilterTypes.CheckboxGroup,
},
];
} satisfies Filters;
}
export default new ReN();
@@ -0,0 +1,167 @@
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 { defaultCover } from "@libs/defaultCover";
export interface IfreedomMetadata {
id: string;
sourceSite: string;
sourceName: string;
filters?: Filters;
}
class IfreedomPlugin implements Plugin.PluginBase {
id: string;
name: string;
icon: string;
site: string;
version: string;
userAgent: string;
cookieString: string;
filters?: Filters;
constructor(metadata: IfreedomMetadata) {
this.id = metadata.id;
this.name = metadata.sourceName + "[ifreedom]";
this.icon = `multisrc/ifreedom/icons/${metadata.id}.png`;
this.site = metadata.sourceSite;
this.version = "1.0.0";
this.userAgent = "";
this.cookieString = "";
this.filters = metadata.filters;
}
async popularNovels(
page: number,
{ filters, showLatestNovels }: Plugin.PopularNovelsOptions,
): Promise<Plugin.NovelItem[]> {
let url = this.site + "/vse-knigi/?sort=" +
(showLatestNovels ? "По дате обновления" : filters?.sort?.value || "По рейтингу");
if (filters?.status?.value instanceof Array) {
url += filters.status.value.map((i) => "&status[]=" + i).join("");
}
if (filters?.lang?.value instanceof Array) {
url += filters.lang.value.map((i) => "&lang[]=" + i).join("");
}
if (filters?.genre?.value instanceof Array) {
url += filters.genre.value.map((i) => "&genre[]=" + i).join("");
}
url += "&bpage=" + page;
const body = await fetchApi(url).then((res) => res.text());
const loadedCheerio = parseHTML(body);
const novels: Plugin.NovelItem[] = loadedCheerio(
"div.one-book-home > div.img-home a",
)
.map((index, element) => ({
name: loadedCheerio(element).attr("title") || "",
cover: loadedCheerio(element).find("img").attr("src"),
url: loadedCheerio(element).attr("href") || "",
}))
.get()
.filter((novel) => novel.name && novel.url);
return novels;
}
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
const body = await fetchApi(novelUrl).then((res) => res.text());
const loadedCheerio = parseHTML(body);
const novel: Plugin.SourceNovel = {
url: novelUrl,
name: loadedCheerio(".entry-title").text(),
cover: loadedCheerio(".img-ranobe > img").attr("src"),
summary: loadedCheerio('meta[name="description"]').attr("content"),
};
loadedCheerio("div.data-ranobe").each(function () {
switch (loadedCheerio(this).find("b").text()) {
case "Автор":
novel.author = loadedCheerio(this)
.find("div.data-value")
.text()
.trim();
break;
case "Жанры":
novel.genres = loadedCheerio("div.data-value > a")
.map((index, element) => loadedCheerio(element).text()?.trim())
.get()
.join(",");
break;
case "Статус книги":
novel.status = loadedCheerio("div.data-value")
.text()
.includes("активен")
? NovelStatus.Ongoing
: NovelStatus.Completed;
break;
}
});
if (novel.author == "Не указан") delete novel.author;
const chapters: Plugin.ChapterItem[] = [];
loadedCheerio("div.li-ranobe").each(function () {
const name = loadedCheerio(this).find("a").text();
const url = loadedCheerio(this).find("a").attr("href");
if (!loadedCheerio(this).find("label.buy-ranobe").length && name && url) {
const releaseTime = loadedCheerio(this)
.find("div.li-col2-ranobe").text().trim();
chapters.push({ name, releaseTime, url });
}
});
novel.chapters = chapters.reverse();
return novel;
}
async parseChapter(chapterUrl: string): Promise<string> {
const body = await fetchApi(chapterUrl).then((res) => res.text());
const loadedCheerio = parseHTML(body);
loadedCheerio(".entry-content img").each(function () {
const srcset = loadedCheerio(this).attr("srcset")?.split?.(" ");
if (srcset?.length) {
const bestlink = srcset.filter((url) => url.startsWith("http")).unshift();
if (typeof bestlink == 'string') loadedCheerio(this).attr("src", bestlink);
}
});
const chapterText = loadedCheerio(".entry-content").html() || "";
return chapterText;
}
async searchNovels(
searchTerm: string,
page: number | undefined = 1,
): Promise<Plugin.NovelItem[]> {
const url =
this.site + "/vse-knigi/?searchname=" + searchTerm + "&bpage=" + page;
const result = await fetchApi(url).then((res) => res.text());
const loadedCheerio = parseHTML(result);
const novels: Plugin.NovelItem[] = loadedCheerio(
"div.one-book-home > div.img-home a",
)
.map((index, element) => ({
name: loadedCheerio(element).attr("title") || "",
cover: loadedCheerio(element).find("img").attr("src"),
url: loadedCheerio(element).attr("href") || "",
}))
.get()
.filter((novel) => novel.name && novel.url);
return novels;
}
fetchImage = fetchFile;
}
const plugin = new IfreedomPlugin({"id":"ifreedom","sourceSite":"https://ifreedom.su","sourceName":"Свободный Мир Ранобэ","filters":{"sort":{"type":FilterTypes.Picker,"label":"Сортировка:","options":[{"label":"По дате добавления","value":"По дате добавления"},{"label":"По дате обновления","value":"По дате обновления"},{"label":"По количеству глав","value":"По количеству глав"},{"label":"По названию","value":"По названию"},{"label":"По просмотрам","value":"По просмотрам"},{"label":"По рейтингу","value":"По рейтингу"}],"value":"По рейтингу"},"status":{"type":FilterTypes.CheckboxGroup,"label":"Статус:","options":[{"label":"Перевод активен","value":"Перевод активен"},{"label":"Перевод приостановлен","value":"Перевод приостановлен"},{"label":"Произведение завершено","value":"Произведение завершено"}],"value":[]},"lang":{"type":FilterTypes.CheckboxGroup,"label":"Язык:","options":[{"label":"Английский","value":"Английский"},{"label":"Китайский","value":"Китайский"},{"label":"Корейский","value":"Корейский"},{"label":"Японский","value":"Японский"}],"value":[]},"genre":{"type":FilterTypes.CheckboxGroup,"label":"Жанры:","options":[{"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":"Милитари"},{"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":"Триллер"},{"label":"Ужасы","value":"Ужасы"},{"label":"Фантастика","value":"Фантастика"},{"label":"Фэнтези","value":"Фэнтези"},{"label":"Школьная жизнь","value":"Школьная жизнь"},{"label":"Экшн","value":"Экшн"},{"label":"Эротика","value":"Эротика"},{"label":"Этти","value":"Этти"},{"label":"Яой","value":"Яой"},{"label":"Adult","value":"Adult"},{"label":"Mature","value":"Mature"},{"label":"Xianxia","value":"Xianxia"},{"label":"Xuanhuan","value":"Xuanhuan"}],"value":[]}}});
export default plugin;
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+8 -7
View File
@@ -15,7 +15,8 @@ interface MadaraOptionPath {
chapter?: string;
}
const MadaraDefaultPath: MadaraOptionPath = {
const MadaraDefaultPath = {
genres: "novel-genre",
novels: "novel",
novel: "novel",
chapter: "novel",
@@ -70,12 +71,12 @@ class MadaraPlugin implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = [];
let url = this.site;
if (filters?.genres && this.options?.path?.genres) {
url += this.options?.path?.genres + filters.genres;
if (filters?.genres?.value) {
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
"/" +
filters.genres.value;
} else {
url += this.options?.path?.novels
? this.options.path.novels
: MadaraDefaultPath.novels;
url += this.options?.path?.novels || MadaraDefaultPath.novels;
}
url +=
@@ -83,7 +84,7 @@ class MadaraPlugin implements Plugin.PluginBase {
pageNo +
"/" +
"?m_orderby=" +
(showLatestNovels ? "latest" : filters?.sort || "rating");
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
const body = await fetchApi(url).then((res) => res.text());
+1 -1
View File
@@ -57,7 +57,7 @@ class Smakolykytl implements Plugin.PluginBase {
.map((tags) => tags?.title)
.filter((tags) => tags);
if (tags.length > 0) {
if (tags.length) {
novel.genres = tags.join(", ");
}