fix: Plugins (#578)
* test * test * fix * any * update * update * fix LightNovelReader * Why did I do that * typo * fix * type * unused icons have been removed * madara... * ops * fix * close #563 * update * LitSpace * fix jaomix * fix renovels * fix ficbook * update ficbook
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://ar-mtl.club/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://arnovel.me/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "home/novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"الترتيب حسب","values":[{"label":"A-Z","value":"alphabet"},{"label":"الآخير","value":"latest"},{"label":"الأكثر مشاهدة","value":"views"},{"label":"التقييم","value":"rating"},{"label":"جديد","value":"new-manga"},{"label":"شائع","value":"trending"}],"inputType":FilterInputs.Picker},{"key":"genres","label":"التصنيفات","values":[{"label":"أكشن","value":"%d8%a3%d9%83%d8%b4%d9%86"},{"label":"إتشي","value":"%d8%a5%d8%aa%d8%b4%d9%80%d9%8a"},{"label":"بالغ","value":"%d8%a8%d8%a7%d9%84%d8%ba"},{"label":"تاريخي","value":"%d8%aa%d8%a7%d8%b1%d9%8a%d8%ae%d9%8a"},{"label":"تراجدي","value":"%d8%aa%d8%b1%d8%a7%d8%ac%d8%af%d9%8a"},{"label":"جوسي","value":"%d8%ac%d9%88%d8%b3%d9%8a"},{"label":"حريم","value":"%d8%ad%d8%b1%d9%8a%d9%85"},{"label":"حياة مدرسية","value":"%d8%ad%d9%8a%d8%a7%d8%a9-%d9%85%d8%af%d8%b1%d8%b3%d9%8a%d8%a9"},{"label":"خارق لطبيعية","value":"%d8%ae%d8%a7%d8%b1%d9%82-%d9%84%d8%b7%d8%a8%d9%8a%d8%b9%d9%8a%d8%a9"},{"label":"خيال","value":"%d8%ae%d9%8a%d8%a7%d9%84"},{"label":"خيال علمي","value":"%d8%ae%d9%8a%d8%a7%d9%84-%d8%b9%d9%84%d9%85%d9%8a"},{"label":"دراما","value":"%d8%af%d8%b1%d8%a7%d9%85%d8%a7"},{"label":"راشد","value":"%d8%b1%d8%a7%d8%b4%d8%af"},{"label":"رعب","value":"%d8%b1%d8%b9%d8%a8"},{"label":"رومنسي","value":"%d8%b1%d9%88%d9%85%d9%86%d8%b3%d9%8a"},{"label":"رياضي","value":"%d8%b1%d9%8a%d8%a7%d8%b6%d9%8a"},{"label":"سينين","value":"%d8%b3%d9%8a%d9%86%d9%8a%d9%86"},{"label":"شريحة من الحياة","value":"%d8%b4%d8%b1%d9%8a%d8%ad%d8%a9-%d9%85%d9%86-%d8%a7%d9%84%d8%ad%d9%8a%d8%a7%d8%a9"},{"label":"شوجو","value":"%d8%b4%d9%88%d8%ac%d9%88"},{"label":"شونين","value":"%d8%b4%d9%88%d9%86%d9%8a%d9%86"},{"label":"غموض","value":"%d8%ba%d9%85%d9%88%d8%b6"},{"label":"فنون قتال","value":"%d9%81%d9%86%d9%88%d9%86-%d9%82%d8%aa%d8%a7%d9%84"},{"label":"كوميديا","value":"%d9%83%d9%88%d9%85%d9%8a%d8%af%d9%8a%d8%a7"},{"label":"مغامرات","value":"%d9%85%d8%ba%d8%a7%d9%85%d8%b1%d8%a7%d8%aa"},{"label":"منتهية","value":"%d9%85%d9%86%d8%aa%d9%87%d9%8a%d8%a9"},{"label":"ميكا","value":"%d9%85%d9%8a%d9%83%d8%a7"},{"label":"نفسي","value":"%d9%86%d9%81%d8%b3%d9%8a"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://hizomanga.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "serie" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "manga-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "serie/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"ترتيب حسب","values":[{"label":"A-Z","value":"alphabet"},{"label":"New","value":"new-manga"},{"label":"الأحدث","value":"latest"},{"label":"الأكثر مشاهدة","value":"views"},{"label":"التقييم","value":"rating"},{"label":"الرائج","value":"trending"}],"inputType":FilterInputs.Picker},{"key":"genres","label":"التصنيفات","values":[{"label":"آلات","value":"mechanisms"},{"label":"أكشن","value":"action"},{"label":"إثارة","value":"excitement"},{"label":"إيسكاي","value":"%d8%a5%d9%8a%d8%b3%d9%83%d8%a7%d9%8a"},{"label":"الحياة اليومية","value":"slice-of-life"},{"label":"الحياة مدرسية","value":"school-life"},{"label":"تاريخي","value":"historical"},{"label":"تراجيدي","value":"tragic"},{"label":"جريمة","value":"%d8%ac%d8%b1%d9%8a%d9%85%d8%a9"},{"label":"جندر بندر","value":"%d8%ac%d9%86%d8%af%d8%b1-%d8%a8%d9%86%d8%af%d8%b1"},{"label":"جوسي","value":"josei"},{"label":"حريم","value":"harem"},{"label":"خارق للطبيعة","value":"supernatural"},{"label":"خيال","value":"fantasy"},{"label":"خيال علمي","value":"sci-fi"},{"label":"دراما","value":"drama"},{"label":"دموي","value":"%d8%af%d9%85%d9%88%d9%8a"},{"label":"راشد","value":"mature"},{"label":"رعب","value":"horror"},{"label":"رومانسي","value":"romance"},{"label":"رياضة","value":"sports"},{"label":"زمنكاني","value":"my-time"},{"label":"زومبي","value":"%d8%b2%d9%88%d9%85%d8%a8%d9%8a"},{"label":"سينين","value":"seinen"},{"label":"شريحة من الحياة","value":"%d8%b4%d8%b1%d9%8a%d8%ad%d8%a9-%d9%85%d9%86-%d8%a7%d9%84%d8%ad%d9%8a%d8%a7%d8%a9"},{"label":"شوجو","value":"shoujo"},{"label":"شونين","value":"shounen"},{"label":"طبي","value":"%d8%b7%d8%a8%d9%8a"},{"label":"غموض","value":"ambiguity"},{"label":"فنون قتالية","value":"martial-arts"},{"label":"قوة خارقة","value":"superpower"},{"label":"كوميدي","value":"comedy"},{"label":"مغامرات","value":"adventure"},{"label":"نفسي","value":"psychological"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://novel4up.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"ترتيب حسب:","values":[{"label":"أ-ي","value":"alphabet"},{"label":"الأحدث","value":"latest"},{"label":"الأكثر مشاهدة","value":"views"},{"label":"التقييم","value":"rating"},{"label":"الجديد","value":"new-manga"},{"label":"الشائع","value":"trending"}],"inputType":FilterInputs.Picker},{"key":"genres","label":"التصنيفات","values":[{"label":"أكشن","value":"action"},{"label":"الخارق للطبيعة","value":"supernatural"},{"label":"تاريخي","value":"historical"},{"label":"تحقيق","value":"detective"},{"label":"تراجيديا","value":"tragedy"},{"label":"حريم","value":"harem"},{"label":"حياة مدرسية","value":"school-life"},{"label":"خيال علمي","value":"sci-fi"},{"label":"دراما","value":"drama"},{"label":"رعب","value":"horror"},{"label":"رومانسي","value":"romance"},{"label":"رياضة","value":"sports"},{"label":"سحر","value":"magic"},{"label":"شريحة من الحياة","value":"slice-of-life"},{"label":"شوانهوا","value":"xuanhuan"},{"label":"شوجو","value":"shoujo"},{"label":"شونين","value":"shounen"},{"label":"غموض","value":"mystery"},{"label":"فانتازيا","value":"fantasy"},{"label":"فنون قتال","value":"martial-arts"},{"label":"كوميديا","value":"comedy"},{"label":"مغامرة","value":"adventure"},{"label":"ميكا","value":"mecha"},{"label":"نفسي","value":"psychological"},{"label":"ون شوت","value":"one-shot"},{"label":"ووشيا","value":"wuxia"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://riwyat.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker},{"key":"genres","label":"GENRES","values":[{"label":"أكشن","value":"%d8%a3%d9%83%d8%b4%d9%86"},{"label":"أيتشي","value":"%d8%a3%d9%8a%d8%aa%d8%b4%d9%8a"},{"label":"استراتجي","value":"%d8%a7%d8%b3%d8%aa%d8%b1%d8%a7%d8%aa%d8%ac%d9%8a"},{"label":"العصر الحديث","value":"%d8%a7%d9%84%d8%b9%d8%b5%d8%b1-%d8%a7%d9%84%d8%ad%d8%af%d9%8a%d8%ab"},{"label":"انتقام","value":"%d8%a7%d9%86%d8%aa%d9%82%d8%a7%d9%85"},{"label":"بالغ","value":"%d8%a8%d8%a7%d9%84%d8%ba"},{"label":"بناء مملكة","value":"%d8%a8%d9%86%d8%a7%d8%a1-%d9%85%d9%85%d9%84%d9%83%d8%a9"},{"label":"بوليسي","value":"%d8%a8%d9%88%d9%84%d9%8a%d8%b3%d9%8a"},{"label":"تاريخي","value":"%d8%aa%d8%a7%d8%b1%d9%8a%d8%ae%d9%8a"},{"label":"جوسي","value":"%d8%ac%d9%88%d8%b3%d9%8a"},{"label":"حريم","value":"%d8%ad%d8%b1%d9%8a%d9%85"},{"label":"حياة يومية","value":"%d8%ad%d9%8a%d8%a7%d8%a9-%d9%8a%d9%88%d9%85%d9%8a%d8%a9"},{"label":"خيال","value":"%d8%ae%d9%8a%d8%a7%d9%84"},{"label":"خيال علمي","value":"%d8%ae%d9%8a%d8%a7%d9%84-%d8%b9%d9%84%d9%85%d9%8a"},{"label":"دراما","value":"%d8%af%d8%b1%d8%a7%d9%85%d8%a7"},{"label":"رعب","value":"%d8%b1%d8%b9%d8%a8"},{"label":"رومانسية","value":"%d8%b1%d9%88%d9%85%d8%a7%d9%86%d8%b3%d9%8a%d8%a9"},{"label":"رياضي","value":"%d8%b1%d9%8a%d8%a7%d8%b6%d9%8a"},{"label":"زيانشيا","value":"xianxia"},{"label":"سنين","value":"%d8%b3%d9%86%d9%8a%d9%86"},{"label":"شريحة حياة","value":"%d8%b4%d8%b1%d9%8a%d8%ad%d8%a9-%d8%ad%d9%8a%d8%a7%d8%a9"},{"label":"شوانهوان","value":"xuanhuan"},{"label":"شوجو","value":"%d8%b4%d9%88%d8%ac%d9%88"},{"label":"شونين","value":"%d8%b4%d9%88%d9%86%d9%8a%d9%86"},{"label":"شيانشيا","value":"%d8%b4%d9%8a%d8%a7%d9%86%d8%b4%d9%8a%d8%a7"},{"label":"عسكري","value":"%d8%b9%d8%b3%d9%83%d8%b1%d9%8a"},{"label":"غموض","value":"%d8%ba%d9%85%d9%88%d8%b6"},{"label":"فانتازيا","value":"%d9%81%d8%a7%d9%86%d8%aa%d8%a7%d8%b2%d9%8a%d8%a7"},{"label":"فنون قتالية","value":"%d9%81%d9%86%d9%88%d9%86-%d9%82%d8%aa%d8%a7%d9%84%d9%8a%d8%a9"},{"label":"قوى خارقة","value":"%d9%82%d9%88%d9%89-%d8%ae%d8%a7%d8%b1%d9%82%d8%a9"},{"label":"كوميديا","value":"%d9%83%d9%88%d9%85%d9%8a%d8%af%d9%8a%d8%a7"},{"label":"كوميك","value":"%d9%83%d9%88%d9%85%d9%8a%d9%83"},{"label":"للكبار","value":"%d9%84%d9%84%d9%83%d8%a8%d8%a7%d8%b1"},{"label":"مأساة","value":"%d9%85%d8%a3%d8%b3%d8%a7%d8%a9"},{"label":"مانجا","value":"%d9%85%d8%a7%d9%86%d8%ac%d8%a7"},{"label":"مانها","value":"%d9%85%d8%a7%d9%86%d9%87%d8%a7"},{"label":"مانهوا","value":"%d9%85%d8%a7%d9%86%d9%87%d9%88%d8%a7"},{"label":"مدرسي","value":"%d9%85%d8%af%d8%b1%d8%b3%d9%8a"},{"label":"مصاصي دماء","value":"%d9%85%d8%b5%d8%a7%d8%b5%d9%8a-%d8%af%d9%85%d8%a7%d8%a1"},{"label":"مغامرة","value":"%d9%85%d8%ba%d8%a7%d9%85%d8%b1%d8%a9"},{"label":"مكتملة","value":"%d9%85%d9%83%d8%aa%d9%85%d9%84%d8%a9"},{"label":"نفسي","value":"%d9%86%d9%81%d8%b3%d9%8a"},{"label":"نهاية العالم","value":"%d9%86%d9%87%d8%a7%d9%8a%d8%a9-%d8%a7%d9%84%d8%b9%d8%a7%d9%84%d9%85"},{"label":"واب تون","value":"%d9%88%d8%a7%d8%a8-%d8%aa%d9%88%d9%86"},{"label":"وان شوت","value":"%d9%88%d8%a7%d9%86-%d8%b4%d9%88%d8%aa"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://teamxnovel.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://boxnovel.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "manga-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker},{"key":"genres","label":"GENRES","values":[{"label":"Action","value":"action"},{"label":"Adventure","value":"adventure"},{"label":"Comedy","value":"comedy"},{"label":"Drama","value":"drama"},{"label":"Ecchi","value":"ecchi"},{"label":"Fantasy","value":"fantasy"},{"label":"Gender Bender","value":"gender-bender"},{"label":"Harem","value":"harem"},{"label":"Historical","value":"historical"},{"label":"Horror","value":"horror"},{"label":"Josei","value":"josei"},{"label":"Martial Arts","value":"martial-arts"},{"label":"Mature","value":"mature"},{"label":"Mecha","value":"mecha"},{"label":"Mystery","value":"mystery"},{"label":"Psychological","value":"psychological"},{"label":"Romance","value":"romance"},{"label":"School Life","value":"school-life"},{"label":"Sci-fi","value":"sci-fi"},{"label":"Seinen","value":"seinen"},{"label":"Shoujo","value":"shoujo"},{"label":"Shounen","value":"shounen"},{"label":"Slice of Life","value":"slice-of-life"},{"label":"Smut","value":"smut"},{"label":"Sports","value":"sports"},{"label":"Supernatural","value":"supernatural"},{"label":"Tragedy","value":"tragedy"},{"label":"Wuxia","value":"wuxia"},{"label":"Xianxia","value":"xianxia"},{"label":"Xuanhuan","value":"xuanhuan"},{"label":"Yaoi","value":"yaoi"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://clicknovel.net/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://cratenovel.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://daonovel.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novels-list" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novels-list/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://fanstranslations.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://1stkissnovel.org/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker},{"key":"genres","label":"GENRES","values":[{"label":"Action","value":"action"},{"label":"Adult","value":"adult"},{"label":"Adventure","value":"adventure"},{"label":"Anime","value":"anime"},{"label":"Apocalypse","value":"apocalypse"},{"label":"Boy's Love","value":"boys-love"},{"label":"Comedy","value":"comedy"},{"label":"Completed","value":"completed"},{"label":"Cooking","value":"cooking"},{"label":"Demons","value":"demons"},{"label":"Detective","value":"detective"},{"label":"Drama","value":"drama"},{"label":"Ecchi","value":"ecchi"},{"label":"Fan-Fiction","value":"fan-fiction"},{"label":"Fantasy","value":"fantasy"},{"label":"Food","value":"food"},{"label":"Game","value":"game"},{"label":"Gender Bender","value":"gender-bender"},{"label":"Girls Love","value":"girls-love"},{"label":"Harem","value":"harem"},{"label":"Historical","value":"historical"},{"label":"Horror","value":"horror"},{"label":"Humor","value":"humor"},{"label":"Isekai","value":"isekai"},{"label":"Josei","value":"josei"},{"label":"Magic","value":"magic"},{"label":"Magical","value":"magical"},{"label":"Manhua","value":"manhua"},{"label":"Manhwa","value":"manhwa"},{"label":"Martial Arts","value":"martial-arts"},{"label":"Mature","value":"mature"},{"label":"Mecha","value":"mecha"},{"label":"Medical","value":"medical"},{"label":"Military","value":"military"},{"label":"Moder","value":"moder"},{"label":"Modern","value":"modern"},{"label":"Murim","value":"murim"},{"label":"Music","value":"music"},{"label":"Mystery","value":"mystery"},{"label":"Psychological","value":"psychological"},{"label":"Reverse","value":"reverse"},{"label":"Reverse harem","value":"reverse-harem"},{"label":"Romance","value":"romance"},{"label":"School Life","value":"school-life"},{"label":"Sci-fi","value":"sci-fi"},{"label":"Seinen","value":"seinen"},{"label":"Shoujo","value":"shoujo"},{"label":"Shoujo Ai","value":"shoujo-ai"},{"label":"Shounen","value":"shounen"},{"label":"Shounen Ai","value":"shounen-ai"},{"label":"Slice of Life","value":"slice-of-life"},{"label":"Smut","value":"smut"},{"label":"Sports","value":"sports"},{"label":"Super power","value":"super-power"},{"label":"Superhero","value":"superhero"},{"label":"Supernatural","value":"supernatural"},{"label":"Thriller","value":"thriller"},{"label":"Tragedy","value":"tragedy"},{"label":"Urban Life","value":"urban-life"},{"label":"Vampire","value":"vampire"},{"label":"Webtoons","value":"webtoons"},{"label":"Wuxia","value":"wuxia"},{"label":"Xianxia","value":"xianxia"},{"label":"Xuanhuan","value":"xuanhuan"},{"label":"Yaoi","value":"yaoi"},{"label":"Yuri","value":"yuri"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://freenovel.me/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker},{"key":"genres","label":"GENRES","values":[{"label":"Action","value":"action"},{"label":"Adult","value":"adult"},{"label":"Adventure","value":"adventure"},{"label":"Cartoon","value":"cartoon"},{"label":"Comedy","value":"comedy"},{"label":"Cooking","value":"cooking"},{"label":"Detective","value":"detective"},{"label":"Drama","value":"drama"},{"label":"Fantasy","value":"fantasy"},{"label":"Game","value":"game"},{"label":"Gender Bender","value":"gender-bender"},{"label":"Historical","value":"historical"},{"label":"History","value":"history"},{"label":"Horror","value":"horror"},{"label":"Josei","value":"josei"},{"label":"Live action","value":"live-action"},{"label":"Martial","value":"martial"},{"label":"Martial Arts","value":"martial-arts"},{"label":"Mature","value":"mature"},{"label":"Mecha","value":"mecha"},{"label":"Mystery","value":"mystery"},{"label":"One shot","value":"one-shot"},{"label":"Premium","value":"premium"},{"label":"Psychological","value":"psychological"},{"label":"Reincarnation","value":"reincarnation"},{"label":"Romance","value":"romance"},{"label":"School Life","value":"school-life"},{"label":"Sci-fi","value":"sci-fi"},{"label":"Slice of Life","value":"slice-of-life"},{"label":"Smut","value":"smut"},{"label":"Sports","value":"sports"},{"label":"Supernatural","value":"supernatural"},{"label":"Tragedy","value":"tragedy"},{"label":"Wuxia","value":"wuxia"},{"label":"Xianxia","value":"xianxia"},{"label":"Xuanhuan","value":"xuanhuan"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://guavaread.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker},{"key":"genres","label":"GENRES","values":[{"label":"Action","value":"action"},{"label":"Adult","value":"adult"},{"label":"Adventure","value":"adventure"},{"label":"Comedy","value":"comedy"},{"label":"Complete","value":"complete"},{"label":"Cooking","value":"cooking"},{"label":"Detective","value":"detective"},{"label":"Drama","value":"drama"},{"label":"Dropped","value":"dropped"},{"label":"Ecchi","value":"ecchi"},{"label":"Fantasy","value":"fantasy"},{"label":"Gender Bender","value":"gender-bender"},{"label":"Historical","value":"historical"},{"label":"Horror","value":"horror"},{"label":"Josei","value":"josei"},{"label":"Mature","value":"mature"},{"label":"Mystery","value":"mystery"},{"label":"Original Story","value":"original-story"},{"label":"Pilot","value":"pilot"},{"label":"Psychological","value":"psychological"},{"label":"R-15","value":"r-15"},{"label":"R-19","value":"r-19"},{"label":"Reverse Harem","value":"reverse-harem"},{"label":"Romance","value":"romance"},{"label":"School Life","value":"school-life"},{"label":"Sci-fi","value":"sci-fi"},{"label":"Shoujo","value":"shoujo"},{"label":"Slice of Life","value":"slice-of-life"},{"label":"Smut","value":"smut"},{"label":"Supernatural","value":"supernatural"},{"label":"Tragedy","value":"tragedy"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://lightnovelheaven.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "series" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "series/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -143,22 +143,17 @@ export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
|
||||
const url =
|
||||
baseUrl + `/search/autocomplete?dataType=json&query=${searchTerm}`;
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.json();
|
||||
const data = body?.results || [];
|
||||
const body = await result.json() as { results: ResultsEntity[] };
|
||||
|
||||
const novels: Novel.Item[] = [];
|
||||
|
||||
data.forEach((item) => {
|
||||
let novelUrl = item.link;
|
||||
let novelName = item.original_title;
|
||||
let novelCover = item.image;
|
||||
|
||||
body.results.forEach((item) =>
|
||||
novels.push({
|
||||
url: novelUrl,
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
});
|
||||
});
|
||||
url: item.link,
|
||||
name: item.original_title,
|
||||
cover: item.image,
|
||||
})
|
||||
);
|
||||
|
||||
return novels;
|
||||
};
|
||||
@@ -166,3 +161,10 @@ export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
|
||||
export const fetchImage: Plugin.fetchImage = async function (url) {
|
||||
return await fetchFile(url, {});
|
||||
};
|
||||
|
||||
interface ResultsEntity {
|
||||
overview: string;
|
||||
original_title: string;
|
||||
link: string;
|
||||
image: string;
|
||||
}
|
||||
@@ -1,197 +1,195 @@
|
||||
|
||||
import { load as parseHTML } from "cheerio";
|
||||
// import dayjs from 'dayjs';
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
// import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
// import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
|
||||
// import { showToast } from "@libs/showToast";
|
||||
// import { Filter, FilterInputs } from "@libs/filterInputs";
|
||||
// import { NovelStatus } from '@libs/novelStatus';
|
||||
// import { defaultCover } from "@libs/defaultCover";
|
||||
|
||||
|
||||
export const id = "Ltnovel.com";
|
||||
export const name = "Ltnovel.com";
|
||||
export const icon = "multisrc/readwn/icon/ltnovel.png";
|
||||
export const version = "1.0.0";
|
||||
export const site = "https://www.ltnovel.com/";
|
||||
// export const filters: Filter[] = [];
|
||||
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ filters, showLatestNovels }
|
||||
) {
|
||||
const novels: Novel.Item[] = [];
|
||||
const pageNo = page - 1;
|
||||
|
||||
const url = baseUrl + 'list/all/all-onclick-' + pageNo +'.html';
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('li.novel-item').each(function () {
|
||||
const novelName = loadedCheerio(this).find('h4').text();
|
||||
const novelUrl = baseUrl + loadedCheerio(this).find('a').attr('href');
|
||||
|
||||
const coverUri = loadedCheerio(this)
|
||||
.find('.novel-cover > img')
|
||||
.attr('data-src');
|
||||
|
||||
const novelCover = baseUrl + coverUri;
|
||||
|
||||
const novel = { name: novelName, cover: novelCover, url: novelUrl };
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
|
||||
return novels;
|
||||
import { load as parseHTML } from "cheerio";
|
||||
// import dayjs from 'dayjs';
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
// import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
|
||||
// import { showToast } from "@libs/showToast";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
// import { NovelStatus } from '@libs/novelStatus';
|
||||
// import { defaultCover } from "@libs/defaultCover";
|
||||
|
||||
export const id = "Ltnovel.com";
|
||||
export const name = "Ltnovel.com";
|
||||
export const icon = "multisrc/readwn/icons/ltnovel.png";
|
||||
export const version = "1.0.0";
|
||||
export const site = "https://www.ltnovel.com/";
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ filters, showLatestNovels }
|
||||
) {
|
||||
const novels: Novel.Item[] = [];
|
||||
const pageNo = page - 1;
|
||||
|
||||
let url = site + 'list/';
|
||||
url += (filters?.genres || 'all') + '/';
|
||||
url += (filters?.status || 'all') + '-';
|
||||
url += (showLatestNovels ? 'lastdotime' : filters?.sort || 'newstime') + '-';
|
||||
url += pageNo + '.html';
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('li.novel-item').each(function () {
|
||||
const novelName = loadedCheerio(this).find('h4').text();
|
||||
const novelUrl = site + loadedCheerio(this).find('a').attr('href');
|
||||
|
||||
const coverUri = loadedCheerio(this)
|
||||
.find('.novel-cover > img')
|
||||
.attr('data-src');
|
||||
|
||||
const novelCover = site + coverUri;
|
||||
|
||||
const novel = { name: novelName, cover: novelCover, url: novelUrl };
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi(novelUrl);
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi(novelUrl);
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
novel.name = loadedCheerio('h1.novel-title').text();
|
||||
|
||||
const coverUri = loadedCheerio('figure.cover > img').attr('data-src');
|
||||
novel.cover = baseUrl + coverUri;
|
||||
|
||||
novel.summary = loadedCheerio('.summary')
|
||||
.text()
|
||||
.replace('Summary', '')
|
||||
.trim();
|
||||
|
||||
novel.genres = '';
|
||||
|
||||
loadedCheerio('div.categories > ul > li').each(function () {
|
||||
novel.genres += loadedCheerio(this).text().trim() + ',';
|
||||
});
|
||||
|
||||
loadedCheerio('div.header-stats > span').each(function () {
|
||||
if (loadedCheerio(this).find('small').text() === 'Status') {
|
||||
novel.status = loadedCheerio(this).find('strong').text();
|
||||
}
|
||||
});
|
||||
|
||||
novel.genres = novel.genres.slice(0, -1);
|
||||
|
||||
novel.author = loadedCheerio('span[itemprop=author]').text();
|
||||
|
||||
let novelChapters: Chapter.Item[] = [];
|
||||
|
||||
const novelId = novelUrl.replace('.html', '').replace(baseUrl, '');
|
||||
|
||||
const latestChapterNo = parseInt(loadedCheerio('.header-stats')
|
||||
.find('span > strong')
|
||||
.first()
|
||||
.text()
|
||||
.trim());
|
||||
|
||||
let lastChapterNo = 1;
|
||||
loadedCheerio('.chapter-list li').each(function () {
|
||||
const chapterName = loadedCheerio(this)
|
||||
.find('a .chapter-title')
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
const chapterUrl = baseUrl + loadedCheerio(this).find('a').attr('href')?.trim();
|
||||
|
||||
const releaseDate = loadedCheerio(this)
|
||||
.find('a .chapter-update')
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
lastChapterNo = parseInt(loadedCheerio(this).find('a .chapter-no').text().trim());
|
||||
|
||||
const chapter = { name: chapterName, releaseTime: releaseDate, url: chapterUrl };
|
||||
|
||||
novelChapters.push(chapter);
|
||||
});
|
||||
|
||||
// Itterate once more before loop to finish off
|
||||
lastChapterNo++;
|
||||
for (let i = lastChapterNo; i <= latestChapterNo; i++) {
|
||||
const chapterName = 'Chapter ' + i;
|
||||
const chapterUrl = baseUrl + novelId+'_'+ i +'.html';
|
||||
const releaseDate = null;
|
||||
|
||||
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 result = await fetchApi(chapterUrl);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const chapterText = loadedCheerio('.chapter-content').html();
|
||||
return chapterText;
|
||||
|
||||
novel.name = loadedCheerio('h1.novel-title').text();
|
||||
|
||||
const coverUri = loadedCheerio('figure.cover > img').attr('data-src');
|
||||
novel.cover = site + coverUri;
|
||||
|
||||
novel.summary = loadedCheerio('.summary')
|
||||
.text()
|
||||
.replace('Summary', '')
|
||||
.trim();
|
||||
|
||||
novel.genres = '';
|
||||
|
||||
loadedCheerio('div.categories > ul > li').each(function () {
|
||||
novel.genres += loadedCheerio(this).text().trim() + ',';
|
||||
});
|
||||
|
||||
loadedCheerio('div.header-stats > span').each(function () {
|
||||
if (loadedCheerio(this).find('small').text() === 'Status') {
|
||||
novel.status = loadedCheerio(this).find('strong').text();
|
||||
}
|
||||
});
|
||||
|
||||
novel.genres = novel.genres.slice(0, -1);
|
||||
|
||||
novel.author = loadedCheerio('span[itemprop=author]').text();
|
||||
|
||||
let novelChapters: Chapter.Item[] = [];
|
||||
|
||||
const novelId = novelUrl.replace('.html', '').replace(site, '');
|
||||
|
||||
const latestChapterNo = parseInt(loadedCheerio('.header-stats')
|
||||
.find('span > strong')
|
||||
.first()
|
||||
.text()
|
||||
.trim());
|
||||
|
||||
let lastChapterNo = 1;
|
||||
loadedCheerio('.chapter-list li').each(function () {
|
||||
const chapterName = loadedCheerio(this)
|
||||
.find('a .chapter-title')
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
const chapterUrl = site + loadedCheerio(this).find('a').attr('href')?.trim();
|
||||
|
||||
const releaseDate = loadedCheerio(this)
|
||||
.find('a .chapter-update')
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
lastChapterNo = parseInt(loadedCheerio(this).find('a .chapter-no').text().trim());
|
||||
|
||||
const chapter = { name: chapterName, releaseTime: parseMadaraDate(releaseDate), url: chapterUrl };
|
||||
|
||||
novelChapters.push(chapter);
|
||||
});
|
||||
|
||||
// Itterate once more before loop to finish off
|
||||
lastChapterNo++;
|
||||
for (let i = lastChapterNo; i <= latestChapterNo; i++) {
|
||||
const chapterName = 'Chapter ' + i;
|
||||
const chapterUrl = site + novelId+'_'+ i +'.html';
|
||||
const releaseDate = null;
|
||||
|
||||
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 result = await fetchApi(chapterUrl);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const chapterText = loadedCheerio('.chapter-content').html();
|
||||
return chapterText;
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const searchUrl = site + 'e/search/index.php';
|
||||
|
||||
const result = await fetchApi(searchUrl, {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Referer: site + 'search.html',
|
||||
Origin: site,
|
||||
'user-agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36',
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
show: 'title',
|
||||
tempid: 1,
|
||||
tbname: 'news',
|
||||
keyboard: searchTerm,
|
||||
}),
|
||||
});
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio('li.novel-item').each(function () {
|
||||
const novelName = loadedCheerio(this).find('h4').text();
|
||||
const novelUrl = site + loadedCheerio(this).find('a').attr('href');
|
||||
|
||||
const coverUri = site + loadedCheerio(this).find('img').attr('data-src');
|
||||
const novelCover = site + coverUri;
|
||||
const novel = {
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
url: novelUrl,
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const searchUrl = baseUrl + 'e/search/index.php';
|
||||
|
||||
const result = await fetchApi(searchUrl, {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Referer: baseUrl + 'search.html',
|
||||
Origin: baseUrl,
|
||||
'user-agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36',
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
show: 'title',
|
||||
tempid: 1,
|
||||
tbname: 'news',
|
||||
keyboard: searchTerm,
|
||||
}),
|
||||
});
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio('li.novel-item').each(function () {
|
||||
const novelName = loadedCheerio(this).find('h4').text();
|
||||
const novelUrl = baseUrl + loadedCheerio(this).find('a').attr('href');
|
||||
|
||||
const coverUri = baseUrl + loadedCheerio(this).find('img').attr('data-src');
|
||||
const novelCover = baseUrl + coverUri;
|
||||
const novel = {
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
url: novelUrl,
|
||||
};
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url, {});
|
||||
};
|
||||
|
||||
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = fetchFile;
|
||||
|
||||
export const filters = [{"key":"sort","label":"Sort By","values":[{"label":"New","value":"newstime"},{"label":"Popular","value":"onclick"},{"label":"Updates","value":"lastdotime"}], "inputType":FilterInputs.Picker},{"key":"status","label":"Status","values":[{"label":"All","value":"all"},{"label":"Completed","value":"Completed"},{"label":"Ongoing","value":"Ongoing"}], "inputType":FilterInputs.Picker},{"key":"genres","label":"Genre / Category","values":[{"label":"All","value":"all"},{"label":"Action","value":"action"},{"label":"Adult","value":"adult"},{"label":"Adventure","value":"adventure"},{"label":"Comedy","value":"comedy"},{"label":"Contemporary Romance","value":"contemporary-romance"},{"label":"Drama","value":"drama"},{"label":"Eastern Fantasy","value":"eastern-fantasy"},{"label":"Ecchi","value":"ecchi"},{"label":"Fantasy","value":"fantasy"},{"label":"Fantasy Romance","value":"fantasy-romance"},{"label":"Game","value":"game"},{"label":"Gender Bender","value":"gender-bender"},{"label":"Harem","value":"harem"},{"label":"Historical","value":"historical"},{"label":"Horror","value":"horror"},{"label":"Josei","value":"josei"},{"label":"Lolicon","value":"lolicon"},{"label":"Magical Realism","value":"magical-realism"},{"label":"Martial Arts","value":"martial-arts"},{"label":"Mature","value":"mature"},{"label":"Mecha","value":"mecha"},{"label":"Mystery","value":"mystery"},{"label":"Psychological","value":"psychological"},{"label":"Romance","value":"romance"},{"label":"School Life","value":"school-life"},{"label":"Sci-fi","value":"sci-fi"},{"label":"Seinen","value":"seinen"},{"label":"Shoujo","value":"shoujo"},{"label":"Shounen","value":"shounen"},{"label":"Shounen Ai","value":"shounen-ai"},{"label":"Slice of Life","value":"slice-of-life"},{"label":"Smut","value":"smut"},{"label":"Sports","value":"sports"},{"label":"Supernatural","value":"supernatural"},{"label":"Tragedy","value":"tragedy"},{"label":"Video Games","value":"video-games"},{"label":"Wuxia","value":"wuxia"},{"label":"Xianxia","value":"xianxia"},{"label":"Xuanhuan","value":"xuanhuan"},{"label":"Yaoi","value":"yaoi"}], "inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://www.lunarletters.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "series" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "series/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://mtlnovel.club/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://mtl-novel.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "manga-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker},{"key":"genres","label":"GENRES","values":[{"label":"Action","value":"action"},{"label":"Adult","value":"adult"},{"label":"Adventure","value":"adventure"},{"label":"Chinese","value":"chinese"},{"label":"Comedy","value":"comedy"},{"label":"Conan","value":"conan"},{"label":"Drama","value":"drama"},{"label":"Erciyuan","value":"erciyuan"},{"label":"Fairy Tail","value":"fairy-tail"},{"label":"Faloo","value":"faloo"},{"label":"Fan-Fiction","value":"fan-fiction"},{"label":"Fantasy","value":"fantasy"},{"label":"FoodWars!","value":"foodwars"},{"label":"Game","value":"game"},{"label":"Gender Bender","value":"gender-bender"},{"label":"Harem","value":"harem"},{"label":"Historical","value":"historical"},{"label":"Horror","value":"horror"},{"label":"Hunter x Hunter","value":"hunter-x-hunter"},{"label":"Japanese","value":"japanese"},{"label":"Jojo","value":"jojo"},{"label":"Josei","value":"josei"},{"label":"Korean","value":"korean"},{"label":"Martial Arts","value":"martial-arts"},{"label":"Marvel","value":"marvel"},{"label":"Mature","value":"mature"},{"label":"Mecha","value":"mecha"},{"label":"Military","value":"military"},{"label":"Mystery","value":"mystery"},{"label":"Naruto","value":"naruto"},{"label":"One piece","value":"one-piece"},{"label":"Pokemon","value":"pokemon"},{"label":"Psychological","value":"psychological"},{"label":"Romance","value":"romance"},{"label":"School Life","value":"school-life"},{"label":"Sci-fi","value":"sci-fi"},{"label":"Seinen","value":"seinen"},{"label":"Shoujo","value":"shoujo"},{"label":"Shoujo Ai","value":"shoujo-ai"},{"label":"Shounen Ai","value":"shounen-ai"},{"label":"Slice of Life","value":"slice-of-life"},{"label":"Smut","value":"smut"},{"label":"Sport","value":"sport"},{"label":"Sports","value":"sports"},{"label":"Supernatural","value":"supernatural"},{"label":"System","value":"system"},{"label":"Tragedy","value":"tragedy"},{"label":"Urban","value":"urban"},{"label":"Urban Life","value":"urban-life"},{"label":"Wuxia","value":"wuxia"},{"label":"Xianxia","value":"xianxia"},{"label":"Xuanhuan","value":"xuanhuan"},{"label":"Yaoi","value":"yaoi"},{"label":"Yuri","value":"yuri"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://mostnovel.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "manga" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "manga-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "manga/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker},{"key":"genres","label":"GENRES","values":[{"label":"ACG","value":"acg"},{"label":"Action","value":"action"},{"label":"Adult","value":"adult"},{"label":"Adventure","value":"adventure"},{"label":"AGA","value":"aga"},{"label":"Anime","value":"anime"},{"label":"Anime & Comics","value":"anime-comics"},{"label":"Bender","value":"bender"},{"label":"BL","value":"bl"},{"label":"Cartoon","value":"cartoon"},{"label":"Comedy","value":"comedy"},{"label":"Comic","value":"comic"},{"label":"Contemporary Romance","value":"contemporary-romance"},{"label":"Cooking","value":"cooking"},{"label":"Cultivation","value":"cultivation"},{"label":"Detective","value":"detective"},{"label":"Doujinshi","value":"doujinshi"},{"label":"Drama","value":"drama"},{"label":"Eastern Fantasy","value":"eastern-fantasy"},{"label":"Ecchi","value":"ecchi"},{"label":"Fantasy","value":"fantasy"},{"label":"Gender Bender","value":"gender-bender"},{"label":"Harem","value":"harem"},{"label":"Historical","value":"historical"},{"label":"Horror","value":"horror"},{"label":"Horror&Thriller","value":"horrorthriller"},{"label":"Josei","value":"josei"},{"label":"Live action","value":"live-action"},{"label":"Magical Realism","value":"magical-realism"},{"label":"Manga","value":"manga"},{"label":"Manhua","value":"manhua"},{"label":"Manhwa","value":"manhwa"},{"label":"Martial Arts","value":"martial-arts"},{"label":"Mature","value":"mature"},{"label":"Mecha","value":"mecha"},{"label":"Modern","value":"modern"},{"label":"Movies","value":"movies"},{"label":"Mystery","value":"mystery"},{"label":"One shot","value":"one-shot"},{"label":"Psychological","value":"psychological"},{"label":"Romance","value":"romance"},{"label":"School Life","value":"school-life"},{"label":"Sci-fi","value":"sci-fi"},{"label":"Seinen","value":"seinen"},{"label":"Shoujo","value":"shoujo"},{"label":"Shoujo Ai","value":"shoujo-ai"},{"label":"Shounen","value":"shounen"},{"label":"Shounen Ai","value":"shounen-ai"},{"label":"Slice of Life","value":"slice-of-life"},{"label":"Smart MC","value":"smart-mc"},{"label":"Smut","value":"smut"},{"label":"Soft Yaoi","value":"soft-yaoi"},{"label":"Soft Yuri","value":"soft-yuri"},{"label":"Sports","value":"sports"},{"label":"Supernatural","value":"supernatural"},{"label":"Tragedy","value":"tragedy"},{"label":"Urban Life","value":"urban-life"},{"label":"Video Games","value":"video-games"},{"label":"Webtoon","value":"webtoon"},{"label":"Wuxia","value":"wuxia"},{"label":"Xianxia","value":"xianxia"},{"label":"Xuanhuan","value":"xuanhuan"},{"label":"Yaoi","value":"yaoi"},{"label":"Yuri","value":"yuri"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://mysticalmerries.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "series" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "series/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker},{"key":"genres","label":"GENRES","values":[{"label":"Action","value":"action"},{"label":"Adaptation","value":"adaptation"},{"label":"Adventure","value":"adventure"},{"label":"bilibili","value":"bilibili"},{"label":"Chinese Novel","value":"chinese-novel"},{"label":"Comedy","value":"comedy"},{"label":"Crossdressing","value":"crossdressing"},{"label":"Drama","value":"drama"},{"label":"Dropped","value":"dropped"},{"label":"Family","value":"family"},{"label":"Fantasy","value":"fantasy"},{"label":"Gender Bender","value":"gender-bender"},{"label":"Historical","value":"historical"},{"label":"Horror","value":"horror"},{"label":"Isekai","value":"isekai"},{"label":"Korean Novel","value":"korean-novell"},{"label":"Manga","value":"manga"},{"label":"Manhua","value":"manhua"},{"label":"Manhwa","value":"manhwa"},{"label":"Modern Romance","value":"modern-romance"},{"label":"Mystery","value":"mystery"},{"label":"Office Workers","value":"office-workers"},{"label":"One shot","value":"one-shot"},{"label":"Pilot Novel","value":"pilot-novel"},{"label":"R15","value":"r15"},{"label":"R19","value":"r19"},{"label":"Regression","value":"regression"},{"label":"Reincarnation","value":"reincarnation"},{"label":"Reverse Harem","value":"reverse-harem"},{"label":"Romance Fantasy","value":"romance-fantasy"},{"label":"School Life","value":"school-life"},{"label":"Sci-fi","value":"sci-fi"},{"label":"Slice of Life","value":"slice-of-life"},{"label":"Smut","value":"smut"},{"label":"Supernatural","value":"supernatural"},{"label":"Violence","value":"violence"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://www.neosekaitranslations.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker},{"key":"genres","label":"GENRES","values":[{"label":"Action","value":"action"},{"label":"Adventure","value":"adventure"},{"label":"Comedy","value":"comedy"},{"label":"Drama","value":"drama"},{"label":"Fantasy","value":"fantasy"},{"label":"Harem","value":"harem"},{"label":"Horror","value":"horror"},{"label":"Mature","value":"mature"},{"label":"Mecha","value":"mecha"},{"label":"Mystery","value":"mystery"},{"label":"Psychological","value":"psychological"},{"label":"Romance","value":"romance"},{"label":"School Life","value":"school-life"},{"label":"Sci-Fi","value":"sci-fi"},{"label":"Slice of Life","value":"slice-of-life"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://nocturnetls.net/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novels" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "manga-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novels/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker},{"key":"genres","label":"GENRES","values":[{"label":"Action","value":"action"},{"label":"Adult","value":"adult"},{"label":"Adventure","value":"adventure"},{"label":"Anime","value":"anime"},{"label":"Cartoon","value":"cartoon"},{"label":"Comedy","value":"comedy"},{"label":"Comic","value":"comic"},{"label":"Cooking","value":"cooking"},{"label":"Detective","value":"detective"},{"label":"Doujinshi","value":"doujinshi"},{"label":"Drama","value":"drama"},{"label":"Ecchi","value":"ecchi"},{"label":"Fantasy","value":"fantasy"},{"label":"Gender Bender","value":"gender-bender"},{"label":"Harem","value":"harem"},{"label":"Historical","value":"historical"},{"label":"Horror","value":"horror"},{"label":"Josei","value":"josei"},{"label":"Live action","value":"live-action"},{"label":"Manga","value":"manga"},{"label":"Manhua","value":"manhua"},{"label":"Manhwa","value":"manhwa"},{"label":"Martial Arts","value":"martial-arts"},{"label":"Mature","value":"mature"},{"label":"Mecha","value":"mecha"},{"label":"Mystery","value":"mystery"},{"label":"One shot","value":"one-shot"},{"label":"Psychological","value":"psychological"},{"label":"Romance","value":"romance"},{"label":"School Life","value":"school-life"},{"label":"Sci-fi","value":"sci-fi"},{"label":"Seinen","value":"seinen"},{"label":"Shoujo","value":"shoujo"},{"label":"Shoujo Ai","value":"shoujo-ai"},{"label":"Shounen","value":"shounen"},{"label":"Shounen Ai","value":"shounen-ai"},{"label":"Slice of Life","value":"slice-of-life"},{"label":"Smut","value":"smut"},{"label":"Soft Yaoi","value":"soft-yaoi"},{"label":"Soft Yuri","value":"soft-yuri"},{"label":"Sports","value":"sports"},{"label":"Supernatural","value":"supernatural"},{"label":"Tragedy","value":"tragedy"},{"label":"Webtoon","value":"webtoon"},{"label":"Yaoi","value":"yaoi"},{"label":"Yuri","value":"yuri"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://noobchan.xyz/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://www.novelmultiverse.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker},{"key":"genres","label":"GENRES","values":[{"label":"Action","value":"action"},{"label":"Adult","value":"adult"},{"label":"Adventure","value":"adventure"},{"label":"Anime","value":"anime"},{"label":"Cartoon","value":"cartoon"},{"label":"Comedy","value":"comedy"},{"label":"Comic","value":"comic"},{"label":"Cooking","value":"cooking"},{"label":"Detective","value":"detective"},{"label":"Doujinshi","value":"doujinshi"},{"label":"Drama","value":"drama"},{"label":"Ecchi","value":"ecchi"},{"label":"Fantasy","value":"fantasy"},{"label":"Gender Bender","value":"gender-bender"},{"label":"Harem","value":"harem"},{"label":"Historical","value":"historical"},{"label":"Horror","value":"horror"},{"label":"Josei","value":"josei"},{"label":"Live action","value":"live-action"},{"label":"Manga","value":"manga"},{"label":"Manhua","value":"manhua"},{"label":"Manhwa","value":"manhwa"},{"label":"Martial Arts","value":"martial-arts"},{"label":"Mature","value":"mature"},{"label":"Mecha","value":"mecha"},{"label":"Mystery","value":"mystery"},{"label":"One shot","value":"one-shot"},{"label":"Original","value":"original"},{"label":"Psychological","value":"psychological"},{"label":"Romance","value":"romance"},{"label":"School Life","value":"school-life"},{"label":"Sci-fi","value":"sci-fi"},{"label":"Seinen","value":"seinen"},{"label":"Shoujo","value":"shoujo"},{"label":"Shoujo Ai","value":"shoujo-ai"},{"label":"Shounen","value":"shounen"},{"label":"Shounen Ai","value":"shounen-ai"},{"label":"Slice of Life","value":"slice-of-life"},{"label":"Smut","value":"smut"},{"label":"Soft Yaoi","value":"soft-yaoi"},{"label":"Soft Yuri","value":"soft-yuri"},{"label":"Sports","value":"sports"},{"label":"Supernatural","value":"supernatural"},{"label":"Tragedy","value":"tragedy"},{"label":"Translation","value":"translation"},{"label":"Webtoon","value":"webtoon"},{"label":"Wuxia","value":"wuxia"},{"label":"Xianxia","value":"xianxia"},{"label":"Xuanhuan","value":"xuanhuan"},{"label":"Yaoi","value":"yaoi"},{"label":"Yuri","value":"yuri"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,31 +2,37 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export const id = "novelr18";
|
||||
export const name = "NovelR18 [madara]";
|
||||
export const name = "NovelR18 [madara]";
|
||||
export const icon = "multisrc/madara/icons/novelr18.png";
|
||||
export const version = "1.0.0";
|
||||
export const site = "https://novelr18.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Sort by :","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest Update","value":"latest"},{"label":"Most Views","value":"views"},{"label":"Newly Added Novels","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://noveltranslate.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "all-novels" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "manga-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "all-novels/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker},{"key":"genres","label":"GENRES","values":[{"label":"Action","value":"action"},{"label":"Adult","value":"adult"},{"label":"Adventure","value":"adventure"},{"label":"Bleach","value":"bleach"},{"label":"Chinese","value":"chinese"},{"label":"Comedy","value":"comedy"},{"label":"Conan","value":"conan"},{"label":"cooking","value":"cooking"},{"label":"Dragon Ball","value":"dragon-ball"},{"label":"Drama","value":"drama"},{"label":"Ecchi","value":"ecchi"},{"label":"Erciyuan","value":"erciyuan"},{"label":"Fairy Tail","value":"fairy-tail"},{"label":"Faloo","value":"faloo"},{"label":"Fan-Fiction","value":"fan-fiction"},{"label":"Fantasy","value":"fantasy"},{"label":"FoodWars!","value":"foodwars"},{"label":"Game","value":"game"},{"label":"Gender Bender","value":"gender-bender"},{"label":"Harem","value":"harem"},{"label":"Historical","value":"historical"},{"label":"Horror","value":"horror"},{"label":"Hunter x Hunter","value":"hunter-x-hunter"},{"label":"Isekai","value":"isekai"},{"label":"Japanese","value":"japanese"},{"label":"Jojo","value":"jojo"},{"label":"Josei","value":"josei"},{"label":"Korean","value":"korean"},{"label":"Martial Arts","value":"martial-arts"},{"label":"Marvel","value":"marvel"},{"label":"Mature","value":"mature"},{"label":"Mecha","value":"mecha"},{"label":"Military","value":"military"},{"label":"Mystery","value":"mystery"},{"label":"Naruto","value":"naruto"},{"label":"One piece","value":"one-piece"},{"label":"Pokemon","value":"pokemon"},{"label":"Political","value":"political"},{"label":"Psychological","value":"psychological"},{"label":"Romance","value":"romance"},{"label":"School Life","value":"school-life"},{"label":"Sci-fi","value":"sci-fi"},{"label":"Seinen","value":"seinen"},{"label":"Shoujo","value":"shoujo"},{"label":"Shoujo Ai","value":"shoujo-ai"},{"label":"Shounen","value":"shounen"},{"label":"Shounen Ai","value":"shounen-ai"},{"label":"Slice of Life","value":"slice-of-life"},{"label":"Smut","value":"smut"},{"label":"Sport","value":"sport"},{"label":"Sports","value":"sports"},{"label":"Supernatural","value":"supernatural"},{"label":"System","value":"system"},{"label":"Toriko","value":"toriko"},{"label":"Tragedy","value":"tragedy"},{"label":"Urban","value":"urban"},{"label":"Urban Life","value":"urban-life"},{"label":"Wuxia","value":"wuxia"},{"label":"Xianxia","value":"xianxia"},{"label":"Xuanhuan","value":"xuanhuan"},{"label":"Yaoi","value":"yaoi"},{"label":"Yuri","value":"yuri"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -1,197 +1,195 @@
|
||||
|
||||
import { load as parseHTML } from "cheerio";
|
||||
// import dayjs from 'dayjs';
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
// import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
// import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
|
||||
// import { showToast } from "@libs/showToast";
|
||||
// import { Filter, FilterInputs } from "@libs/filterInputs";
|
||||
// import { NovelStatus } from '@libs/novelStatus';
|
||||
// import { defaultCover } from "@libs/defaultCover";
|
||||
|
||||
|
||||
export const id = "Novelmt.com";
|
||||
export const name = "Novelmt.com";
|
||||
export const icon = "multisrc/readwn/icon/novelmt.png";
|
||||
export const version = "1.0.0";
|
||||
export const site = "https://www.novelmt.com/";
|
||||
// export const filters: Filter[] = [];
|
||||
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ filters, showLatestNovels }
|
||||
) {
|
||||
const novels: Novel.Item[] = [];
|
||||
const pageNo = page - 1;
|
||||
|
||||
const url = baseUrl + 'list/all/all-onclick-' + pageNo +'.html';
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('li.novel-item').each(function () {
|
||||
const novelName = loadedCheerio(this).find('h4').text();
|
||||
const novelUrl = baseUrl + loadedCheerio(this).find('a').attr('href');
|
||||
|
||||
const coverUri = loadedCheerio(this)
|
||||
.find('.novel-cover > img')
|
||||
.attr('data-src');
|
||||
|
||||
const novelCover = baseUrl + coverUri;
|
||||
|
||||
const novel = { name: novelName, cover: novelCover, url: novelUrl };
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
|
||||
return novels;
|
||||
import { load as parseHTML } from "cheerio";
|
||||
// import dayjs from 'dayjs';
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
// import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
|
||||
// import { showToast } from "@libs/showToast";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
// import { NovelStatus } from '@libs/novelStatus';
|
||||
// import { defaultCover } from "@libs/defaultCover";
|
||||
|
||||
export const id = "Novelmt.com";
|
||||
export const name = "Novelmt.com";
|
||||
export const icon = "multisrc/readwn/icons/novelmt.png";
|
||||
export const version = "1.0.0";
|
||||
export const site = "https://www.novelmt.com/";
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ filters, showLatestNovels }
|
||||
) {
|
||||
const novels: Novel.Item[] = [];
|
||||
const pageNo = page - 1;
|
||||
|
||||
let url = site + 'list/';
|
||||
url += (filters?.genres || 'all') + '/';
|
||||
url += (filters?.status || 'all') + '-';
|
||||
url += (showLatestNovels ? 'lastdotime' : filters?.sort || 'newstime') + '-';
|
||||
url += pageNo + '.html';
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('li.novel-item').each(function () {
|
||||
const novelName = loadedCheerio(this).find('h4').text();
|
||||
const novelUrl = site + loadedCheerio(this).find('a').attr('href');
|
||||
|
||||
const coverUri = loadedCheerio(this)
|
||||
.find('.novel-cover > img')
|
||||
.attr('data-src');
|
||||
|
||||
const novelCover = site + coverUri;
|
||||
|
||||
const novel = { name: novelName, cover: novelCover, url: novelUrl };
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi(novelUrl);
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi(novelUrl);
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
novel.name = loadedCheerio('h1.novel-title').text();
|
||||
|
||||
const coverUri = loadedCheerio('figure.cover > img').attr('data-src');
|
||||
novel.cover = baseUrl + coverUri;
|
||||
|
||||
novel.summary = loadedCheerio('.summary')
|
||||
.text()
|
||||
.replace('Summary', '')
|
||||
.trim();
|
||||
|
||||
novel.genres = '';
|
||||
|
||||
loadedCheerio('div.categories > ul > li').each(function () {
|
||||
novel.genres += loadedCheerio(this).text().trim() + ',';
|
||||
});
|
||||
|
||||
loadedCheerio('div.header-stats > span').each(function () {
|
||||
if (loadedCheerio(this).find('small').text() === 'Status') {
|
||||
novel.status = loadedCheerio(this).find('strong').text();
|
||||
}
|
||||
});
|
||||
|
||||
novel.genres = novel.genres.slice(0, -1);
|
||||
|
||||
novel.author = loadedCheerio('span[itemprop=author]').text();
|
||||
|
||||
let novelChapters: Chapter.Item[] = [];
|
||||
|
||||
const novelId = novelUrl.replace('.html', '').replace(baseUrl, '');
|
||||
|
||||
const latestChapterNo = parseInt(loadedCheerio('.header-stats')
|
||||
.find('span > strong')
|
||||
.first()
|
||||
.text()
|
||||
.trim());
|
||||
|
||||
let lastChapterNo = 1;
|
||||
loadedCheerio('.chapter-list li').each(function () {
|
||||
const chapterName = loadedCheerio(this)
|
||||
.find('a .chapter-title')
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
const chapterUrl = baseUrl + loadedCheerio(this).find('a').attr('href')?.trim();
|
||||
|
||||
const releaseDate = loadedCheerio(this)
|
||||
.find('a .chapter-update')
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
lastChapterNo = parseInt(loadedCheerio(this).find('a .chapter-no').text().trim());
|
||||
|
||||
const chapter = { name: chapterName, releaseTime: releaseDate, url: chapterUrl };
|
||||
|
||||
novelChapters.push(chapter);
|
||||
});
|
||||
|
||||
// Itterate once more before loop to finish off
|
||||
lastChapterNo++;
|
||||
for (let i = lastChapterNo; i <= latestChapterNo; i++) {
|
||||
const chapterName = 'Chapter ' + i;
|
||||
const chapterUrl = baseUrl + novelId+'_'+ i +'.html';
|
||||
const releaseDate = null;
|
||||
|
||||
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 result = await fetchApi(chapterUrl);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const chapterText = loadedCheerio('.chapter-content').html();
|
||||
return chapterText;
|
||||
|
||||
novel.name = loadedCheerio('h1.novel-title').text();
|
||||
|
||||
const coverUri = loadedCheerio('figure.cover > img').attr('data-src');
|
||||
novel.cover = site + coverUri;
|
||||
|
||||
novel.summary = loadedCheerio('.summary')
|
||||
.text()
|
||||
.replace('Summary', '')
|
||||
.trim();
|
||||
|
||||
novel.genres = '';
|
||||
|
||||
loadedCheerio('div.categories > ul > li').each(function () {
|
||||
novel.genres += loadedCheerio(this).text().trim() + ',';
|
||||
});
|
||||
|
||||
loadedCheerio('div.header-stats > span').each(function () {
|
||||
if (loadedCheerio(this).find('small').text() === 'Status') {
|
||||
novel.status = loadedCheerio(this).find('strong').text();
|
||||
}
|
||||
});
|
||||
|
||||
novel.genres = novel.genres.slice(0, -1);
|
||||
|
||||
novel.author = loadedCheerio('span[itemprop=author]').text();
|
||||
|
||||
let novelChapters: Chapter.Item[] = [];
|
||||
|
||||
const novelId = novelUrl.replace('.html', '').replace(site, '');
|
||||
|
||||
const latestChapterNo = parseInt(loadedCheerio('.header-stats')
|
||||
.find('span > strong')
|
||||
.first()
|
||||
.text()
|
||||
.trim());
|
||||
|
||||
let lastChapterNo = 1;
|
||||
loadedCheerio('.chapter-list li').each(function () {
|
||||
const chapterName = loadedCheerio(this)
|
||||
.find('a .chapter-title')
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
const chapterUrl = site + loadedCheerio(this).find('a').attr('href')?.trim();
|
||||
|
||||
const releaseDate = loadedCheerio(this)
|
||||
.find('a .chapter-update')
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
lastChapterNo = parseInt(loadedCheerio(this).find('a .chapter-no').text().trim());
|
||||
|
||||
const chapter = { name: chapterName, releaseTime: parseMadaraDate(releaseDate), url: chapterUrl };
|
||||
|
||||
novelChapters.push(chapter);
|
||||
});
|
||||
|
||||
// Itterate once more before loop to finish off
|
||||
lastChapterNo++;
|
||||
for (let i = lastChapterNo; i <= latestChapterNo; i++) {
|
||||
const chapterName = 'Chapter ' + i;
|
||||
const chapterUrl = site + novelId+'_'+ i +'.html';
|
||||
const releaseDate = null;
|
||||
|
||||
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 result = await fetchApi(chapterUrl);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const chapterText = loadedCheerio('.chapter-content').html();
|
||||
return chapterText;
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const searchUrl = site + 'e/search/index.php';
|
||||
|
||||
const result = await fetchApi(searchUrl, {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Referer: site + 'search.html',
|
||||
Origin: site,
|
||||
'user-agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36',
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
show: 'title',
|
||||
tempid: 1,
|
||||
tbname: 'news',
|
||||
keyboard: searchTerm,
|
||||
}),
|
||||
});
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio('li.novel-item').each(function () {
|
||||
const novelName = loadedCheerio(this).find('h4').text();
|
||||
const novelUrl = site + loadedCheerio(this).find('a').attr('href');
|
||||
|
||||
const coverUri = site + loadedCheerio(this).find('img').attr('data-src');
|
||||
const novelCover = site + coverUri;
|
||||
const novel = {
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
url: novelUrl,
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const searchUrl = baseUrl + 'e/search/index.php';
|
||||
|
||||
const result = await fetchApi(searchUrl, {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Referer: baseUrl + 'search.html',
|
||||
Origin: baseUrl,
|
||||
'user-agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36',
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
show: 'title',
|
||||
tempid: 1,
|
||||
tbname: 'news',
|
||||
keyboard: searchTerm,
|
||||
}),
|
||||
});
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio('li.novel-item').each(function () {
|
||||
const novelName = loadedCheerio(this).find('h4').text();
|
||||
const novelUrl = baseUrl + loadedCheerio(this).find('a').attr('href');
|
||||
|
||||
const coverUri = baseUrl + loadedCheerio(this).find('img').attr('data-src');
|
||||
const novelCover = baseUrl + coverUri;
|
||||
const novel = {
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
url: novelUrl,
|
||||
};
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url, {});
|
||||
};
|
||||
|
||||
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = fetchFile;
|
||||
|
||||
export const filters = [{"key":"sort","label":"Sort By","values":[{"label":"New","value":"newstime"},{"label":"Popular","value":"onclick"},{"label":"Updates","value":"lastdotime"}], "inputType":FilterInputs.Picker},{"key":"status","label":"Status","values":[{"label":"All","value":"all"},{"label":"Completed","value":"Completed"},{"label":"Ongoing","value":"Ongoing"}], "inputType":FilterInputs.Picker},{"key":"genres","label":"Genre / Category","values":[{"label":"All","value":"all"},{"label":"Action","value":"action"},{"label":"Adult","value":"adult"},{"label":"Adventure","value":"adventure"},{"label":"Billionaire","value":"billionaire"},{"label":"CEO","value":"ceo"},{"label":"Chinese","value":"chinese"},{"label":"Comedy","value":"comedy"},{"label":"Contemporary Romance","value":"contemporary-romance"},{"label":"Drama","value":"drama"},{"label":"Eastern Fantasy","value":"eastern-fantasy"},{"label":"Ecchi","value":"ecchi"},{"label":"Erciyuan","value":"erciyuan"},{"label":"Faloo","value":"faloo"},{"label":"Fan-Fiction","value":"fan-fiction"},{"label":"Fantasy","value":"fantasy"},{"label":"Fantasy Romance","value":"fantasy-romance"},{"label":"Farming","value":"farming"},{"label":"Game","value":"game"},{"label":"Games","value":"games"},{"label":"Gay Romance","value":"gay-romance"},{"label":"Gender Bender","value":"gender-bender"},{"label":"Harem","value":"harem"},{"label":"Historical","value":"historical"},{"label":"Historical Romance","value":"historical-romance"},{"label":"Horror","value":"horror"},{"label":"Isekai","value":"isekai"},{"label":"Japanese","value":"japanese"},{"label":"Josei","value":"josei"},{"label":"Korean","value":"korean"},{"label":"Lolicon","value":"lolicon"},{"label":"Magic","value":"magic"},{"label":"Magical Realism","value":"magical-realism"},{"label":"Martial Arts","value":"martial-arts"},{"label":"Mature","value":"mature"},{"label":"Mecha","value":"mecha"},{"label":"Military","value":"military"},{"label":"Modern Life","value":"modern-life"},{"label":"Modern Romance","value":"modern-romance"},{"label":"Mystery","value":"mystery"},{"label":"Psychological","value":"psychological"},{"label":"Romance","value":"romance"},{"label":"Romantic","value":"romantic"},{"label":"School Life","value":"school-life"},{"label":"Sci-fi","value":"sci-fi"},{"label":"Seinen","value":"seinen"},{"label":"Shoujo","value":"shoujo"},{"label":"Shoujo Ai","value":"shoujo-ai"},{"label":"Shounen","value":"shounen"},{"label":"Shounen Ai","value":"shounen-ai"},{"label":"Slice of Life","value":"slice-of-life"},{"label":"Smut","value":"smut"},{"label":"Sports","value":"sports"},{"label":"Supernatural","value":"supernatural"},{"label":"Tragedy","value":"tragedy"},{"label":"Two-dimensional","value":"two-dimensional"},{"label":"Urban","value":"urban"},{"label":"Urban Life","value":"urban-life"},{"label":"Video Games","value":"video-games"},{"label":"Virtual Reality","value":"virtual-reality"},{"label":"Wuxia","value":"wuxia"},{"label":"Xianxia","value":"xianxia"},{"label":"Xuanhuan","value":"xuanhuan"},{"label":"Yaoi","value":"yaoi"},{"label":"Yuri","value":"yuri"}], "inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://novelroom.net/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "manga" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "manga/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://novelstic.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://www.onlymtl.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://readwebnovels.net/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -1,197 +1,195 @@
|
||||
|
||||
import { load as parseHTML } from "cheerio";
|
||||
// import dayjs from 'dayjs';
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
// import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
// import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
|
||||
// import { showToast } from "@libs/showToast";
|
||||
// import { Filter, FilterInputs } from "@libs/filterInputs";
|
||||
// import { NovelStatus } from '@libs/novelStatus';
|
||||
// import { defaultCover } from "@libs/defaultCover";
|
||||
|
||||
|
||||
export const id = "Readwn.com";
|
||||
export const name = "Readwn.com";
|
||||
export const icon = "multisrc/readwn/icon/readwn.png";
|
||||
export const version = "1.0.0";
|
||||
export const site = "https://www.readwn.com/";
|
||||
// export const filters: Filter[] = [];
|
||||
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ filters, showLatestNovels }
|
||||
) {
|
||||
const novels: Novel.Item[] = [];
|
||||
const pageNo = page - 1;
|
||||
|
||||
const url = baseUrl + 'list/all/all-onclick-' + pageNo +'.html';
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('li.novel-item').each(function () {
|
||||
const novelName = loadedCheerio(this).find('h4').text();
|
||||
const novelUrl = baseUrl + loadedCheerio(this).find('a').attr('href');
|
||||
|
||||
const coverUri = loadedCheerio(this)
|
||||
.find('.novel-cover > img')
|
||||
.attr('data-src');
|
||||
|
||||
const novelCover = baseUrl + coverUri;
|
||||
|
||||
const novel = { name: novelName, cover: novelCover, url: novelUrl };
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
|
||||
return novels;
|
||||
import { load as parseHTML } from "cheerio";
|
||||
// import dayjs from 'dayjs';
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
// import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
|
||||
// import { showToast } from "@libs/showToast";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
// import { NovelStatus } from '@libs/novelStatus';
|
||||
// import { defaultCover } from "@libs/defaultCover";
|
||||
|
||||
export const id = "Readwn.com";
|
||||
export const name = "Readwn.com";
|
||||
export const icon = "multisrc/readwn/icons/readwn.png";
|
||||
export const version = "1.0.0";
|
||||
export const site = "https://www.readwn.com/";
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ filters, showLatestNovels }
|
||||
) {
|
||||
const novels: Novel.Item[] = [];
|
||||
const pageNo = page - 1;
|
||||
|
||||
let url = site + 'list/';
|
||||
url += (filters?.genres || 'all') + '/';
|
||||
url += (filters?.status || 'all') + '-';
|
||||
url += (showLatestNovels ? 'lastdotime' : filters?.sort || 'newstime') + '-';
|
||||
url += pageNo + '.html';
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('li.novel-item').each(function () {
|
||||
const novelName = loadedCheerio(this).find('h4').text();
|
||||
const novelUrl = site + loadedCheerio(this).find('a').attr('href');
|
||||
|
||||
const coverUri = loadedCheerio(this)
|
||||
.find('.novel-cover > img')
|
||||
.attr('data-src');
|
||||
|
||||
const novelCover = site + coverUri;
|
||||
|
||||
const novel = { name: novelName, cover: novelCover, url: novelUrl };
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi(novelUrl);
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi(novelUrl);
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
novel.name = loadedCheerio('h1.novel-title').text();
|
||||
|
||||
const coverUri = loadedCheerio('figure.cover > img').attr('data-src');
|
||||
novel.cover = baseUrl + coverUri;
|
||||
|
||||
novel.summary = loadedCheerio('.summary')
|
||||
.text()
|
||||
.replace('Summary', '')
|
||||
.trim();
|
||||
|
||||
novel.genres = '';
|
||||
|
||||
loadedCheerio('div.categories > ul > li').each(function () {
|
||||
novel.genres += loadedCheerio(this).text().trim() + ',';
|
||||
});
|
||||
|
||||
loadedCheerio('div.header-stats > span').each(function () {
|
||||
if (loadedCheerio(this).find('small').text() === 'Status') {
|
||||
novel.status = loadedCheerio(this).find('strong').text();
|
||||
}
|
||||
});
|
||||
|
||||
novel.genres = novel.genres.slice(0, -1);
|
||||
|
||||
novel.author = loadedCheerio('span[itemprop=author]').text();
|
||||
|
||||
let novelChapters: Chapter.Item[] = [];
|
||||
|
||||
const novelId = novelUrl.replace('.html', '').replace(baseUrl, '');
|
||||
|
||||
const latestChapterNo = parseInt(loadedCheerio('.header-stats')
|
||||
.find('span > strong')
|
||||
.first()
|
||||
.text()
|
||||
.trim());
|
||||
|
||||
let lastChapterNo = 1;
|
||||
loadedCheerio('.chapter-list li').each(function () {
|
||||
const chapterName = loadedCheerio(this)
|
||||
.find('a .chapter-title')
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
const chapterUrl = baseUrl + loadedCheerio(this).find('a').attr('href')?.trim();
|
||||
|
||||
const releaseDate = loadedCheerio(this)
|
||||
.find('a .chapter-update')
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
lastChapterNo = parseInt(loadedCheerio(this).find('a .chapter-no').text().trim());
|
||||
|
||||
const chapter = { name: chapterName, releaseTime: releaseDate, url: chapterUrl };
|
||||
|
||||
novelChapters.push(chapter);
|
||||
});
|
||||
|
||||
// Itterate once more before loop to finish off
|
||||
lastChapterNo++;
|
||||
for (let i = lastChapterNo; i <= latestChapterNo; i++) {
|
||||
const chapterName = 'Chapter ' + i;
|
||||
const chapterUrl = baseUrl + novelId+'_'+ i +'.html';
|
||||
const releaseDate = null;
|
||||
|
||||
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 result = await fetchApi(chapterUrl);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const chapterText = loadedCheerio('.chapter-content').html();
|
||||
return chapterText;
|
||||
|
||||
novel.name = loadedCheerio('h1.novel-title').text();
|
||||
|
||||
const coverUri = loadedCheerio('figure.cover > img').attr('data-src');
|
||||
novel.cover = site + coverUri;
|
||||
|
||||
novel.summary = loadedCheerio('.summary')
|
||||
.text()
|
||||
.replace('Summary', '')
|
||||
.trim();
|
||||
|
||||
novel.genres = '';
|
||||
|
||||
loadedCheerio('div.categories > ul > li').each(function () {
|
||||
novel.genres += loadedCheerio(this).text().trim() + ',';
|
||||
});
|
||||
|
||||
loadedCheerio('div.header-stats > span').each(function () {
|
||||
if (loadedCheerio(this).find('small').text() === 'Status') {
|
||||
novel.status = loadedCheerio(this).find('strong').text();
|
||||
}
|
||||
});
|
||||
|
||||
novel.genres = novel.genres.slice(0, -1);
|
||||
|
||||
novel.author = loadedCheerio('span[itemprop=author]').text();
|
||||
|
||||
let novelChapters: Chapter.Item[] = [];
|
||||
|
||||
const novelId = novelUrl.replace('.html', '').replace(site, '');
|
||||
|
||||
const latestChapterNo = parseInt(loadedCheerio('.header-stats')
|
||||
.find('span > strong')
|
||||
.first()
|
||||
.text()
|
||||
.trim());
|
||||
|
||||
let lastChapterNo = 1;
|
||||
loadedCheerio('.chapter-list li').each(function () {
|
||||
const chapterName = loadedCheerio(this)
|
||||
.find('a .chapter-title')
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
const chapterUrl = site + loadedCheerio(this).find('a').attr('href')?.trim();
|
||||
|
||||
const releaseDate = loadedCheerio(this)
|
||||
.find('a .chapter-update')
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
lastChapterNo = parseInt(loadedCheerio(this).find('a .chapter-no').text().trim());
|
||||
|
||||
const chapter = { name: chapterName, releaseTime: parseMadaraDate(releaseDate), url: chapterUrl };
|
||||
|
||||
novelChapters.push(chapter);
|
||||
});
|
||||
|
||||
// Itterate once more before loop to finish off
|
||||
lastChapterNo++;
|
||||
for (let i = lastChapterNo; i <= latestChapterNo; i++) {
|
||||
const chapterName = 'Chapter ' + i;
|
||||
const chapterUrl = site + novelId+'_'+ i +'.html';
|
||||
const releaseDate = null;
|
||||
|
||||
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 result = await fetchApi(chapterUrl);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const chapterText = loadedCheerio('.chapter-content').html();
|
||||
return chapterText;
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const searchUrl = site + 'e/search/index.php';
|
||||
|
||||
const result = await fetchApi(searchUrl, {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Referer: site + 'search.html',
|
||||
Origin: site,
|
||||
'user-agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36',
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
show: 'title',
|
||||
tempid: 1,
|
||||
tbname: 'news',
|
||||
keyboard: searchTerm,
|
||||
}),
|
||||
});
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio('li.novel-item').each(function () {
|
||||
const novelName = loadedCheerio(this).find('h4').text();
|
||||
const novelUrl = site + loadedCheerio(this).find('a').attr('href');
|
||||
|
||||
const coverUri = site + loadedCheerio(this).find('img').attr('data-src');
|
||||
const novelCover = site + coverUri;
|
||||
const novel = {
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
url: novelUrl,
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const searchUrl = baseUrl + 'e/search/index.php';
|
||||
|
||||
const result = await fetchApi(searchUrl, {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Referer: baseUrl + 'search.html',
|
||||
Origin: baseUrl,
|
||||
'user-agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36',
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
show: 'title',
|
||||
tempid: 1,
|
||||
tbname: 'news',
|
||||
keyboard: searchTerm,
|
||||
}),
|
||||
});
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio('li.novel-item').each(function () {
|
||||
const novelName = loadedCheerio(this).find('h4').text();
|
||||
const novelUrl = baseUrl + loadedCheerio(this).find('a').attr('href');
|
||||
|
||||
const coverUri = baseUrl + loadedCheerio(this).find('img').attr('data-src');
|
||||
const novelCover = baseUrl + coverUri;
|
||||
const novel = {
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
url: novelUrl,
|
||||
};
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url, {});
|
||||
};
|
||||
|
||||
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = fetchFile;
|
||||
|
||||
export const filters = [{"key":"sort","label":"Sort By","values":[{"label":"New","value":"newstime"},{"label":"Popular","value":"onclick"},{"label":"Updates","value":"lastdotime"}], "inputType":FilterInputs.Picker},{"key":"status","label":"Status","values":[{"label":"All","value":"all"},{"label":"Completed","value":"Completed"},{"label":"Ongoing","value":"Ongoing"}], "inputType":FilterInputs.Picker},{"key":"genres","label":"Genre / Category","values":[{"label":"All","value":"all"},{"label":"Action","value":"action"},{"label":"Adventure","value":"adventure"},{"label":"Chinese","value":"chinese"},{"label":"Comedy","value":"comedy"},{"label":"Contemporary Romance","value":"contemporary-romance"},{"label":"Drama","value":"drama"},{"label":"Eastern Fantasy","value":"eastern-fantasy"},{"label":"Ecchi","value":"ecchi"},{"label":"Erciyuan","value":"erciyuan"},{"label":"Faloo","value":"faloo"},{"label":"Fan-Fiction","value":"fan-fiction"},{"label":"Fantasy","value":"fantasy"},{"label":"Fantasy Romance","value":"fantasy-romance"},{"label":"Game","value":"game"},{"label":"Gender Bender","value":"gender-bender"},{"label":"Harem","value":"harem"},{"label":"Hentai","value":"hentai"},{"label":"Historical","value":"historical"},{"label":"Horror","value":"horror"},{"label":"Isekai","value":"isekai"},{"label":"Japanese","value":"japanese"},{"label":"Josei","value":"josei"},{"label":"Korean","value":"korean"},{"label":"Lolicon","value":"lolicon"},{"label":"Magic","value":"magic"},{"label":"Magical Realism","value":"magical-realism"},{"label":"Martial Arts","value":"martial-arts"},{"label":"Mecha","value":"mecha"},{"label":"Military","value":"military"},{"label":"Mystery","value":"mystery"},{"label":"Official Circles","value":"official_circles"},{"label":"Psychological","value":"psychological"},{"label":"Romance","value":"romance"},{"label":"School Life","value":"school-life"},{"label":"Sci-fi","value":"sci-fi"},{"label":"Science Fiction","value":"science_fiction"},{"label":"Seinen","value":"seinen"},{"label":"Shoujo","value":"shoujo"},{"label":"Shoujo Ai","value":"shoujo-ai"},{"label":"Shounen","value":"shounen"},{"label":"Shounen Ai","value":"shounen-ai"},{"label":"Slice of Life","value":"slice-of-life"},{"label":"Sports","value":"sports"},{"label":"Supernatural","value":"supernatural"},{"label":"Suspense Thriller","value":"suspense_thriller"},{"label":"Tragedy","value":"tragedy"},{"label":"Travel Through Time","value":"travel_through_time"},{"label":"Two-dimensional","value":"two-dimensional"},{"label":"Urban","value":"urban"},{"label":"Urban Life","value":"urban-life"},{"label":"Video Games","value":"video-games"},{"label":"Virtual Reality","value":"virtual-reality"},{"label":"Wuxia","value":"wuxia"},{"label":"Wuxia Xianxia","value":"wuxia_xianxia"},{"label":"Xianxia","value":"xianxia"},{"label":"Xuanhuan","value":"xuanhuan"},{"label":"Yaoi","value":"yaoi"},{"label":"Yuri","value":"yuri"}], "inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://sleepytranslations.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "series" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "series/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://www.sonicmtl.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker},{"key":"genres","label":"GENRES","values":[{"label":"Action","value":"action"},{"label":"Adult","value":"adult"},{"label":"Adventure","value":"adventure"},{"label":"Comedy","value":"comedy"},{"label":"Cooking","value":"cooking"},{"label":"Detective","value":"detective"},{"label":"Doujinshi","value":"doujinshi"},{"label":"Drama","value":"drama"},{"label":"Ecchi","value":"ecchi"},{"label":"Fan-Fiction","value":"fan-fiction"},{"label":"Fantasy","value":"fantasy"},{"label":"Gender Bender","value":"gender-bender"},{"label":"Harem","value":"harem"},{"label":"Historical","value":"historical"},{"label":"Horror","value":"horror"},{"label":"Josei","value":"josei"},{"label":"Live action","value":"live-action"},{"label":"Manga","value":"manga"},{"label":"Manhua","value":"manhua"},{"label":"Manhwa","value":"manhwa"},{"label":"Martial Arts","value":"martial-arts"},{"label":"Mature","value":"mature"},{"label":"Mecha","value":"mecha"},{"label":"Mystery","value":"mystery"},{"label":"One shot","value":"one-shot"},{"label":"Psychological","value":"psychological"},{"label":"Romance","value":"romance"},{"label":"School Life","value":"school-life"},{"label":"Sci-fi","value":"sci-fi"},{"label":"Seinen","value":"seinen"},{"label":"Shoujo","value":"shoujo"},{"label":"Shoujo Ai","value":"shoujo-ai"},{"label":"Shounen","value":"shounen"},{"label":"Shounen Ai","value":"shounen-ai"},{"label":"Slice of Life","value":"slice-of-life"},{"label":"Smut","value":"smut"},{"label":"Soft Yaoi","value":"soft-yaoi"},{"label":"Soft Yuri","value":"soft-yuri"},{"label":"Sports","value":"sports"},{"label":"Supernatural","value":"supernatural"},{"label":"Tragedy","value":"tragedy"},{"label":"Urban Life","value":"urban-life"},{"label":"Wuxia","value":"wuxia"},{"label":"Xianxia","value":"xianxia"},{"label":"Xuanhuan","value":"xuanhuan"},{"label":"Yaoi","value":"yaoi"},{"label":"Yuri","value":"yuri"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://sugarbbscan.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "series" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "series/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://sweetescapetranslations.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "manga" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "manga-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "manga/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker},{"key":"genres","label":"GENRES","values":[{"label":"Action","value":"action"},{"label":"Adult","value":"adult"},{"label":"Comedy","value":"comedy"},{"label":"Drama","value":"drama"},{"label":"Fantasy","value":"fantasy"},{"label":"Gender Bender","value":"gender-bender"},{"label":"Mature","value":"mature"},{"label":"Modern","value":"modern"},{"label":"Mystery","value":"mystery"},{"label":"R-15","value":"r-15"},{"label":"Romance","value":"romance"},{"label":"Slice of Life","value":"slice-of-life"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://www.webnovelover.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker},{"key":"genres","label":"GENRES","values":[{"label":"Action","value":"action"},{"label":"Adapted into drama","value":"adapted-into-drama"},{"label":"Adventure","value":"adventure"},{"label":"Comedy","value":"comedy"},{"label":"Completed","value":"completed"},{"label":"Drama","value":"drama"},{"label":"English Novel","value":"english-novel"},{"label":"Fantasy","value":"fantasy"},{"label":"Gender Bender","value":"gender-bender"},{"label":"Harem","value":"harem"},{"label":"Historical","value":"historical"},{"label":"Horror","value":"horror"},{"label":"Indo Novel","value":"indo-novel"},{"label":"Josei","value":"josei"},{"label":"Martial Arts","value":"martial-arts"},{"label":"Mature","value":"mature"},{"label":"Mystery","value":"mystery"},{"label":"Rebirth","value":"rebirth"},{"label":"Reincarnation","value":"reincarnation"},{"label":"Romance","value":"romance"},{"label":"Sci-fi","value":"sci-fi"},{"label":"Shoujo","value":"shoujo"},{"label":"Shounen","value":"shounen"},{"label":"Slice of Life","value":"slice-of-life"},{"label":"Spiritual","value":"spiritual"},{"label":"Supernatural","value":"supernatural"},{"label":"Tragedy","value":"tragedy"},{"label":"Transmigration","value":"transmigration"},{"label":"University","value":"university"},{"label":"Wuxia","value":"wuxia"},{"label":"Xianxia","value":"xianxia"},{"label":"Xuanhuan","value":"xuanhuan"},{"label":"Youth","value":"youth"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://wuxiaworld.site/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://zetrotranslation.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker},{"key":"genres","label":"GENRES","values":[{"label":"Action","value":"action"},{"label":"Adventure","value":"adventure"},{"label":"Comedy","value":"comedy"},{"label":"Dark Elf","value":"dark-elf"},{"label":"Drama","value":"drama"},{"label":"Ecchi","value":"ecchi"},{"label":"Fantasy","value":"fantasy"},{"label":"Harem","value":"harem"},{"label":"Horror","value":"horror"},{"label":"Isekai","value":"isekai"},{"label":"Mecha","value":"mecha"},{"label":"Mystery","value":"mystery"},{"label":"NTR","value":"ntr"},{"label":"Original Works","value":"original-works"},{"label":"Rom-Com","value":"rom-com"},{"label":"Romance","value":"romance"},{"label":"School","value":"school"},{"label":"Shoujo","value":"shoujo"},{"label":"Slice of Life","value":"slice-of-life"},{"label":"Villain","value":"villain"},{"label":"Yuri","value":"yuri"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://zinnovel.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "manga" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "manga-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "manga/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker},{"key":"genres","label":"GENRES","values":[{"label":"Action","value":"action"},{"label":"Adult","value":"adult"},{"label":"Adventure","value":"adventure"},{"label":"Anime","value":"anime"},{"label":"Cartoon","value":"cartoon"},{"label":"Comedy","value":"comedy"},{"label":"Comic","value":"comic"},{"label":"Cooking","value":"cooking"},{"label":"Detective","value":"detective"},{"label":"Doujinshi","value":"doujinshi"},{"label":"Drama","value":"drama"},{"label":"Ecchi","value":"ecchi"},{"label":"Fantasy","value":"fantasy"},{"label":"Game","value":"game"},{"label":"Gender Bender","value":"gender-bender"},{"label":"Harem","value":"harem"},{"label":"Historical","value":"historical"},{"label":"Horror","value":"horror"},{"label":"Isekai","value":"isekai"},{"label":"Josei","value":"josei"},{"label":"Live action","value":"live-action"},{"label":"Lolicon","value":"lolicon"},{"label":"Magical Realism","value":"magical-realism"},{"label":"Manga","value":"manga"},{"label":"Manhua","value":"manhua"},{"label":"Manhwa","value":"manhwa"},{"label":"Martial Arts","value":"martial-arts"},{"label":"Mature","value":"mature"},{"label":"Mecha","value":"mecha"},{"label":"Mystery","value":"mystery"},{"label":"One shot","value":"one-shot"},{"label":"Psychological","value":"psychological"},{"label":"Reincarnation","value":"reincarnation"},{"label":"Romance","value":"romance"},{"label":"School Life","value":"school-life"},{"label":"Sci-fi","value":"sci-fi"},{"label":"Seinen","value":"seinen"},{"label":"Shoujo","value":"shoujo"},{"label":"Shoujo Ai","value":"shoujo-ai"},{"label":"Shounen","value":"shounen"},{"label":"Shounen Ai","value":"shounen-ai"},{"label":"Slice of Life","value":"slice-of-life"},{"label":"Smut","value":"smut"},{"label":"Soft Yaoi","value":"soft-yaoi"},{"label":"Soft Yuri","value":"soft-yuri"},{"label":"Sports","value":"sports"},{"label":"Supernatural","value":"supernatural"},{"label":"Tragedy","value":"tragedy"},{"label":"Video Games","value":"video-games"},{"label":"Webtoon","value":"webtoon"},{"label":"Wuxia","value":"wuxia"},{"label":"Xianxia","value":"xianxia"},{"label":"Xuanhuan","value":"xuanhuan"},{"label":"Yaoi","value":"yaoi"},{"label":"Yuri","value":"yuri"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -7,7 +7,7 @@ export const id = "DDL.com";
|
||||
export const name = "Divine Dao Library";
|
||||
export const site = "https://www.divinedaolibrary.com/";
|
||||
export const version = "1.0.0";
|
||||
export const icon = "src/`en/divinedaolibrary/icon.png";
|
||||
export const icon = "src/en/divinedaolibrary/icon.png";
|
||||
|
||||
const baseUrl = site;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import cheerio from "cheerio";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Chapter, Novel, Plugin } from "@typings/plugin";
|
||||
|
||||
@@ -18,7 +18,7 @@ export const popularNovels: Plugin.popularNovels = async function (page) {
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = cheerio.load(body);
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
let novels: Novel.Item[] = [];
|
||||
|
||||
@@ -48,7 +48,7 @@ export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
const result = await fetchApi(url, {}, pluginId);
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = cheerio.load(body);
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
let novel: Novel.instance = { url };
|
||||
|
||||
@@ -100,7 +100,7 @@ export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi(url, {}, pluginId);
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = cheerio.load(body);
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
let chapterText = loadedCheerio(".chapter-content").html();
|
||||
return chapterText;
|
||||
@@ -112,7 +112,7 @@ export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = cheerio.load(body);
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
let novels: Novel.Item[] = [];
|
||||
|
||||
@@ -138,3 +138,5 @@ export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = fetchFile;
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://meionovel.id/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker},{"key":"genres","label":"GENRES","values":[{"label":"Action","value":"action"},{"label":"Adventure","value":"adventure"},{"label":"Comedy","value":"comedy"},{"label":"Cooking","value":"cooking"},{"label":"Drama","value":"drama"},{"label":"Ecchi","value":"ecchi"},{"label":"Fanfiction","value":"fanfiction"},{"label":"Fantasy","value":"fantasy"},{"label":"Gender Bender","value":"gender-bender"},{"label":"Harem","value":"harem"},{"label":"Historical","value":"historical"},{"label":"Horror","value":"horror"},{"label":"Isekai","value":"isekai"},{"label":"Josei","value":"josei"},{"label":"Magic","value":"magic"},{"label":"Martial Arts","value":"martial-arts"},{"label":"Mature","value":"mature"},{"label":"Mecha","value":"mecha"},{"label":"Mystery","value":"mystery"},{"label":"One shot","value":"one-shot"},{"label":"Psychological","value":"psychological"},{"label":"Reverse Harem","value":"reverse-harem"},{"label":"Romance","value":"romance"},{"label":"School Life","value":"school-life"},{"label":"Sci-fi","value":"sci-fi"},{"label":"Seinen","value":"seinen"},{"label":"Shoujo","value":"shoujo"},{"label":"Shoujo Ai","value":"shoujo-ai"},{"label":"Shounen","value":"shounen"},{"label":"Shounen Ai","value":"shounen-ai"},{"label":"Slice of Life","value":"slice-of-life"},{"label":"Smut","value":"smut"},{"label":"Sports","value":"sports"},{"label":"Supernatural","value":"supernatural"},{"label":"Tragedy","value":"tragedy"},{"label":"Virtual Reality","value":"virtual-reality"},{"label":"Wuxia","value":"wuxia"},{"label":"Xianxia","value":"xianxia"},{"label":"Xuanhuan","value":"xuanhuan"},{"label":"Yuri","value":"yuri"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://morenovel.net/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker},{"key":"genres","label":"GENRES","values":[{"label":"Action","value":"action"},{"label":"Adventure","value":"adventure"},{"label":"Comedy","value":"comedy"},{"label":"Drama","value":"drama"},{"label":"Ecchi","value":"ecchi"},{"label":"Fantasy","value":"fantasy"},{"label":"Gender Bender","value":"gender-bender"},{"label":"Harem","value":"harem"},{"label":"Historical","value":"historical"},{"label":"Horror","value":"horror"},{"label":"Josei","value":"josei"},{"label":"Martial Arts","value":"martial-arts"},{"label":"Mature","value":"mature"},{"label":"Mecha","value":"mecha"},{"label":"Mystery","value":"mystery"},{"label":"Psychological","value":"psychological"},{"label":"Romance","value":"romance"},{"label":"School Life","value":"school-life"},{"label":"Sci-fi","value":"sci-fi"},{"label":"Seinen","value":"seinen"},{"label":"Shoujo","value":"shoujo"},{"label":"Shoujo Ai","value":"shoujo-ai"},{"label":"Shounen","value":"shounen"},{"label":"Shounen Ai","value":"shounen-ai"},{"label":"Slice of Life","value":"slice-of-life"},{"label":"Sports","value":"sports"},{"label":"Supernatural","value":"supernatural"},{"label":"Tragedy","value":"tragedy"},{"label":"Wuxia","value":"wuxia"},{"label":"Xianxia","value":"xianxia"},{"label":"Xuanhuan","value":"xuanhuan"},{"label":"Yaoi","value":"yaoi"},{"label":"Yuri","value":"yuri"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://wbnovel.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker},{"key":"genres","label":"GENRES","values":[{"label":"Action","value":"action"},{"label":"Adventure","value":"adventure"},{"label":"Alchemy","value":"alchemy"},{"label":"Comedy","value":"comedy"},{"label":"Crafting","value":"crafting"},{"label":"Cultivation","value":"cultivation"},{"label":"Drama","value":"drama"},{"label":"Dungeon Crawling","value":"dungeon-crawling"},{"label":"Eci","value":"eci"},{"label":"Fantasy","value":"fantasy"},{"label":"Game","value":"game"},{"label":"Harem","value":"harem"},{"label":"Horror","value":"horror"},{"label":"Josei","value":"josei"},{"label":"Kingdom Building","value":"kingdom-building"},{"label":"Martial Arts","value":"martial-arts"},{"label":"Mature","value":"mature"},{"label":"Mecha","value":"mecha"},{"label":"Modern Setting","value":"modern-setting"},{"label":"Mystery","value":"mystery"},{"label":"Pets","value":"pets"},{"label":"Psychological","value":"psychological"},{"label":"Reincarnator","value":"reincarnator"},{"label":"Romance","value":"romance"},{"label":"School Life","value":"school-life"},{"label":"Sci-fi","value":"sci-fi"},{"label":"Seinen","value":"seinen"},{"label":"Shounen","value":"shounen"},{"label":"Slice of Life","value":"slice-of-life"},{"label":"Sports","value":"sports"},{"label":"Supernatural","value":"supernatural"},{"label":"Tragedy","value":"tragedy"},{"label":"Transmigration","value":"transmigration"},{"label":"Virtual Reality","value":"virtual-reality"},{"label":"Wuxia","value":"wuxia"},{"label":"Xianxia","value":"xianxia"},{"label":"Xuanhuan","value":"xuanhuan"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -1,4 +1,4 @@
|
||||
import cheerio from "cheerio";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Chapter, Novel, Plugin } from "@typings/plugin";
|
||||
|
||||
@@ -21,15 +21,16 @@ export const popularNovels: Plugin.popularNovels = async function (page) {
|
||||
body: `mode=get_data_novel_list_p&novel_menu=3&np_day=${day}&np_rank=1&np_distributor=0&np_genre=00&np_order=1&np_genre_ex_1=00&np_genre_ex_2=00&list_limit=${list_limit}&is_query_first=true`,
|
||||
method: "POST",
|
||||
});
|
||||
const resJson = await res.json() as response;
|
||||
|
||||
const resJson = await res.json();
|
||||
const novels: Novel.Item[] = []
|
||||
|
||||
const novels: Novel.Item[] = resJson.list.map((novel) => {
|
||||
return {
|
||||
resJson?.list?.forEach((novel) => {
|
||||
novels.push({
|
||||
url: baseUrl + "novel/list/" + novel.wr_id,
|
||||
name: novel.wr_subject,
|
||||
cover: baseUrl + novel.np_dir + "/thumbnail/" + novel.np_thumbnail,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
return novels;
|
||||
@@ -43,7 +44,7 @@ export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
const result = await fetchApi(novelUrl);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = cheerio.load(body, { decodeEntities: false });
|
||||
const loadedCheerio = parseHTML(body, { decodeEntities: false });
|
||||
const name = loadedCheerio("h5.pt-2").text();
|
||||
const summary = loadedCheerio(".pt-1.mt-1.pb-1.mb-1").text();
|
||||
const author = loadedCheerio(".post-item-list-cate-v")
|
||||
@@ -75,14 +76,14 @@ export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
const resJson = await res.json();
|
||||
const resJson = await res.json() as responseBook
|
||||
|
||||
chapters = resJson.list.map((chapter) => {
|
||||
return {
|
||||
resJson?.list?.forEach((chapter) => {
|
||||
chapters.push({
|
||||
name: chapter.wr_subject,
|
||||
url: baseUrl + `novel/view/${chapter.wr_id}/2`,
|
||||
releaseTime: chapter.wr_datetime,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const novel: Novel.instance = {
|
||||
@@ -102,7 +103,7 @@ export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi(chapterUrl);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = cheerio.load(body);
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const contentTag = loadedCheerio("#id_wr_content > p");
|
||||
|
||||
@@ -129,19 +130,54 @@ export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
|
||||
body: `mode=get_data_novel_list_p_sch&search_novel=${searchTerm}&list_limit=0`,
|
||||
method: "POST",
|
||||
});
|
||||
const resJson = await rawResults.json() as response;
|
||||
|
||||
const results = await rawResults.json();
|
||||
if (results.list_count === 0) {
|
||||
return [];
|
||||
}
|
||||
const novels: Novel.Item[] = []
|
||||
|
||||
const novels: Novel.Item[] = results.list.map((result) => {
|
||||
return {
|
||||
url: baseUrl + "novel/list/" + result.wr_id,
|
||||
name: result.wr_subject,
|
||||
cover:
|
||||
baseUrl + result.np_dir + "/thumbnail/" + result.np_thumbnail,
|
||||
};
|
||||
resJson?.list?.forEach((novel) => {
|
||||
novels.push({
|
||||
url: baseUrl + "novel/list/" + novel.wr_id,
|
||||
name: novel.wr_subject,
|
||||
cover: baseUrl + novel.np_dir + "/thumbnail/" + novel.np_thumbnail,
|
||||
});
|
||||
});
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = fetchFile;
|
||||
|
||||
interface response {
|
||||
list_limit: number;
|
||||
list?: (ListEntity)[] | null;
|
||||
list_count: number;
|
||||
}
|
||||
interface ListEntity {
|
||||
wr_id: string;
|
||||
wr_subject: string;
|
||||
np_dir: string;
|
||||
np_type_02: string;
|
||||
np_thumbnail: string;
|
||||
np_author: string;
|
||||
wr_subject2: string;
|
||||
wr_datetime: string;
|
||||
np_distributor: string;
|
||||
np_genre: string;
|
||||
np_country: string;
|
||||
np_age: string;
|
||||
is_scrap: number;
|
||||
}
|
||||
|
||||
interface responseBook {
|
||||
list?: (ListEntity2)[] | null;
|
||||
download_time: string;
|
||||
}
|
||||
interface ListEntity2 {
|
||||
wr_id: string;
|
||||
wr_subject: string;
|
||||
wr_datetime: string;
|
||||
url_view1: string;
|
||||
novel_c_style1: string;
|
||||
novel_c_str1: string;
|
||||
data_novel_c_view: string;
|
||||
}
|
||||
+223
-267
File diff suppressed because one or more lines are too long
@@ -0,0 +1,228 @@
|
||||
import { Chapter, Novel, Plugin } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
|
||||
export const id = "freedlit.space";
|
||||
export const name = "LitSpace";
|
||||
export const site = "https://freedlit.space";
|
||||
export const version = "1.0.0";
|
||||
export const icon = "src/ru/freedlit/icon.png";
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ showLatestNovels, filters },
|
||||
) {
|
||||
let url = 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=" + page;
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
let novels: Novel.Item[] = [];
|
||||
loadedCheerio("#bookListBlock > div > div").each(function () {
|
||||
const name = loadedCheerio(this).find("div > h4 > a").text()?.trim();
|
||||
const cover = loadedCheerio(this).find("div > a > img").attr("src")?.trim();
|
||||
const url = loadedCheerio(this).find("div > h4 > a").attr("href")?.trim();
|
||||
if (!name || !url) return;
|
||||
|
||||
novels.push({ name, cover, url });
|
||||
});
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi(novelUrl);
|
||||
const body = await result.text();
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
let novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
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(),
|
||||
genres: loadedCheerio(".genre-list > a")
|
||||
.map((index, element) => loadedCheerio(element).text()).get().join(","),
|
||||
};
|
||||
|
||||
let chapters: Chapter.Item[] = [];
|
||||
|
||||
loadedCheerio("#nav-contents > div").each(function () {
|
||||
const name = loadedCheerio(this).find("a").text();
|
||||
const releaseTime = loadedCheerio(this).find('span[class="date"]').text();
|
||||
const url = loadedCheerio(this).find("a").attr("href");
|
||||
if (!name || !url) return;
|
||||
|
||||
chapters.push({ name, releaseTime, url });
|
||||
});
|
||||
|
||||
novel.chapters = chapters;
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi(chapterUrl);
|
||||
const body = await result.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;
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
|
||||
const url = `${site}/search?query=${searchTerm}&type=all`;
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
let novels: Novel.Item[] = [];
|
||||
loadedCheerio("#bookListBlock > div").each(function () {
|
||||
const name = loadedCheerio(this).find("h4 > a").text()?.trim();
|
||||
const cover = loadedCheerio(this).find("a > img").attr("src")?.trim();
|
||||
const url = loadedCheerio(this).find("h4 > a").attr("href")?.trim();
|
||||
if (!name || !url) return;
|
||||
|
||||
novels.push({ name, cover, url });
|
||||
});
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const filters = [
|
||||
{
|
||||
key: "sort",
|
||||
label: "Сортировка:",
|
||||
values: [
|
||||
{ label: "По популярности", value: "popular" },
|
||||
{ label: "По количеству комментариев", value: "comments" },
|
||||
{ label: "По количеству лайков", value: "likes" },
|
||||
{ label: "По новизне", value: "recent" },
|
||||
{ label: "По просмотрам", value: "views" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "genre",
|
||||
label: "Жанры:",
|
||||
values: [
|
||||
{ label: "Любой жанр", value: "all" },
|
||||
{ label: "Альтернативная история", value: "alternative-history" },
|
||||
{ label: "Антиутопия", value: "dystopia" },
|
||||
{ label: "Бизнес-литература", value: "business-literature" },
|
||||
{ label: "Боевая фантастика", value: "combat-fiction" },
|
||||
{ label: "Боевик", value: "action" },
|
||||
{ label: "Боевое фэнтези", value: "combat-fantasy" },
|
||||
{ label: "Бояръ-Аниме", value: "boyar-anime" },
|
||||
{ label: "Героическая фантастика", value: "heroic-fiction" },
|
||||
{ label: "Героическое фэнтези", value: "heroic-fantasy" },
|
||||
{ label: "Городское фэнтези", value: "urban-fantasy" },
|
||||
{ label: "Гримдарк", value: "grimdark" },
|
||||
{ label: "Детектив", value: "mystery" },
|
||||
{ label: "Детская литература", value: "kids-literature" },
|
||||
{ label: "Документальная проза", value: "biography" },
|
||||
{ label: "Историческая проза", value: "historical-fiction" },
|
||||
{ label: "Исторический детектив", value: "historical-mystery" },
|
||||
{ label: "Исторический любовный роман", value: "historical-romantic-novel" },
|
||||
{ label: "Историческое фэнтези", value: "historical-fantasy" },
|
||||
{ label: "Киберпанк", value: "cyberpunk" },
|
||||
{ label: "Космическая фантастика", value: "cosmic-fiction" },
|
||||
{ label: "ЛитРПГ", value: "litrpg" },
|
||||
{ label: "Лоу / Низкое фэнтези", value: "low-fantasy" },
|
||||
{ label: "Любовное фэнтези", value: "romantic-fantasy" },
|
||||
{ label: "Любовный роман", value: "romantic-novel" },
|
||||
{ label: "Мистика", value: "mystic" },
|
||||
{ label: "Мистический детектив", value: "mystic-detective" },
|
||||
{ label: "Научная фантастика", value: "science-fiction" },
|
||||
{ label: "Подростковая проза", value: "young-adult" },
|
||||
{ label: "Политический роман", value: "political-romance" },
|
||||
{ label: "Попаданцы", value: "accidental-travel" },
|
||||
{ label: "Попаданцы в магические миры", value: "magic-worlds-travel" },
|
||||
{ label: "Попаданцы во времени", value: "time-travel" },
|
||||
{ label: "Порнотика", value: "pornotica" },
|
||||
{ label: "Постапокалипсис", value: "post-apocalypse" },
|
||||
{ label: "Поэзия", value: "poetry" },
|
||||
{ label: "Приключения", value: "adventure" },
|
||||
{ label: "Публицистика", value: "journalism" },
|
||||
{ label: "Пьеса", value: "play" },
|
||||
{ label: "Развитие личности", value: "how-to-book" },
|
||||
{ label: "Разное", value: "other" },
|
||||
{ label: "Реализм", value: "Realism" },
|
||||
{ label: "РеалРПГ", value: "realrpg" },
|
||||
{ label: "Репликация", value: "replication" },
|
||||
{ label: "Романтическая эротика", value: "romantic-erotic-fiction" },
|
||||
{ label: "Сказка", value: "fairy-tale" },
|
||||
{ label: "Слэш", value: "slash" },
|
||||
{ label: "Современная проза", value: "modern-prose" },
|
||||
{ label: "Современный любовный роман", value: "modern-romantic-novel" },
|
||||
{ label: "Социальная фантастика", value: "social-fiction" },
|
||||
{ label: "Стимпанк", value: "steampunk" },
|
||||
{ label: "Сценарий", value: "scenario" },
|
||||
{ label: "Сюаньхуань", value: "xuanhuan" },
|
||||
{ label: "Сянься", value: "xianxia" },
|
||||
{ label: "Тёмное фэнтези", value: "dark-fantasy" },
|
||||
{ label: "Триллер", value: "thriller" },
|
||||
{ label: "Уся", value: "wuxia" },
|
||||
{ label: "Фантастика", value: "fiction" },
|
||||
{ label: "Фантастический детектив", value: "mystery-fiction" },
|
||||
{ label: "Фанфик", value: "fan-fiction" },
|
||||
{ label: "Фемслэш", value: "femslash" },
|
||||
{ label: "Фэнтези", value: "fantasy" },
|
||||
{ label: "Хоррор", value: "horror" },
|
||||
{ label: "Шпионский детектив", value: "spy-crime" },
|
||||
{ label: "Эпическое фэнтези", value: "epic-fantasy" },
|
||||
{ label: "Эротика", value: "erotic-fiction" },
|
||||
{ label: "Эротическая фантастика", value: "erotic-fiction" },
|
||||
{ label: "Эротический фанфик", value: "erotic-fan-fiction" },
|
||||
{ label: "Эротическое фэнтези", value: "erotic-fantasy" },
|
||||
{ label: "Этническое фэнтези", value: "ethnic-fantasy" },
|
||||
{ label: "Юмор", value: "humor" },
|
||||
{ label: "Юмористическая фантастика", value: "humor-fiction" },
|
||||
{ label: "Юмористическое фэнтези", value: "humor-fantasy" },
|
||||
{ label: "RPS", value: "rps" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "status",
|
||||
label: "Статус:",
|
||||
values: [
|
||||
{ label: "Любой статус", value: "all" },
|
||||
{ label: "В процессе", value: "in-process" },
|
||||
{ label: "Завершено", value: "finished" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "access",
|
||||
label: "Доступ:",
|
||||
values: [
|
||||
{ label: "Любой доступ", value: "all" },
|
||||
{ label: "Бесплатные", value: "free" },
|
||||
{ label: "Платные", value: "paid" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "adult",
|
||||
label: "Возрастные ограничения:",
|
||||
values: [
|
||||
{ label: "Скрыть 18+", value: "hide" },
|
||||
{ label: "Показать +18", value: "show" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
];
|
||||
export const fetchImage: Plugin.fetchImage = fetchFile;
|
||||
+223
-267
File diff suppressed because one or more lines are too long
@@ -1,341 +0,0 @@
|
||||
import cheerio from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
import { fetchApi } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { Chapter, Novel, Plugin } from "@typings/plugin";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
|
||||
export const id = "AT";
|
||||
export const name = "Автор Тудей";
|
||||
export const site = "https://author.today";
|
||||
export const version = "1.0.0";
|
||||
export const icon = "src/ru/authortoday/icon.png";
|
||||
|
||||
const baseUrl = site;
|
||||
const apiUrl = "https://api.author.today/";
|
||||
|
||||
const token = "Bearer guest";
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ showLatestNovels, filters }
|
||||
) {
|
||||
let url = apiUrl + "v1/catalog/search?page=" + page;
|
||||
if (filters?.genre) {
|
||||
url += "&genre=" + filters.genre;
|
||||
}
|
||||
|
||||
url +=
|
||||
"&sorting=" +
|
||||
(showLatestNovels ? "recent" : filters?.sort || "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");
|
||||
|
||||
const result = await fetchApi(url, {
|
||||
headers: {
|
||||
Authorization: token,
|
||||
},
|
||||
});
|
||||
const json = await result.json();
|
||||
|
||||
if (json?.code === "NotFound") {
|
||||
return [];
|
||||
}
|
||||
|
||||
let novels: Novel.Item[] = [];
|
||||
json.searchResults.forEach((novel) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
cover: novel?.coverUrl
|
||||
? "https://cm.author.today/content/" + novel.coverUrl
|
||||
: defaultCover,
|
||||
url: baseUrl + "/work/" + novel.id,
|
||||
})
|
||||
);
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const workID = novelUrl.split("/")[4];
|
||||
let result = await fetchApi(`${apiUrl}v1/work/${workID}/details`, {
|
||||
headers: {
|
||||
Authorization: token,
|
||||
},
|
||||
});
|
||||
|
||||
let json = await result.json();
|
||||
let novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
name: json.title,
|
||||
cover: json?.coverUrl ? json.coverUrl.split("?")[0] : defaultCover,
|
||||
author:
|
||||
json?.originalAuthor ||
|
||||
json?.authorFIO ||
|
||||
json?.coAuthorFIO ||
|
||||
json?.secondCoAuthorFIO ||
|
||||
json?.translator ||
|
||||
"",
|
||||
genres: json?.tags?.join(", "),
|
||||
status: json?.isFinished
|
||||
? NovelStatus.Completed
|
||||
: NovelStatus.Ongoing,
|
||||
};
|
||||
|
||||
novel.summary = "";
|
||||
novel.summary += json?.annotation ? json.annotation + "\n" : "";
|
||||
novel.summary += json?.authorNotes
|
||||
? "Примечания автора:\n" + json.authorNotes
|
||||
: "";
|
||||
// all chapters
|
||||
result = await fetchApi(`${apiUrl}v1/work/${workID}/content`, {
|
||||
headers: {
|
||||
Authorization: token,
|
||||
},
|
||||
});
|
||||
json = await result.json();
|
||||
|
||||
let chapters: Chapter.Item[] = [];
|
||||
json?.forEach((chapter, index) => {
|
||||
if (chapter?.isAvailable && !chapter?.isDraft) {
|
||||
chapters.push({
|
||||
name: chapter?.title || `Глава ${index + 1}`,
|
||||
releaseTime: dayjs(
|
||||
chapter?.publishTime || chapter.lastModificationTime
|
||||
).format("LLL"),
|
||||
url: `${apiUrl}v1/work/${workID}/chapter/${chapter.id}/text`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
novel.chapters = chapters;
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi(chapterUrl, {
|
||||
headers: {
|
||||
Authorization: token,
|
||||
},
|
||||
});
|
||||
const json = await result.json();
|
||||
|
||||
if (json?.code) {
|
||||
return json.code + "\n" + json?.message;
|
||||
}
|
||||
|
||||
let key = json.key.split("").reverse().join("") + "@_@";
|
||||
let text = "";
|
||||
|
||||
for (let i = 0; i < json.text.length; i++) {
|
||||
text += String.fromCharCode(
|
||||
json.text.charCodeAt(i) ^ key.charCodeAt(Math.floor(i % key.length))
|
||||
);
|
||||
}
|
||||
|
||||
const loadedCheerio = cheerio.load(text);
|
||||
loadedCheerio("img").each(function () {
|
||||
if (!loadedCheerio(this).attr("src")?.startsWith("http")) {
|
||||
const src = loadedCheerio(this).attr("src");
|
||||
loadedCheerio(this).attr("src", baseUrl + src);
|
||||
}
|
||||
});
|
||||
const chapterText = loadedCheerio.html();
|
||||
return chapterText;
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
|
||||
const url = `${baseUrl}/search?category=works&q=${searchTerm}`;
|
||||
const result = await fetchApi(url, {
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0",
|
||||
referer: baseUrl,
|
||||
});
|
||||
const body = await result.text();
|
||||
const loadedCheerio = cheerio.load(body);
|
||||
let novels: Novel.Item[] = [];
|
||||
|
||||
loadedCheerio("div.book-row").each(function () {
|
||||
const name = loadedCheerio(this)
|
||||
.find('div[class="book-title"] a')
|
||||
.text()
|
||||
.trim();
|
||||
let cover = loadedCheerio(this).find("img").attr("src");
|
||||
const url =
|
||||
baseUrl +
|
||||
"/work/" +
|
||||
loadedCheerio(this)
|
||||
.find('div[class="book-title"] a')
|
||||
.attr("href")
|
||||
?.split("/")[2];
|
||||
|
||||
if (cover) {
|
||||
cover = cover.split("?")[0];
|
||||
} else {
|
||||
cover = defaultCover;
|
||||
}
|
||||
|
||||
novels.push({ name, cover, url });
|
||||
});
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const filters = [
|
||||
{
|
||||
key: "sort",
|
||||
label: "Сортировка",
|
||||
values: [
|
||||
{ label: "По популярности", value: "popular" },
|
||||
{ label: "По количеству лайков", value: "likes" },
|
||||
{ label: "По комментариям", value: "comments" },
|
||||
{ label: "По новизне", value: "recent" },
|
||||
{ label: "По просмотрам", value: "views" },
|
||||
{ label: "Набирающие популярность", value: "trending" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "genre",
|
||||
label: "Жанры",
|
||||
values: [
|
||||
{ label: "Альтернативная история", value: "sf-history" },
|
||||
{ label: "Антиутопия", value: "dystopia" },
|
||||
{ label: "Бизнес-литература", value: "biznes-literatura" },
|
||||
{ label: "Боевая фантастика", value: "sf-action" },
|
||||
{ label: "Боевик", value: "action" },
|
||||
{ label: "Боевое фэнтези", value: "fantasy-action" },
|
||||
{ label: "Бояръ-Аниме", value: "boyar-anime" },
|
||||
{ label: "Героическая фантастика", value: "sf-heroic" },
|
||||
{ label: "Героическое фэнтези", value: "heroic-fantasy" },
|
||||
{ label: "Городское фэнтези", value: "urban-fantasy" },
|
||||
{ label: "Детектив", value: "detective" },
|
||||
{ label: "Детская литература", value: "detskaya-literatura" },
|
||||
{ label: "Документальная проза", value: "non-fiction" },
|
||||
{ label: "Историческая проза", value: "historical-fiction" },
|
||||
{
|
||||
label: "Исторические приключения",
|
||||
value: "historical-adventure",
|
||||
},
|
||||
{ label: "Исторический детектив", value: "historical-mystery" },
|
||||
{
|
||||
label: "Исторический любовный роман",
|
||||
value: "historical-romance",
|
||||
},
|
||||
{ label: "Историческое фэнтези", value: "historical-fantasy" },
|
||||
{ label: "Киберпанк", value: "cyberpunk" },
|
||||
{ label: "Короткий любовный роман", value: "short-romance" },
|
||||
{ label: "Космическая фантастика", value: "sf-space" },
|
||||
{ label: "ЛитРПГ", value: "litrpg" },
|
||||
{ label: "Любовное фэнтези", value: "love-fantasy" },
|
||||
{ label: "Любовные романы", value: "romance" },
|
||||
{ label: "Мистика", value: "paranormal" },
|
||||
{ label: "Назад в СССР", value: "back-to-ussr" },
|
||||
{ label: "Научная фантастика", value: "science-fiction" },
|
||||
{ label: "Подростковая проза", value: "teen-prose" },
|
||||
{ label: "Политический роман", value: "political-fiction" },
|
||||
{ label: "Попаданцы", value: "popadantsy" },
|
||||
{ label: "Попаданцы в космос", value: "popadantsy-v-kosmos" },
|
||||
{
|
||||
label: "Попаданцы в магические миры",
|
||||
value: "popadantsy-v-magicheskie-miry",
|
||||
},
|
||||
{ label: "Попаданцы во времени", value: "popadantsy-vo-vremeni" },
|
||||
{ label: "Постапокалипсис", value: "postapocalyptic" },
|
||||
{ label: "Поэзия", value: "poetry" },
|
||||
{ label: "Приключения", value: "adventure" },
|
||||
{ label: "Публицистика", value: "publicism" },
|
||||
{ label: "Развитие личности", value: "razvitie-lichnosti" },
|
||||
{ label: "Разное", value: "other" },
|
||||
{ label: "РеалРПГ", value: "realrpg" },
|
||||
{ label: "Романтическая эротика", value: "romantic-erotika" },
|
||||
{ label: "Сказка", value: "fairy-tale" },
|
||||
{ label: "Современная проза", value: "modern-prose" },
|
||||
{
|
||||
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: "fanfiction" },
|
||||
{ label: "Фэнтези", value: "fantasy" },
|
||||
{ label: "Шпионский детектив", value: "spy-mystery" },
|
||||
{ label: "Эпическое фэнтези", value: "epic-fantasy" },
|
||||
{ label: "Эротика", value: "erotica" },
|
||||
{ label: "Эротическая фантастика", value: "sf-erotika" },
|
||||
{ label: "Эротический фанфик", value: "fanfiction-erotika" },
|
||||
{ label: "Эротическое фэнтези", value: "fantasy-erotika" },
|
||||
{ label: "Юмор", value: "humor" },
|
||||
{ label: "Юмористическая фантастика", value: "sf-humor" },
|
||||
{ label: "Юмористическое фэнтези", value: "ironical-fantasy" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "form",
|
||||
label: "Форма произведения",
|
||||
values: [
|
||||
{ label: "Любой", value: "any" },
|
||||
{ label: "Перевод", value: "translation" },
|
||||
{ label: "Повесть", value: "tale" },
|
||||
{ label: "Рассказ", value: "story" },
|
||||
{ label: "Роман", value: "novel" },
|
||||
{ label: "Сборник поэзии", value: "poetry" },
|
||||
{ label: "Сборник рассказов", value: "story-book" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "state",
|
||||
label: "Статус произведения",
|
||||
values: [
|
||||
{ label: "Любой статус", value: "any" },
|
||||
{ label: "В процессе", value: "in-progress" },
|
||||
{ label: "Завершено", value: "finished" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "series",
|
||||
label: "Статус цикла",
|
||||
values: [
|
||||
{ label: "Не важно", value: "any" },
|
||||
{ label: "Вне цикла", value: "out" },
|
||||
{ label: "Цикл завершен", value: "finished" },
|
||||
{ label: "Цикл не завершен", value: "unfinished" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "access",
|
||||
label: "Тип доступа",
|
||||
values: [
|
||||
{ label: "Любой", value: "any" },
|
||||
{ label: "Платный", value: "paid" },
|
||||
{ label: "Бесплатный", value: "free" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "promo",
|
||||
label: "Промо-фрагмент",
|
||||
values: [
|
||||
{ label: "Скрывать", value: "hide" },
|
||||
{ label: "Показывать", value: "show" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,546 @@
|
||||
import { Chapter, Novel, 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";
|
||||
|
||||
export const id = "AT";
|
||||
export const name = "Автор Тудей";
|
||||
export const site = "https://author.today";
|
||||
export const version = "1.0.0";
|
||||
export const icon = "src/ru/authortoday/icon.png";
|
||||
|
||||
const apiUrl = "https://api.author.today/";
|
||||
const token = "Bearer guest";
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ showLatestNovels, filters }
|
||||
) {
|
||||
let url = apiUrl + "v1/catalog/search?page=" + page;
|
||||
if (filters?.genre) {
|
||||
url += "&genre=" + filters.genre;
|
||||
}
|
||||
|
||||
url +=
|
||||
"&sorting=" + (showLatestNovels ? "recent" : filters?.sort || "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");
|
||||
|
||||
const result = await fetchApi(url, {
|
||||
headers: {
|
||||
Authorization: token,
|
||||
},
|
||||
});
|
||||
const json = (await result.json()) as response;
|
||||
|
||||
if (json?.code === "NotFound") {
|
||||
return [];
|
||||
}
|
||||
|
||||
let novels: Novel.Item[] = [];
|
||||
json?.searchResults?.forEach((novel) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
cover: novel?.coverUrl
|
||||
? "https://cm.author.today/content/" + novel.coverUrl
|
||||
: defaultCover,
|
||||
url: site + "/work/" + novel.id,
|
||||
})
|
||||
);
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const workID = novelUrl.split("/")[4];
|
||||
let result = await fetchApi(`${apiUrl}v1/work/${workID}/details`, {
|
||||
headers: {
|
||||
Authorization: token,
|
||||
},
|
||||
});
|
||||
|
||||
let json = (await result.json()) as responseBook;
|
||||
let novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
name: json.title,
|
||||
cover: json?.coverUrl ? json.coverUrl.split("?")[0] : defaultCover,
|
||||
author:
|
||||
json?.originalAuthor ||
|
||||
json?.authorFIO ||
|
||||
json?.coAuthorFIO ||
|
||||
json?.secondCoAuthorFIO ||
|
||||
json?.translator ||
|
||||
"",
|
||||
genres: json?.tags?.join(", "),
|
||||
status: json?.isFinished ? NovelStatus.Completed : NovelStatus.Ongoing,
|
||||
};
|
||||
|
||||
novel.summary = "";
|
||||
novel.summary += json?.annotation ? json.annotation + "\n" : "";
|
||||
novel.summary += json?.authorNotes
|
||||
? "Примечания автора:\n" + json.authorNotes
|
||||
: "";
|
||||
// all chapters
|
||||
let chaptersRaw = await fetchApi(`${apiUrl}v1/work/${workID}/content`, {
|
||||
headers: {
|
||||
Authorization: token,
|
||||
},
|
||||
});
|
||||
|
||||
let chaptersJSON = (await chaptersRaw.json()) as ChaptersEntity[];
|
||||
|
||||
let chapters: Chapter.Item[] = [];
|
||||
chaptersJSON?.forEach((chapter, index) => {
|
||||
if (chapter?.isAvailable && !chapter?.isDraft) {
|
||||
chapters.push({
|
||||
name: chapter?.title || `Глава ${index + 1}`,
|
||||
releaseTime: dayjs(
|
||||
chapter?.publishTime || chapter.lastModificationTime
|
||||
).format("LLL"),
|
||||
url: `${apiUrl}v1/work/${workID}/chapter/${chapter.id}/text`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
novel.chapters = chapters;
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi(chapterUrl, {
|
||||
headers: {
|
||||
Authorization: token,
|
||||
},
|
||||
});
|
||||
const json = (await result.json()) as encryptedСhapter;
|
||||
|
||||
if (json?.code) {
|
||||
return json.code + "\n" + json?.message;
|
||||
}
|
||||
|
||||
let key = json.key.split("").reverse().join("") + "@_@";
|
||||
let text = "";
|
||||
|
||||
for (let i = 0; i < json.text.length; i++) {
|
||||
text += String.fromCharCode(
|
||||
json.text.charCodeAt(i) ^ key.charCodeAt(Math.floor(i % key.length))
|
||||
);
|
||||
}
|
||||
|
||||
const loadedCheerio = parseHTML(text);
|
||||
loadedCheerio("img").each(function () {
|
||||
if (!loadedCheerio(this).attr("src")?.startsWith("http")) {
|
||||
const src = loadedCheerio(this).attr("src");
|
||||
loadedCheerio(this).attr("src", site + src);
|
||||
}
|
||||
});
|
||||
const chapterText = loadedCheerio.html();
|
||||
return chapterText;
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
|
||||
const url = `${site}/search?category=works&q=${searchTerm}`;
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
const loadedCheerio = parseHTML(body);
|
||||
let novels: Novel.Item[] = [];
|
||||
|
||||
loadedCheerio("div.book-row").each(function () {
|
||||
const name = loadedCheerio(this)
|
||||
.find('div[class="book-title"] a')
|
||||
.text()
|
||||
.trim();
|
||||
let cover = loadedCheerio(this).find("img").attr("src");
|
||||
const url =
|
||||
site +
|
||||
"/work/" +
|
||||
loadedCheerio(this)
|
||||
.find('div[class="book-title"] a')
|
||||
.attr("href")
|
||||
?.split("/")[2];
|
||||
|
||||
if (cover) {
|
||||
cover = cover.split("?")[0];
|
||||
} else {
|
||||
cover = defaultCover;
|
||||
}
|
||||
|
||||
novels.push({ name, cover, url });
|
||||
});
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = fetchFile;
|
||||
|
||||
export const filters = [
|
||||
{
|
||||
key: "sort",
|
||||
label: "Сортировка",
|
||||
values: [
|
||||
{ label: "По популярности", value: "popular" },
|
||||
{ label: "По количеству лайков", value: "likes" },
|
||||
{ label: "По комментариям", value: "comments" },
|
||||
{ label: "По новизне", value: "recent" },
|
||||
{ label: "По просмотрам", value: "views" },
|
||||
{ label: "Набирающие популярность", value: "trending" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "genre",
|
||||
label: "Жанры",
|
||||
values: [
|
||||
{ label: "Альтернативная история", value: "sf-history" },
|
||||
{ label: "Антиутопия", value: "dystopia" },
|
||||
{ label: "Бизнес-литература", value: "biznes-literatura" },
|
||||
{ label: "Боевая фантастика", value: "sf-action" },
|
||||
{ label: "Боевик", value: "action" },
|
||||
{ label: "Боевое фэнтези", value: "fantasy-action" },
|
||||
{ label: "Бояръ-Аниме", value: "boyar-anime" },
|
||||
{ label: "Героическая фантастика", value: "sf-heroic" },
|
||||
{ label: "Героическое фэнтези", value: "heroic-fantasy" },
|
||||
{ label: "Городское фэнтези", value: "urban-fantasy" },
|
||||
{ label: "Детектив", value: "detective" },
|
||||
{ label: "Детская литература", value: "detskaya-literatura" },
|
||||
{ label: "Документальная проза", value: "non-fiction" },
|
||||
{ label: "Историческая проза", value: "historical-fiction" },
|
||||
{
|
||||
label: "Исторические приключения",
|
||||
value: "historical-adventure",
|
||||
},
|
||||
{ label: "Исторический детектив", value: "historical-mystery" },
|
||||
{
|
||||
label: "Исторический любовный роман",
|
||||
value: "historical-romance",
|
||||
},
|
||||
{ label: "Историческое фэнтези", value: "historical-fantasy" },
|
||||
{ label: "Киберпанк", value: "cyberpunk" },
|
||||
{ label: "Короткий любовный роман", value: "short-romance" },
|
||||
{ label: "Космическая фантастика", value: "sf-space" },
|
||||
{ label: "ЛитРПГ", value: "litrpg" },
|
||||
{ label: "Любовное фэнтези", value: "love-fantasy" },
|
||||
{ label: "Любовные романы", value: "romance" },
|
||||
{ label: "Мистика", value: "paranormal" },
|
||||
{ label: "Назад в СССР", value: "back-to-ussr" },
|
||||
{ label: "Научная фантастика", value: "science-fiction" },
|
||||
{ label: "Подростковая проза", value: "teen-prose" },
|
||||
{ label: "Политический роман", value: "political-fiction" },
|
||||
{ label: "Попаданцы", value: "popadantsy" },
|
||||
{ label: "Попаданцы в космос", value: "popadantsy-v-kosmos" },
|
||||
{
|
||||
label: "Попаданцы в магические миры",
|
||||
value: "popadantsy-v-magicheskie-miry",
|
||||
},
|
||||
{ label: "Попаданцы во времени", value: "popadantsy-vo-vremeni" },
|
||||
{ label: "Постапокалипсис", value: "postapocalyptic" },
|
||||
{ label: "Поэзия", value: "poetry" },
|
||||
{ label: "Приключения", value: "adventure" },
|
||||
{ label: "Публицистика", value: "publicism" },
|
||||
{ label: "Развитие личности", value: "razvitie-lichnosti" },
|
||||
{ label: "Разное", value: "other" },
|
||||
{ label: "РеалРПГ", value: "realrpg" },
|
||||
{ label: "Романтическая эротика", value: "romantic-erotika" },
|
||||
{ label: "Сказка", value: "fairy-tale" },
|
||||
{ label: "Современная проза", value: "modern-prose" },
|
||||
{
|
||||
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: "fanfiction" },
|
||||
{ label: "Фэнтези", value: "fantasy" },
|
||||
{ label: "Шпионский детектив", value: "spy-mystery" },
|
||||
{ label: "Эпическое фэнтези", value: "epic-fantasy" },
|
||||
{ label: "Эротика", value: "erotica" },
|
||||
{ label: "Эротическая фантастика", value: "sf-erotika" },
|
||||
{ label: "Эротический фанфик", value: "fanfiction-erotika" },
|
||||
{ label: "Эротическое фэнтези", value: "fantasy-erotika" },
|
||||
{ label: "Юмор", value: "humor" },
|
||||
{ label: "Юмористическая фантастика", value: "sf-humor" },
|
||||
{ label: "Юмористическое фэнтези", value: "ironical-fantasy" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "form",
|
||||
label: "Форма произведения",
|
||||
values: [
|
||||
{ label: "Любой", value: "any" },
|
||||
{ label: "Перевод", value: "translation" },
|
||||
{ label: "Повесть", value: "tale" },
|
||||
{ label: "Рассказ", value: "story" },
|
||||
{ label: "Роман", value: "novel" },
|
||||
{ label: "Сборник поэзии", value: "poetry" },
|
||||
{ label: "Сборник рассказов", value: "story-book" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "state",
|
||||
label: "Статус произведения",
|
||||
values: [
|
||||
{ label: "Любой статус", value: "any" },
|
||||
{ label: "В процессе", value: "in-progress" },
|
||||
{ label: "Завершено", value: "finished" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "series",
|
||||
label: "Статус цикла",
|
||||
values: [
|
||||
{ label: "Не важно", value: "any" },
|
||||
{ label: "Вне цикла", value: "out" },
|
||||
{ label: "Цикл завершен", value: "finished" },
|
||||
{ label: "Цикл не завершен", value: "unfinished" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "access",
|
||||
label: "Тип доступа",
|
||||
values: [
|
||||
{ label: "Любой", value: "any" },
|
||||
{ label: "Платный", value: "paid" },
|
||||
{ label: "Бесплатный", value: "free" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "promo",
|
||||
label: "Промо-фрагмент",
|
||||
values: [
|
||||
{ label: "Скрывать", value: "hide" },
|
||||
{ label: "Показывать", value: "show" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
];
|
||||
|
||||
interface response {
|
||||
searchResults?: SearchResultsEntity[] | null;
|
||||
realTotalCount: number;
|
||||
code?: string;
|
||||
errorMessage?: null;
|
||||
isLastPage: boolean;
|
||||
format: Format;
|
||||
duration: Duration;
|
||||
isWorkMarkEnabled: boolean;
|
||||
}
|
||||
interface SearchResultsEntity {
|
||||
id: number;
|
||||
title: string;
|
||||
annotation: string;
|
||||
authorId: number;
|
||||
authorFIO: string;
|
||||
authorUserName: string;
|
||||
originalAuthor?: null;
|
||||
coAuthorId?: number | null;
|
||||
coAuthorFIO?: string | null;
|
||||
coAuthorUserName?: string | null;
|
||||
coAuthorConfirmed: boolean;
|
||||
seriesId: number;
|
||||
seriesTitle?: string | null;
|
||||
publishTime: string;
|
||||
lastModificationTime: string;
|
||||
finishTime?: string | null;
|
||||
isDraft: boolean;
|
||||
formEnum: string;
|
||||
genreId: number;
|
||||
firstSubGenreId: number;
|
||||
secondSubGenreId?: number | null;
|
||||
adultOnly: boolean;
|
||||
tags?: (string | null)[] | null;
|
||||
coverUrl: string;
|
||||
viewCount: number;
|
||||
commentCount: number;
|
||||
reviewCount: number;
|
||||
textLength: number;
|
||||
likeCount: number;
|
||||
showAuthor: boolean;
|
||||
price?: number | null;
|
||||
discount?: null;
|
||||
discountStartDate?: null;
|
||||
discountEndDate?: null;
|
||||
status: string;
|
||||
promoFragment: boolean;
|
||||
isExclusive: boolean;
|
||||
format: string;
|
||||
marks?: null[] | null;
|
||||
isLiked: boolean;
|
||||
isWorkMarkEnabled: boolean;
|
||||
workInLibraryState: string;
|
||||
}
|
||||
interface Format {
|
||||
enumValue: string;
|
||||
value: string;
|
||||
title: string;
|
||||
mobileTitle: string;
|
||||
}
|
||||
interface Duration {
|
||||
value: string;
|
||||
title: string;
|
||||
mobileTitle: string;
|
||||
}
|
||||
|
||||
interface responseBook {
|
||||
chapters?: ChaptersEntity[] | null;
|
||||
allowDownloads: boolean;
|
||||
downloadErrorCode: string;
|
||||
downloadErrorMessage: string;
|
||||
privacyDownloads: string;
|
||||
collectionCount: number;
|
||||
annotation: string;
|
||||
authorNotes: string;
|
||||
atRecommendation: boolean;
|
||||
seriesWorkIds?: number[] | null;
|
||||
seriesWorkNumber: number;
|
||||
reviewCount: number;
|
||||
tags?: string[] | null;
|
||||
orderId?: null;
|
||||
orderStatus: string;
|
||||
orderStatusMessage?: null;
|
||||
contests?: null[] | null;
|
||||
galleryImages?: GalleryImagesEntity[] | null;
|
||||
booktrailerVideoUrl?: null;
|
||||
isExclusive: boolean;
|
||||
freeChapterCount: number;
|
||||
promoFragment: boolean;
|
||||
recommendations?: RecommendationsEntity[] | null;
|
||||
linkedWork?: null;
|
||||
id: number;
|
||||
title: string;
|
||||
coverUrl: string;
|
||||
lastModificationTime: string;
|
||||
lastUpdateTime: string;
|
||||
finishTime?: null;
|
||||
isFinished: boolean;
|
||||
textLength: number;
|
||||
textLengthLastRead: number;
|
||||
price: number;
|
||||
discount?: null;
|
||||
workForm: string;
|
||||
status: string;
|
||||
authorId: number;
|
||||
authorFIO: string;
|
||||
authorUserName: string;
|
||||
originalAuthor?: null;
|
||||
translator?: null;
|
||||
reciter?: null;
|
||||
coAuthorId?: null;
|
||||
coAuthorFIO?: null;
|
||||
coAuthorUserName?: null;
|
||||
coAuthorConfirmed: boolean;
|
||||
secondCoAuthorId?: null;
|
||||
secondCoAuthorFIO?: null;
|
||||
secondCoAuthorUserName?: null;
|
||||
secondCoAuthorConfirmed: boolean;
|
||||
isPurchased: boolean;
|
||||
userLikeId?: null;
|
||||
lastReadTime?: null;
|
||||
lastChapterId?: null;
|
||||
lastChapterProgress: number;
|
||||
likeCount: number;
|
||||
commentCount: number;
|
||||
rewardCount: number;
|
||||
rewardsEnabled: boolean;
|
||||
inLibraryState: string;
|
||||
addedToLibraryTime: string;
|
||||
updateInLibraryTime: string;
|
||||
privacyDisplay: string;
|
||||
state: string;
|
||||
isDraft: boolean;
|
||||
enableRedLine: boolean;
|
||||
enableTTS: boolean;
|
||||
adultOnly: boolean;
|
||||
seriesId: number;
|
||||
seriesOrder: number;
|
||||
seriesTitle: string;
|
||||
afterword: string;
|
||||
seriesNextWorkId?: null;
|
||||
genreId: number;
|
||||
firstSubGenreId: number;
|
||||
secondSubGenreId: number;
|
||||
format: string;
|
||||
marks?: null;
|
||||
purchaseTime?: null;
|
||||
likeTime?: null;
|
||||
markTime?: null;
|
||||
}
|
||||
interface ChaptersEntity {
|
||||
id: number;
|
||||
workId: number;
|
||||
title: string;
|
||||
isDraft: boolean;
|
||||
sortOrder: number;
|
||||
publishTime: string;
|
||||
lastModificationTime: string;
|
||||
textLength: number;
|
||||
isAvailable: boolean;
|
||||
}
|
||||
|
||||
interface GalleryImagesEntity {
|
||||
id: string;
|
||||
caption?: null;
|
||||
url: string;
|
||||
height: number;
|
||||
width: number;
|
||||
}
|
||||
interface RecommendationsEntity {
|
||||
title: string;
|
||||
coverUrl: string;
|
||||
authorId: number;
|
||||
authorFIO: string;
|
||||
authorUserName: string;
|
||||
originalAuthor?: null;
|
||||
coAuthorId?: null;
|
||||
coAuthorFIO?: null;
|
||||
coAuthorUserName?: null;
|
||||
coAuthorConfirmed: boolean;
|
||||
secondCoAuthorId?: null;
|
||||
secondCoAuthorFIO?: null;
|
||||
secondCoAuthorUserName?: null;
|
||||
secondCoAuthorConfirmed: boolean;
|
||||
discount?: null;
|
||||
price?: number | null;
|
||||
lastModificationTime: string;
|
||||
finishTime: string;
|
||||
finished: boolean;
|
||||
isExclusive: boolean;
|
||||
format: string;
|
||||
status: string;
|
||||
state: string;
|
||||
removalReason?: null;
|
||||
removalReasonComment?: null;
|
||||
id: number;
|
||||
}
|
||||
|
||||
interface encryptedСhapter {
|
||||
id: number;
|
||||
title: string;
|
||||
isDraft: boolean;
|
||||
publishTime: string;
|
||||
lastModificationTime: string;
|
||||
text: string;
|
||||
key: string;
|
||||
code?: string;
|
||||
message?: string;
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from 'dayjs';
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
// import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
// import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
|
||||
// import { showToast } from "@libs/showToast";
|
||||
import { Filter, FilterInputs } from "@libs/filterInputs";
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
|
||||
|
||||
export const id = "axel.znovel";
|
||||
export const name = "Аксел";
|
||||
export const icon = "";
|
||||
export const version = "1.0.0";
|
||||
export const site = "https://axel.znovel.space";
|
||||
export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Богини","value":316},{"label":"Везучий главный герой","value":411},{"label":"Виртуальная реальность","value":744},{"label":"Высокомерный герой","value":56},{"label":"Герой-извращенец","value":521},{"label":"Главная героиня — женщина","value":277},{"label":"Главный герой — мужчина","value":419},{"label":"Дворяне","value":487},{"label":"Девочки-волшебницы","value":415},{"label":"Демоны","value":193},{"label":"Дружба","value":293},{"label":"Есть аниме-адаптация","value":10},{"label":"Есть видеоигра","value":13},{"label":"Есть манга-адаптация","value":14},{"label":"Игра на выживание","value":696},{"label":"Из грязи в князи","value":542},{"label":"Королевская особа","value":596},{"label":"Ленивый главный герой","value":389},{"label":"Маги","value":757},{"label":"Магические технологии","value":417}],"inputType":FilterInputs.Checkbox}];
|
||||
|
||||
const baseUrl = site;
|
||||
const domain = baseUrl.split('//')[1];
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ filters }
|
||||
) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}));
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'Book',
|
||||
query:
|
||||
'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}',
|
||||
variables: {
|
||||
hostname: domain,
|
||||
project: novelUrl,
|
||||
url: novelUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
name: json.data.project.title,
|
||||
cover: json.data.project.covers[0]?.url
|
||||
? baseUrl + json.data.project.covers[0].url
|
||||
: defaultCover,
|
||||
summary: parseHTML(json.data.project.annotation?.text).text(),
|
||||
author:
|
||||
json.data.project.persons[0].name.firstName +
|
||||
' ' +
|
||||
json.data.project.persons[0].name.lastName,
|
||||
status:
|
||||
json.data.project.translationStatus === 'active'
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
|
||||
let genres = []
|
||||
.concat(json.data.project.tags, json.data.project.genres)
|
||||
.map((item: any) => item?.nameRu || item?.nameEng)
|
||||
.filter(item => item);
|
||||
|
||||
if (genres.length > 0) {
|
||||
novel.genres = genres.join(',');
|
||||
}
|
||||
|
||||
let novelChapters: Chapter.Item[] = [];
|
||||
json.data.project.subprojects.content.forEach((work: any) =>
|
||||
work.volumes.content.forEach((volume: any) =>
|
||||
volume.chapters.forEach(
|
||||
(chapter: any) =>
|
||||
chapter?.published &&
|
||||
novelChapters.push({
|
||||
name: volume.shortName + ' ' + chapter.title,
|
||||
url: novelUrl + '/' + volume.url + '/' + chapter.url,
|
||||
releaseTime: dayjs(chapter.publishDate).format('LLL'),
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
novel.chapters = novelChapters;
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'EReaderData',
|
||||
query:
|
||||
'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}',
|
||||
variables: {
|
||||
chapterSelector: {
|
||||
fullUrl: chapterUrl,
|
||||
},
|
||||
url: chapterUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
|
||||
return parseHTML(json.data.chapter.text.text).html();
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: {
|
||||
query: searchTerm,
|
||||
},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page: 1,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = [];
|
||||
json?.data?.projects?.content?.forEach((novel: any) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}),
|
||||
);
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url, {});
|
||||
};
|
||||
|
||||
@@ -1,218 +0,0 @@
|
||||
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from 'dayjs';
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
// import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
// import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
|
||||
// import { showToast } from "@libs/showToast";
|
||||
import { Filter, FilterInputs } from "@libs/filterInputs";
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
|
||||
|
||||
export const id = "bel.novel";
|
||||
export const name = "Белманга";
|
||||
export const icon = "";
|
||||
export const version = "1.0.0";
|
||||
export const site = "https://bel.novel.tl";
|
||||
export const filters: Filter[] = [];
|
||||
|
||||
const baseUrl = site;
|
||||
const domain = baseUrl.split('//')[1];
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ filters }
|
||||
) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}));
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'Book',
|
||||
query:
|
||||
'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}',
|
||||
variables: {
|
||||
hostname: domain,
|
||||
project: novelUrl,
|
||||
url: novelUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
name: json.data.project.title,
|
||||
cover: json.data.project.covers[0]?.url
|
||||
? baseUrl + json.data.project.covers[0].url
|
||||
: defaultCover,
|
||||
summary: parseHTML(json.data.project.annotation?.text).text(),
|
||||
author:
|
||||
json.data.project.persons[0].name.firstName +
|
||||
' ' +
|
||||
json.data.project.persons[0].name.lastName,
|
||||
status:
|
||||
json.data.project.translationStatus === 'active'
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
|
||||
let genres = []
|
||||
.concat(json.data.project.tags, json.data.project.genres)
|
||||
.map((item: any) => item?.nameRu || item?.nameEng)
|
||||
.filter(item => item);
|
||||
|
||||
if (genres.length > 0) {
|
||||
novel.genres = genres.join(',');
|
||||
}
|
||||
|
||||
let novelChapters: Chapter.Item[] = [];
|
||||
json.data.project.subprojects.content.forEach((work: any) =>
|
||||
work.volumes.content.forEach((volume: any) =>
|
||||
volume.chapters.forEach(
|
||||
(chapter: any) =>
|
||||
chapter?.published &&
|
||||
novelChapters.push({
|
||||
name: volume.shortName + ' ' + chapter.title,
|
||||
url: novelUrl + '/' + volume.url + '/' + chapter.url,
|
||||
releaseTime: dayjs(chapter.publishDate).format('LLL'),
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
novel.chapters = novelChapters;
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'EReaderData',
|
||||
query:
|
||||
'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}',
|
||||
variables: {
|
||||
chapterSelector: {
|
||||
fullUrl: chapterUrl,
|
||||
},
|
||||
url: chapterUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
|
||||
return parseHTML(json.data.chapter.text.text).html();
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: {
|
||||
query: searchTerm,
|
||||
},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page: 1,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = [];
|
||||
json?.data?.projects?.content?.forEach((novel: any) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}),
|
||||
);
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url, {});
|
||||
};
|
||||
|
||||
@@ -1,226 +0,0 @@
|
||||
import cheerio from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
import { fetchApi } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { Chapter, Novel, Plugin } from "@typings/plugin";
|
||||
|
||||
export const id = "bookriver";
|
||||
export const name = "Bookriver";
|
||||
export const site = "https://bookriver.ru";
|
||||
export const version = "1.0.0";
|
||||
export const icon = "src/ru/bookriver/icon.png";
|
||||
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ showLatestNovels, filters }
|
||||
) {
|
||||
let url = baseUrl + `/genre?page=${page}&perPage=24&sortingType=`;
|
||||
url += showLatestNovels ? "last-update" : filters?.sort || "bestseller";
|
||||
|
||||
if (filters) {
|
||||
if (Array.isArray(filters.genres) && filters.genres.length) {
|
||||
url += "&g=" + filters.genres.join(",");
|
||||
}
|
||||
}
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = cheerio.load(body);
|
||||
let json = loadedCheerio("#__NEXT_DATA__").html();
|
||||
json = JSON.parse(json);
|
||||
|
||||
let novels: Novel.Item[] = [];
|
||||
json.props.pageProps.state.pagesFilter.genre.books.forEach((novel) =>
|
||||
novels.push({
|
||||
name: novel.name,
|
||||
cover: novel.coverImages[0].url,
|
||||
url: baseUrl + "/book/" + novel.slug,
|
||||
})
|
||||
);
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi(novelUrl);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = cheerio.load(body);
|
||||
const json = loadedCheerio("#__NEXT_DATA__").html();
|
||||
const book = JSON.parse(json).props.pageProps.state.book.bookPage;
|
||||
|
||||
let novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
name: book.name,
|
||||
cover: book.coverImages[0].url,
|
||||
summary: book.annotation,
|
||||
author: book.author.name,
|
||||
genres: book.tags.map((item) => item.name).join(", "),
|
||||
status:
|
||||
book.statusComplete === "writing"
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
|
||||
let chapters: Chapter.Item[] = [];
|
||||
|
||||
book.ebook.chapters.forEach((chapter) => {
|
||||
if (chapter.available) {
|
||||
chapters.push({
|
||||
name: chapter.name,
|
||||
releaseTime: dayjs(
|
||||
chapter?.firstPublishedAt || chapter.createdAt
|
||||
).format("LLL"),
|
||||
url:
|
||||
baseUrl +
|
||||
"/reader/" +
|
||||
book.slug +
|
||||
"/" +
|
||||
chapter.chapterId,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
novel.chapters = chapters;
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const url = "https://api.bookriver.ru/api/v1/books/chapter/text/";
|
||||
const result = await fetchApi(url + chapterUrl.split("/").pop());
|
||||
const json = await result.json();
|
||||
|
||||
const chapterText = json.data.content;
|
||||
return chapterText;
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
|
||||
const url = `${baseUrl}/search/books?keyword=${searchTerm}`;
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = cheerio.load(body);
|
||||
let json = loadedCheerio("#__NEXT_DATA__").html();
|
||||
json = JSON.parse(json);
|
||||
|
||||
let novels: Novel.Item[] = [];
|
||||
json.props.pageProps.state.catalog.books.books.forEach((novel) =>
|
||||
novels.push({
|
||||
name: novel.name,
|
||||
cover: novel.coverImages[0].url,
|
||||
url: baseUrl + "/book/" + novel.slug,
|
||||
})
|
||||
);
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const filters = [
|
||||
{
|
||||
key: "sort",
|
||||
label: "Сортировка",
|
||||
values: [
|
||||
{ label: "Бестселлеры", value: "bestseller" },
|
||||
{ label: "Дате добавления", value: "newest" },
|
||||
{ label: "Дате обновления", value: "last-update" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "genres",
|
||||
label: "жанры",
|
||||
values: [
|
||||
{
|
||||
label: "Альтернативная история",
|
||||
value: "alternativnaya-istoriya",
|
||||
},
|
||||
{ label: "Боевая фантастика", value: "boevaya-fantastika" },
|
||||
{ label: "Боевое фэнтези", value: "boevoe-fentezi" },
|
||||
{ label: "Бытовое фэнтези", value: "bytovoe-fentezi" },
|
||||
{
|
||||
label: "Героическая фантастика",
|
||||
value: "geroicheskaya-fantastika",
|
||||
},
|
||||
{ label: "Героическое фэнтези", value: "geroicheskoe-fentezi" },
|
||||
{ label: "Городское фэнтези", value: "gorodskoe-fentezi" },
|
||||
{ label: "Детектив", value: "detektiv" },
|
||||
{
|
||||
label: "Детективная фантастика",
|
||||
value: "detektivnaya-fantastika",
|
||||
},
|
||||
{ label: "Жёсткая эротика", value: "zhyostkaya-erotika" },
|
||||
{ label: "Исторический детектив", value: "istoricheskii-detektiv" },
|
||||
{
|
||||
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: "kriminalnyi-detektiv" },
|
||||
{ label: "ЛитРПГ", value: "litrpg" },
|
||||
{ label: "Любовная фантастика", value: "lyubovnaya-fantastika" },
|
||||
{ label: "Любовное фэнтези", value: "lyubovnoe-fentezi" },
|
||||
{ label: "Любовный роман", value: "lyubovnyi-roman" },
|
||||
{ label: "Мистика", value: "mistika" },
|
||||
{ label: "Молодежная проза", value: "molodezhnaya-proza" },
|
||||
{ label: "Научная фантастика", value: "nauchnaya-fantastika" },
|
||||
{
|
||||
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: "s-elementami-erotiki" },
|
||||
{ label: "Славянское фэнтези", value: "slavyanskoe-fentezi" },
|
||||
{
|
||||
label: "Современный любовный роман",
|
||||
value: "sovremennyi-lyubovnyi-roman",
|
||||
},
|
||||
{ label: "Социальная фантастика", value: "sotsialnaya-fantastika" },
|
||||
{ label: "Тёмное фэнтези", value: "temnoe-fentezi" },
|
||||
{ label: "Фантастика", value: "fantastika" },
|
||||
{ label: "Фэнтези", value: "fentezi" },
|
||||
{ label: "Шпионский детектив", value: "shpionskii-detektiv" },
|
||||
{ label: "Эпическое фэнтези", value: "epicheskoe-fentezi" },
|
||||
{ label: "Эротика", value: "erotika" },
|
||||
{
|
||||
label: "Эротическая фантастика",
|
||||
value: "eroticheskaya-fantastika",
|
||||
},
|
||||
{ label: "Эротический фанфик", value: "eroticheskii-fanfik" },
|
||||
{ label: "Эротическое фэнтези", value: "eroticheskoe-fentezi" },
|
||||
{
|
||||
label: "Юмористический детектив",
|
||||
value: "yumoristicheskii-detektiv",
|
||||
},
|
||||
{
|
||||
label: "Юмористическое фэнтези",
|
||||
value: "yumoristicheskoe-fentezi",
|
||||
},
|
||||
],
|
||||
inputType: FilterInputs.Checkbox,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,293 @@
|
||||
import { Chapter, Novel, Plugin } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export const id = "bookriver";
|
||||
export const name = "Bookriver";
|
||||
export const site = "https://bookriver.ru";
|
||||
export const version = "1.0.0";
|
||||
export const icon = "src/ru/bookriver/icon.png";
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ showLatestNovels, filters }
|
||||
) {
|
||||
let url = site + `/genre?page=${page}&perPage=24&sortingType=`;
|
||||
url += showLatestNovels ? "last-update" : filters?.sort || "bestseller";
|
||||
|
||||
if (filters) {
|
||||
if (Array.isArray(filters.genres) && filters.genres.length) {
|
||||
url += "&g=" + filters.genres.join(",");
|
||||
}
|
||||
}
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
let novels: Novel.Item[] = [];
|
||||
|
||||
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
|
||||
if (jsonRaw) {
|
||||
let json: response = JSON.parse(jsonRaw);
|
||||
json.props.pageProps.state.pagesFilter?.genre?.books?.forEach((novel) =>
|
||||
novels.push({
|
||||
name: novel.name,
|
||||
cover: novel.coverImages[0].url,
|
||||
url: site + "/book/" + novel.slug,
|
||||
})
|
||||
);
|
||||
}
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi(novelUrl);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
|
||||
const json: response = JSON.parse(jsonRaw || '{}');
|
||||
let book = json.props.pageProps.state.book?.bookPage;
|
||||
let novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
name: book?.name,
|
||||
cover: book?.coverImages[0].url,
|
||||
summary: book?.annotation,
|
||||
author: book?.author?.name,
|
||||
genres: book?.tags?.map((item) => item.name).join(", "),
|
||||
status:
|
||||
book?.statusComplete === "writing"
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
|
||||
let chapters: Chapter.Item[] = [];
|
||||
|
||||
book?.ebook?.chapters?.forEach((chapter) => {
|
||||
if (chapter.available) {
|
||||
chapters.push({
|
||||
name: chapter.name,
|
||||
releaseTime: dayjs(
|
||||
chapter?.firstPublishedAt || chapter.createdAt
|
||||
).format("LLL"),
|
||||
url: site + "/reader/" + book?.slug + "/" + chapter.chapterId,
|
||||
});
|
||||
}
|
||||
});
|
||||
novel.chapters = chapters;
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const url = "https://api.bookriver.ru/api/v1/books/chapter/text/";
|
||||
const result = await fetchApi(url + chapterUrl.split("/").pop());
|
||||
const json = (await result.json()) as responseChapter;
|
||||
|
||||
let chapterText = json.data.content || "Конец произведения";
|
||||
if (json.data?.audio?.available) {
|
||||
chapterText += "\n" + json.data.audio.url;
|
||||
}
|
||||
|
||||
return chapterText;
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
|
||||
const url = `https://api.bookriver.ru/api/v1/search/autocomplete?keyword=${searchTerm}&page=1&perPage=10`;
|
||||
const result = await fetchApi(url);
|
||||
const json = (await result.json()) as responseSearch;
|
||||
|
||||
let novels: Novel.Item[] = [];
|
||||
json?.data?.books?.forEach((novel) =>
|
||||
novels.push({
|
||||
name: novel.name,
|
||||
cover: novel.coverImages[0].url,
|
||||
url: site + "/book/" + novel.slug,
|
||||
})
|
||||
);
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = fetchFile;
|
||||
|
||||
export const filters = [
|
||||
{
|
||||
key: "sort",
|
||||
label: "Сортировка",
|
||||
values: [
|
||||
{ label: "Бестселлеры", value: "bestseller" },
|
||||
{ label: "Дате добавления", value: "newest" },
|
||||
{ label: "Дате обновления", value: "last-update" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "genres",
|
||||
label: "жанры",
|
||||
values: [
|
||||
{
|
||||
label: "Альтернативная история",
|
||||
value: "alternativnaya-istoriya",
|
||||
},
|
||||
{ label: "Боевая фантастика", value: "boevaya-fantastika" },
|
||||
{ label: "Боевое фэнтези", value: "boevoe-fentezi" },
|
||||
{ label: "Бытовое фэнтези", value: "bytovoe-fentezi" },
|
||||
{
|
||||
label: "Героическая фантастика",
|
||||
value: "geroicheskaya-fantastika",
|
||||
},
|
||||
{ label: "Героическое фэнтези", value: "geroicheskoe-fentezi" },
|
||||
{ label: "Городское фэнтези", value: "gorodskoe-fentezi" },
|
||||
{ label: "Детектив", value: "detektiv" },
|
||||
{
|
||||
label: "Детективная фантастика",
|
||||
value: "detektivnaya-fantastika",
|
||||
},
|
||||
{ label: "Жёсткая эротика", value: "zhyostkaya-erotika" },
|
||||
{ label: "Исторический детектив", value: "istoricheskii-detektiv" },
|
||||
{
|
||||
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: "kriminalnyi-detektiv" },
|
||||
{ label: "ЛитРПГ", value: "litrpg" },
|
||||
{ label: "Любовная фантастика", value: "lyubovnaya-fantastika" },
|
||||
{ label: "Любовное фэнтези", value: "lyubovnoe-fentezi" },
|
||||
{ label: "Любовный роман", value: "lyubovnyi-roman" },
|
||||
{ label: "Мистика", value: "mistika" },
|
||||
{ label: "Молодежная проза", value: "molodezhnaya-proza" },
|
||||
{ label: "Научная фантастика", value: "nauchnaya-fantastika" },
|
||||
{
|
||||
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: "s-elementami-erotiki" },
|
||||
{ label: "Славянское фэнтези", value: "slavyanskoe-fentezi" },
|
||||
{
|
||||
label: "Современный любовный роман",
|
||||
value: "sovremennyi-lyubovnyi-roman",
|
||||
},
|
||||
{ label: "Социальная фантастика", value: "sotsialnaya-fantastika" },
|
||||
{ label: "Тёмное фэнтези", value: "temnoe-fentezi" },
|
||||
{ label: "Фантастика", value: "fantastika" },
|
||||
{ label: "Фэнтези", value: "fentezi" },
|
||||
{ label: "Шпионский детектив", value: "shpionskii-detektiv" },
|
||||
{ label: "Эпическое фэнтези", value: "epicheskoe-fentezi" },
|
||||
{ label: "Эротика", value: "erotika" },
|
||||
{
|
||||
label: "Эротическая фантастика",
|
||||
value: "eroticheskaya-fantastika",
|
||||
},
|
||||
{ label: "Эротический фанфик", value: "eroticheskii-fanfik" },
|
||||
{ label: "Эротическое фэнтези", value: "eroticheskoe-fentezi" },
|
||||
{
|
||||
label: "Юмористический детектив",
|
||||
value: "yumoristicheskii-detektiv",
|
||||
},
|
||||
{
|
||||
label: "Юмористическое фэнтези",
|
||||
value: "yumoristicheskoe-fentezi",
|
||||
},
|
||||
],
|
||||
inputType: FilterInputs.Checkbox,
|
||||
},
|
||||
];
|
||||
|
||||
interface response {
|
||||
props: {
|
||||
pageProps: {
|
||||
state: {
|
||||
book?: {
|
||||
bookPage: BooksEntity;
|
||||
};
|
||||
pagesFilter?: {
|
||||
genre: {
|
||||
books: BooksEntity[];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
interface BooksEntity {
|
||||
name: string;
|
||||
coverImages: { url: string }[];
|
||||
slug: string;
|
||||
annotation?: string;
|
||||
author?: {
|
||||
name: string;
|
||||
};
|
||||
tags?: { name: string }[];
|
||||
ebook?: {
|
||||
chapters: Chapter[];
|
||||
};
|
||||
statusComplete: string;
|
||||
}
|
||||
|
||||
interface Chapter {
|
||||
name: string;
|
||||
available: boolean;
|
||||
firstPublishedAt?: Date | null;
|
||||
createdAt?: Date | null;
|
||||
chapterId: number | string;
|
||||
}
|
||||
|
||||
interface responseChapter {
|
||||
data: {
|
||||
id: number;
|
||||
bookId: number;
|
||||
name: string;
|
||||
content: string;
|
||||
symbols: number;
|
||||
number: number;
|
||||
authorPages: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
firstPublishedAt: string;
|
||||
status: string;
|
||||
protected: boolean;
|
||||
free: boolean;
|
||||
audio?: {
|
||||
available: boolean;
|
||||
url: string;
|
||||
duration: number;
|
||||
};
|
||||
publicationScheduledFor?: null;
|
||||
};
|
||||
}
|
||||
|
||||
interface responseSearch {
|
||||
data: Data;
|
||||
total: number;
|
||||
}
|
||||
interface Data {
|
||||
books?: BooksEntity[] | null;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+263
-179
@@ -1,8 +1,8 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { Chapter, Novel, Plugin } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { Chapter, Novel, Plugin } from "@typings/plugin";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
|
||||
export const id = "jaomix.ru";
|
||||
export const name = "Jaomix";
|
||||
@@ -10,210 +10,294 @@ export const site = "https://jaomix.ru";
|
||||
export const version = "1.0.0";
|
||||
export const icon = "src/ru/jaomix/icon.png";
|
||||
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ showLatestNovels, filters }
|
||||
page,
|
||||
{ showLatestNovels, filters },
|
||||
) {
|
||||
let url = baseUrl + "/?searchrn&sortby=";
|
||||
url += showLatestNovels ? "upd" : filters?.sort || "count";
|
||||
let url = site + "/?searchrn";
|
||||
|
||||
if (filters) {
|
||||
if (Array.isArray(filters.type) && filters.type.length) {
|
||||
url += filters.type.map((i) => `&lang[]=${i}`).join("");
|
||||
}
|
||||
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 (Array.isArray(filters.genres) && filters.genres.length) {
|
||||
url += filters.genres.map((i) => `&genre[]=${i}`).join("");
|
||||
}
|
||||
}
|
||||
url += "&sortcountchapt=" + (filters?.sortcountchapt || "1");
|
||||
url += "&sortdaycreate=" + (filters?.sortdaycreate || "1");
|
||||
url += "&sortby=" + (showLatestNovels ? "upd" : filters?.sortby || "topweek");
|
||||
url += "&gpage=" + page;
|
||||
|
||||
url += `&page=${page}`;
|
||||
const result = await fetchApi(url);
|
||||
let body = await result.text();
|
||||
|
||||
const result = await fetchApi(url);
|
||||
let body = await result.text();
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
let novels: Novel.Item[] = [];
|
||||
loadedCheerio('div[class="block-home"] > div[class="one"]').each(function () {
|
||||
const name = loadedCheerio(this)
|
||||
.find('div[class="img-home"] > a')
|
||||
.attr("title");
|
||||
const cover = loadedCheerio(this)
|
||||
.find('div[class="img-home"] > a > img')
|
||||
.attr("src")
|
||||
?.replace("-150x150", "");
|
||||
const url = loadedCheerio(this)
|
||||
.find('div[class="img-home"] > a')
|
||||
.attr("href");
|
||||
|
||||
let novels: Novel.Item[] = [];
|
||||
loadedCheerio('div[class="block-home"] > div[class="one"]').each(
|
||||
function () {
|
||||
const name = loadedCheerio(this)
|
||||
.find('div[class="img-home"] > a')
|
||||
.attr("title");
|
||||
const cover = loadedCheerio(this)
|
||||
.find('div[class="img-home"] > a > img')
|
||||
.attr("src")
|
||||
?.replace("-150x150", "");
|
||||
const url = loadedCheerio(this)
|
||||
.find('div[class="img-home"] > a')
|
||||
.attr("href");
|
||||
if (!name || !url) return;
|
||||
|
||||
if (!name || !url) return;
|
||||
novels.push({ name, cover, url });
|
||||
});
|
||||
|
||||
novels.push({ name, cover, url });
|
||||
}
|
||||
);
|
||||
|
||||
return novels;
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi(novelUrl);
|
||||
const body = await result.text();
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi(novelUrl);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
let novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
};
|
||||
|
||||
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] === "Автор:") {
|
||||
novel.author = text.splice(1).join(" ");
|
||||
} else if (text[0] === "Жанры:") {
|
||||
novel.genres = text.splice(1).join(",");
|
||||
} else if (text[0] === "Статус:") {
|
||||
novel.status = text.includes("продолжается")
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed;
|
||||
}
|
||||
});
|
||||
|
||||
const chapters: Chapter.Item[] = [];
|
||||
|
||||
loadedCheerio(".download-chapter div.title").each(function () {
|
||||
chapters.push({
|
||||
name: loadedCheerio(this).find("a").attr("title")!,
|
||||
releaseTime: loadedCheerio(this).find("time").text(),
|
||||
url: loadedCheerio(this).find("a").attr("href")!,
|
||||
});
|
||||
});
|
||||
|
||||
novel.chapters = chapters.reverse();
|
||||
return novel;
|
||||
const loadedCheerio = parseHTML(body);
|
||||
let novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
};
|
||||
|
||||
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] === "Автор:") {
|
||||
novel.author = text.splice(1).join(" ");
|
||||
} else if (text[0] === "Жанры:") {
|
||||
novel.genres = text.splice(1).join(",");
|
||||
} else if (text[0] === "Статус:") {
|
||||
novel.status = text.includes("продолжается")
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed;
|
||||
}
|
||||
});
|
||||
|
||||
const chapters: Chapter.Item[] = [];
|
||||
|
||||
loadedCheerio(".download-chapter div.title").each(function () {
|
||||
const name = loadedCheerio(this).find("a").attr("title");
|
||||
const releaseTime = loadedCheerio(this).find("time").text();
|
||||
const url = loadedCheerio(this).find("a").attr("href");
|
||||
if (!name || !url) return;
|
||||
|
||||
chapters.push({ name, releaseTime, url });
|
||||
});
|
||||
|
||||
novel.chapters = chapters.reverse();
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi(chapterUrl);
|
||||
const body = await result.text();
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const result = await fetchApi(chapterUrl);
|
||||
const body = await result.text();
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio('div[class="adblock-service"]').remove();
|
||||
const chapterText = loadedCheerio('div[class="entry-content"]').html();
|
||||
loadedCheerio('div[class="adblock-service"]').remove();
|
||||
const chapterText = loadedCheerio('div[class="entry-content"]').html();
|
||||
|
||||
return chapterText;
|
||||
return chapterText;
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
|
||||
const url = `${baseUrl}/?searchrn=${searchTerm}&but=Поиск по названию&sortby=upd`;
|
||||
const result = await fetchApi(url);
|
||||
let body = await result.text();
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const url = `${site}/?searchrn=${searchTerm}&but=Поиск по названию&sortby=upd`;
|
||||
const result = await fetchApi(url);
|
||||
let body = await result.text();
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
let novels: Novel.Item[] = [];
|
||||
loadedCheerio('div[class="block-home"] > div[class="one"]').each(
|
||||
function () {
|
||||
const name = loadedCheerio(this)
|
||||
.find('div[class="img-home"] > a')
|
||||
.attr("title");
|
||||
const cover = loadedCheerio(this)
|
||||
.find('div[class="img-home"] > a > img')
|
||||
.attr("src")
|
||||
?.replace("-150x150", "");
|
||||
const url = loadedCheerio(this)
|
||||
.find('div[class="img-home"] > a')
|
||||
.attr("href");
|
||||
let novels: Novel.Item[] = [];
|
||||
loadedCheerio('div[class="block-home"] > div[class="one"]').each(function () {
|
||||
const name = loadedCheerio(this)
|
||||
.find('div[class="img-home"] > a')
|
||||
.attr("title");
|
||||
const cover = loadedCheerio(this)
|
||||
.find('div[class="img-home"] > a > img')
|
||||
.attr("src")
|
||||
?.replace("-150x150", "");
|
||||
const url = loadedCheerio(this)
|
||||
.find('div[class="img-home"] > a')
|
||||
.attr("href");
|
||||
|
||||
if (!name || !url) return;
|
||||
if (!name || !url) return;
|
||||
|
||||
novels.push({ name, cover, url });
|
||||
}
|
||||
);
|
||||
novels.push({ name, cover, url });
|
||||
});
|
||||
|
||||
return novels;
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const filters = [
|
||||
{
|
||||
key: "sort",
|
||||
label: "Сортировка",
|
||||
values: [
|
||||
{ label: "Имя", value: "alphabet" },
|
||||
{ label: "Просмотры", value: "count" },
|
||||
{ label: "Дате добавления", value: "new" },
|
||||
{ label: "Дате обновления", value: "upd" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "type",
|
||||
label: "Тип",
|
||||
values: [
|
||||
{ label: "Английский", value: "Английский" },
|
||||
{ label: "Китайский", value: "Китайский" },
|
||||
{ label: "Корейский", value: "Корейский" },
|
||||
{ label: "Японский", value: "Японский" },
|
||||
],
|
||||
inputType: FilterInputs.Checkbox,
|
||||
},
|
||||
{
|
||||
key: "genres",
|
||||
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: "sortby",
|
||||
label: "Сортировка:",
|
||||
values: [
|
||||
{ label: "Топ недели", value: "topweek" },
|
||||
{ label: "По алфавиту", value: "alphabet" },
|
||||
{ label: "По дате обновления", value: "upd" },
|
||||
{ label: "По дате создания", value: "new" },
|
||||
{ label: "По просмотрам", value: "count" },
|
||||
{ label: "Топ года", value: "topyear" },
|
||||
{ label: "Топ дня", value: "topday" },
|
||||
{ label: "Топ за все время", value: "alltime" },
|
||||
{ label: "Топ месяца", value: "topmonth" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "sortdaycreate",
|
||||
label: "Дата добавления:",
|
||||
values: [
|
||||
{ label: "Любое", value: "1" },
|
||||
{ label: "От 120 до 180 дней", value: "1218" },
|
||||
{ label: "От 180 до 365 дней", value: "1836" },
|
||||
{ label: "От 30 до 60 дней", value: "3060" },
|
||||
{ label: "От 365 дней", value: "365" },
|
||||
{ label: "От 60 до 90 дней", value: "6090" },
|
||||
{ label: "От 90 до 120 дней", value: "9012" },
|
||||
{ label: "Послед. 30 дней", value: "30" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "sortcountchapt",
|
||||
label: "Количество глав:",
|
||||
values: [
|
||||
{ label: "Любое кол-во глав", value: "1" },
|
||||
{ label: "До 500", value: "500" },
|
||||
{ label: "От 1000 до 2000", value: "1020" },
|
||||
{ label: "От 2000 до 3000", value: "2030" },
|
||||
{ label: "От 3000 до 4000", value: "3040" },
|
||||
{ label: "От 4000", value: "400" },
|
||||
{ label: "От 500 до 1000", value: "510" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "genre",
|
||||
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: "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",
|
||||
label: "Выбрать языки:",
|
||||
values: [
|
||||
{ label: "Английский", value: "Английский" },
|
||||
{ label: "Китайский", value: "Китайский" },
|
||||
{ label: "Корейский", value: "Корейский" },
|
||||
{ label: "Японский", value: "Японский" },
|
||||
],
|
||||
inputType: FilterInputs.Checkbox,
|
||||
},
|
||||
];
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = fetchFile;
|
||||
|
||||
@@ -1,218 +0,0 @@
|
||||
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from 'dayjs';
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
// import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
// import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
|
||||
// import { showToast } from "@libs/showToast";
|
||||
import { Filter, FilterInputs } from "@libs/filterInputs";
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
|
||||
|
||||
export const id = "japit.novel";
|
||||
export const name = "Japit Comics";
|
||||
export const icon = "";
|
||||
export const version = "1.0.0";
|
||||
export const site = "https://japit.novel.tl";
|
||||
export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Академия","value":5},{"label":"Андроиды","value":37},{"label":"Беззаботный герой","value":115},{"label":"Боги","value":318},{"label":"Богини","value":316},{"label":"Вампиры","value":742},{"label":"Везучий главный герой","value":411},{"label":"Влияние прошлого","value":517},{"label":"Внезапное обогащение","value":691},{"label":"Военные хроники","value":749},{"label":"Война","value":750},{"label":"Воспоминания прошлого","value":283},{"label":"Герои","value":341},{"label":"Героиня красавица","value":81},{"label":"Герой влюбляется первым","value":561},{"label":"Герой красавец","value":329},{"label":"Герой-бесстыдник","value":632},{"label":"Герой-извращенец","value":521},{"label":"Герой-трудяга","value":330},{"label":"R-15","value":570}],"inputType":FilterInputs.Checkbox}];
|
||||
|
||||
const baseUrl = site;
|
||||
const domain = baseUrl.split('//')[1];
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ filters }
|
||||
) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}));
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'Book',
|
||||
query:
|
||||
'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}',
|
||||
variables: {
|
||||
hostname: domain,
|
||||
project: novelUrl,
|
||||
url: novelUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
name: json.data.project.title,
|
||||
cover: json.data.project.covers[0]?.url
|
||||
? baseUrl + json.data.project.covers[0].url
|
||||
: defaultCover,
|
||||
summary: parseHTML(json.data.project.annotation?.text).text(),
|
||||
author:
|
||||
json.data.project.persons[0].name.firstName +
|
||||
' ' +
|
||||
json.data.project.persons[0].name.lastName,
|
||||
status:
|
||||
json.data.project.translationStatus === 'active'
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
|
||||
let genres = []
|
||||
.concat(json.data.project.tags, json.data.project.genres)
|
||||
.map((item: any) => item?.nameRu || item?.nameEng)
|
||||
.filter(item => item);
|
||||
|
||||
if (genres.length > 0) {
|
||||
novel.genres = genres.join(',');
|
||||
}
|
||||
|
||||
let novelChapters: Chapter.Item[] = [];
|
||||
json.data.project.subprojects.content.forEach((work: any) =>
|
||||
work.volumes.content.forEach((volume: any) =>
|
||||
volume.chapters.forEach(
|
||||
(chapter: any) =>
|
||||
chapter?.published &&
|
||||
novelChapters.push({
|
||||
name: volume.shortName + ' ' + chapter.title,
|
||||
url: novelUrl + '/' + volume.url + '/' + chapter.url,
|
||||
releaseTime: dayjs(chapter.publishDate).format('LLL'),
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
novel.chapters = novelChapters;
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'EReaderData',
|
||||
query:
|
||||
'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}',
|
||||
variables: {
|
||||
chapterSelector: {
|
||||
fullUrl: chapterUrl,
|
||||
},
|
||||
url: chapterUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
|
||||
return parseHTML(json.data.chapter.text.text).html();
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: {
|
||||
query: searchTerm,
|
||||
},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page: 1,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = [];
|
||||
json?.data?.projects?.content?.forEach((novel: any) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}),
|
||||
);
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url, {});
|
||||
};
|
||||
|
||||
@@ -1,218 +0,0 @@
|
||||
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from 'dayjs';
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
// import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
// import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
|
||||
// import { showToast } from "@libs/showToast";
|
||||
import { Filter, FilterInputs } from "@libs/filterInputs";
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
|
||||
|
||||
export const id = "kg.novel";
|
||||
export const name = "Карманная галактика";
|
||||
export const icon = "";
|
||||
export const version = "1.0.0";
|
||||
export const site = "https://kg.novel.tl";
|
||||
export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Алхимия","value":27},{"label":"Андроиды","value":37},{"label":"Анти-рост персонажа","value":25},{"label":"Антигерой","value":43},{"label":"Апатичный главный герой","value":46},{"label":"Аристократия","value":51},{"label":"Асоциальный главный герой","value":42},{"label":"Ассасины","value":61},{"label":"Беззаботный герой","value":115},{"label":"Безработные","value":377},{"label":"Бессмертные","value":359},{"label":"Библиотека","value":393},{"label":"Боги","value":318},{"label":"Боевая академия","value":75},{"label":"Бывший герой","value":290},{"label":"Ведьмы","value":756},{"label":"Везучий главный герой","value":411},{"label":"Верные подданные","value":410},{"label":"Взросление персонажа","value":24},{"label":"R-15","value":570}],"inputType":FilterInputs.Checkbox}];
|
||||
|
||||
const baseUrl = site;
|
||||
const domain = baseUrl.split('//')[1];
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ filters }
|
||||
) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}));
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'Book',
|
||||
query:
|
||||
'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}',
|
||||
variables: {
|
||||
hostname: domain,
|
||||
project: novelUrl,
|
||||
url: novelUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
name: json.data.project.title,
|
||||
cover: json.data.project.covers[0]?.url
|
||||
? baseUrl + json.data.project.covers[0].url
|
||||
: defaultCover,
|
||||
summary: parseHTML(json.data.project.annotation?.text).text(),
|
||||
author:
|
||||
json.data.project.persons[0].name.firstName +
|
||||
' ' +
|
||||
json.data.project.persons[0].name.lastName,
|
||||
status:
|
||||
json.data.project.translationStatus === 'active'
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
|
||||
let genres = []
|
||||
.concat(json.data.project.tags, json.data.project.genres)
|
||||
.map((item: any) => item?.nameRu || item?.nameEng)
|
||||
.filter(item => item);
|
||||
|
||||
if (genres.length > 0) {
|
||||
novel.genres = genres.join(',');
|
||||
}
|
||||
|
||||
let novelChapters: Chapter.Item[] = [];
|
||||
json.data.project.subprojects.content.forEach((work: any) =>
|
||||
work.volumes.content.forEach((volume: any) =>
|
||||
volume.chapters.forEach(
|
||||
(chapter: any) =>
|
||||
chapter?.published &&
|
||||
novelChapters.push({
|
||||
name: volume.shortName + ' ' + chapter.title,
|
||||
url: novelUrl + '/' + volume.url + '/' + chapter.url,
|
||||
releaseTime: dayjs(chapter.publishDate).format('LLL'),
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
novel.chapters = novelChapters;
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'EReaderData',
|
||||
query:
|
||||
'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}',
|
||||
variables: {
|
||||
chapterSelector: {
|
||||
fullUrl: chapterUrl,
|
||||
},
|
||||
url: chapterUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
|
||||
return parseHTML(json.data.chapter.text.text).html();
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: {
|
||||
query: searchTerm,
|
||||
},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page: 1,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = [];
|
||||
json?.data?.projects?.content?.forEach((novel: any) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}),
|
||||
);
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url, {});
|
||||
};
|
||||
|
||||
@@ -1,218 +0,0 @@
|
||||
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from 'dayjs';
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
// import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
// import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
|
||||
// import { showToast } from "@libs/showToast";
|
||||
import { Filter, FilterInputs } from "@libs/filterInputs";
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
|
||||
|
||||
export const id = "kod.novel";
|
||||
export const name = "K.O.D.";
|
||||
export const icon = "";
|
||||
export const version = "1.0.0";
|
||||
export const site = "https://kod.novel.tl";
|
||||
export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Актёрское искусство","value":7},{"label":"Забота о детях","value":127},{"label":"Управление бизнесом","value":109},{"label":"Шоу-бизнес","value":642}],"inputType":FilterInputs.Checkbox}];
|
||||
|
||||
const baseUrl = site;
|
||||
const domain = baseUrl.split('//')[1];
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ filters }
|
||||
) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}));
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'Book',
|
||||
query:
|
||||
'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}',
|
||||
variables: {
|
||||
hostname: domain,
|
||||
project: novelUrl,
|
||||
url: novelUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
name: json.data.project.title,
|
||||
cover: json.data.project.covers[0]?.url
|
||||
? baseUrl + json.data.project.covers[0].url
|
||||
: defaultCover,
|
||||
summary: parseHTML(json.data.project.annotation?.text).text(),
|
||||
author:
|
||||
json.data.project.persons[0].name.firstName +
|
||||
' ' +
|
||||
json.data.project.persons[0].name.lastName,
|
||||
status:
|
||||
json.data.project.translationStatus === 'active'
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
|
||||
let genres = []
|
||||
.concat(json.data.project.tags, json.data.project.genres)
|
||||
.map((item: any) => item?.nameRu || item?.nameEng)
|
||||
.filter(item => item);
|
||||
|
||||
if (genres.length > 0) {
|
||||
novel.genres = genres.join(',');
|
||||
}
|
||||
|
||||
let novelChapters: Chapter.Item[] = [];
|
||||
json.data.project.subprojects.content.forEach((work: any) =>
|
||||
work.volumes.content.forEach((volume: any) =>
|
||||
volume.chapters.forEach(
|
||||
(chapter: any) =>
|
||||
chapter?.published &&
|
||||
novelChapters.push({
|
||||
name: volume.shortName + ' ' + chapter.title,
|
||||
url: novelUrl + '/' + volume.url + '/' + chapter.url,
|
||||
releaseTime: dayjs(chapter.publishDate).format('LLL'),
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
novel.chapters = novelChapters;
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'EReaderData',
|
||||
query:
|
||||
'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}',
|
||||
variables: {
|
||||
chapterSelector: {
|
||||
fullUrl: chapterUrl,
|
||||
},
|
||||
url: chapterUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
|
||||
return parseHTML(json.data.chapter.text.text).html();
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: {
|
||||
query: searchTerm,
|
||||
},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page: 1,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = [];
|
||||
json?.data?.projects?.content?.forEach((novel: any) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}),
|
||||
);
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url, {});
|
||||
};
|
||||
|
||||
@@ -1,218 +0,0 @@
|
||||
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from 'dayjs';
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
// import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
// import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
|
||||
// import { showToast } from "@libs/showToast";
|
||||
import { Filter, FilterInputs } from "@libs/filterInputs";
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
|
||||
|
||||
export const id = "nightnovel";
|
||||
export const name = "Nightnovel";
|
||||
export const icon = "";
|
||||
export const version = "1.0.0";
|
||||
export const site = "https://nightnovel.online";
|
||||
export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Альтернативная реальность","value":30},{"label":"Амнезия","value":31},{"label":"Анал","value":33},{"label":"Ангелы","value":38},{"label":"Аристократия","value":51},{"label":"Артефакты","value":58},{"label":"Ассасины","value":61},{"label":"Банды","value":302},{"label":"БДСМ","value":77},{"label":"Беременность","value":551},{"label":"Боги","value":318},{"label":"Богини","value":316},{"label":"Боевая академия","value":75},{"label":"Божественная сила","value":317},{"label":"Влияние прошлого","value":517},{"label":"Враги становятся союзниками","value":238},{"label":"Вынужденные условия проживания","value":287},{"label":"Герои","value":341},{"label":"Героиня красавица","value":81},{"label":"R-18","value":571}],"inputType":FilterInputs.Checkbox}];
|
||||
|
||||
const baseUrl = site;
|
||||
const domain = baseUrl.split('//')[1];
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ filters }
|
||||
) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}));
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'Book',
|
||||
query:
|
||||
'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}',
|
||||
variables: {
|
||||
hostname: domain,
|
||||
project: novelUrl,
|
||||
url: novelUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
name: json.data.project.title,
|
||||
cover: json.data.project.covers[0]?.url
|
||||
? baseUrl + json.data.project.covers[0].url
|
||||
: defaultCover,
|
||||
summary: parseHTML(json.data.project.annotation?.text).text(),
|
||||
author:
|
||||
json.data.project.persons[0].name.firstName +
|
||||
' ' +
|
||||
json.data.project.persons[0].name.lastName,
|
||||
status:
|
||||
json.data.project.translationStatus === 'active'
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
|
||||
let genres = []
|
||||
.concat(json.data.project.tags, json.data.project.genres)
|
||||
.map((item: any) => item?.nameRu || item?.nameEng)
|
||||
.filter(item => item);
|
||||
|
||||
if (genres.length > 0) {
|
||||
novel.genres = genres.join(',');
|
||||
}
|
||||
|
||||
let novelChapters: Chapter.Item[] = [];
|
||||
json.data.project.subprojects.content.forEach((work: any) =>
|
||||
work.volumes.content.forEach((volume: any) =>
|
||||
volume.chapters.forEach(
|
||||
(chapter: any) =>
|
||||
chapter?.published &&
|
||||
novelChapters.push({
|
||||
name: volume.shortName + ' ' + chapter.title,
|
||||
url: novelUrl + '/' + volume.url + '/' + chapter.url,
|
||||
releaseTime: dayjs(chapter.publishDate).format('LLL'),
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
novel.chapters = novelChapters;
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'EReaderData',
|
||||
query:
|
||||
'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}',
|
||||
variables: {
|
||||
chapterSelector: {
|
||||
fullUrl: chapterUrl,
|
||||
},
|
||||
url: chapterUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
|
||||
return parseHTML(json.data.chapter.text.text).html();
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: {
|
||||
query: searchTerm,
|
||||
},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page: 1,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = [];
|
||||
json?.data?.projects?.content?.forEach((novel: any) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}),
|
||||
);
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url, {});
|
||||
};
|
||||
|
||||
@@ -1,867 +0,0 @@
|
||||
import cheerio from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
import { fetchApi } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { Chapter, Novel, Plugin } from "@typings/plugin";
|
||||
|
||||
export const id = "TL";
|
||||
export const name = "NovelTL";
|
||||
export const site = "https://novel.tl";
|
||||
export const version = "1.0.0";
|
||||
export const icon = "src/ru/noveltl/icon.png";
|
||||
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ filters }
|
||||
) {
|
||||
const result = await fetchApi(baseUrl + "/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: baseUrl,
|
||||
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();
|
||||
let novels: Novel.Item[] = [];
|
||||
|
||||
json?.data?.projects?.content?.forEach((novel) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
url: "https://" + novel.fullUrl,
|
||||
cover: novel?.covers?.[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
})
|
||||
);
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi(baseUrl + "/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: baseUrl,
|
||||
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();
|
||||
|
||||
let novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
name: json.data.project.title,
|
||||
cover: json.data.project?.covers?.[0]?.url
|
||||
? baseUrl + json.data.project.covers[0].url
|
||||
: defaultCover,
|
||||
summary: json.data.project?.annotation?.text,
|
||||
status:
|
||||
json.data.project.translationStatus === "active"
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
|
||||
let genres = []
|
||||
.concat(json.data.project.tags, json.data.project.genres)
|
||||
.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 || "");
|
||||
} else if (person.role == "illustrator" && person.name.firstName) {
|
||||
novel.artist =
|
||||
person.name.firstName + " " + (person.name?.lastName || "");
|
||||
}
|
||||
});
|
||||
|
||||
let novelChapters: Chapter.Item[] = [];
|
||||
json.data.project.subprojects.content.forEach((work) =>
|
||||
work.volumes.content.forEach((volume) =>
|
||||
volume.chapters.forEach(
|
||||
(chapter) =>
|
||||
chapter?.published &&
|
||||
novelChapters.push({
|
||||
name: volume.shortName + " " + chapter.title,
|
||||
url: "https://" + chapter.fullUrl,
|
||||
releaseTime: dayjs(chapter.publishDate).format(
|
||||
"LLL"
|
||||
),
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
novel.chapters = novelChapters;
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi(baseUrl + "/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: baseUrl,
|
||||
body: JSON.stringify({
|
||||
query: "query($url:String){chapter(chapter:{fullUrl:$url}){text{text}}}",
|
||||
variables: {
|
||||
url: chapterUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json = await result.json();
|
||||
|
||||
const loadedCheerio = cheerio.load(json.data.chapter.text.text);
|
||||
loadedCheerio("p > a[href]").each(function () {
|
||||
let url = baseUrl + loadedCheerio(this).attr("href");
|
||||
if (!url.startsWith("http")) {
|
||||
url = baseUrl + url;
|
||||
}
|
||||
loadedCheerio(this).find("picture").remove();
|
||||
loadedCheerio(this).removeAttr("href");
|
||||
loadedCheerio(`<img src="${url}">`).appendTo(this);
|
||||
});
|
||||
|
||||
const chapterText = loadedCheerio.html();
|
||||
return chapterText;
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
|
||||
const result = await fetchApi(baseUrl + "/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.baseUrl,
|
||||
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: 1,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json = await result.json();
|
||||
|
||||
let novels: Novel.Item[] = [];
|
||||
json?.data?.projects?.content?.forEach((novel) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
url: "https://" + novel.fullUrl,
|
||||
cover: novel?.covers?.[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
})
|
||||
);
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const 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,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,949 @@
|
||||
import { Chapter, Novel, 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";
|
||||
|
||||
export const id = "TL";
|
||||
export const name = "NovelTL";
|
||||
export const site = "https://novel.tl";
|
||||
export const version = "1.0.0";
|
||||
export const icon = "src/ru/noveltl/icon.png";
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (
|
||||
page,
|
||||
{ filters }
|
||||
) => {
|
||||
const result = await fetchApi(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: 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;
|
||||
let novels: Novel.Item[] = [];
|
||||
|
||||
json.data?.projects?.content?.forEach((novel) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
url: "https://" + novel.fullUrl,
|
||||
cover: novel?.covers?.[0]?.url
|
||||
? site + novel.covers[0].url
|
||||
: defaultCover,
|
||||
})
|
||||
);
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async (
|
||||
novelUrl
|
||||
) => {
|
||||
const result = await fetchApi(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: 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;
|
||||
let novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
name: json.data.project?.title,
|
||||
cover: json.data.project?.covers?.[0]?.url
|
||||
? site + json.data.project.covers[0].url
|
||||
: defaultCover,
|
||||
summary: json.data.project?.annotation?.text,
|
||||
status:
|
||||
json.data.project?.translationStatus === "active"
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
|
||||
let 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 || "");
|
||||
}
|
||||
});
|
||||
|
||||
let novelChapters: Chapter.Item[] = [];
|
||||
json.data.project?.subprojects?.content?.forEach((work) =>
|
||||
work.volumes.content.forEach((volume) =>
|
||||
volume.chapters.forEach(
|
||||
(chapter) =>
|
||||
chapter?.published &&
|
||||
novelChapters.push({
|
||||
name: volume.shortName + " " + chapter.title,
|
||||
url: "https://" + chapter.fullUrl,
|
||||
releaseTime: dayjs(chapter.publishDate).format("LLL"),
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
novel.chapters = novelChapters;
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async (chapterUrl) => {
|
||||
const result = await fetchApi(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: 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 loadedCheerio = parseHTML(json.data.chapter?.text?.text || "");
|
||||
loadedCheerio("p > a[href]").each(function () {
|
||||
let url = site + loadedCheerio(this).attr("href");
|
||||
if (!url.startsWith("http")) {
|
||||
url = site + url;
|
||||
}
|
||||
loadedCheerio(this).find("picture").remove();
|
||||
loadedCheerio(this).removeAttr("href");
|
||||
loadedCheerio(`<img src="${url}">`).appendTo(this);
|
||||
});
|
||||
|
||||
const chapterText = loadedCheerio.html();
|
||||
return chapterText;
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
const result = await fetchApi(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: 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: 1,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json = (await result.json()) as response;
|
||||
let novels: Novel.Item[] = [];
|
||||
|
||||
json?.data?.projects?.content?.forEach((novel) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
url: "https://" + novel.fullUrl,
|
||||
cover: novel?.covers?.[0]?.url
|
||||
? site + novel.covers[0].url
|
||||
: defaultCover,
|
||||
})
|
||||
);
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = fetchFile;
|
||||
|
||||
export const 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,
|
||||
},
|
||||
];
|
||||
|
||||
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,218 +0,0 @@
|
||||
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from 'dayjs';
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
// import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
// import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
|
||||
// import { showToast } from "@libs/showToast";
|
||||
import { Filter, FilterInputs } from "@libs/filterInputs";
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
|
||||
|
||||
export const id = "original.novel";
|
||||
export const name = "Авторские работы";
|
||||
export const icon = "";
|
||||
export const version = "1.0.0";
|
||||
export const site = "https://original.novel.tl";
|
||||
export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Агрессивные персонажи","value":26},{"label":"Академия","value":5},{"label":"Андроиды","value":37},{"label":"Антимагия","value":41},{"label":"Аристократия","value":51},{"label":"Артефакты","value":58},{"label":"Брошенные дети","value":1},{"label":"Вероломные герои/плетение интриг","value":424},{"label":"Взросление персонажа","value":24},{"label":"Владелец уникального оружия","value":736},{"label":"Герой-извращенец","value":521},{"label":"Главный герой не человек","value":488},{"label":"Государственные интриги","value":212},{"label":"Душещипательная история","value":335},{"label":"Жестокие персонажи","value":4},{"label":"Зверолюди","value":39},{"label":"Знания из прошлой жизни","value":553},{"label":"Искусственный интеллект","value":59},{"label":"Конфликт в семье","value":261},{"label":"Королевства","value":382}],"inputType":FilterInputs.Checkbox}];
|
||||
|
||||
const baseUrl = site;
|
||||
const domain = baseUrl.split('//')[1];
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ filters }
|
||||
) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}));
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'Book',
|
||||
query:
|
||||
'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}',
|
||||
variables: {
|
||||
hostname: domain,
|
||||
project: novelUrl,
|
||||
url: novelUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
name: json.data.project.title,
|
||||
cover: json.data.project.covers[0]?.url
|
||||
? baseUrl + json.data.project.covers[0].url
|
||||
: defaultCover,
|
||||
summary: parseHTML(json.data.project.annotation?.text).text(),
|
||||
author:
|
||||
json.data.project.persons[0].name.firstName +
|
||||
' ' +
|
||||
json.data.project.persons[0].name.lastName,
|
||||
status:
|
||||
json.data.project.translationStatus === 'active'
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
|
||||
let genres = []
|
||||
.concat(json.data.project.tags, json.data.project.genres)
|
||||
.map((item: any) => item?.nameRu || item?.nameEng)
|
||||
.filter(item => item);
|
||||
|
||||
if (genres.length > 0) {
|
||||
novel.genres = genres.join(',');
|
||||
}
|
||||
|
||||
let novelChapters: Chapter.Item[] = [];
|
||||
json.data.project.subprojects.content.forEach((work: any) =>
|
||||
work.volumes.content.forEach((volume: any) =>
|
||||
volume.chapters.forEach(
|
||||
(chapter: any) =>
|
||||
chapter?.published &&
|
||||
novelChapters.push({
|
||||
name: volume.shortName + ' ' + chapter.title,
|
||||
url: novelUrl + '/' + volume.url + '/' + chapter.url,
|
||||
releaseTime: dayjs(chapter.publishDate).format('LLL'),
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
novel.chapters = novelChapters;
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'EReaderData',
|
||||
query:
|
||||
'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}',
|
||||
variables: {
|
||||
chapterSelector: {
|
||||
fullUrl: chapterUrl,
|
||||
},
|
||||
url: chapterUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
|
||||
return parseHTML(json.data.chapter.text.text).html();
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: {
|
||||
query: searchTerm,
|
||||
},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page: 1,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = [];
|
||||
json?.data?.projects?.content?.forEach((novel: any) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}),
|
||||
);
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url, {});
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,406 +0,0 @@
|
||||
import cheerio from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { Chapter, Novel, Plugin } from "@typings/plugin";
|
||||
|
||||
export const id = "RLIB";
|
||||
export const name = "RanobeLib";
|
||||
export const site = "https://ranobelib.me";
|
||||
export const version = "1.0.0";
|
||||
export const icon = "src/ru/ranobelib/icon.png";
|
||||
|
||||
const baseUrl = site;
|
||||
var ui = null;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ showLatestNovels, filters }
|
||||
) {
|
||||
let url = `${baseUrl}/manga-list?sort=`;
|
||||
url += showLatestNovels ? "last_chapter_at" : filters?.sort || "rate";
|
||||
url += "&dir=" + (filters?.order || "desc");
|
||||
|
||||
if (filters) {
|
||||
if (Array.isArray(filters.type) && filters.type.length) {
|
||||
url += filters.type.map((i) => `&types[]=${i}`).join("");
|
||||
}
|
||||
|
||||
if (Array.isArray(filters.format) && filters.format.length) {
|
||||
url += filters.format
|
||||
.map((i) => `&format[include][]=${i}`)
|
||||
.join("");
|
||||
}
|
||||
|
||||
if (Array.isArray(filters.status) && filters.status.length) {
|
||||
url += filters.status.map((i) => `&status[]=${i}`).join("");
|
||||
}
|
||||
|
||||
if (Array.isArray(filters.statuss) && filters.statuss.length) {
|
||||
url += filters.statuss.map((i) => `&manga_status[]=${i}`).join("");
|
||||
}
|
||||
|
||||
if (Array.isArray(filters.genres) && filters.genres.length) {
|
||||
url += filters.genres
|
||||
.map((i) => `&genres[include][]=${i}`)
|
||||
.join("");
|
||||
}
|
||||
|
||||
if (Array.isArray(filters.tags) && filters.tags.length) {
|
||||
url += filters.tags.map((i) => `&tags[include][]=${i}`).join("");
|
||||
}
|
||||
}
|
||||
|
||||
url += "&page=" + page;
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = cheerio.load(body);
|
||||
ui = loadedCheerio("a.header-right-menu__item")
|
||||
.attr("href")
|
||||
?.replace?.(/[^0-9]/g, "");
|
||||
|
||||
let novels: Novel.Item[] = [];
|
||||
loadedCheerio(".media-card-wrap").each(function () {
|
||||
const name = loadedCheerio(this).find(".media-card__title").text();
|
||||
const cover = loadedCheerio(this).find("a.media-card").attr("data-src");
|
||||
const url = loadedCheerio(this).find("a.media-card").attr("href");
|
||||
if (!url) return;
|
||||
novels.push({ name, cover, url });
|
||||
});
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi(novelUrl);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = cheerio.load(body);
|
||||
|
||||
let novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
};
|
||||
|
||||
novel.name = loadedCheerio(".media-name__main").text().trim();
|
||||
|
||||
novel.cover = loadedCheerio(".media-sidebar__cover img").attr("src");
|
||||
|
||||
novel.summary = loadedCheerio(".media-description__text").text().trim();
|
||||
|
||||
novel.genres = loadedCheerio('div[class="media-tags"]')
|
||||
.text()
|
||||
.trim()
|
||||
.replace(/[\n\r]+/g, ", ")
|
||||
.replace(/ /g, "");
|
||||
|
||||
loadedCheerio(
|
||||
'div[class="media-info-list paper"] > [class="media-info-list__item"]'
|
||||
).each(function () {
|
||||
let name = loadedCheerio(this)
|
||||
.find('div[class="media-info-list__title"]')
|
||||
.text();
|
||||
|
||||
if (name === "Статус перевода") {
|
||||
novel.status =
|
||||
loadedCheerio(this)
|
||||
.find("div:nth-child(2)")
|
||||
.text()
|
||||
.trim() === "Продолжается"
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed;
|
||||
} else if (name === "Автор") {
|
||||
novel.author = loadedCheerio(this)
|
||||
.find("div:nth-child(2)")
|
||||
.text()
|
||||
.trim();
|
||||
}
|
||||
});
|
||||
|
||||
let chapters: Chapter.Item[] = [];
|
||||
|
||||
let chaptersJson = body.match(
|
||||
/window.__DATA__ = [\s\S]*?window._SITE_COLOR_/gm
|
||||
);
|
||||
|
||||
chaptersJson = chaptersJson?.[0]
|
||||
?.replace("window.__DATA__ = ", "")
|
||||
?.replace("window._SITE_COLOR_", "")
|
||||
?.trim()
|
||||
?.slice(0, -1);
|
||||
|
||||
chaptersJson = JSON.parse(chaptersJson);
|
||||
ui = chaptersJson?.user?.id;
|
||||
|
||||
chaptersJson.chapters.list.forEach((chapter) =>
|
||||
chapters.push({
|
||||
name: `Том ${chapter.chapter_volume} Глава ${chapter.chapter_number} ${chapter.chapter_name}`?.trim(),
|
||||
releaseTime: dayjs(chapter.chapter_created_at).format("LLL"),
|
||||
url:
|
||||
`${baseUrl}/${chaptersJson.manga.slug}/v${chapter.chapter_volume}/c${chapter.chapter_number}?bid=` +
|
||||
(chapter?.branch_id || ""),
|
||||
})
|
||||
);
|
||||
|
||||
novel.chapters = chapters.reverse();
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi(chapterUrl + (ui ? `&ui=${ui}` : ""));
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = cheerio.load(body);
|
||||
|
||||
loadedCheerio(".reader-container img").each(function () {
|
||||
const url =
|
||||
loadedCheerio(this).attr("data-src") ||
|
||||
loadedCheerio(this).attr("src");
|
||||
if (!url?.startsWith("http")) {
|
||||
loadedCheerio(this).attr("src", baseUrl + url);
|
||||
} else {
|
||||
loadedCheerio(this).attr("src", url);
|
||||
}
|
||||
loadedCheerio(this).removeAttr("data-src");
|
||||
});
|
||||
|
||||
const chapterText = loadedCheerio(".reader-container").html();
|
||||
|
||||
return chapterText;
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
|
||||
const result = await fetchApi(
|
||||
`${baseUrl}/search?q=${searchTerm}&type=manga`
|
||||
);
|
||||
const body = await result.json();
|
||||
|
||||
let novels: Novel.Item[] = [];
|
||||
body.forEach((novel) =>
|
||||
novels.push({
|
||||
name: novel?.rus_name || novel.name,
|
||||
cover: novel.coverImage,
|
||||
url: novel?.href || baseUrl + "/" + novel.slug,
|
||||
})
|
||||
);
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const filters = [
|
||||
{
|
||||
key: "sort",
|
||||
label: "Сортировка",
|
||||
values: [
|
||||
{ label: "Рейтинг", value: "rate" },
|
||||
{ label: "Имя", value: "name" },
|
||||
{ label: "Просмотры", value: "views" },
|
||||
{ label: "Дате добавления", value: "created_at" },
|
||||
{ label: "Дате обновления", value: "last_chapter_at" },
|
||||
{ label: "Количество глав", value: "chap_count" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "order",
|
||||
label: "Порядок",
|
||||
values: [
|
||||
{ label: "По убыванию", value: "desc" },
|
||||
{ label: "По возрастанию", value: "asc" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "type",
|
||||
label: "Тип",
|
||||
values: [
|
||||
{ label: "Авторский", value: "14" },
|
||||
{ label: "Английский", value: "13" },
|
||||
{ label: "Китай", value: "12" },
|
||||
{ label: "Корея", value: "11" },
|
||||
{ label: "Фанфик", value: "15" },
|
||||
{ label: "Япония", value: "10" },
|
||||
],
|
||||
inputType: FilterInputs.Checkbox,
|
||||
},
|
||||
{
|
||||
key: "format",
|
||||
label: "Формат выпуска",
|
||||
values: [
|
||||
{ label: "4-кома (Ёнкома)", value: "1" },
|
||||
{ label: "В цвете", value: "4" },
|
||||
{ label: "Веб", value: "6" },
|
||||
{ label: "Вебтун", value: "7" },
|
||||
{ label: "Додзинси", value: "3" },
|
||||
{ label: "Сборник", value: "2" },
|
||||
{ label: "Сингл", value: "5" },
|
||||
],
|
||||
inputType: FilterInputs.Checkbox,
|
||||
},
|
||||
{
|
||||
key: "status",
|
||||
label: "Статус перевода",
|
||||
values: [
|
||||
{ label: "Продолжается", value: "1" },
|
||||
{ label: "Завершен", value: "2" },
|
||||
{ label: "Заморожен", value: "3" },
|
||||
{ label: "Заброшен", value: "4" },
|
||||
],
|
||||
inputType: FilterInputs.Checkbox,
|
||||
},
|
||||
{
|
||||
key: "statuss",
|
||||
label: "Статус тайтла",
|
||||
values: [
|
||||
{ label: "Онгоинг", value: "1" },
|
||||
{ label: "Завершён", value: "2" },
|
||||
{ label: "Анонс", value: "3" },
|
||||
{ label: "Приостановлен", value: "4" },
|
||||
{ label: "Выпуск прекращён", value: "5" },
|
||||
],
|
||||
inputType: FilterInputs.Checkbox,
|
||||
},
|
||||
{
|
||||
key: "genres",
|
||||
label: "Жанры",
|
||||
values: [
|
||||
{ label: "Арт", value: "32" },
|
||||
{ label: "Безумие", value: "91" },
|
||||
{ label: "Боевик", value: "34" },
|
||||
{ label: "Боевые искусства", value: "35" },
|
||||
{ label: "Вампиры", value: "36" },
|
||||
{ label: "Военное", value: "89" },
|
||||
{ label: "Гарем", value: "37" },
|
||||
{ label: "Гендерная интрига", value: "38" },
|
||||
{ label: "Героическое фэнтези", value: "39" },
|
||||
{ label: "Демоны", value: "81" },
|
||||
{ label: "Детектив", value: "40" },
|
||||
{ label: "Детское", value: "88" },
|
||||
{ label: "Дзёсэй", value: "41" },
|
||||
{ label: "Драма", value: "43" },
|
||||
{ label: "Игра", value: "44" },
|
||||
{ label: "Исекай", value: "79" },
|
||||
{ label: "История", value: "45" },
|
||||
{ label: "Киберпанк", value: "46" },
|
||||
{ label: "Кодомо", value: "76" },
|
||||
{ label: "Комедия", value: "47" },
|
||||
{ label: "Космос", value: "83" },
|
||||
{ label: "Магия", value: "85" },
|
||||
{ label: "Махо-сёдзё", value: "48" },
|
||||
{ label: "Машины", value: "90" },
|
||||
{ label: "Меха", value: "49" },
|
||||
{ label: "Мистика", value: "50" },
|
||||
{ label: "Музыка", value: "80" },
|
||||
{ label: "Научная фантастика", value: "51" },
|
||||
{ label: "Омегаверс", value: "77" },
|
||||
{ label: "Пародия", value: "86" },
|
||||
{ label: "Повседневность", value: "52" },
|
||||
{ label: "Полиция", value: "82" },
|
||||
{ label: "Постапокалиптика", value: "53" },
|
||||
{ label: "Приключения", value: "54" },
|
||||
{ label: "Психология", value: "55" },
|
||||
{ label: "Романтика", value: "56" },
|
||||
{ label: "Самурайский боевик", value: "57" },
|
||||
{ label: "Сверхъестественное", value: "58" },
|
||||
{ label: "Сёдзё", value: "59" },
|
||||
{ label: "Сёдзё-ай", value: "60" },
|
||||
{ label: "Сёнэн", value: "61" },
|
||||
{ label: "Сёнэн-ай", value: "62" },
|
||||
{ label: "Спорт", value: "63" },
|
||||
{ label: "Супер сила", value: "87" },
|
||||
{ label: "Сэйнэн", value: "64" },
|
||||
{ label: "Трагедия", value: "65" },
|
||||
{ label: "Триллер", value: "66" },
|
||||
{ label: "Ужасы", value: "67" },
|
||||
{ label: "Фантастика", value: "68" },
|
||||
{ label: "Фэнтези", value: "69" },
|
||||
{ label: "Школа", value: "70" },
|
||||
{ label: "Эротика", value: "71" },
|
||||
{ label: "Этти", value: "72" },
|
||||
{ label: "Юри", value: "73" },
|
||||
{ label: "Яой", value: "74" },
|
||||
],
|
||||
inputType: FilterInputs.Checkbox,
|
||||
},
|
||||
{
|
||||
key: "tags",
|
||||
label: "Теги",
|
||||
values: [
|
||||
{ label: "Авантюристы", value: "328" },
|
||||
{ label: "Антигерой", value: "176" },
|
||||
{ label: "Бессмертные", value: "333" },
|
||||
{ label: "Боги", value: "218" },
|
||||
{ label: "Борьба за власть", value: "309" },
|
||||
{ label: "Брат и сестра", value: "360" },
|
||||
{ label: "Ведьма", value: "339" },
|
||||
{ label: "Видеоигры", value: "204" },
|
||||
{ label: "Виртуальная реальность", value: "214" },
|
||||
{ label: "Владыка демонов", value: "349" },
|
||||
{ label: "Военные", value: "198" },
|
||||
{ label: "Воспоминания из другого мира", value: "310" },
|
||||
{ label: "Выживание", value: "212" },
|
||||
{ label: "ГГ женщина", value: "294" },
|
||||
{ label: "ГГ имба", value: "292" },
|
||||
{ label: "ГГ мужчина", value: "295" },
|
||||
{ label: "ГГ не ояш", value: "325" },
|
||||
{ label: "ГГ не человек", value: "331" },
|
||||
{ label: "ГГ ояш", value: "326" },
|
||||
{ label: "Главный герой бог", value: "324" },
|
||||
{ label: "Глупый ГГ", value: "298" },
|
||||
{ label: "Горничные", value: "171" },
|
||||
{ label: "Гуро", value: "306" },
|
||||
{ label: "Гяру", value: "197" },
|
||||
{ label: "Демоны", value: "157" },
|
||||
{ label: "Драконы", value: "313" },
|
||||
{ label: "Древний мир", value: "317" },
|
||||
{ label: "Зверолюди", value: "163" },
|
||||
{ label: "Зомби", value: "155" },
|
||||
{ label: "Исторические фигуры", value: "323" },
|
||||
{ label: "Кулинария", value: "158" },
|
||||
{ label: "Культивация", value: "161" },
|
||||
{ label: "ЛГБТ", value: "344" },
|
||||
{ label: "ЛитРПГ", value: "319" },
|
||||
{ label: "Лоли", value: "206" },
|
||||
{ label: "Магия", value: "170" },
|
||||
{ label: "Машинный перевод", value: "345" },
|
||||
{ label: "Медицина", value: "159" },
|
||||
{ label: "Межгалактическая война", value: "330" },
|
||||
{ label: "Монстр Девушки", value: "207" },
|
||||
{ label: "Монстры", value: "208" },
|
||||
{ label: "Мрачный мир", value: "316" },
|
||||
{ label: "Музыка", value: "358" },
|
||||
{ label: "Музыка", value: "209" },
|
||||
{ label: "Ниндзя", value: "199" },
|
||||
{ label: "Обратный Гарем", value: "210" },
|
||||
{ label: "Офисные Работники", value: "200" },
|
||||
{ label: "Пираты", value: "341" },
|
||||
{ label: "Подземелья", value: "314" },
|
||||
{ label: "Политика", value: "311" },
|
||||
{ label: "Полиция", value: "201" },
|
||||
{ label: "Преступники / Криминал", value: "205" },
|
||||
{ label: "Призраки / Духи", value: "196" },
|
||||
{ label: "Призыватели", value: "329" },
|
||||
{ label: "Прыжки между мирами", value: "321" },
|
||||
{ label: "Путешествие в другой мир", value: "318" },
|
||||
{ label: "Путешествие во времени", value: "213" },
|
||||
{ label: "Рабы", value: "355" },
|
||||
{ label: "Ранги силы", value: "312" },
|
||||
{ label: "Реинкарнация", value: "154" },
|
||||
{ label: "Самураи", value: "202" },
|
||||
{ label: "Скрытие личности", value: "315" },
|
||||
{ label: "Средневековье", value: "174" },
|
||||
{ label: "Традиционные игры", value: "203" },
|
||||
{ label: "Умный ГГ", value: "303" },
|
||||
{ label: "Характерный рост", value: "332" },
|
||||
{ label: "Хикикомори", value: "167" },
|
||||
{ label: "Эволюция", value: "322" },
|
||||
{ label: "Элементы РПГ", value: "327" },
|
||||
{ label: "Эльфы", value: "217" },
|
||||
{ label: "Якудза", value: "165" },
|
||||
],
|
||||
inputType: FilterInputs.Checkbox,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,479 @@
|
||||
import { Chapter, Novel, Plugin } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export const id = "RLIB";
|
||||
export const name = "RanobeLib";
|
||||
export const site = "https://ranobelib.me";
|
||||
export const version = "1.0.0";
|
||||
export const icon = "src/ru/ranobelib/icon.png";
|
||||
|
||||
var ui: string | number | undefined;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ showLatestNovels, filters }
|
||||
) {
|
||||
let url = `${site}/manga-list?sort=`;
|
||||
url += showLatestNovels ? "last_chapter_at" : filters?.sort || "rate";
|
||||
url += "&dir=" + (filters?.order || "desc");
|
||||
|
||||
if (filters) {
|
||||
if (Array.isArray(filters.type) && filters.type.length) {
|
||||
url += filters.type.map((i) => `&types[]=${i}`).join("");
|
||||
}
|
||||
|
||||
if (Array.isArray(filters.format) && filters.format?.length) {
|
||||
url += filters.format.map((i) => `&format[include][]=${i}`).join("");
|
||||
}
|
||||
|
||||
if (Array.isArray(filters.status) && filters.status.length) {
|
||||
url += filters.status.map((i) => `&status[]=${i}`).join("");
|
||||
}
|
||||
|
||||
if (Array.isArray(filters.statuss) && filters.statuss.length) {
|
||||
url += filters.statuss.map((i) => `&manga_status[]=${i}`).join("");
|
||||
}
|
||||
|
||||
if (Array.isArray(filters.genres) && filters.genres.length) {
|
||||
url += filters.genres.map((i) => `&genres[include][]=${i}`).join("");
|
||||
}
|
||||
|
||||
if (Array.isArray(filters.tags) && filters.tags.length) {
|
||||
url += filters.tags.map((i) => `&tags[include][]=${i}`).join("");
|
||||
}
|
||||
}
|
||||
|
||||
url += "&page=" + page;
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
ui = loadedCheerio("a.header-right-menu__item")
|
||||
.attr("href")
|
||||
?.replace?.(/[^0-9]/g, "");
|
||||
|
||||
let novels: Novel.Item[] = [];
|
||||
loadedCheerio(".media-card-wrap").each(function () {
|
||||
const name = loadedCheerio(this).find(".media-card__title").text();
|
||||
const cover = loadedCheerio(this).find("a.media-card").attr("data-src");
|
||||
const url = loadedCheerio(this).find("a.media-card").attr("href");
|
||||
if (!url) return;
|
||||
novels.push({ name, cover, url });
|
||||
});
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi(novelUrl);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
let novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
};
|
||||
|
||||
novel.name = loadedCheerio(".media-name__main").text().trim();
|
||||
|
||||
novel.cover = loadedCheerio(".media-sidebar__cover img").attr("src");
|
||||
|
||||
novel.summary = loadedCheerio(".media-description__text").text().trim();
|
||||
|
||||
novel.genres = loadedCheerio('div[class="media-tags"]')
|
||||
.text()
|
||||
.trim()
|
||||
.replace(/[\n\r]+/g, ", ")
|
||||
.replace(/ /g, "");
|
||||
|
||||
loadedCheerio(
|
||||
'div[class="media-info-list paper"] > [class="media-info-list__item"]'
|
||||
).each(function () {
|
||||
let name = loadedCheerio(this)
|
||||
.find('div[class="media-info-list__title"]')
|
||||
.text();
|
||||
|
||||
if (name === "Статус перевода") {
|
||||
novel.status =
|
||||
loadedCheerio(this).find("div:nth-child(2)").text().trim() ===
|
||||
"Продолжается"
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed;
|
||||
} else if (name === "Автор") {
|
||||
novel.author = loadedCheerio(this)
|
||||
.find("div:nth-child(2)")
|
||||
.text()
|
||||
.trim();
|
||||
}
|
||||
});
|
||||
|
||||
let chapters: Chapter.Item[] = [];
|
||||
|
||||
let chaptersRaw: any = body.match(
|
||||
/window.__DATA__ = [\s\S]*?window._SITE_COLOR_/gm
|
||||
);
|
||||
|
||||
chaptersRaw = chaptersRaw?.[0]
|
||||
?.replace("window.__DATA__ = ", "")
|
||||
?.replace("window._SITE_COLOR_", "")
|
||||
?.trim()
|
||||
?.slice(0, -1);
|
||||
|
||||
let chaptersJson: responseBook = JSON.parse(chaptersRaw);
|
||||
ui = chaptersJson?.user?.id;
|
||||
|
||||
chaptersJson.chapters?.list?.forEach((chapter) =>
|
||||
chapters.push({
|
||||
name: `Том ${chapter.chapter_volume} Глава ${chapter.chapter_number} ${chapter.chapter_name}`?.trim(),
|
||||
releaseTime: dayjs(chapter.chapter_created_at).format("LLL"),
|
||||
url:
|
||||
`${site}/${chaptersJson.manga.slug}/v${chapter.chapter_volume}/c${chapter.chapter_number}?bid=` +
|
||||
(chapter?.branch_id || ""),
|
||||
})
|
||||
);
|
||||
|
||||
novel.chapters = chapters.reverse();
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi(chapterUrl + (ui ? `&ui=${ui}` : ""));
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio(".reader-container img").each(function () {
|
||||
const url =
|
||||
loadedCheerio(this).attr("data-src") || loadedCheerio(this).attr("src");
|
||||
if (!url?.startsWith("http")) {
|
||||
loadedCheerio(this).attr("src", site + url);
|
||||
} else {
|
||||
loadedCheerio(this).attr("src", url);
|
||||
}
|
||||
loadedCheerio(this).removeAttr("data-src");
|
||||
});
|
||||
|
||||
const chapterText = loadedCheerio(".reader-container").html();
|
||||
|
||||
return chapterText;
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
|
||||
const result = await fetchApi(`${site}/search?q=${searchTerm}&type=manga`);
|
||||
const body = (await result.json()) as Manga[];
|
||||
let novels: Novel.Item[] = [];
|
||||
body.forEach((novel) =>
|
||||
novels.push({
|
||||
name: novel?.rus_name || novel.name,
|
||||
cover: novel?.coverImage,
|
||||
url: novel?.href || site + "/" + novel.slug,
|
||||
})
|
||||
);
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = fetchFile;
|
||||
|
||||
export const filters = [
|
||||
{
|
||||
key: "sort",
|
||||
label: "Сортировка",
|
||||
values: [
|
||||
{ label: "Рейтинг", value: "rate" },
|
||||
{ label: "Имя", value: "name" },
|
||||
{ label: "Просмотры", value: "views" },
|
||||
{ label: "Дате добавления", value: "created_at" },
|
||||
{ label: "Дате обновления", value: "last_chapter_at" },
|
||||
{ label: "Количество глав", value: "chap_count" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "order",
|
||||
label: "Порядок",
|
||||
values: [
|
||||
{ label: "По убыванию", value: "desc" },
|
||||
{ label: "По возрастанию", value: "asc" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "type",
|
||||
label: "Тип",
|
||||
values: [
|
||||
{ label: "Авторский", value: "14" },
|
||||
{ label: "Английский", value: "13" },
|
||||
{ label: "Китай", value: "12" },
|
||||
{ label: "Корея", value: "11" },
|
||||
{ label: "Фанфик", value: "15" },
|
||||
{ label: "Япония", value: "10" },
|
||||
],
|
||||
inputType: FilterInputs.Checkbox,
|
||||
},
|
||||
{
|
||||
key: "format",
|
||||
label: "Формат выпуска",
|
||||
values: [
|
||||
{ label: "4-кома (Ёнкома)", value: "1" },
|
||||
{ label: "В цвете", value: "4" },
|
||||
{ label: "Веб", value: "6" },
|
||||
{ label: "Вебтун", value: "7" },
|
||||
{ label: "Додзинси", value: "3" },
|
||||
{ label: "Сборник", value: "2" },
|
||||
{ label: "Сингл", value: "5" },
|
||||
],
|
||||
inputType: FilterInputs.Checkbox,
|
||||
},
|
||||
{
|
||||
key: "status",
|
||||
label: "Статус перевода",
|
||||
values: [
|
||||
{ label: "Продолжается", value: "1" },
|
||||
{ label: "Завершен", value: "2" },
|
||||
{ label: "Заморожен", value: "3" },
|
||||
{ label: "Заброшен", value: "4" },
|
||||
],
|
||||
inputType: FilterInputs.Checkbox,
|
||||
},
|
||||
{
|
||||
key: "statuss",
|
||||
label: "Статус тайтла",
|
||||
values: [
|
||||
{ label: "Онгоинг", value: "1" },
|
||||
{ label: "Завершён", value: "2" },
|
||||
{ label: "Анонс", value: "3" },
|
||||
{ label: "Приостановлен", value: "4" },
|
||||
{ label: "Выпуск прекращён", value: "5" },
|
||||
],
|
||||
inputType: FilterInputs.Checkbox,
|
||||
},
|
||||
{
|
||||
key: "genres",
|
||||
label: "Жанры",
|
||||
values: [
|
||||
{ label: "Арт", value: "32" },
|
||||
{ label: "Безумие", value: "91" },
|
||||
{ label: "Боевик", value: "34" },
|
||||
{ label: "Боевые искусства", value: "35" },
|
||||
{ label: "Вампиры", value: "36" },
|
||||
{ label: "Военное", value: "89" },
|
||||
{ label: "Гарем", value: "37" },
|
||||
{ label: "Гендерная интрига", value: "38" },
|
||||
{ label: "Героическое фэнтези", value: "39" },
|
||||
{ label: "Демоны", value: "81" },
|
||||
{ label: "Детектив", value: "40" },
|
||||
{ label: "Детское", value: "88" },
|
||||
{ label: "Дзёсэй", value: "41" },
|
||||
{ label: "Драма", value: "43" },
|
||||
{ label: "Игра", value: "44" },
|
||||
{ label: "Исекай", value: "79" },
|
||||
{ label: "История", value: "45" },
|
||||
{ label: "Киберпанк", value: "46" },
|
||||
{ label: "Кодомо", value: "76" },
|
||||
{ label: "Комедия", value: "47" },
|
||||
{ label: "Космос", value: "83" },
|
||||
{ label: "Магия", value: "85" },
|
||||
{ label: "Махо-сёдзё", value: "48" },
|
||||
{ label: "Машины", value: "90" },
|
||||
{ label: "Меха", value: "49" },
|
||||
{ label: "Мистика", value: "50" },
|
||||
{ label: "Музыка", value: "80" },
|
||||
{ label: "Научная фантастика", value: "51" },
|
||||
{ label: "Омегаверс", value: "77" },
|
||||
{ label: "Пародия", value: "86" },
|
||||
{ label: "Повседневность", value: "52" },
|
||||
{ label: "Полиция", value: "82" },
|
||||
{ label: "Постапокалиптика", value: "53" },
|
||||
{ label: "Приключения", value: "54" },
|
||||
{ label: "Психология", value: "55" },
|
||||
{ label: "Романтика", value: "56" },
|
||||
{ label: "Самурайский боевик", value: "57" },
|
||||
{ label: "Сверхъестественное", value: "58" },
|
||||
{ label: "Сёдзё", value: "59" },
|
||||
{ label: "Сёдзё-ай", value: "60" },
|
||||
{ label: "Сёнэн", value: "61" },
|
||||
{ label: "Сёнэн-ай", value: "62" },
|
||||
{ label: "Спорт", value: "63" },
|
||||
{ label: "Супер сила", value: "87" },
|
||||
{ label: "Сэйнэн", value: "64" },
|
||||
{ label: "Трагедия", value: "65" },
|
||||
{ label: "Триллер", value: "66" },
|
||||
{ label: "Ужасы", value: "67" },
|
||||
{ label: "Фантастика", value: "68" },
|
||||
{ label: "Фэнтези", value: "69" },
|
||||
{ label: "Школа", value: "70" },
|
||||
{ label: "Эротика", value: "71" },
|
||||
{ label: "Этти", value: "72" },
|
||||
{ label: "Юри", value: "73" },
|
||||
{ label: "Яой", value: "74" },
|
||||
],
|
||||
inputType: FilterInputs.Checkbox,
|
||||
},
|
||||
{
|
||||
key: "tags",
|
||||
label: "Теги",
|
||||
values: [
|
||||
{ label: "Авантюристы", value: "328" },
|
||||
{ label: "Антигерой", value: "176" },
|
||||
{ label: "Бессмертные", value: "333" },
|
||||
{ label: "Боги", value: "218" },
|
||||
{ label: "Борьба за власть", value: "309" },
|
||||
{ label: "Брат и сестра", value: "360" },
|
||||
{ label: "Ведьма", value: "339" },
|
||||
{ label: "Видеоигры", value: "204" },
|
||||
{ label: "Виртуальная реальность", value: "214" },
|
||||
{ label: "Владыка демонов", value: "349" },
|
||||
{ label: "Военные", value: "198" },
|
||||
{ label: "Воспоминания из другого мира", value: "310" },
|
||||
{ label: "Выживание", value: "212" },
|
||||
{ label: "ГГ женщина", value: "294" },
|
||||
{ label: "ГГ имба", value: "292" },
|
||||
{ label: "ГГ мужчина", value: "295" },
|
||||
{ label: "ГГ не ояш", value: "325" },
|
||||
{ label: "ГГ не человек", value: "331" },
|
||||
{ label: "ГГ ояш", value: "326" },
|
||||
{ label: "Главный герой бог", value: "324" },
|
||||
{ label: "Глупый ГГ", value: "298" },
|
||||
{ label: "Горничные", value: "171" },
|
||||
{ label: "Гуро", value: "306" },
|
||||
{ label: "Гяру", value: "197" },
|
||||
{ label: "Демоны", value: "157" },
|
||||
{ label: "Драконы", value: "313" },
|
||||
{ label: "Древний мир", value: "317" },
|
||||
{ label: "Зверолюди", value: "163" },
|
||||
{ label: "Зомби", value: "155" },
|
||||
{ label: "Исторические фигуры", value: "323" },
|
||||
{ label: "Кулинария", value: "158" },
|
||||
{ label: "Культивация", value: "161" },
|
||||
{ label: "ЛГБТ", value: "344" },
|
||||
{ label: "ЛитРПГ", value: "319" },
|
||||
{ label: "Лоли", value: "206" },
|
||||
{ label: "Магия", value: "170" },
|
||||
{ label: "Машинный перевод", value: "345" },
|
||||
{ label: "Медицина", value: "159" },
|
||||
{ label: "Межгалактическая война", value: "330" },
|
||||
{ label: "Монстр Девушки", value: "207" },
|
||||
{ label: "Монстры", value: "208" },
|
||||
{ label: "Мрачный мир", value: "316" },
|
||||
{ label: "Музыка", value: "358" },
|
||||
{ label: "Музыка", value: "209" },
|
||||
{ label: "Ниндзя", value: "199" },
|
||||
{ label: "Обратный Гарем", value: "210" },
|
||||
{ label: "Офисные Работники", value: "200" },
|
||||
{ label: "Пираты", value: "341" },
|
||||
{ label: "Подземелья", value: "314" },
|
||||
{ label: "Политика", value: "311" },
|
||||
{ label: "Полиция", value: "201" },
|
||||
{ label: "Преступники / Криминал", value: "205" },
|
||||
{ label: "Призраки / Духи", value: "196" },
|
||||
{ label: "Призыватели", value: "329" },
|
||||
{ label: "Прыжки между мирами", value: "321" },
|
||||
{ label: "Путешествие в другой мир", value: "318" },
|
||||
{ label: "Путешествие во времени", value: "213" },
|
||||
{ label: "Рабы", value: "355" },
|
||||
{ label: "Ранги силы", value: "312" },
|
||||
{ label: "Реинкарнация", value: "154" },
|
||||
{ label: "Самураи", value: "202" },
|
||||
{ label: "Скрытие личности", value: "315" },
|
||||
{ label: "Средневековье", value: "174" },
|
||||
{ label: "Традиционные игры", value: "203" },
|
||||
{ label: "Умный ГГ", value: "303" },
|
||||
{ label: "Характерный рост", value: "332" },
|
||||
{ label: "Хикикомори", value: "167" },
|
||||
{ label: "Эволюция", value: "322" },
|
||||
{ label: "Элементы РПГ", value: "327" },
|
||||
{ label: "Эльфы", value: "217" },
|
||||
{ label: "Якудза", value: "165" },
|
||||
],
|
||||
inputType: FilterInputs.Checkbox,
|
||||
},
|
||||
];
|
||||
|
||||
interface responseBook {
|
||||
hasStickyPermission: boolean;
|
||||
bookmark?: null;
|
||||
auth: boolean;
|
||||
comments_version: string;
|
||||
manga: Manga;
|
||||
chapters: Chapters;
|
||||
user?: User;
|
||||
}
|
||||
interface Manga {
|
||||
id: number;
|
||||
name: string;
|
||||
rusName?: string;
|
||||
rus_name?: string;
|
||||
engName?: string;
|
||||
slug: string;
|
||||
status: number;
|
||||
chapters_count: number;
|
||||
altNames?: string[] | null;
|
||||
coverImage?: string;
|
||||
href?: string;
|
||||
}
|
||||
interface Chapters {
|
||||
list?: ListEntity[] | null;
|
||||
teams?: TeamsEntity[] | null;
|
||||
branches?: BranchesEntity[] | null;
|
||||
is_paid?: null[] | null;
|
||||
}
|
||||
interface ListEntity {
|
||||
chapter_id: number;
|
||||
chapter_slug: string;
|
||||
chapter_name: string;
|
||||
chapter_number: string;
|
||||
chapter_volume: number;
|
||||
chapter_moderated: number;
|
||||
chapter_user_id: number;
|
||||
chapter_expired_at: string;
|
||||
chapter_scanlator_id: number;
|
||||
chapter_created_at: string;
|
||||
status?: null;
|
||||
price: number;
|
||||
branch_id: number;
|
||||
username: string;
|
||||
}
|
||||
interface TeamsEntity {
|
||||
name: string;
|
||||
alt_name: string;
|
||||
cover: string;
|
||||
slug: string;
|
||||
id: number;
|
||||
branch_id: number;
|
||||
sale: number;
|
||||
href: string;
|
||||
pivot: Pivot;
|
||||
}
|
||||
interface Pivot {
|
||||
manga_id: number;
|
||||
team_id: number;
|
||||
}
|
||||
interface BranchesEntity {
|
||||
id: number;
|
||||
manga_id: number;
|
||||
name: string;
|
||||
teams?: TeamsEntity1[] | null;
|
||||
is_subscribed: boolean;
|
||||
}
|
||||
interface TeamsEntity1 {
|
||||
id: number;
|
||||
name: string;
|
||||
slug: string;
|
||||
cover: string;
|
||||
branch_id: number;
|
||||
is_active: number;
|
||||
}
|
||||
interface User {
|
||||
id: number;
|
||||
avatar: string;
|
||||
access: boolean;
|
||||
isAdmin: boolean;
|
||||
paid: boolean;
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
import cheerio from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { Novel, Plugin } from "@typings/plugin";
|
||||
|
||||
export const id = "RNRF";
|
||||
export const name = "РанобэРФ";
|
||||
export const site = "https://ранобэ.рф";
|
||||
export const version = "1.0.0";
|
||||
export const icon = "src/ru/ranoberf/icon.png";
|
||||
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ showLatestNovels, filters }
|
||||
) {
|
||||
let url = baseUrl + "/books?order=";
|
||||
url += showLatestNovels
|
||||
? "lastPublishedChapter"
|
||||
: filters?.sort || "popular";
|
||||
url += "&page=" + page;
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = cheerio.load(body);
|
||||
let json = loadedCheerio("#__NEXT_DATA__").html();
|
||||
json = JSON.parse(json);
|
||||
|
||||
let novels: Novel.Item[] = [];
|
||||
json.props.pageProps.totalData.items.forEach((novel) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
cover: novel?.verticalImage?.url
|
||||
? baseUrl + novel.verticalImage.url
|
||||
: defaultCover,
|
||||
url: baseUrl + "/" + novel.slug,
|
||||
})
|
||||
);
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi(novelUrl);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = cheerio.load(body);
|
||||
const json = loadedCheerio("#__NEXT_DATA__").html();
|
||||
const book = JSON.parse(json).props.pageProps.book;
|
||||
|
||||
let novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
name: book.title,
|
||||
cover: book?.verticalImage?.url
|
||||
? baseUrl + book.verticalImage.url
|
||||
: defaultCover,
|
||||
summary: book.description,
|
||||
author: book?.author || "",
|
||||
genres: book.genres.map((item) => item.title).join(", "),
|
||||
status: book.additionalInfo.includes("Активен")
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
|
||||
let chapters: Novel.Item[] = [];
|
||||
|
||||
book.chapters.forEach((chapter) => {
|
||||
if (!chapter.isDonate || chapter.isUserPaid) {
|
||||
chapters.push({
|
||||
name: chapter.title,
|
||||
releaseTime: dayjs(chapter.publishedAt).format("LLL"),
|
||||
url: baseUrl + chapter.url,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
novel.chapters = chapters.reverse();
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi(chapterUrl);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = cheerio.load(body);
|
||||
let json = loadedCheerio("#__NEXT_DATA__").html();
|
||||
json = JSON.parse(json);
|
||||
|
||||
let temp = cheerio.load(json.props.pageProps.chapter.content.text);
|
||||
|
||||
temp("img").each(function () {
|
||||
if (!loadedCheerio(this).attr("src")?.startsWith("http")) {
|
||||
const src = loadedCheerio(this).attr("src");
|
||||
loadedCheerio(this).attr("src", baseUrl + src);
|
||||
}
|
||||
});
|
||||
|
||||
const chapterText = temp.html();
|
||||
|
||||
return chapterText;
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
|
||||
const url = `${baseUrl}/v3/books?filter[or][0][title][like]=${searchTerm}&filter[or][1][titleEn][like]=${searchTerm}&filter[or][2][fullTitle][like]=${searchTerm}&filter[status][]=active&filter[status][]=abandoned&filter[status][]=completed&expand=verticalImage`;
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.json();
|
||||
let novels: Novel.Item[] = [];
|
||||
|
||||
body.items.forEach((novel) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
cover: novel?.verticalImage?.url
|
||||
? baseUrl + novel.verticalImage.url
|
||||
: defaultCover,
|
||||
url: baseUrl + "/" + novel.slug,
|
||||
})
|
||||
);
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const filters = [
|
||||
{
|
||||
key: "sort",
|
||||
label: "Сортировка",
|
||||
values: [
|
||||
{ label: "Рейтинг", value: "popular" },
|
||||
{ label: "Дате добавления", value: "new" },
|
||||
{ label: "Дате обновления", value: "lastPublishedChapter" },
|
||||
{ label: "Законченные", value: "completed" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,398 @@
|
||||
import { Chapter, Novel, 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";
|
||||
|
||||
export const id = "RNRF";
|
||||
export const name = "РанобэРФ";
|
||||
export const site = "https://ранобэ.рф";
|
||||
export const version = "1.0.0";
|
||||
export const icon = "src/ru/ranoberf/icon.png";
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ showLatestNovels, filters }
|
||||
) {
|
||||
let url = site + "/books?order=";
|
||||
url += showLatestNovels ? "lastPublishedChapter" : filters?.sort || "popular";
|
||||
url += "&page=" + page;
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
|
||||
let json: response = JSON.parse(jsonRaw || '{}');
|
||||
|
||||
let novels: Novel.Item[] = [];
|
||||
json.props.pageProps?.totalData?.items?.forEach((novel) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
cover: novel?.verticalImage?.url
|
||||
? site + novel.verticalImage.url
|
||||
: defaultCover,
|
||||
url: site + "/" + novel.slug,
|
||||
})
|
||||
);
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi(novelUrl);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
|
||||
const book = (JSON.parse(jsonRaw || '{}') as response).props.pageProps.book;
|
||||
|
||||
let novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
name: book?.title,
|
||||
cover: book?.verticalImage?.url
|
||||
? site + book.verticalImage.url
|
||||
: defaultCover,
|
||||
summary: book?.description,
|
||||
author: book?.author || "",
|
||||
genres: book?.genres.map((item) => item.title).join(", "),
|
||||
status: book?.additionalInfo.includes("Активен")
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
|
||||
let chapters: Chapter.Item[] = [];
|
||||
|
||||
book?.chapters?.forEach((chapter) => {
|
||||
if (!chapter?.isDonate || chapter?.isUserPaid) {
|
||||
chapters.push({
|
||||
name: chapter.title,
|
||||
releaseTime: dayjs(chapter.publishedAt).format("LLL"),
|
||||
url: site + chapter.url,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
novel.chapters = chapters.reverse();
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi(chapterUrl);
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
let jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
|
||||
let json: response = JSON.parse(jsonRaw || '{}');
|
||||
|
||||
loadedCheerio = parseHTML(json.props.pageProps?.chapter?.content?.text || "");
|
||||
loadedCheerio("img").each(function () {
|
||||
if (!loadedCheerio(this).attr("src")?.startsWith("http")) {
|
||||
const src = loadedCheerio(this).attr("src");
|
||||
loadedCheerio(this).attr("src", site + src);
|
||||
}
|
||||
});
|
||||
|
||||
const chapterText = loadedCheerio.html();
|
||||
|
||||
return chapterText;
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
|
||||
const url = `${site}/v3/books?filter[or][0][title][like]=${searchTerm}&filter[or][1][titleEn][like]=${searchTerm}&filter[or][2][fullTitle][like]=${searchTerm}&filter[status][]=active&filter[status][]=abandoned&filter[status][]=completed&expand=verticalImage`;
|
||||
const result = await fetchApi(url);
|
||||
const body = (await result.json()) as { items: Item[] };
|
||||
let novels: Novel.Item[] = [];
|
||||
|
||||
body.items.forEach((novel) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
cover: novel?.verticalImage?.url
|
||||
? site + novel.verticalImage.url
|
||||
: defaultCover,
|
||||
url: site + "/" + novel.slug,
|
||||
})
|
||||
);
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = fetchFile;
|
||||
|
||||
export const filters = [
|
||||
{
|
||||
key: "sort",
|
||||
label: "Сортировка",
|
||||
values: [
|
||||
{ label: "Рейтинг", value: "popular" },
|
||||
{ label: "Дате добавления", value: "new" },
|
||||
{ label: "Дате обновления", value: "lastPublishedChapter" },
|
||||
{ label: "Законченные", value: "completed" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
];
|
||||
|
||||
interface response {
|
||||
props: Props;
|
||||
page: string;
|
||||
query: Query;
|
||||
buildId: string;
|
||||
isFallback: boolean;
|
||||
gssp: boolean;
|
||||
appGip: boolean;
|
||||
dynamicIds?: string[];
|
||||
}
|
||||
|
||||
interface Props {
|
||||
pageProps: PageProps;
|
||||
isMobile: boolean;
|
||||
theme: string;
|
||||
__N_SSP: boolean;
|
||||
}
|
||||
|
||||
interface PageProps {
|
||||
initialReduxState: InitialReduxState;
|
||||
book?: PagePropsBook;
|
||||
totalData?: TotalData;
|
||||
layoutViewCookies?: string;
|
||||
displayBanner?: boolean;
|
||||
chapter?: PagePropsChapter;
|
||||
}
|
||||
|
||||
interface PagePropsBook {
|
||||
id: number;
|
||||
countryId: number;
|
||||
creatorId: number;
|
||||
lastChapterId: number;
|
||||
sourceId: number;
|
||||
imgVerticalId: number;
|
||||
imgHorizontalId: number;
|
||||
imgSquareId: number;
|
||||
imageVerticalId: number;
|
||||
imageHorizontalId: number;
|
||||
title: string;
|
||||
titleEn: string;
|
||||
fullTitle: string;
|
||||
fullTitleEn: string;
|
||||
description: string;
|
||||
slug: string;
|
||||
author: string;
|
||||
additionalInfo: string;
|
||||
importantInfo: string;
|
||||
status: string;
|
||||
views: number;
|
||||
likes: number;
|
||||
dislikes: number;
|
||||
intervalSec: number;
|
||||
intervalForNextFreeChapterSec: number;
|
||||
chapterCost: number;
|
||||
chapterPrice: number;
|
||||
chapterPriceCollected: number;
|
||||
maxSubscriptionChapters: number;
|
||||
maxDonateChapters: number;
|
||||
isDisplayOnMainPage: boolean;
|
||||
withoutSchedule: boolean;
|
||||
nextFreeChapterPublishedAt: null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
userRating: null;
|
||||
userBookmark: null;
|
||||
country: Country;
|
||||
genres: Genre[];
|
||||
timer: Timer;
|
||||
chapters: ChapterElement[];
|
||||
verticalImage: Image;
|
||||
horizontalImage: Image;
|
||||
squareImage: Image;
|
||||
}
|
||||
|
||||
interface ChapterElement {
|
||||
id: number;
|
||||
bookId: number;
|
||||
title: string;
|
||||
url: string;
|
||||
numberChapter: string;
|
||||
tom: number | null;
|
||||
chapterShortNumber: number | null;
|
||||
isDonate: boolean;
|
||||
isSponsored: boolean;
|
||||
isSubscription: boolean;
|
||||
isEdited: boolean;
|
||||
publishedAt: Date;
|
||||
price: number;
|
||||
views: number;
|
||||
isUserPaid?: boolean;
|
||||
}
|
||||
|
||||
interface Country {
|
||||
id: number;
|
||||
title: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
interface Genre {
|
||||
id: number;
|
||||
title: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
interface Image {
|
||||
id: number;
|
||||
alt: string;
|
||||
path: string;
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface Timer {
|
||||
value: null;
|
||||
for: null;
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface PagePropsChapter {
|
||||
id: number;
|
||||
bookId: number;
|
||||
editorId: null;
|
||||
translatorId: number;
|
||||
parserChapterId: null;
|
||||
title: string;
|
||||
slug: string;
|
||||
status: string;
|
||||
tom: null;
|
||||
views: number;
|
||||
isSponsored: boolean;
|
||||
isSubscription: boolean;
|
||||
isDonate: boolean;
|
||||
isIndexing: boolean;
|
||||
publishedAt: Date;
|
||||
changeAvailabilityAt: null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
numberChapter: string;
|
||||
price: number;
|
||||
content: Content;
|
||||
book: ChapterBook;
|
||||
userBookmark: null;
|
||||
isUserPaid: boolean;
|
||||
nextChapter: null;
|
||||
previousChapter: PreviousChapter;
|
||||
corrections: null;
|
||||
}
|
||||
|
||||
interface ChapterBook {
|
||||
id: number;
|
||||
title: string;
|
||||
titleEn: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface Content {
|
||||
id: number;
|
||||
text: string;
|
||||
symbolsCount: number;
|
||||
}
|
||||
|
||||
interface PreviousChapter {
|
||||
id: number;
|
||||
title: string;
|
||||
views: number;
|
||||
publishedAt: Date;
|
||||
url: string;
|
||||
numberChapter: string;
|
||||
}
|
||||
|
||||
interface InitialReduxState {
|
||||
data?: Data;
|
||||
auth: Auth;
|
||||
banners?: Banners;
|
||||
corrections?: Corrections;
|
||||
readerSettings?: ReaderSettings;
|
||||
}
|
||||
|
||||
interface Auth {
|
||||
isAuth: boolean;
|
||||
user: null;
|
||||
}
|
||||
|
||||
interface Banners {
|
||||
displaySale: boolean;
|
||||
}
|
||||
|
||||
interface Corrections {
|
||||
items: null;
|
||||
data: null;
|
||||
}
|
||||
|
||||
interface Data {
|
||||
bookPage: BookPage | null;
|
||||
chapterPage: ChapterPage | null;
|
||||
}
|
||||
|
||||
interface BookPage {
|
||||
bookId: number;
|
||||
bookmark: null;
|
||||
}
|
||||
|
||||
interface ChapterPage {
|
||||
chapterId: number;
|
||||
bookId: number;
|
||||
bookmark: null;
|
||||
}
|
||||
|
||||
interface ReaderSettings {
|
||||
fontSize: number;
|
||||
lineHeight: number;
|
||||
textIndent: number;
|
||||
sidebarHide: number;
|
||||
textAlign: string;
|
||||
}
|
||||
|
||||
interface TotalData {
|
||||
items: Item[];
|
||||
_links: Links;
|
||||
pagesData: PagesData;
|
||||
}
|
||||
|
||||
interface Links {
|
||||
self: First;
|
||||
first: First;
|
||||
last: First;
|
||||
next: First;
|
||||
}
|
||||
|
||||
interface First {
|
||||
href: string;
|
||||
}
|
||||
|
||||
interface Item {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
slug: string;
|
||||
url: string;
|
||||
status: string;
|
||||
dislikes: number;
|
||||
likes: number;
|
||||
userRating: null;
|
||||
lastChapter: ChapterElement;
|
||||
verticalImage: Image;
|
||||
horizontalImage: Image;
|
||||
squareImage: Image;
|
||||
}
|
||||
|
||||
interface PagesData {
|
||||
totalCount: number;
|
||||
pageCount: number;
|
||||
currentPage: number;
|
||||
perPage: number;
|
||||
}
|
||||
|
||||
interface Query {
|
||||
book?: string;
|
||||
chapter?: string;
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from 'dayjs';
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
// import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
// import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
|
||||
// import { showToast } from "@libs/showToast";
|
||||
import { Filter, FilterInputs } from "@libs/filterInputs";
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
|
||||
|
||||
export const id = "red.novel";
|
||||
export const name = "Север и Юг";
|
||||
export const icon = "";
|
||||
export const version = "1.0.0";
|
||||
export const site = "https://red.novel.tl";
|
||||
export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Героиня красавица","value":81},{"label":"Герой влюбляется первым","value":561},{"label":"Главный герой — мужчина","value":419},{"label":"Дружба","value":293},{"label":"Жизнь в одиночку","value":396},{"label":"Сложные семейные отношения","value":149}],"inputType":FilterInputs.Checkbox}];
|
||||
|
||||
const baseUrl = site;
|
||||
const domain = baseUrl.split('//')[1];
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ filters }
|
||||
) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}));
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'Book',
|
||||
query:
|
||||
'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}',
|
||||
variables: {
|
||||
hostname: domain,
|
||||
project: novelUrl,
|
||||
url: novelUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
name: json.data.project.title,
|
||||
cover: json.data.project.covers[0]?.url
|
||||
? baseUrl + json.data.project.covers[0].url
|
||||
: defaultCover,
|
||||
summary: parseHTML(json.data.project.annotation?.text).text(),
|
||||
author:
|
||||
json.data.project.persons[0].name.firstName +
|
||||
' ' +
|
||||
json.data.project.persons[0].name.lastName,
|
||||
status:
|
||||
json.data.project.translationStatus === 'active'
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
|
||||
let genres = []
|
||||
.concat(json.data.project.tags, json.data.project.genres)
|
||||
.map((item: any) => item?.nameRu || item?.nameEng)
|
||||
.filter(item => item);
|
||||
|
||||
if (genres.length > 0) {
|
||||
novel.genres = genres.join(',');
|
||||
}
|
||||
|
||||
let novelChapters: Chapter.Item[] = [];
|
||||
json.data.project.subprojects.content.forEach((work: any) =>
|
||||
work.volumes.content.forEach((volume: any) =>
|
||||
volume.chapters.forEach(
|
||||
(chapter: any) =>
|
||||
chapter?.published &&
|
||||
novelChapters.push({
|
||||
name: volume.shortName + ' ' + chapter.title,
|
||||
url: novelUrl + '/' + volume.url + '/' + chapter.url,
|
||||
releaseTime: dayjs(chapter.publishDate).format('LLL'),
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
novel.chapters = novelChapters;
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'EReaderData',
|
||||
query:
|
||||
'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}',
|
||||
variables: {
|
||||
chapterSelector: {
|
||||
fullUrl: chapterUrl,
|
||||
},
|
||||
url: chapterUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
|
||||
return parseHTML(json.data.chapter.text.text).html();
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: {
|
||||
query: searchTerm,
|
||||
},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page: 1,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = [];
|
||||
json?.data?.projects?.content?.forEach((novel: any) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}),
|
||||
);
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url, {});
|
||||
};
|
||||
|
||||
@@ -1,932 +0,0 @@
|
||||
import dayjs from "dayjs";
|
||||
import { fetchApi } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { Chapter, Novel, Plugin } from "@typings/plugin";
|
||||
|
||||
export const id = "ReN";
|
||||
export const name = "Renovels";
|
||||
export const site = "https://renovels.org";
|
||||
export const version = "1.0.0";
|
||||
export const icon = "src/ru/renovels/icon.png";
|
||||
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ showLatestNovels, filters }
|
||||
) {
|
||||
let url = baseUrl + "/api/search/catalog/?count=30&ordering=";
|
||||
url += filters?.order ? filters?.order?.replace("+", "") : "-";
|
||||
url += showLatestNovels ? "chapter_date" : filters?.sort || "rating";
|
||||
|
||||
if (filters?.type?.length) {
|
||||
url += filters.type.map((i) => `&types=${i}`).join("");
|
||||
}
|
||||
|
||||
if (filters?.statuss?.length) {
|
||||
url += filters?.statuss.map((i) => `&status=${i}`).join("");
|
||||
}
|
||||
|
||||
if (filters?.genres?.length) {
|
||||
url += filters.genres.map((i) => `&genres=${i}`).join("");
|
||||
}
|
||||
|
||||
if (filters?.categories?.length) {
|
||||
url += filters.categories.map((i) => `&categories=${i}`).join("");
|
||||
}
|
||||
|
||||
url += "&page=" + page;
|
||||
|
||||
const result = await fetchApi(url);
|
||||
let body = await result.json();
|
||||
|
||||
let novels: Novel.Item[] = [];
|
||||
|
||||
body.content.forEach((novel) =>
|
||||
novels.push({
|
||||
name: novel.rus_name,
|
||||
cover:
|
||||
baseUrl + (novel.img?.high || novel.img?.mid || novel.img.low),
|
||||
url: baseUrl + "/novel/" + novel.dir,
|
||||
})
|
||||
);
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
let novelID = novelUrl.split("/")[4];
|
||||
const result = await fetchApi(baseUrl + "/api/titles/" + novelID);
|
||||
let body = await result.json();
|
||||
|
||||
let novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
name: body.content.rus_name,
|
||||
summary: body.content.description,
|
||||
cover:
|
||||
baseUrl +
|
||||
(body.content.img?.high ||
|
||||
body.content.img?.mid ||
|
||||
body.content.img.low),
|
||||
status:
|
||||
body.content.status.name === "Продолжается"
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
|
||||
let tags = []
|
||||
.concat(body.content.genres, body.content.categories, [])
|
||||
.map((item) => item.name);
|
||||
|
||||
if (tags.length > 0) {
|
||||
novel.genres = tags.join(", ");
|
||||
}
|
||||
|
||||
let all = (body.content.count_chapters / 100 + 1) ^ 0;
|
||||
let chapters: Chapter.Item[] = [];
|
||||
|
||||
for (let i = 0; i < all; i++) {
|
||||
let chapterResult = await fetchApi(
|
||||
baseUrl +
|
||||
"/api/titles/chapters/?branch_id=" +
|
||||
body.content.branches[0].id +
|
||||
"&count=100&page=" +
|
||||
(i + 1)
|
||||
);
|
||||
let volumes = await chapterResult.json();
|
||||
|
||||
volumes.content.forEach((chapter) => {
|
||||
if (!chapter.is_paid || chapter.is_bought) {
|
||||
chapters.push({
|
||||
name: `Том ${chapter.tome} Глава ${chapter.chapter} ${chapter.name}`?.trim(),
|
||||
releaseTime: dayjs(chapter.upload_date).format("LLL"),
|
||||
url: `${baseUrl}/novel/${novelID}/${chapter.id}/`,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
novel.chapters = chapters.reverse();
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
let url = baseUrl + "/api/titles/chapters/" + chapterUrl.split("/")[5];
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.json();
|
||||
|
||||
return body.content.content;
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
|
||||
const url = `${baseUrl}/api/search/?query=${searchTerm}&count=100&field=titles`;
|
||||
const result = await fetchApi(url);
|
||||
let body = await result.json();
|
||||
let novels: Novel.Item[] = [];
|
||||
|
||||
body.content.forEach((novel) =>
|
||||
novels.push({
|
||||
name: novel.rus_name,
|
||||
cover:
|
||||
baseUrl + (novel.img?.high || novel.img?.mid || novel.img.low),
|
||||
url: baseUrl + "/novel/" + novel.dir,
|
||||
})
|
||||
);
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const filters = [
|
||||
{
|
||||
key: "sort",
|
||||
label: "Сортировка",
|
||||
values: [
|
||||
{ label: "Рейтинг", value: "rating" },
|
||||
{ label: "Просмотры", value: "views" },
|
||||
{ label: "Лайкам", value: "votes" },
|
||||
{ label: "Дате добавления", value: "id" },
|
||||
{ label: "Дате обновления", value: "chapter_date" },
|
||||
{ label: "Количество глав", value: "count_chapters" },
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "order",
|
||||
label: "Порядок",
|
||||
values: [
|
||||
{ label: "По убыванию", value: "-" }, // desc
|
||||
{ label: "По возрастанию", value: "+" }, // asc
|
||||
],
|
||||
inputType: FilterInputs.Picker,
|
||||
},
|
||||
{
|
||||
key: "type",
|
||||
label: "Тип",
|
||||
values: [
|
||||
{ label: "Авторское", value: "1" },
|
||||
{ label: "Другое", value: "7" },
|
||||
{ label: "Запад", value: "5" },
|
||||
{ label: "Китай", value: "4" },
|
||||
{ label: "Корея", value: "3" },
|
||||
{ label: "Фанфики", value: "6" },
|
||||
{ label: "Япония", value: "2" },
|
||||
],
|
||||
inputType: FilterInputs.Checkbox,
|
||||
},
|
||||
{
|
||||
key: "statuss",
|
||||
label: "Статус тайтла",
|
||||
values: [
|
||||
{ label: "Закончен", value: "0" },
|
||||
{ label: "Продолжается", value: "1" },
|
||||
{ label: "Заморожен", value: "2" },
|
||||
{ label: "Нет переводчика", value: "3" },
|
||||
{ label: "Анонс", value: "4" },
|
||||
{ label: "Лицензировано", value: "5" },
|
||||
],
|
||||
inputType: FilterInputs.Checkbox,
|
||||
},
|
||||
{
|
||||
key: "genres",
|
||||
label: "Жанры",
|
||||
values: [
|
||||
{ label: "Боевик", value: "112" },
|
||||
{ label: "Война", value: "123" },
|
||||
{ label: "Детектив", value: "114" },
|
||||
{ label: "Драма", value: "125" },
|
||||
{ label: "Историческая проза", value: "115" },
|
||||
{ label: "ЛитРПГ", value: "109" },
|
||||
{ label: "Любовные романы", value: "116" },
|
||||
{ label: "Мистика", value: "103" },
|
||||
{ label: "Научная фантастика", value: "121" },
|
||||
{ label: "Повседневность", value: "128" },
|
||||
{ label: "Подростковая проза", value: "113" },
|
||||
{ label: "Политический роман", value: "119" },
|
||||
{ label: "Попаданцы", value: "108" },
|
||||
{ label: "Поэзия", value: "105" },
|
||||
{ label: "Разное", value: "111" },
|
||||
{ label: "РеалРПГ", value: "117" },
|
||||
{ label: "Романтика", value: "126" },
|
||||
{ label: "Современная проза", value: "102" },
|
||||
{ label: "Современная фантастика", value: "127" },
|
||||
{ label: "Спорт", value: "122" },
|
||||
{ label: "Трагедия", value: "129" },
|
||||
{ label: "Триллер", value: "110" },
|
||||
{ label: "Триллер и саспенс", value: "124" },
|
||||
{ label: "Ужасы", value: "106" },
|
||||
{ label: "Фантастика", value: "101" },
|
||||
{ label: "Фанфик", value: "107" },
|
||||
{ label: "Фьюжн роман", value: "120" },
|
||||
{ label: "Фэнтези", value: "100" },
|
||||
{ label: "Эротика", value: "118" },
|
||||
{ label: "Юмор", value: "104" },
|
||||
],
|
||||
inputType: FilterInputs.Checkbox,
|
||||
},
|
||||
{
|
||||
key: "categories",
|
||||
label: "Категории",
|
||||
values: [
|
||||
{ label: "[Награжденная работа]", value: "648" },
|
||||
{ label: "18+", value: "423" },
|
||||
{ label: "Абьюзеры", value: "663" },
|
||||
{ label: "Авантюристы", value: "436" },
|
||||
{ label: "Автоматоны", value: "593" },
|
||||
{ label: "Агрессивные персонажи", value: "502" },
|
||||
{ label: "Ад", value: "569" },
|
||||
{ label: "Адаптация в радиопостановку", value: "581" },
|
||||
{ label: "Академия", value: "153" },
|
||||
{ label: "Актеры озвучки", value: "627" },
|
||||
{ label: "Активный главный герой", value: "251" },
|
||||
{ label: "Алхимия", value: "233" },
|
||||
{ label: "Альтернативная история", value: "147" },
|
||||
{ label: "Альтернативный мир", value: "156" },
|
||||
{ label: "Амнезия/Потеря памяти", value: "344" },
|
||||
{ label: "Анабиоз", value: "685" },
|
||||
{ label: "Ангелы", value: "183" },
|
||||
{ label: "Андрогинные персонажи", value: "321" },
|
||||
{ label: "Андроиды", value: "203" },
|
||||
{ label: "Анти-магия", value: "533" },
|
||||
{ label: "Антигерой", value: "432" },
|
||||
{ label: "Антикварный магазин", value: "623" },
|
||||
{ label: "Антисоциальный главный герой", value: "613" },
|
||||
{ label: "Антиутопия", value: "142" },
|
||||
{ label: "Апатичный протагонист", value: "157" },
|
||||
{ label: "Апокалипсис", value: "402" },
|
||||
{ label: "Аранжированный брак", value: "375" },
|
||||
{ label: "Армия", value: "639" },
|
||||
{ label: "Артефакты", value: "234" },
|
||||
{ label: "Артисты", value: "523" },
|
||||
{ label: "Банды", value: "630" },
|
||||
{ label: "БДСМ", value: "699" },
|
||||
{ label: "Бедный главный герой", value: "398" },
|
||||
{ label: "Безжалостный главный герой", value: "261" },
|
||||
{ label: "Беззаботный главный герой", value: "438" },
|
||||
{ label: "Безусловная любовь", value: "679" },
|
||||
{ label: "Беременность", value: "250" },
|
||||
{ label: "Бесполый главный герой", value: "325" },
|
||||
{ label: "Бессмертные", value: "368" },
|
||||
{ label: "Бесстрашный протагонист", value: "656" },
|
||||
{ label: "Бесстыдный главный герой", value: "352" },
|
||||
{ label: "Бесчестный главный герой", value: "716" },
|
||||
{ label: "Библиотека", value: "428" },
|
||||
{ label: "Бизнес-литература", value: "121" },
|
||||
{ label: "Бизнесмен", value: "769" },
|
||||
{ label: "Биочип", value: "238" },
|
||||
{ label: "Бисексуальный главный герой", value: "773" },
|
||||
{ label: "Близнецы", value: "263" },
|
||||
{ label: "Боги", value: "317" },
|
||||
{ label: "Богини", value: "439" },
|
||||
{ label: "Боевая академия", value: "450" },
|
||||
{ label: "Боевая фантастика", value: "149" },
|
||||
{ label: "Боевое фэнтези", value: "104" },
|
||||
{ label: "Боевые духи", value: "433" },
|
||||
{ label: "Боевые соревнования", value: "493" },
|
||||
{ label: "Божественная защита", value: "421" },
|
||||
{ label: "Божественные силы", value: "327" },
|
||||
{ label: "Борьба за власть", value: "599" },
|
||||
{ label: "Брак", value: "446" },
|
||||
{ label: "Брак по расчету", value: "188" },
|
||||
{ label: "Братский комплекс", value: "159" },
|
||||
{ label: "Братство", value: "487" },
|
||||
{ label: "Братья и сестры", value: "577" },
|
||||
{ label: "Буддизм", value: "745" },
|
||||
{ label: "Быстрая культивация", value: "366" },
|
||||
{ label: "Быстрообучаемый", value: "324" },
|
||||
{ label: "Валькирии", value: "692" },
|
||||
{ label: "Вампиры", value: "360" },
|
||||
{ label: "Ваншот", value: "702" },
|
||||
{ label: "Ведьмы", value: "281" },
|
||||
{ label: "Вежливый главный герой", value: "379" },
|
||||
{ label: "Верные подчиненные", value: "328" },
|
||||
{ label: "Взрослый главный герой", value: "295" },
|
||||
{ label: "Видит то, чего не видят другие", value: "671" },
|
||||
{ label: "Виртуальная реальность", value: "401" },
|
||||
{ label: "Владелец магазина", value: "681" },
|
||||
{ label: "Внезапная сила", value: "455" },
|
||||
{ label: "Внезапное богатство", value: "765" },
|
||||
{ label: "Внешний вид отличается от факт", value: "419" },
|
||||
{ label: "Военные Летописи", value: "743" },
|
||||
{ label: "Возвращение из другого мира", value: "696" },
|
||||
{ label: "Войны", value: "181" },
|
||||
{ label: "Вокалоид", value: "701" },
|
||||
{ label: "Волшебники/Волшебницы", value: "539" },
|
||||
{ label: "Волшебные звери", value: "308" },
|
||||
{ label: "Воображаемый друг", value: "651" },
|
||||
{ label: "Воры", value: "414" },
|
||||
{ label: "Воскрешение", value: "199" },
|
||||
{ label: "Враги становятся возлюбленными", value: "498" },
|
||||
{ label: "Враги становятся союзниками", value: "562" },
|
||||
{ label: "Врата в другой мир", value: "609" },
|
||||
{ label: "Врачи", value: "376" },
|
||||
{ label: "Временной парадокс", value: "275" },
|
||||
{ label: "Всемогущий главный герой", value: "169" },
|
||||
{ label: "Вторжение на землю", value: "198" },
|
||||
{ label: "Второй шанс", value: "231" },
|
||||
{ label: "Вуайеризм", value: "790" },
|
||||
{ label: "Выживание", value: "380" },
|
||||
{ label: "Высокомерные персонажи", value: "361" },
|
||||
{ label: "Гадание", value: "595" },
|
||||
{ label: "Гарем рабов", value: "777" },
|
||||
{ label: "Гг - женщина", value: "186" },
|
||||
{ label: "Гг - мужчина", value: "166" },
|
||||
{ label: "Гг силен с самого начала", value: "171" },
|
||||
{ label: "Геймеры", value: "392" },
|
||||
{ label: "Генералы", value: "326" },
|
||||
{ label: "Генетические модификации", value: "657" },
|
||||
{ label: "Гениальный главный герой", value: "617" },
|
||||
{ label: "Герои", value: "285" },
|
||||
{ label: "Героиня — сорванец", value: "583" },
|
||||
{ label: "Героическая фантастика", value: "140" },
|
||||
{ label: "Героическое фэнтези", value: "107" },
|
||||
{ label: "Герой влюбляется первым", value: "187" },
|
||||
{ label: "Гетерохромия", value: "570" },
|
||||
{ label: "Гильдии", value: "411" },
|
||||
{ label: "Гипнотизм", value: "755" },
|
||||
{ label: "Главный герой — бог", value: "547" },
|
||||
{ label: "Главный герой — гуманоид", value: "636" },
|
||||
{ label: "Главный герой — наполовину чел", value: "445" },
|
||||
{ label: "Главный герой — отец", value: "792" },
|
||||
{ label: "Главный герой — раб", value: "780" },
|
||||
{ label: "Главный герой — ребенок", value: "488" },
|
||||
{ label: "Главный герой — рубака", value: "476" },
|
||||
{ label: "Главный герой влюбляется первы", value: "683" },
|
||||
{ label: "Главный герой играет роль", value: "472" },
|
||||
{ label: "Главный герой носит очки", value: "672" },
|
||||
{ label: "Главный герой пацифист", value: "698" },
|
||||
{ label: "Гладиаторы", value: "603" },
|
||||
{ label: "Глуповатый главный герой", value: "385" },
|
||||
{ label: "Гоблины", value: "586" },
|
||||
{ label: "Големы", value: "620" },
|
||||
{ label: "Гомункул", value: "789" },
|
||||
{ label: "Горничные", value: "459" },
|
||||
{ label: "Городское фэнтези", value: "105" },
|
||||
{ label: "Госпиталь", value: "800" },
|
||||
{ label: "Готовка", value: "302" },
|
||||
{ label: "Гриндинг", value: "393" },
|
||||
{ label: "Дао Компаньон", value: "462" },
|
||||
{ label: "Даосизм", value: "760" },
|
||||
{ label: "Дварфы", value: "323" },
|
||||
{ label: "Двойная личность", value: "642" },
|
||||
{ label: "Двойник", value: "601" },
|
||||
{ label: "Дворецкий", value: "660" },
|
||||
{ label: "Дворяне", value: "168" },
|
||||
{ label: "Дворянство/Аристократия", value: "437" },
|
||||
{ label: "Девушки-монстры", value: "669" },
|
||||
{ label: "Демоническая техника культивац", value: "722" },
|
||||
{ label: "Демоны", value: "152" },
|
||||
{ label: "Денежный долг", value: "783" },
|
||||
{ label: "Депрессия", value: "555" },
|
||||
{ label: "Детектив", value: "116" },
|
||||
{ label: "Детективы", value: "612" },
|
||||
{ label: "Детская литература", value: "123" },
|
||||
{ label: "Дискриминация", value: "162" },
|
||||
{ label: "Добыча денег в приоритете", value: "396" },
|
||||
{ label: "Документальная проза", value: "122" },
|
||||
{ label: "Долгая разлука", value: "307" },
|
||||
{ label: "Домашние дела", value: "290" },
|
||||
{ label: "Домогательство", value: "470" },
|
||||
{ label: "Драконы", value: "303" },
|
||||
{ label: "Драконьи всадники", value: "607" },
|
||||
{ label: "Древние времена", value: "221" },
|
||||
{ label: "Древний Китай", value: "374" },
|
||||
{ label: "Дружба", value: "217" },
|
||||
{ label: "Друзья детства", value: "282" },
|
||||
{ label: "Друзья становятся врагами", value: "567" },
|
||||
{ label: "Друиды", value: "497" },
|
||||
{ label: "Дух лисы", value: "784" },
|
||||
{ label: "Духи/призраки", value: "172" },
|
||||
{ label: "Духовный советник", value: "435" },
|
||||
{ label: "Душевность", value: "633" },
|
||||
{ label: "Души", value: "254" },
|
||||
{ label: "Европейская атмосфера", value: "521" },
|
||||
{ label: "Ёкаи", value: "575" },
|
||||
{ label: "Есть аниме-адаптация", value: "154" },
|
||||
{ label: "Есть видеоигра по мотивам", value: "552" },
|
||||
{ label: "Есть манга", value: "155" },
|
||||
{ label: "Есть манхва-адаптация", value: "517" },
|
||||
{ label: "Есть маньхуа-адаптация", value: "388" },
|
||||
{ label: "Есть сериал-адаптация", value: "492" },
|
||||
{ label: "Есть фильм", value: "173" },
|
||||
{ label: "Женища-наставник", value: "505" },
|
||||
{ label: "Жесткая, двуличная личность", value: "346" },
|
||||
{ label: "Жестокие персонажи", value: "503" },
|
||||
{ label: "Жестокое обращение с ребенком", value: "654" },
|
||||
{ label: "Жестокость", value: "245" },
|
||||
{ label: "Животноводство", value: "754" },
|
||||
{ label: "Животные черты", value: "528" },
|
||||
{ label: "Жизнь в квартире", value: "606" },
|
||||
{ label: "Жрицы", value: "615" },
|
||||
{ label: "Заботливый главный герой", value: "288" },
|
||||
{ label: "Забывчивый главный герой", value: "628" },
|
||||
{ label: "Заговоры", value: "289" },
|
||||
{ label: "Закалка тела", value: "362" },
|
||||
{ label: "Законники", value: "756" },
|
||||
{ label: "Замкнутый главный герой", value: "589" },
|
||||
{ label: "Запечатанная сила", value: "430" },
|
||||
{ label: "Застенчивые персонажи", value: "508" },
|
||||
{ label: "Звери", value: "237" },
|
||||
{ label: "Звери-компаньоны", value: "301" },
|
||||
{ label: "Злой протагонист", value: "243" },
|
||||
{ label: "Злые боги", value: "504" },
|
||||
{ label: "Злые организации", value: "563" },
|
||||
{ label: "Злые религии", value: "734" },
|
||||
{ label: "Знаменитости", value: "473" },
|
||||
{ label: "Знаменитый главный герой", value: "531" },
|
||||
{ label: "Знания современного мира", value: "297" },
|
||||
{ label: "Зомби", value: "409" },
|
||||
{ label: "Игра на выживание", value: "274" },
|
||||
{ label: "Игривый протагонист", value: "717" },
|
||||
{ label: "Игровая система рейтинга", value: "391" },
|
||||
{ label: "Игровые элементы", value: "266" },
|
||||
{ label: "Игрушки (18+)", value: "676" },
|
||||
{ label: "Из грязи в князи", value: "416" },
|
||||
{ label: "Из женщины в мужчину", value: "709" },
|
||||
{ label: "Из мужчины в женщину", value: "715" },
|
||||
{ label: "Из полного в худого", value: "767" },
|
||||
{ label: "Из слабого в сильного", value: "202" },
|
||||
{ label: "Извращенный главный герой", value: "434" },
|
||||
{ label: "Изгои", value: "534" },
|
||||
{ label: "Изменение расы", value: "664" },
|
||||
{ label: "Изменения внешнего вида", value: "300" },
|
||||
{ label: "Изменения личности", value: "219" },
|
||||
{ label: "Изнасилование", value: "229" },
|
||||
{ label: "Изображения жестокости", value: "355" },
|
||||
{ label: "ИИ", value: "235" },
|
||||
{ label: "Империи", value: "748" },
|
||||
{ label: "Инвалидность", value: "684" },
|
||||
{ label: "Индустриализация", value: "292" },
|
||||
{ label: "Инженер", value: "163" },
|
||||
{ label: "Инцест", value: "525" },
|
||||
{ label: "Искусственный интеллект", value: "236" },
|
||||
{ label: "Исследования", value: "670" },
|
||||
{ label: "Историческая проза", value: "119" },
|
||||
{ label: "Исторические приключения", value: "120" },
|
||||
{ label: "Исторический детектив", value: "117" },
|
||||
{ label: "Исторический любовный роман", value: "138" },
|
||||
{ label: "Историческое фэнтези", value: "110" },
|
||||
{ label: "Каннибализм", value: "456" },
|
||||
{ label: "Карточные игры", value: "682" },
|
||||
{ label: "Киберпанк", value: "141" },
|
||||
{ label: "Киберспорт", value: "686" },
|
||||
{ label: "Кланы", value: "796" },
|
||||
{ label: "Класс безработного", value: "735" },
|
||||
{ label: "Клоны", value: "447" },
|
||||
{ label: "Клубы", value: "216" },
|
||||
{ label: "Книги", value: "427" },
|
||||
{ label: "Книги навыков", value: "400" },
|
||||
{ label: "Книжный червь", value: "519" },
|
||||
{ label: "Коварство", value: "230" },
|
||||
{ label: "Коллеги", value: "704" },
|
||||
{ label: "Колледж/Университет", value: "726" },
|
||||
{ label: "Кома", value: "776" },
|
||||
{ label: "Командная работа", value: "496" },
|
||||
{ label: "Комедийный оттенок", value: "582" },
|
||||
{ label: "Комплекс неполноценности", value: "550" },
|
||||
{ label: "Комплекс семейных отношений", value: "223" },
|
||||
{ label: "Конкуренция", value: "746" },
|
||||
{ label: "Контроль разума/сознания", value: "357" },
|
||||
{ label: "Копейщик", value: "425" },
|
||||
{ label: "Королевская битва", value: "797" },
|
||||
{ label: "Королевская власть", value: "178" },
|
||||
{ label: "Королевства", value: "259" },
|
||||
{ label: "Короткий любовный роман", value: "136" },
|
||||
{ label: "Коррупция", value: "457" },
|
||||
{ label: "Космическая фантастика", value: "146" },
|
||||
{ label: "Космические войны", value: "697" },
|
||||
{ label: "Красивый герой", value: "226" },
|
||||
{ label: "Крафт", value: "377" },
|
||||
{ label: "Кризис личности", value: "640" },
|
||||
{ label: "Кругосветное путешествие", value: "353" },
|
||||
{ label: "Кудере", value: "506" },
|
||||
{ label: "Кузены", value: "718" },
|
||||
{ label: "Кузнец", value: "518" },
|
||||
{ label: "Кукловоды", value: "501" },
|
||||
{ label: "Куклы/марионетки", value: "624" },
|
||||
{ label: "Культивация", value: "241" },
|
||||
{ label: "Куннилингус", value: "667" },
|
||||
{ label: "Легенды", value: "500" },
|
||||
{ label: "Легкая жизнь", value: "644" },
|
||||
{ label: "Ленивый главный герой", value: "621" },
|
||||
{ label: "Лидерство", value: "494" },
|
||||
{ label: "Лоли", value: "213" },
|
||||
{ label: "Лотерея", value: "477" },
|
||||
{ label: "Любовное фэнтези", value: "135" },
|
||||
{ label: "Любовный треугольник", value: "218" },
|
||||
{ label: "Любовь детства", value: "560" },
|
||||
{ label: "Любовь с первого взгляда", value: "737" },
|
||||
{ label: "Магические надписи", value: "453" },
|
||||
{ label: "Магические печати", value: "369" },
|
||||
{ label: "Магические технологии", value: "440" },
|
||||
{ label: "Магия", value: "165" },
|
||||
{ label: "Магия призыва", value: "418" },
|
||||
{ label: "Мазохистские персонажи", value: "688" },
|
||||
{ label: "Манипулятивные персонажи", value: "249" },
|
||||
{ label: "Мания", value: "212" },
|
||||
{ label: "Мастер на все руки", value: "466" },
|
||||
{ label: "Мастурбация", value: "788" },
|
||||
{ label: "Махо-сёдзё", value: "556" },
|
||||
{ label: "Медицинские знания", value: "507" },
|
||||
{ label: "Медленная романтическая линия", value: "232" },
|
||||
{ label: "Медленное развитие на старте", value: "694" },
|
||||
{ label: "Межпространственные путешестви", value: "404" },
|
||||
{ label: "Менеджмент", value: "294" },
|
||||
{ label: "Мертвый главный герой", value: "761" },
|
||||
{ label: "Месть", value: "209" },
|
||||
{ label: "Метаморфы", value: "335" },
|
||||
{ label: "Меч и магия", value: "180" },
|
||||
{ label: "Мечник", value: "647" },
|
||||
{ label: "Мечты", value: "738" },
|
||||
{ label: "Милая история", value: "725" },
|
||||
{ label: "Милое дитя", value: "637" },
|
||||
{ label: "Милый главный герой", value: "714" },
|
||||
{ label: "Мировое дерево", value: "463" },
|
||||
{ label: "Мистика", value: "151" },
|
||||
{ label: "Мистический ореол вокруг семьи", value: "483" },
|
||||
{ label: "Мифические звери", value: "491" },
|
||||
{ label: "Мифология", value: "530" },
|
||||
{ label: "Младшие братья", value: "785" },
|
||||
{ label: "Младшие сестры", value: "527" },
|
||||
{ label: "ММОРПГ (ЛитРПГ)", value: "395" },
|
||||
{ label: "Множество перемещенных людей", value: "549" },
|
||||
{ label: "Множество реальностей", value: "370" },
|
||||
{ label: "Множество реинкарнированных лю", value: "330" },
|
||||
{ label: "Модели", value: "678" },
|
||||
{ label: "Молчаливый персонаж", value: "721" },
|
||||
{ label: "Монстры", value: "192" },
|
||||
{ label: "Мужская гей-пара", value: "706" },
|
||||
{ label: "Мужчина-яндере", value: "267" },
|
||||
{ label: "Музыка", value: "635" },
|
||||
{ label: "Музыкальные группы", value: "634" },
|
||||
{ label: "Мутации", value: "693" },
|
||||
{ label: "Мутированные существа", value: "405" },
|
||||
{ label: "Навык кражи", value: "662" },
|
||||
{ label: "Навязчивая любовь", value: "206" },
|
||||
{ label: "Наемники", value: "412" },
|
||||
{ label: "Назойливый возлюбленный", value: "561" },
|
||||
{ label: "Наивный главный герой", value: "189" },
|
||||
{ label: "Наркотики", value: "689" },
|
||||
{ label: "Нарциссический главный герой", value: "532" },
|
||||
{ label: "Насилие сексуального характера", value: "774" },
|
||||
{ label: "Наследование", value: "452" },
|
||||
{ label: "Научная фантастика", value: "144" },
|
||||
{ label: "Национализм", value: "406" },
|
||||
{ label: "Не блещущий внешне главный гер", value: "415" },
|
||||
{ label: "Не родные братья и сестры", value: "526" },
|
||||
{ label: "Небеса", value: "568" },
|
||||
{ label: "Небесное испытание", value: "367" },
|
||||
{ label: "Негуманоидный главный герой", value: "659" },
|
||||
{ label: "Недоверчивый главный герой", value: "546" },
|
||||
{ label: "Недооцененный главный герой", value: "258" },
|
||||
{ label: "Недоразумения", value: "309" },
|
||||
{ label: "Неизлечимая болезнь", value: "196" },
|
||||
{ label: "Некромант", value: "397" },
|
||||
{ label: "Нелинейная история", value: "269" },
|
||||
{ label: "Ненавистный главный герой", value: "548" },
|
||||
{ label: "Ненадежный рассказчик", value: "277" },
|
||||
{ label: "Нерезиденты", value: "772" },
|
||||
{ label: "Нерешительный главный герой", value: "744" },
|
||||
{ label: "Несерьезный главный герой", value: "384" },
|
||||
{ label: "Несколько временных линий", value: "576" },
|
||||
{ label: "Несколько гг", value: "268" },
|
||||
{ label: "Несколько главных героев", value: "537" },
|
||||
{ label: "Несколько идентичностей", value: "652" },
|
||||
{ label: "Несколько личностей", value: "536" },
|
||||
{ label: "Нетораре", value: "732" },
|
||||
{ label: "Нетори", value: "514" },
|
||||
{ label: "Неудачливый главный герой", value: "618" },
|
||||
{ label: "Ниндзя", value: "441" },
|
||||
{ label: "Новелла", value: "103" },
|
||||
{ label: "Обещание из детства", value: "742" },
|
||||
{ label: "Обманщик", value: "485" },
|
||||
{ label: "Обмен телами", value: "214" },
|
||||
{ label: "Обнаженка", value: "490" },
|
||||
{ label: "Обольщение", value: "731" },
|
||||
{ label: "Оборотни", value: "661" },
|
||||
{ label: "Обратный гарем", value: "351" },
|
||||
{ label: "Общество монстров", value: "329" },
|
||||
{ label: "Обязательство", value: "602" },
|
||||
{ label: "Огнестрельное оружие", value: "291" },
|
||||
{ label: "Ограниченное время жизни", value: "194" },
|
||||
{ label: "Одержимость", value: "512" },
|
||||
{ label: "Одинокий главный герой", value: "394" },
|
||||
{ label: "Одиночество", value: "306" },
|
||||
{ label: "Одиночное проживание", value: "313" },
|
||||
{ label: "Околосмертные переживания", value: "759" },
|
||||
{ label: "Оммёдзи", value: "597" },
|
||||
{ label: "Омоложение", value: "338" },
|
||||
{ label: "Организованная преступность", value: "540" },
|
||||
{ label: "Оргия", value: "775" },
|
||||
{ label: "Орки", value: "331" },
|
||||
{ label: "Освоение навыков", value: "336" },
|
||||
{ label: "Основано на аниме", value: "605" },
|
||||
{ label: "Основано на видео игре", value: "720" },
|
||||
{ label: "Основано на визуальной новелле", value: "793" },
|
||||
{ label: "Основано на песне", value: "700" },
|
||||
{ label: "Основано на фильме", value: "604" },
|
||||
{ label: "Осторожный главный герой", value: "240" },
|
||||
{ label: "Отаку", value: "571" },
|
||||
{ label: "Открытый космос", value: "264" },
|
||||
{ label: "Отношения в сети", value: "727" },
|
||||
{ label: "Отношения между людьми и нелюд", value: "311" },
|
||||
{ label: "Отношения на расстоянии", value: "791" },
|
||||
{ label: "Отношения Сенпай-Коухай", value: "572" },
|
||||
{ label: "Отношения ученика и учителя", value: "515" },
|
||||
{ label: "Отношения учитель-ученик", value: "429" },
|
||||
{ label: "Отношения хозяин-слуга", value: "312" },
|
||||
{ label: "Отомэ игра", value: "733" },
|
||||
{ label: "Отсутствие здравого смысла", value: "645" },
|
||||
{ label: "Отсутствие родителей", value: "625" },
|
||||
{ label: "Офисный роман", value: "707" },
|
||||
{ label: "Официанты", value: "703" },
|
||||
{ label: "Охотники", value: "378" },
|
||||
{ label: "Очаровательный главный герой", value: "687" },
|
||||
{ label: "Падшее дворянство", value: "655" },
|
||||
{ label: "Падшие ангелы", value: "565" },
|
||||
{ label: "Пайзури", value: "665" },
|
||||
{ label: "Паразиты", value: "591" },
|
||||
{ label: "Параллельные миры", value: "207" },
|
||||
{ label: "Парк", value: "265" },
|
||||
{ label: "Парк развлечений", value: "632" },
|
||||
{ label: "Пародия", value: "407" },
|
||||
{ label: "Певцы/Певицы", value: "739" },
|
||||
{ label: "Первая любовь", value: "729" },
|
||||
{ label: "Первоисточник новеллы — манга", value: "611" },
|
||||
{ label: "Первый раз", value: "787" },
|
||||
{ label: "Перемещение в игровой мир", value: "588" },
|
||||
{ label: "Перемещение в иной мир", value: "750" },
|
||||
{ label: "Перерождение в ином мире", value: "751" },
|
||||
{ label: "Переселение души/трансмиграция", value: "257" },
|
||||
{ label: "Персонаж использует щит", value: "666" },
|
||||
{ label: "Петля времени", value: "200" },
|
||||
{ label: "Пираты", value: "771" },
|
||||
{ label: "Писатели", value: "482" },
|
||||
{ label: "Питомцы", value: "349" },
|
||||
{ label: "Племенное общество", value: "381" },
|
||||
{ label: "Повелитель демонов", value: "283" },
|
||||
{ label: "Подземелья", value: "322" },
|
||||
{ label: "Подростковая проза", value: "114" },
|
||||
{ label: "Пожелания", value: "278" },
|
||||
{ label: "Познание Дао", value: "443" },
|
||||
{ label: "Покинутое дитя", value: "590" },
|
||||
{ label: "Полигамия", value: "386" },
|
||||
{ label: "Политика", value: "170" },
|
||||
{ label: "Политический роман", value: "133" },
|
||||
{ label: "Полиция", value: "764" },
|
||||
{ label: "Полулюди", value: "420" },
|
||||
{ label: "Популярный любовный интерес", value: "342" },
|
||||
{ label: "Постапокалипсис", value: "145" },
|
||||
{ label: "Постапокалиптика", value: "184" },
|
||||
{ label: "Потерянные цивилизации", value: "749" },
|
||||
{ label: "Похищения людей", value: "723" },
|
||||
{ label: "Поэзия", value: "111" },
|
||||
{ label: "Правонарушители", value: "713" },
|
||||
{ label: "Прагматичный главный герой", value: "592" },
|
||||
{ label: "Преданный любовный интерес", value: "225" },
|
||||
{ label: "Предательство", value: "222" },
|
||||
{ label: "Предвидение", value: "177" },
|
||||
{ label: "Прекрасная героиня", value: "158" },
|
||||
{ label: "Преступники", value: "736" },
|
||||
{ label: "Преступность", value: "204" },
|
||||
{ label: "Призванный герой", value: "262" },
|
||||
{ label: "Призраки", value: "176" },
|
||||
{ label: "Принуждение к отношениям", value: "643" },
|
||||
{ label: "Принцессы", value: "795" },
|
||||
{ label: "Притворная пара", value: "641" },
|
||||
{ label: "Причудливые персонажи", value: "387" },
|
||||
{ label: "Пришельцы/инопланетяне", value: "197" },
|
||||
{ label: "Программист", value: "691" },
|
||||
{ label: "Проклятия", value: "174" },
|
||||
{ label: "Промывание мозгов", value: "578" },
|
||||
{ label: "Пропуск времени", value: "256" },
|
||||
{ label: "Пророчества", value: "499" },
|
||||
{ label: "Проститутки", value: "786" },
|
||||
{ label: "Прошлое играет большую роль", value: "320" },
|
||||
{ label: "Прыжки между мирами", value: "741" },
|
||||
{ label: "Психические силы", value: "359" },
|
||||
{ label: "Психопаты", value: "270" },
|
||||
{ label: "Публицистика", value: "124" },
|
||||
{ label: "Путешествие во времени", value: "201" },
|
||||
{ label: "Пытка", value: "280" },
|
||||
{ label: "Рабы", value: "778" },
|
||||
{ label: "Развитие личности", value: "125" },
|
||||
{ label: "Развод", value: "758" },
|
||||
{ label: "Разумные предметы", value: "286" },
|
||||
{ label: "Расизм", value: "408" },
|
||||
{ label: "Рассказ", value: "185" },
|
||||
{ label: "Расторжения помолвки", value: "343" },
|
||||
{ label: "Расы зооморфов", value: "315" },
|
||||
{ label: "Реализм", value: "246" },
|
||||
{ label: "Ревность", value: "730" },
|
||||
{ label: "Редакторы", value: "705" },
|
||||
{ label: "Реинкарнация", value: "372" },
|
||||
{ label: "Реинкарнация в монстра", value: "310" },
|
||||
{ label: "Реинкарнация в объект", value: "711" },
|
||||
{ label: "Религии", value: "616" },
|
||||
{ label: "Репортеры", value: "781" },
|
||||
{ label: "Ресторан", value: "680" },
|
||||
{ label: "Решительный главный герой", value: "242" },
|
||||
{ label: "Робкий главный герой", value: "763" },
|
||||
{ label: "Родитель одиночка", value: "708" },
|
||||
{ label: "Родительский комплекс", value: "587" },
|
||||
{ label: "Родословная", value: "239" },
|
||||
{ label: "Романтическая эротика", value: "132" },
|
||||
{ label: "Романтический подсюжет", value: "271" },
|
||||
{ label: "Рост персонажа", value: "215" },
|
||||
{ label: "Рыцари", value: "260" },
|
||||
{ label: "Садистские персонажи", value: "674" },
|
||||
{ label: "Самоотверженный главный герой", value: "747" },
|
||||
{ label: "Самурай", value: "677" },
|
||||
{ label: "Сборник коротких историй", value: "520" },
|
||||
{ label: "Связанные сюжетные линии", value: "535" },
|
||||
{ label: "Святые", value: "752" },
|
||||
{ label: "Священники", value: "413" },
|
||||
{ label: "Сдержанный главный герой", value: "762" },
|
||||
{ label: "Секретные организации", value: "417" },
|
||||
{ label: "Секреты", value: "626" },
|
||||
{ label: "Секс рабы", value: "779" },
|
||||
{ label: "Семейный конфликт", value: "757" },
|
||||
{ label: "Семь добродетелей", value: "334" },
|
||||
{ label: "Семь смертных грехов", value: "253" },
|
||||
{ label: "Семья", value: "347" },
|
||||
{ label: "Сёнэн-ай подсюжет", value: "690" },
|
||||
{ label: "Серийные убийцы", value: "557" },
|
||||
{ label: "Сестринский комплекс", value: "558" },
|
||||
{ label: "Сила духа", value: "373" },
|
||||
{ label: "Сила, требующая платы за польз", value: "579" },
|
||||
{ label: "Сильная пара", value: "228" },
|
||||
{ label: "Сильный в сильнейшего", value: "442" },
|
||||
{ label: "Сильный любовный интерес", value: "273" },
|
||||
{ label: "Синдром восьмиклассника", value: "211" },
|
||||
{ label: "Сироты", value: "319" },
|
||||
{ label: "Система уровней", value: "305" },
|
||||
{ label: "Системный администратор", value: "538" },
|
||||
{ label: "Сказка", value: "126" },
|
||||
{ label: "Скрытие истинной личности", value: "451" },
|
||||
{ label: "Скрытие истинных способностей", value: "348" },
|
||||
{ label: "Скрытный главный герой", value: "252" },
|
||||
{ label: "Скрытые способности", value: "247" },
|
||||
{ label: "Скульпторы", value: "448" },
|
||||
{ label: "Слабо выраженная романтическая", value: "318" },
|
||||
{ label: "Слабый главный герой", value: "298" },
|
||||
{ label: "Слепой главный герой", value: "794" },
|
||||
{ label: "Слуги", value: "333" },
|
||||
{ label: "Слэш", value: "131" },
|
||||
{ label: "Смерть", value: "175" },
|
||||
{ label: "Смерть близких", value: "444" },
|
||||
{ label: "Собственнические персонажи", value: "227" },
|
||||
{ label: "Современная проза", value: "150" },
|
||||
{ label: "Современность", value: "167" },
|
||||
{ label: "Современный любовный роман", value: "137" },
|
||||
{ label: "Сожительство", value: "544" },
|
||||
{ label: "Создание армии", value: "287" },
|
||||
{ label: "Создание артефактов", value: "382" },
|
||||
{ label: "Создание клана", value: "513" },
|
||||
{ label: "Создание королевства", value: "293" },
|
||||
{ label: "Создание навыков", value: "337" },
|
||||
{ label: "Создание секты", value: "469" },
|
||||
{ label: "Солдаты/военные", value: "193" },
|
||||
{ label: "Сон", value: "622" },
|
||||
{ label: "Состоятельные персонажи", value: "190" },
|
||||
{ label: "Социальная иерархия по силе", value: "255" },
|
||||
{ label: "Социальная фантастика", value: "148" },
|
||||
{ label: "Социальные изгои", value: "542" },
|
||||
{ label: "Спасение мира", value: "638" },
|
||||
{ label: "Специальные способности", value: "179" },
|
||||
{ label: "Спокойный главный герой", value: "160" },
|
||||
{ label: "Справедливый главный герой", value: "719" },
|
||||
{ label: "Средневековье", value: "296" },
|
||||
{ label: "Ссорящаяся пара", value: "383" },
|
||||
{ label: "Сталкеры", value: "710" },
|
||||
{ label: "Старение", value: "299" },
|
||||
{ label: "Стимпанк", value: "139" },
|
||||
{ label: "Стоические персонажи", value: "509" },
|
||||
{ label: "Стокгольмский синдром", value: "675" },
|
||||
{ label: "Стратег", value: "495" },
|
||||
{ label: "Стратегические битвы", value: "272" },
|
||||
{ label: "Стратегия", value: "798" },
|
||||
{ label: "Стрелки", value: "522" },
|
||||
{ label: "Стрельба из лука", value: "461" },
|
||||
{ label: "Студенческий совет", value: "551" },
|
||||
{ label: "Судьба", value: "364" },
|
||||
{ label: "Суккубы", value: "545" },
|
||||
{ label: "Супер герои", value: "799" },
|
||||
{ label: "Суровая подготовка", value: "356" },
|
||||
{ label: "Таинственная болезнь", value: "195" },
|
||||
{ label: "Таинственное прошлое", value: "358" },
|
||||
{ label: "Тайная личность", value: "399" },
|
||||
{ label: "Тайные отношения", value: "768" },
|
||||
{ label: "Танцоры", value: "782" },
|
||||
{ label: "Телохранители", value: "516" },
|
||||
{ label: "Темное фэнтези", value: "106" },
|
||||
{ label: "Тентакли", value: "712" },
|
||||
{ label: "Террористы", value: "574" },
|
||||
{ label: "Технологический разрыв", value: "658" },
|
||||
{ label: "Тихие персонажи", value: "600" },
|
||||
{ label: "Толстый главный герой", value: "389" },
|
||||
{ label: "Торговцы", value: "489" },
|
||||
{ label: "Травля/буллинг", value: "210" },
|
||||
{ label: "Травник", value: "724" },
|
||||
{ label: "Трагическое прошлое", value: "276" },
|
||||
{ label: "Трансплантация воспоминаний", value: "449" },
|
||||
{ label: "Триллер", value: "134" },
|
||||
{ label: "Трудолюбивый главный герой", value: "164" },
|
||||
{ label: "Тюрьма", value: "541" },
|
||||
{ label: "Убийства", value: "205" },
|
||||
{ label: "Убийцы", value: "345" },
|
||||
{ label: "Убийцы драконов", value: "646" },
|
||||
{ label: "Уверенный главный герой", value: "363" },
|
||||
{ label: "Удачливый главный герой", value: "478" },
|
||||
{ label: "Ужасы", value: "112" },
|
||||
{ label: "Укротитель монстров", value: "422" },
|
||||
{ label: "Умения из прошлой жизни", value: "371" },
|
||||
{ label: "Умная пара", value: "554" },
|
||||
{ label: "Умный главный герой", value: "161" },
|
||||
{ label: "Уникальная техника Культивации", value: "350" },
|
||||
{ label: "Уникальное оружие", value: "426" },
|
||||
{ label: "Управление бизнесом", value: "403" },
|
||||
{ label: "Управление временем", value: "279" },
|
||||
{ label: "Управление кровью", value: "753" },
|
||||
{ label: "Упрямый главный герой", value: "695" },
|
||||
{ label: "Уродливый главный герой", value: "766" },
|
||||
{ label: "Ускоренный рост", value: "390" },
|
||||
{ label: "Усыновленные дети", value: "543" },
|
||||
{ label: "Усыновленный главный герой", value: "486" },
|
||||
{ label: "Уход за детьми", value: "474" },
|
||||
{ label: "Учителя", value: "431" },
|
||||
{ label: "Фамильяры", value: "596" },
|
||||
{ label: "Фанатизм", value: "650" },
|
||||
{ label: "Фантастические существа", value: "410" },
|
||||
{ label: "Фантастический детектив", value: "115" },
|
||||
{ label: "Фанфик", value: "113" },
|
||||
{ label: "Фанфикшн", value: "464" },
|
||||
{ label: "Фармацевт", value: "728" },
|
||||
{ label: "Фарминг", value: "458" },
|
||||
{ label: "Феи", value: "316" },
|
||||
{ label: "Фелляция", value: "631" },
|
||||
{ label: "Фемслэш", value: "127" },
|
||||
{ label: "Фениксы", value: "454" },
|
||||
{ label: "Фетиш груди", value: "559" },
|
||||
{ label: "Философия", value: "208" },
|
||||
{ label: "Фильмы", value: "479" },
|
||||
{ label: "Флэшбэки", value: "585" },
|
||||
{ label: "Фобии", value: "220" },
|
||||
{ label: "Фольклор", value: "529" },
|
||||
{ label: "Футанари", value: "619" },
|
||||
{ label: "Футуристический сеттинг", value: "460" },
|
||||
{ label: "Фэнтези мир", value: "244" },
|
||||
{ label: "Хакеры", value: "475" },
|
||||
{ label: "Харизматический герой", value: "467" },
|
||||
{ label: "Хикикомори/Затворники", value: "524" },
|
||||
{ label: "Хитроумный главный герой", value: "224" },
|
||||
{ label: "Хозяин подземелий", value: "608" },
|
||||
{ label: "Холодный главный герой", value: "354" },
|
||||
{ label: "Хорошие отношения с семьей", value: "566" },
|
||||
{ label: "Хранители могил", value: "191" },
|
||||
{ label: "Целители", value: "465" },
|
||||
{ label: "Цзянши", value: "740" },
|
||||
{ label: "Цундэрэ", value: "510" },
|
||||
{ label: "Чаты", value: "629" },
|
||||
{ label: "Человеческое оружие", value: "580" },
|
||||
{ label: "Честный главный герой", value: "341" },
|
||||
{ label: "Читы", value: "339" },
|
||||
{ label: "Шантаж", value: "584" },
|
||||
{ label: "Шеф-повар", value: "340" },
|
||||
{ label: "Шикигами", value: "598" },
|
||||
{ label: "Школа только для девочек", value: "668" },
|
||||
{ label: "Шота", value: "573" },
|
||||
{ label: "Шоу-бизнес", value: "481" },
|
||||
{ label: "Шпионский детектив", value: "118" },
|
||||
{ label: "Шпионы", value: "614" },
|
||||
{ label: "Эволюция", value: "304" },
|
||||
{ label: "Эгоистичный главный герой", value: "594" },
|
||||
{ label: "Эйдетическая память", value: "468" },
|
||||
{ label: "Экзорсизм", value: "564" },
|
||||
{ label: "Экономика", value: "553" },
|
||||
{ label: "Эксгибиционизм", value: "673" },
|
||||
{ label: "Эксперименты с людьми", value: "248" },
|
||||
{ label: "Элементальная магия", value: "471" },
|
||||
{ label: "Эльфы", value: "284" },
|
||||
{ label: "Эмоционально слабый гг", value: "770" },
|
||||
{ label: "Эпизодический", value: "649" },
|
||||
{ label: "Эпическое фэнтези", value: "109" },
|
||||
{ label: "Эротическая фантастика", value: "129" },
|
||||
{ label: "Эротический фанфик", value: "128" },
|
||||
{ label: "Эротическое фэнтези", value: "130" },
|
||||
{ label: "Юмористическая фантастика", value: "143" },
|
||||
{ label: "Юмористическое фэнтези", value: "108" },
|
||||
{ label: "Юный любовный интерес", value: "511" },
|
||||
{ label: "Яды", value: "484" },
|
||||
{ label: "Языкастые персонажи", value: "480" },
|
||||
{ label: "Языковой барьер", value: "182" },
|
||||
{ label: "Яндере", value: "314" },
|
||||
{ label: "Японские силы самообороны", value: "610" },
|
||||
{ label: "Ярко выраженная романтика", value: "365" },
|
||||
{ label: "R-15 Японское возрастное огр.", value: "332" },
|
||||
{ label: "R-18", value: "424" },
|
||||
],
|
||||
inputType: FilterInputs.Checkbox,
|
||||
},
|
||||
];
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,218 +0,0 @@
|
||||
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from 'dayjs';
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
// import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
// import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
|
||||
// import { showToast } from "@libs/showToast";
|
||||
import { Filter, FilterInputs } from "@libs/filterInputs";
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
|
||||
|
||||
export const id = "ruranobe";
|
||||
export const name = "РуРанобэ";
|
||||
export const icon = "";
|
||||
export const version = "1.0.0";
|
||||
export const site = "https://ruranobe.ru";
|
||||
export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Автоматы","value":64},{"label":"Агрессивные персонажи","value":26},{"label":"Ад","value":338},{"label":"Адаптация манги","value":8},{"label":"Академия","value":5},{"label":"Алхимия","value":27},{"label":"Альтернативная реальность","value":30},{"label":"Амнезия","value":31},{"label":"Анабиоз","value":170},{"label":"Ангелы","value":38},{"label":"Андроиды","value":37},{"label":"Антигерой","value":43},{"label":"Антимагия","value":41},{"label":"Апатичный главный герой","value":46},{"label":"Апокалипсис","value":47},{"label":"Аристократия","value":51},{"label":"Артефакты","value":58},{"label":"Асоциальный главный герой","value":42},{"label":"R-15","value":570},{"label":"R-18","value":571}],"inputType":FilterInputs.Checkbox}];
|
||||
|
||||
const baseUrl = site;
|
||||
const domain = baseUrl.split('//')[1];
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ filters }
|
||||
) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}));
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'Book',
|
||||
query:
|
||||
'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}',
|
||||
variables: {
|
||||
hostname: domain,
|
||||
project: novelUrl,
|
||||
url: novelUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
name: json.data.project.title,
|
||||
cover: json.data.project.covers[0]?.url
|
||||
? baseUrl + json.data.project.covers[0].url
|
||||
: defaultCover,
|
||||
summary: parseHTML(json.data.project.annotation?.text).text(),
|
||||
author:
|
||||
json.data.project.persons[0].name.firstName +
|
||||
' ' +
|
||||
json.data.project.persons[0].name.lastName,
|
||||
status:
|
||||
json.data.project.translationStatus === 'active'
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
|
||||
let genres = []
|
||||
.concat(json.data.project.tags, json.data.project.genres)
|
||||
.map((item: any) => item?.nameRu || item?.nameEng)
|
||||
.filter(item => item);
|
||||
|
||||
if (genres.length > 0) {
|
||||
novel.genres = genres.join(',');
|
||||
}
|
||||
|
||||
let novelChapters: Chapter.Item[] = [];
|
||||
json.data.project.subprojects.content.forEach((work: any) =>
|
||||
work.volumes.content.forEach((volume: any) =>
|
||||
volume.chapters.forEach(
|
||||
(chapter: any) =>
|
||||
chapter?.published &&
|
||||
novelChapters.push({
|
||||
name: volume.shortName + ' ' + chapter.title,
|
||||
url: novelUrl + '/' + volume.url + '/' + chapter.url,
|
||||
releaseTime: dayjs(chapter.publishDate).format('LLL'),
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
novel.chapters = novelChapters;
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'EReaderData',
|
||||
query:
|
||||
'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}',
|
||||
variables: {
|
||||
chapterSelector: {
|
||||
fullUrl: chapterUrl,
|
||||
},
|
||||
url: chapterUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
|
||||
return parseHTML(json.data.chapter.text.text).html();
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: {
|
||||
query: searchTerm,
|
||||
},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page: 1,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = [];
|
||||
json?.data?.projects?.content?.forEach((novel: any) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}),
|
||||
);
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url, {});
|
||||
};
|
||||
|
||||
@@ -1,218 +0,0 @@
|
||||
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from 'dayjs';
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
// import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
// import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
|
||||
// import { showToast } from "@libs/showToast";
|
||||
import { Filter, FilterInputs } from "@libs/filterInputs";
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
|
||||
|
||||
export const id = "sferdrakon.novel";
|
||||
export const name = "Уголок дракона";
|
||||
export const icon = "";
|
||||
export const version = "1.0.0";
|
||||
export const site = "https://sferdrakon.novel.tl";
|
||||
export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Главный герой — мужчина","value":419},{"label":"Демоны","value":193},{"label":"Драконы","value":218},{"label":"Есть аниме-адаптация","value":10},{"label":"Есть видеоигра","value":13},{"label":"Есть манга-адаптация","value":14},{"label":"Звероподобные","value":79},{"label":"Изнасилование","value":574},{"label":"Королевская особа","value":596},{"label":"Магия","value":412},{"label":"Магия призыва","value":694},{"label":"Наездники на драконах","value":216},{"label":"Рыцари","value":383},{"label":"Цундере","value":728},{"label":"Эльфы","value":235}],"inputType":FilterInputs.Checkbox}];
|
||||
|
||||
const baseUrl = site;
|
||||
const domain = baseUrl.split('//')[1];
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ filters }
|
||||
) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}));
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'Book',
|
||||
query:
|
||||
'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}',
|
||||
variables: {
|
||||
hostname: domain,
|
||||
project: novelUrl,
|
||||
url: novelUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
name: json.data.project.title,
|
||||
cover: json.data.project.covers[0]?.url
|
||||
? baseUrl + json.data.project.covers[0].url
|
||||
: defaultCover,
|
||||
summary: parseHTML(json.data.project.annotation?.text).text(),
|
||||
author:
|
||||
json.data.project.persons[0].name.firstName +
|
||||
' ' +
|
||||
json.data.project.persons[0].name.lastName,
|
||||
status:
|
||||
json.data.project.translationStatus === 'active'
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
|
||||
let genres = []
|
||||
.concat(json.data.project.tags, json.data.project.genres)
|
||||
.map((item: any) => item?.nameRu || item?.nameEng)
|
||||
.filter(item => item);
|
||||
|
||||
if (genres.length > 0) {
|
||||
novel.genres = genres.join(',');
|
||||
}
|
||||
|
||||
let novelChapters: Chapter.Item[] = [];
|
||||
json.data.project.subprojects.content.forEach((work: any) =>
|
||||
work.volumes.content.forEach((volume: any) =>
|
||||
volume.chapters.forEach(
|
||||
(chapter: any) =>
|
||||
chapter?.published &&
|
||||
novelChapters.push({
|
||||
name: volume.shortName + ' ' + chapter.title,
|
||||
url: novelUrl + '/' + volume.url + '/' + chapter.url,
|
||||
releaseTime: dayjs(chapter.publishDate).format('LLL'),
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
novel.chapters = novelChapters;
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'EReaderData',
|
||||
query:
|
||||
'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}',
|
||||
variables: {
|
||||
chapterSelector: {
|
||||
fullUrl: chapterUrl,
|
||||
},
|
||||
url: chapterUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
|
||||
return parseHTML(json.data.chapter.text.text).html();
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: {
|
||||
query: searchTerm,
|
||||
},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page: 1,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = [];
|
||||
json?.data?.projects?.content?.forEach((novel: any) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}),
|
||||
);
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url, {});
|
||||
};
|
||||
|
||||
@@ -1,218 +0,0 @@
|
||||
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from 'dayjs';
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
// import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
// import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
|
||||
// import { showToast } from "@libs/showToast";
|
||||
import { Filter, FilterInputs } from "@libs/filterInputs";
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
|
||||
|
||||
export const id = "si.novel";
|
||||
export const name = "Сказочные истории";
|
||||
export const icon = "";
|
||||
export const version = "1.0.0";
|
||||
export const site = "https://si.novel.tl";
|
||||
export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Аристократия","value":51},{"label":"Война","value":750},{"label":"Главная героиня — женщина","value":277},{"label":"Драконы","value":218},{"label":"Древний Китай","value":34},{"label":"Жестокие персонажи","value":4},{"label":"Злобные благородные девы","value":743},{"label":"Комедийный подтекст","value":147},{"label":"Культивация","value":171},{"label":"Магия","value":412},{"label":"Отомэ-игры","value":504},{"label":"Перерождение в другом мире","value":580},{"label":"Перерождение в другом мире","value":720},{"label":"Повелитель демонов","value":191},{"label":"Попаданец в другой мир","value":723},{"label":"Смерть","value":185},{"label":"Средневековье","value":436},{"label":"Фэнтези","value":267},{"label":"Харизматичный протагонист","value":120},{"label":"Хитрый главный герой","value":173}],"inputType":FilterInputs.Checkbox}];
|
||||
|
||||
const baseUrl = site;
|
||||
const domain = baseUrl.split('//')[1];
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ filters }
|
||||
) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}));
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'Book',
|
||||
query:
|
||||
'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}',
|
||||
variables: {
|
||||
hostname: domain,
|
||||
project: novelUrl,
|
||||
url: novelUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
name: json.data.project.title,
|
||||
cover: json.data.project.covers[0]?.url
|
||||
? baseUrl + json.data.project.covers[0].url
|
||||
: defaultCover,
|
||||
summary: parseHTML(json.data.project.annotation?.text).text(),
|
||||
author:
|
||||
json.data.project.persons[0].name.firstName +
|
||||
' ' +
|
||||
json.data.project.persons[0].name.lastName,
|
||||
status:
|
||||
json.data.project.translationStatus === 'active'
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
|
||||
let genres = []
|
||||
.concat(json.data.project.tags, json.data.project.genres)
|
||||
.map((item: any) => item?.nameRu || item?.nameEng)
|
||||
.filter(item => item);
|
||||
|
||||
if (genres.length > 0) {
|
||||
novel.genres = genres.join(',');
|
||||
}
|
||||
|
||||
let novelChapters: Chapter.Item[] = [];
|
||||
json.data.project.subprojects.content.forEach((work: any) =>
|
||||
work.volumes.content.forEach((volume: any) =>
|
||||
volume.chapters.forEach(
|
||||
(chapter: any) =>
|
||||
chapter?.published &&
|
||||
novelChapters.push({
|
||||
name: volume.shortName + ' ' + chapter.title,
|
||||
url: novelUrl + '/' + volume.url + '/' + chapter.url,
|
||||
releaseTime: dayjs(chapter.publishDate).format('LLL'),
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
novel.chapters = novelChapters;
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'EReaderData',
|
||||
query:
|
||||
'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}',
|
||||
variables: {
|
||||
chapterSelector: {
|
||||
fullUrl: chapterUrl,
|
||||
},
|
||||
url: chapterUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
|
||||
return parseHTML(json.data.chapter.text.text).html();
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: {
|
||||
query: searchTerm,
|
||||
},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page: 1,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = [];
|
||||
json?.data?.projects?.content?.forEach((novel: any) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}),
|
||||
);
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url, {});
|
||||
};
|
||||
|
||||
@@ -1,218 +0,0 @@
|
||||
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from 'dayjs';
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
// import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
// import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
|
||||
// import { showToast } from "@libs/showToast";
|
||||
import { Filter, FilterInputs } from "@libs/filterInputs";
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
|
||||
|
||||
export const id = "snailulitka.novel";
|
||||
export const name = "Snailulitka";
|
||||
export const icon = "";
|
||||
export const version = "1.0.0";
|
||||
export const site = "https://snailulitka.novel.tl";
|
||||
export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Алхимия","value":27},{"label":"Ассасины","value":61},{"label":"Бедный главный герой","value":541},{"label":"Богачи","value":753},{"label":"Виртуальная реальность","value":744},{"label":"Война","value":750},{"label":"Геймеры","value":301},{"label":"Героиня красавица","value":81},{"label":"Герой красавец","value":329},{"label":"Гильдии","value":324},{"label":"Главный герой - гений","value":308},{"label":"Главный герой — знаменитость","value":263},{"label":"Главный герой — мужчина","value":419},{"label":"Дворфы","value":224},{"label":"Драконы","value":218},{"label":"Есть маньхуа-адаптация","value":15},{"label":"Игровая рейтинговая система","value":300},{"label":"Книги навыков","value":654},{"label":"Лекари","value":334},{"label":"Месть","value":588}],"inputType":FilterInputs.Checkbox}];
|
||||
|
||||
const baseUrl = site;
|
||||
const domain = baseUrl.split('//')[1];
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ filters }
|
||||
) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}));
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'Book',
|
||||
query:
|
||||
'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}',
|
||||
variables: {
|
||||
hostname: domain,
|
||||
project: novelUrl,
|
||||
url: novelUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
name: json.data.project.title,
|
||||
cover: json.data.project.covers[0]?.url
|
||||
? baseUrl + json.data.project.covers[0].url
|
||||
: defaultCover,
|
||||
summary: parseHTML(json.data.project.annotation?.text).text(),
|
||||
author:
|
||||
json.data.project.persons[0].name.firstName +
|
||||
' ' +
|
||||
json.data.project.persons[0].name.lastName,
|
||||
status:
|
||||
json.data.project.translationStatus === 'active'
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
|
||||
let genres = []
|
||||
.concat(json.data.project.tags, json.data.project.genres)
|
||||
.map((item: any) => item?.nameRu || item?.nameEng)
|
||||
.filter(item => item);
|
||||
|
||||
if (genres.length > 0) {
|
||||
novel.genres = genres.join(',');
|
||||
}
|
||||
|
||||
let novelChapters: Chapter.Item[] = [];
|
||||
json.data.project.subprojects.content.forEach((work: any) =>
|
||||
work.volumes.content.forEach((volume: any) =>
|
||||
volume.chapters.forEach(
|
||||
(chapter: any) =>
|
||||
chapter?.published &&
|
||||
novelChapters.push({
|
||||
name: volume.shortName + ' ' + chapter.title,
|
||||
url: novelUrl + '/' + volume.url + '/' + chapter.url,
|
||||
releaseTime: dayjs(chapter.publishDate).format('LLL'),
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
novel.chapters = novelChapters;
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'EReaderData',
|
||||
query:
|
||||
'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}',
|
||||
variables: {
|
||||
chapterSelector: {
|
||||
fullUrl: chapterUrl,
|
||||
},
|
||||
url: chapterUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
|
||||
return parseHTML(json.data.chapter.text.text).html();
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: {
|
||||
query: searchTerm,
|
||||
},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page: 1,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = [];
|
||||
json?.data?.projects?.content?.forEach((novel: any) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}),
|
||||
);
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url, {});
|
||||
};
|
||||
|
||||
@@ -1,218 +0,0 @@
|
||||
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from 'dayjs';
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
// import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
// import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
|
||||
// import { showToast } from "@libs/showToast";
|
||||
import { Filter, FilterInputs } from "@libs/filterInputs";
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
|
||||
|
||||
export const id = "tsundoku.novel";
|
||||
export const name = "TsundokuTrans";
|
||||
export const icon = "";
|
||||
export const version = "1.0.0";
|
||||
export const site = "https://tsundoku.novel.tl";
|
||||
export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Боги","value":318},{"label":"Боевая академия","value":75},{"label":"Вампиры","value":742},{"label":"Влияние прошлого","value":517},{"label":"Война","value":750},{"label":"Главная героиня — женщина","value":277},{"label":"Главный герой — мужчина","value":419},{"label":"Демоны","value":193},{"label":"Драконы","value":218},{"label":"Королевская особа","value":596},{"label":"Коррупция","value":157},{"label":"Магия","value":412},{"label":"Мастер подземелий","value":222},{"label":"Мечи и магия","value":697},{"label":"Мечники","value":698},{"label":"Монстры","value":454},{"label":"Недооцененный главный герой","value":734},{"label":"От слабого к сильному","value":752},{"label":"R-15","value":570},{"label":"R-18","value":571}],"inputType":FilterInputs.Checkbox}];
|
||||
|
||||
const baseUrl = site;
|
||||
const domain = baseUrl.split('//')[1];
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ filters }
|
||||
) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}));
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'Book',
|
||||
query:
|
||||
'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}',
|
||||
variables: {
|
||||
hostname: domain,
|
||||
project: novelUrl,
|
||||
url: novelUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
name: json.data.project.title,
|
||||
cover: json.data.project.covers[0]?.url
|
||||
? baseUrl + json.data.project.covers[0].url
|
||||
: defaultCover,
|
||||
summary: parseHTML(json.data.project.annotation?.text).text(),
|
||||
author:
|
||||
json.data.project.persons[0].name.firstName +
|
||||
' ' +
|
||||
json.data.project.persons[0].name.lastName,
|
||||
status:
|
||||
json.data.project.translationStatus === 'active'
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
|
||||
let genres = []
|
||||
.concat(json.data.project.tags, json.data.project.genres)
|
||||
.map((item: any) => item?.nameRu || item?.nameEng)
|
||||
.filter(item => item);
|
||||
|
||||
if (genres.length > 0) {
|
||||
novel.genres = genres.join(',');
|
||||
}
|
||||
|
||||
let novelChapters: Chapter.Item[] = [];
|
||||
json.data.project.subprojects.content.forEach((work: any) =>
|
||||
work.volumes.content.forEach((volume: any) =>
|
||||
volume.chapters.forEach(
|
||||
(chapter: any) =>
|
||||
chapter?.published &&
|
||||
novelChapters.push({
|
||||
name: volume.shortName + ' ' + chapter.title,
|
||||
url: novelUrl + '/' + volume.url + '/' + chapter.url,
|
||||
releaseTime: dayjs(chapter.publishDate).format('LLL'),
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
novel.chapters = novelChapters;
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'EReaderData',
|
||||
query:
|
||||
'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}',
|
||||
variables: {
|
||||
chapterSelector: {
|
||||
fullUrl: chapterUrl,
|
||||
},
|
||||
url: chapterUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
|
||||
return parseHTML(json.data.chapter.text.text).html();
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: {
|
||||
query: searchTerm,
|
||||
},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page: 1,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = [];
|
||||
json?.data?.projects?.content?.forEach((novel: any) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}),
|
||||
);
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url, {});
|
||||
};
|
||||
|
||||
@@ -1,218 +0,0 @@
|
||||
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from 'dayjs';
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
// import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
// import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
|
||||
// import { showToast } from "@libs/showToast";
|
||||
import { Filter, FilterInputs } from "@libs/filterInputs";
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
|
||||
|
||||
export const id = "ukr.novel";
|
||||
export const name = "Переклади українською мовою";
|
||||
export const icon = "";
|
||||
export const version = "1.0.0";
|
||||
export const site = "https://ukr.novel.tl";
|
||||
export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Альтернативная реальность","value":30},{"label":"Апатичный главный герой","value":46},{"label":"Асоциальный главный герой","value":42},{"label":"Владелец уникального оружия","value":736},{"label":"Гарем рабов","value":656},{"label":"Герои","value":341},{"label":"Героиня красавица","value":81},{"label":"Герой использует щит","value":636},{"label":"Герой-трудяга","value":330},{"label":"Главный герой — мужчина","value":419},{"label":"Глухой к любви герой","value":194},{"label":"Демоны","value":193},{"label":"Друзья детства","value":128},{"label":"Есть аниме-адаптация","value":10},{"label":"Есть манга-адаптация","value":14},{"label":"Есть CD дорама-адаптация","value":12},{"label":"Зверолюди","value":39},{"label":"Магия","value":412},{"label":"Магия призыва","value":694},{"label":"От слабого к сильному","value":752}],"inputType":FilterInputs.Checkbox}];
|
||||
|
||||
const baseUrl = site;
|
||||
const domain = baseUrl.split('//')[1];
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ filters }
|
||||
) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}));
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'Book',
|
||||
query:
|
||||
'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}',
|
||||
variables: {
|
||||
hostname: domain,
|
||||
project: novelUrl,
|
||||
url: novelUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
name: json.data.project.title,
|
||||
cover: json.data.project.covers[0]?.url
|
||||
? baseUrl + json.data.project.covers[0].url
|
||||
: defaultCover,
|
||||
summary: parseHTML(json.data.project.annotation?.text).text(),
|
||||
author:
|
||||
json.data.project.persons[0].name.firstName +
|
||||
' ' +
|
||||
json.data.project.persons[0].name.lastName,
|
||||
status:
|
||||
json.data.project.translationStatus === 'active'
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
|
||||
let genres = []
|
||||
.concat(json.data.project.tags, json.data.project.genres)
|
||||
.map((item: any) => item?.nameRu || item?.nameEng)
|
||||
.filter(item => item);
|
||||
|
||||
if (genres.length > 0) {
|
||||
novel.genres = genres.join(',');
|
||||
}
|
||||
|
||||
let novelChapters: Chapter.Item[] = [];
|
||||
json.data.project.subprojects.content.forEach((work: any) =>
|
||||
work.volumes.content.forEach((volume: any) =>
|
||||
volume.chapters.forEach(
|
||||
(chapter: any) =>
|
||||
chapter?.published &&
|
||||
novelChapters.push({
|
||||
name: volume.shortName + ' ' + chapter.title,
|
||||
url: novelUrl + '/' + volume.url + '/' + chapter.url,
|
||||
releaseTime: dayjs(chapter.publishDate).format('LLL'),
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
novel.chapters = novelChapters;
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
body: JSON.stringify({
|
||||
operationName: 'EReaderData',
|
||||
query:
|
||||
'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}',
|
||||
variables: {
|
||||
chapterSelector: {
|
||||
fullUrl: chapterUrl,
|
||||
},
|
||||
url: chapterUrl,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
|
||||
return parseHTML(json.data.chapter.text.text).html();
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
|
||||
'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',
|
||||
'Alt-Used': 'api.novel.tl',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
referrer: baseUrl,
|
||||
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 url covers{url}}}}',
|
||||
variables: {
|
||||
filter: {
|
||||
query: searchTerm,
|
||||
},
|
||||
hostname: domain,
|
||||
limit: 40,
|
||||
page: 1,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json: any = await result.json();
|
||||
const novels: Novel.Item[] = [];
|
||||
json?.data?.projects?.content?.forEach((novel: any) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
url: baseUrl + '/r/' + novel.url,
|
||||
cover: novel.covers[0]?.url
|
||||
? baseUrl + novel.covers[0].url
|
||||
: defaultCover,
|
||||
}),
|
||||
);
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url, {});
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Chapter, Novel, Plugin } from "@typings/plugin";
|
||||
import cheerio from "cheerio";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
|
||||
const pluginId = "awuxia.com";
|
||||
@@ -24,7 +24,7 @@ export const popularNovels: Plugin.popularNovels = async function (page) {
|
||||
});
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = cheerio.load(body);
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
let novels: Novel.Item[] = [];
|
||||
|
||||
@@ -62,7 +62,7 @@ export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = cheerio.load(body);
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
let novel: Novel.instance = { url };
|
||||
|
||||
@@ -152,7 +152,7 @@ export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = cheerio.load(body);
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
let chapterText = loadedCheerio(".post-body.entry-content").html();
|
||||
|
||||
@@ -165,7 +165,7 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = cheerio.load(body);
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
let novels: Novel.Item[] = [];
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Chapter, Novel, Plugin } from "@typings/plugin";
|
||||
import cheerio from "cheerio";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
|
||||
export const id = "oasisTL.wp";
|
||||
export const name = "Oasis Translations";
|
||||
@@ -16,7 +16,7 @@ export const popularNovels: Plugin.popularNovels = async function (page) {
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = cheerio.load(body);
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
let novels: Novel.Item[] = [];
|
||||
|
||||
@@ -51,7 +51,7 @@ export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = cheerio.load(body);
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
let novel: Novel.instance = { url };
|
||||
|
||||
@@ -113,7 +113,7 @@ export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = cheerio.load(body);
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio("div#jp-post-flair").remove();
|
||||
|
||||
@@ -130,7 +130,7 @@ export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = cheerio.load(body);
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
let novels: Novel.Item[] = [];
|
||||
loadedCheerio(".menu-item-1819")
|
||||
@@ -160,3 +160,5 @@ export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = fetchFile;
|
||||
@@ -1,129 +0,0 @@
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Chapter, Novel, Plugin } from "@typings/plugin";
|
||||
|
||||
export const id = "skynovels.net";
|
||||
export const name = "SkyNovels";
|
||||
export const site = "https://www.skynovels.net/";
|
||||
export const version = "1.0.0";
|
||||
export const icon = "src/es/skynovels/icon.png";
|
||||
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (page) {
|
||||
const url = "https://api.skynovels.net/api/novels?&q";
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.json();
|
||||
|
||||
const novels: Novel.Item[] = [];
|
||||
|
||||
body.novels.map((res) => {
|
||||
const novelName = res.nvl_title;
|
||||
const novelCover =
|
||||
"https://api.skynovels.net/api/get-image/" +
|
||||
res.image +
|
||||
"/novels/false";
|
||||
const novelUrl =
|
||||
baseUrl + "novelas/" + res.id + "/" + res.nvl_name + "/";
|
||||
|
||||
const novel = { name: novelName, url: novelUrl, cover: novelCover };
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novUrl) {
|
||||
let novelId = novUrl.substring().split("/")[4];
|
||||
const url =
|
||||
"https://api.skynovels.net/api/novel/" + novelId + "/reading?&q";
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.json();
|
||||
|
||||
const item = body.novel[0];
|
||||
|
||||
let novel: Novel.instance = { url: novUrl };
|
||||
|
||||
novel.name = item.nvl_title;
|
||||
|
||||
novel.cover =
|
||||
"https://api.skynovels.net/api/get-image/" +
|
||||
item.image +
|
||||
"/novels/false";
|
||||
|
||||
let genres: string[] = [];
|
||||
item.genres.map((genre) => genres.push(genre.genre_name));
|
||||
novel.genres = genres.join(",");
|
||||
novel.author = item.nvl_writer;
|
||||
novel.summary = item.nvl_content;
|
||||
novel.status = item.nvl_status;
|
||||
|
||||
let novelChapters: Chapter.Item[] = [];
|
||||
|
||||
item.volumes.map((volume) => {
|
||||
volume.chapters.map((chapter) => {
|
||||
const chapterName = chapter.chp_index_title;
|
||||
const releaseDate = new Date(chapter.createdAt).toDateString();
|
||||
const chapterUrl = novUrl + chapter.id + "/" + chapter.chp_name;
|
||||
|
||||
const chap = {
|
||||
name: chapterName,
|
||||
releaseTime: releaseDate,
|
||||
url: chapterUrl,
|
||||
};
|
||||
|
||||
novelChapters.push(chap);
|
||||
});
|
||||
});
|
||||
|
||||
novel.chapters = novelChapters;
|
||||
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapUrl) {
|
||||
let chapterId = chapUrl.split("/")[6];
|
||||
const url = `https://api.skynovels.net/api/novel-chapter/${chapterId}`;
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.json();
|
||||
|
||||
const item = body.chapter[0];
|
||||
|
||||
let chapterText = item.chp_content;
|
||||
|
||||
return chapterText;
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
|
||||
searchTerm = searchTerm.toLowerCase();
|
||||
const url = "https://api.skynovels.net/api/novels?&q";
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.json();
|
||||
|
||||
let results = body.novels.filter((novel) =>
|
||||
novel.nvl_title.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
|
||||
const novels: Novel.Item[] = [];
|
||||
|
||||
results.map((res) => {
|
||||
const novelName = res.nvl_title;
|
||||
const novelCover =
|
||||
"https://api.skynovels.net/api/get-image/" +
|
||||
res.image +
|
||||
"/novels/false";
|
||||
const novelUrl =
|
||||
baseUrl + "novelas/" + res.id + "/" + res.nvl_name + "/";
|
||||
|
||||
const novel = { name: novelName, url: novelUrl, cover: novelCover };
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
|
||||
return novels;
|
||||
};
|
||||
@@ -0,0 +1,266 @@
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Chapter, Novel, Plugin } from "@typings/plugin";
|
||||
|
||||
export const id = "skynovels.net";
|
||||
export const name = "SkyNovels";
|
||||
export const site = "https://www.skynovels.net/";
|
||||
export const version = "1.0.0";
|
||||
export const icon = "src/es/skynovels/icon.png";
|
||||
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (page) {
|
||||
const url = "https://api.skynovels.net/api/novels?&q";
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.json() as response;
|
||||
|
||||
const novels: Novel.Item[] = [];
|
||||
|
||||
body.novels?.forEach((res) => {
|
||||
const novelName = res.nvl_title;
|
||||
const novelCover =
|
||||
"https://api.skynovels.net/api/get-image/" +
|
||||
res.image +
|
||||
"/novels/false";
|
||||
const novelUrl =
|
||||
baseUrl + "novelas/" + res.id + "/" + res.nvl_name + "/";
|
||||
|
||||
const novel = { name: novelName, url: novelUrl, cover: novelCover };
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novUrl) {
|
||||
const novelId = novUrl.split("/")[4];
|
||||
const url =
|
||||
"https://api.skynovels.net/api/novel/" + novelId + "/reading?&q";
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.json() as responseBook;
|
||||
|
||||
const item = body?.novel?.[0];
|
||||
|
||||
let novel: Novel.instance = { url: novUrl };
|
||||
|
||||
novel.name = item?.nvl_title;
|
||||
|
||||
novel.cover =
|
||||
"https://api.skynovels.net/api/get-image/" +
|
||||
item?.image +
|
||||
"/novels/false";
|
||||
|
||||
let genres: string[] = [];
|
||||
item?.genres?.forEach((genre) => genres.push(genre.genre_name));
|
||||
novel.genres = genres.join(",");
|
||||
novel.author = item?.nvl_writer;
|
||||
novel.summary = item?.nvl_content;
|
||||
novel.status = item?.nvl_status;
|
||||
|
||||
let novelChapters: Chapter.Item[] = [];
|
||||
|
||||
item?.volumes?.forEach((volume) => {
|
||||
volume?.chapters?.forEach((chapter) => {
|
||||
const chapterName = chapter.chp_index_title;
|
||||
const releaseDate = new Date(chapter.createdAt).toDateString();
|
||||
const chapterUrl = novUrl + chapter.id + "/" + chapter.chp_name;
|
||||
|
||||
const chap = {
|
||||
name: chapterName,
|
||||
releaseTime: releaseDate,
|
||||
url: chapterUrl,
|
||||
};
|
||||
|
||||
novelChapters.push(chap);
|
||||
});
|
||||
});
|
||||
|
||||
novel.chapters = novelChapters;
|
||||
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapUrl) {
|
||||
let chapterId: string = chapUrl.split("/")[6];
|
||||
const url = `https://api.skynovels.net/api/novel-chapter/${chapterId}`;
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.json() as responseChapter;
|
||||
|
||||
const item = body?.chapter?.[0];
|
||||
|
||||
let chapterText = item?.chp_content || '404';
|
||||
|
||||
return chapterText;
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
|
||||
searchTerm = searchTerm.toLowerCase();
|
||||
const url = "https://api.skynovels.net/api/novels?&q";
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.json() as response;;
|
||||
|
||||
let results = body?.novels?.filter((novel) =>
|
||||
novel.nvl_title.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
|
||||
const novels: Novel.Item[] = [];
|
||||
|
||||
results?.forEach((res) => {
|
||||
const novelName = res.nvl_title;
|
||||
const novelCover =
|
||||
"https://api.skynovels.net/api/get-image/" +
|
||||
res.image +
|
||||
"/novels/false";
|
||||
const novelUrl =
|
||||
baseUrl + "novelas/" + res.id + "/" + res.nvl_name + "/";
|
||||
|
||||
const novel = { name: novelName, url: novelUrl, cover: novelCover };
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = fetchFile;
|
||||
|
||||
interface response {
|
||||
novels?: (NovelsEntity)[] | null;
|
||||
}
|
||||
interface NovelsEntity {
|
||||
id: number;
|
||||
nvl_author?: number | null;
|
||||
nvl_content: string;
|
||||
nvl_title: string;
|
||||
nvl_acronym?: string | null;
|
||||
nvl_status: string;
|
||||
nvl_publication_date?: string | null;
|
||||
nvl_name: string;
|
||||
nvl_recommended: number;
|
||||
nvl_writer: string;
|
||||
nvl_translator?: string | null;
|
||||
nvl_translator_eng?: string | null;
|
||||
image: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
nvl_chapters: number;
|
||||
nvl_last_update: string;
|
||||
nvl_rating?: number | null;
|
||||
nvl_ratings_count: number;
|
||||
genres?: (GenresEntity)[] | null;
|
||||
}
|
||||
interface GenresEntity {
|
||||
id: number;
|
||||
genre_name: string;
|
||||
}
|
||||
|
||||
|
||||
interface responseBook {
|
||||
novel?: (NovelEntity)[] | null;
|
||||
}
|
||||
interface NovelEntity {
|
||||
id: number;
|
||||
nvl_author: number;
|
||||
nvl_content: string;
|
||||
nvl_title: string;
|
||||
nvl_acronym: string;
|
||||
nvl_status: string;
|
||||
nvl_publication_date: string;
|
||||
nvl_name: string;
|
||||
nvl_recommended: number;
|
||||
nvl_writer: string;
|
||||
nvl_translator: string;
|
||||
nvl_translator_eng: string;
|
||||
image: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
nvl_chapters: number;
|
||||
nvl_last_update: string;
|
||||
nvl_rating: number;
|
||||
bookmarks?: (BookmarksEntity)[] | null;
|
||||
volumes?: (VolumesEntity)[] | null;
|
||||
novel_ratings?: (NovelRatingsEntity)[] | null;
|
||||
collaborators?: (CollaboratorsEntity)[] | null;
|
||||
genres?: (GenresEntity)[] | null;
|
||||
}
|
||||
interface BookmarksEntity {
|
||||
id: number;
|
||||
user_id: number;
|
||||
chp_id: number;
|
||||
chp_name: string;
|
||||
}
|
||||
interface VolumesEntity {
|
||||
vlm_title: string;
|
||||
id: number;
|
||||
nvl_id: number;
|
||||
user_id?: number | null;
|
||||
chapters?: (ChaptersEntity)[] | null;
|
||||
}
|
||||
interface ChaptersEntity {
|
||||
id: number;
|
||||
chp_index_title: string;
|
||||
chp_name: string;
|
||||
chp_number: number;
|
||||
chp_status: string;
|
||||
createdAt: string;
|
||||
}
|
||||
interface NovelRatingsEntity {
|
||||
user_id: number;
|
||||
rate_value: number;
|
||||
rate_comment: string;
|
||||
replys_count: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
id: number;
|
||||
user_login: string;
|
||||
image?: string | null;
|
||||
likes?: (LikesEntity | null)[] | null;
|
||||
}
|
||||
interface LikesEntity {
|
||||
id: number;
|
||||
user_id: number;
|
||||
user_login: string;
|
||||
}
|
||||
interface CollaboratorsEntity {
|
||||
user_id: number;
|
||||
user_login: string;
|
||||
}
|
||||
|
||||
|
||||
interface responseChapter {
|
||||
chapter?: (ChapterEntity)[] | null;
|
||||
}
|
||||
interface ChapterEntity {
|
||||
id: number;
|
||||
chp_author: number;
|
||||
chp_translator?: null;
|
||||
nvl_id: number;
|
||||
vlm_id: number;
|
||||
chp_number: number;
|
||||
chp_content: string;
|
||||
chp_review?: null;
|
||||
chp_title?: null;
|
||||
chp_index_title: string;
|
||||
chp_status: string;
|
||||
chp_name: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
nvl_title: string;
|
||||
nvl_name: string;
|
||||
user_login: string;
|
||||
reactions_count: number;
|
||||
comments?: (null)[] | null;
|
||||
reactions?: (null)[] | null;
|
||||
total_reactions?: (TotalReactionsEntity)[] | null;
|
||||
}
|
||||
interface TotalReactionsEntity {
|
||||
reaction_id: number;
|
||||
reaction_name: string;
|
||||
reaction_count: number;
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://turkcelightnovels.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "light-novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "light-novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Order by","values":[{"label":"Rating","value":"rating"},{"label":"A-Z","value":"alphabet"},{"label":"Latest","value":"latest"},{"label":"Most Views","value":"views"},{"label":"New","value":"new-manga"},{"label":"Trending","value":"trending"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -2,6 +2,7 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Novel, Plugin, Chapter } from "@typings/plugin";
|
||||
import { FilterInputs } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus"
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
@@ -14,19 +15,24 @@ export const version = "1.0.0";
|
||||
export const site = "https://www.webnoveloku.com/";
|
||||
const baseUrl = site;
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => {
|
||||
export const popularNovels: Plugin.popularNovels = async (pageNo, {filters, showLatestNovels}) => {
|
||||
const novels: Novel.Item[] = [];
|
||||
const sortOrder = showLatestNovels
|
||||
? '?m_orderby=latest'
|
||||
: '/?m_orderby=rating';
|
||||
|
||||
let url = site + "novel" + '/page/' + pageNo + sortOrder;
|
||||
let url = site;
|
||||
|
||||
if (filters?.genres) {
|
||||
url += "novel-genre/" + filters.genres + '/';
|
||||
} else {
|
||||
url += "novel/";
|
||||
}
|
||||
|
||||
url += '/page/' + pageNo + '/' +
|
||||
'?m_orderby=' + (showLatestNovels ? 'latest' : (filters?.sort || 'rating'));
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
@@ -202,4 +208,6 @@ export const searchNovels: Plugin.searchNovels = async (searchTerm) => {
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async (url) => {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
};
|
||||
|
||||
export const filters = [{"key":"sort","label":"Sırala","values":[{"label":"A-Z","value":"alphabet"},{"label":"En Son","value":"latest"},{"label":"Görüntülenme","value":"views"},{"label":"Popülarite","value":"trending"},{"label":"Reyting","value":"rating"},{"label":"Yeni","value":"new-manga"}],"inputType":FilterInputs.Picker}];
|
||||
@@ -0,0 +1,245 @@
|
||||
import { Chapter, Novel, Plugin } from "@typings/plugin";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export const id = "smakolykytl";
|
||||
export const name = "Смаколики";
|
||||
export const site = "https://smakolykytl.site/";
|
||||
export const version = "1.0.0";
|
||||
export const icon = "src/ua/smakolykytl/icon.png";
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (
|
||||
page,
|
||||
{ showLatestNovels },
|
||||
) {
|
||||
const url = showLatestNovels
|
||||
? "https://api.smakolykytl.site/api/user/updates"
|
||||
: "https://api.smakolykytl.site/api/user/projects";
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const json = (await result.json()) as response;
|
||||
|
||||
let novels: Novel.Item[] = [];
|
||||
(json?.projects || json?.updates)?.forEach((novel) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
cover: novel.image.url,
|
||||
url: site + "titles/" + novel.id,
|
||||
}),
|
||||
);
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
const id = novelUrl.split("/").pop();
|
||||
const result = await fetchApi(
|
||||
"https://api.smakolykytl.site/api/user/projects/" + id,
|
||||
);
|
||||
const book = (await result.json()) as response;
|
||||
|
||||
let novel: Novel.instance = {
|
||||
url: novelUrl,
|
||||
name: book?.project?.title,
|
||||
cover: book?.project?.image?.url,
|
||||
summary: book?.project?.description,
|
||||
author: book?.project?.author,
|
||||
status: book?.project?.status_translate.includes("Триває")
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
let tags = [book?.project?.genres, book?.project?.tags]
|
||||
.flat()
|
||||
.map((tags) => tags?.title)
|
||||
.filter((tags) => tags);
|
||||
|
||||
if (tags.length > 0) {
|
||||
novel.genres = tags.join(", ");
|
||||
}
|
||||
|
||||
let chapters: Chapter.Item[] = [];
|
||||
const res = await fetchApi(
|
||||
"https://api.smakolykytl.site/api/user/projects/" + id + "/books",
|
||||
);
|
||||
const data = (await res.json()) as response;
|
||||
data?.books?.forEach((volume) =>
|
||||
volume?.chapters?.map((chapter) =>
|
||||
chapters.push({
|
||||
name: volume.title + " " + chapter.title,
|
||||
releaseTime: dayjs(chapter.modifiedAt).format("LLL"),
|
||||
url: site + "read/" + chapter.id,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
novel.chapters = chapters;
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const id = chapterUrl.split("/").pop();
|
||||
const result = await fetchApi(
|
||||
"https://api.smakolykytl.site/api/user/chapters/" + id,
|
||||
);
|
||||
const json = (await result.json()) as response;
|
||||
const chapterRaw: HTML[] = JSON.parse(json?.chapter?.content || "{}");
|
||||
|
||||
const chapterText = jsonToHtml(chapterRaw);
|
||||
return chapterText;
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
|
||||
const result = await fetchApi(
|
||||
"https://api.smakolykytl.site/api/user/projects",
|
||||
);
|
||||
const json = (await result.json()) as response;
|
||||
let novels: Novel.Item[] = [];
|
||||
|
||||
json?.projects
|
||||
?.filter(
|
||||
(novel) =>
|
||||
novel.title.includes(searchTerm) || String(novel.id) === searchTerm,
|
||||
)
|
||||
?.forEach((novel) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
cover: novel.image.url,
|
||||
url: site + "titles/" + novel.id,
|
||||
}),
|
||||
);
|
||||
return novels;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = fetchFile;
|
||||
|
||||
function jsonToHtml(json: HTML[], html: string = "") {
|
||||
json.forEach((element) => {
|
||||
switch (element.type) {
|
||||
case "hardBreak":
|
||||
html += "<br>";
|
||||
break;
|
||||
case "horizontalRule":
|
||||
html += "<hr>";
|
||||
break;
|
||||
case "image":
|
||||
if (element.attrs) {
|
||||
const attrs = Object.entries(element.attrs)
|
||||
.filter((attr) => attr?.[1])
|
||||
.map((attr) => `${attr[0]}="${attr[1]}"`);
|
||||
html += "<img " + attrs.join("; ") + ">";
|
||||
}
|
||||
break;
|
||||
case "paragraph":
|
||||
html +=
|
||||
"<p>" + (element.content ? jsonToHtml(element.content) : "<br>") + "</p>";
|
||||
break;
|
||||
case "text":
|
||||
html += element.text;
|
||||
break;
|
||||
default:
|
||||
html += JSON.stringify(element, null, "\t"); //maybe I missed something.
|
||||
break;
|
||||
}
|
||||
});
|
||||
return html;
|
||||
}
|
||||
|
||||
interface response {
|
||||
projects?: TopLevelProject[];
|
||||
updates?: Update[];
|
||||
project?: TopLevelProject;
|
||||
books?: BookElement[];
|
||||
chapter?: TopLevelChapter;
|
||||
}
|
||||
|
||||
interface BookElement {
|
||||
id: number;
|
||||
rank: number;
|
||||
title: string;
|
||||
chapters: PurpleChapter[];
|
||||
}
|
||||
|
||||
interface PurpleChapter {
|
||||
id: number;
|
||||
title: string;
|
||||
rank: string;
|
||||
modifiedAt: Date;
|
||||
}
|
||||
|
||||
interface TopLevelChapter {
|
||||
id: number;
|
||||
title: string;
|
||||
rank: string;
|
||||
content: string;
|
||||
modifiedAt: Date;
|
||||
book: ChapterBook;
|
||||
}
|
||||
|
||||
interface ChapterBook {
|
||||
id: number;
|
||||
rank: number;
|
||||
title: string;
|
||||
chapters: FluffyChapter[];
|
||||
project: BookProject;
|
||||
}
|
||||
|
||||
interface FluffyChapter {
|
||||
id: number;
|
||||
rank: string;
|
||||
}
|
||||
|
||||
interface BookProject {
|
||||
id: number;
|
||||
}
|
||||
|
||||
interface TopLevelProject {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
author: string;
|
||||
translator: string;
|
||||
modifiedAt: Date;
|
||||
alternatives: string;
|
||||
release: string;
|
||||
nation: string;
|
||||
status: string;
|
||||
status_translate: string;
|
||||
image: Image;
|
||||
tags?: Genre[];
|
||||
genres?: Genre[];
|
||||
}
|
||||
|
||||
interface Genre {
|
||||
id: number;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface Image {
|
||||
id: number;
|
||||
url: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface Update {
|
||||
id: number;
|
||||
title: string;
|
||||
author: string;
|
||||
translator: string;
|
||||
modifiedAt: Date;
|
||||
image: Image;
|
||||
}
|
||||
|
||||
interface HTML {
|
||||
type: string;
|
||||
content?: HTML[];
|
||||
attrs?: Attrs;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
interface Attrs {
|
||||
src: string;
|
||||
alt: string | null;
|
||||
title: string | null;
|
||||
}
|
||||
Reference in New Issue
Block a user