Prettier (#852)
* add prettier * lint-staged * prettier * upgrade husky
This commit is contained in:
+157
-150
@@ -1,16 +1,16 @@
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
class freedlit implements Plugin.PluginBase {
|
||||
id = "freedlit.space";
|
||||
name = "LitSpace";
|
||||
site = "https://freedlit.space";
|
||||
version = "1.0.0";
|
||||
icon = "src/ru/freedlit/icon.png";
|
||||
id = 'freedlit.space';
|
||||
name = 'LitSpace';
|
||||
site = 'https://freedlit.space';
|
||||
version = '1.0.0';
|
||||
icon = 'src/ru/freedlit/icon.png';
|
||||
|
||||
async popularNovels(
|
||||
page: number,
|
||||
@@ -19,73 +19,75 @@ class freedlit implements Plugin.PluginBase {
|
||||
filters,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + "/get-books/all/list/" + page + "?sort=";
|
||||
url += showLatestNovels ? "recent" : filters?.sort?.value || "popular";
|
||||
url += "&status=" + (filters?.status?.value || "all");
|
||||
url += "&access=" + (filters?.access?.value || "all");
|
||||
url += "&adult=" + (filters?.adult?.value || "hide");
|
||||
let url = this.site + '/get-books/all/list/' + page + '?sort=';
|
||||
url += showLatestNovels ? 'recent' : filters?.sort?.value || 'popular';
|
||||
url += '&status=' + (filters?.status?.value || 'all');
|
||||
url += '&access=' + (filters?.access?.value || 'all');
|
||||
url += '&adult=' + (filters?.adult?.value || 'hide');
|
||||
|
||||
if (filters?.genre?.value?.include?.length) {
|
||||
url += filters.genre.value.include
|
||||
.map((id) => "&genres_included[]=" + id)
|
||||
.join("");
|
||||
.map(id => '&genres_included[]=' + id)
|
||||
.join('');
|
||||
}
|
||||
|
||||
if (filters?.genre?.value?.exclude?.length) {
|
||||
url += filters.genre.value.exclude
|
||||
.map((id) => "&genres_excluded[]=" + id)
|
||||
.join("");
|
||||
.map(id => '&genres_excluded[]=' + id)
|
||||
.join('');
|
||||
}
|
||||
|
||||
const body = await fetchApi(url).then((res) => res.text());
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
loadedCheerio("#bookListBlock > div > div").each((index, element) => {
|
||||
const name = loadedCheerio(element).find("div > h4 > a").text()?.trim();
|
||||
loadedCheerio('#bookListBlock > div > div').each((index, element) => {
|
||||
const name = loadedCheerio(element).find('div > h4 > a').text()?.trim();
|
||||
const cover = loadedCheerio(element)
|
||||
.find("div > a > img")
|
||||
.attr("src")
|
||||
.find('div > a > img')
|
||||
.attr('src')
|
||||
?.trim();
|
||||
const url = loadedCheerio(element)
|
||||
.find("div > h4 > a")
|
||||
.attr("href")
|
||||
.find('div > h4 > a')
|
||||
.attr('href')
|
||||
?.trim();
|
||||
if (!name || !url) return;
|
||||
|
||||
novels.push({ name, cover, path: url.replace(this.site, "") });
|
||||
novels.push({ name, cover, path: url.replace(this.site, '') });
|
||||
});
|
||||
|
||||
return novels;
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const body = await fetchApi(this.site + novelPath).then((res) => res.text());
|
||||
const body = await fetchApi(this.site + novelPath).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
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")
|
||||
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(","),
|
||||
.join(','),
|
||||
};
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
|
||||
loadedCheerio("a.chapter-line").each((chapterIndex, element) => {
|
||||
const name = loadedCheerio(element).find("h6").text();
|
||||
const url = loadedCheerio(element).attr("href");
|
||||
loadedCheerio('a.chapter-line').each((chapterIndex, element) => {
|
||||
const name = loadedCheerio(element).find('h6').text();
|
||||
const url = loadedCheerio(element).attr('href');
|
||||
if (!name || !url) return;
|
||||
|
||||
const releaseDate = loadedCheerio(element).find('span[class="date"]').text();
|
||||
const releaseDate = loadedCheerio(element)
|
||||
.find('span[class="date"]')
|
||||
.text();
|
||||
chapters.push({
|
||||
name,
|
||||
path: url.replace(this.site, ""),
|
||||
path: url.replace(this.site, ''),
|
||||
releaseTime: this.parseDate(releaseDate),
|
||||
chapterNumber: chapterIndex + 1,
|
||||
});
|
||||
@@ -96,35 +98,37 @@ class freedlit implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const body = await fetchApi(this.site + chapterPath).then((res) => res.text());
|
||||
const body = await fetchApi(this.site + chapterPath).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio("div.mobile-block").remove();
|
||||
loadedCheerio("div.standart-block").remove();
|
||||
loadedCheerio('div.mobile-block').remove();
|
||||
loadedCheerio('div.standart-block').remove();
|
||||
|
||||
const chapterText = loadedCheerio('div[class="chapter"]').html() || "";
|
||||
const chapterText = loadedCheerio('div[class="chapter"]').html() || '';
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
||||
const url = this.site + "/search?query=" + searchTerm + "&type=all";
|
||||
const body = await fetchApi(url).then((res) => res.text());
|
||||
const url = this.site + '/search?query=' + searchTerm + '&type=all';
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
loadedCheerio("#bookGridBlock > div").each((index, element) => {
|
||||
const name = loadedCheerio(element).find("h4 > a").text()?.trim();
|
||||
const cover = loadedCheerio(element).find("a > img").attr("src")?.trim();
|
||||
const url = loadedCheerio(element).find("h4 > a").attr("href")?.trim();
|
||||
loadedCheerio('#bookGridBlock > div').each((index, element) => {
|
||||
const name = loadedCheerio(element).find('h4 > a').text()?.trim();
|
||||
const cover = loadedCheerio(element).find('a > img').attr('src')?.trim();
|
||||
const url = loadedCheerio(element).find('h4 > a').attr('href')?.trim();
|
||||
if (!name || !url) return;
|
||||
|
||||
novels.push({ name, cover, path: url.replace(this.site, "") });
|
||||
novels.push({ name, cover, path: url.replace(this.site, '') });
|
||||
});
|
||||
|
||||
return novels;
|
||||
}
|
||||
|
||||
parseDate = (dateString: string | undefined = "") => {
|
||||
parseDate = (dateString: string | undefined = '') => {
|
||||
const months: { [key: string]: number } = {
|
||||
января: 1,
|
||||
февраля: 2,
|
||||
@@ -140,9 +144,9 @@ class freedlit implements Plugin.PluginBase {
|
||||
декабря: 12,
|
||||
};
|
||||
|
||||
const [day, month, year] = dateString.split(" ");
|
||||
const [day, month, year] = dateString.split(' ');
|
||||
if (day && months[month] && year) {
|
||||
return dayjs(year + "-" + months[month] + "-" + day).format("LL");
|
||||
return dayjs(year + '-' + months[month] + '-' + day).format('LL');
|
||||
}
|
||||
return dateString || null;
|
||||
};
|
||||
@@ -151,124 +155,127 @@ class freedlit implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Сортировка:",
|
||||
value: "popular",
|
||||
label: 'Сортировка:',
|
||||
value: 'popular',
|
||||
options: [
|
||||
{ label: "По популярности", value: "popular" },
|
||||
{ label: "По количеству комментариев", value: "comments" },
|
||||
{ label: "По количеству лайков", value: "likes" },
|
||||
{ label: "По новизне", value: "recent" },
|
||||
{ label: "По просмотрам", value: "views" },
|
||||
{ label: 'По популярности', value: 'popular' },
|
||||
{ label: 'По количеству комментариев', value: 'comments' },
|
||||
{ label: 'По количеству лайков', value: 'likes' },
|
||||
{ label: 'По новизне', value: 'recent' },
|
||||
{ label: 'По просмотрам', value: 'views' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
genre: {
|
||||
label: "Жанры:",
|
||||
label: 'Жанры:',
|
||||
value: { include: [], exclude: [] },
|
||||
options: [
|
||||
{ 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" },
|
||||
{ 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' },
|
||||
],
|
||||
type: FilterTypes.ExcludableCheckboxGroup,
|
||||
},
|
||||
status: {
|
||||
label: "Статус:",
|
||||
value: "all",
|
||||
label: 'Статус:',
|
||||
value: 'all',
|
||||
options: [
|
||||
{ label: "Любой статус", value: "all" },
|
||||
{ label: "В процессе", value: "in-process" },
|
||||
{ label: "Завершено", value: "finished" },
|
||||
{ label: 'Любой статус', value: 'all' },
|
||||
{ label: 'В процессе', value: 'in-process' },
|
||||
{ label: 'Завершено', value: 'finished' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
access: {
|
||||
label: "Доступ:",
|
||||
value: "all",
|
||||
label: 'Доступ:',
|
||||
value: 'all',
|
||||
options: [
|
||||
{ label: "Любой доступ", value: "all" },
|
||||
{ label: "Бесплатные", value: "free" },
|
||||
{ label: "Платные", value: "paid" },
|
||||
{ label: 'Любой доступ', value: 'all' },
|
||||
{ label: 'Бесплатные', value: 'free' },
|
||||
{ label: 'Платные', value: 'paid' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
adult: {
|
||||
label: "Возрастные ограничения:",
|
||||
value: "hide",
|
||||
label: 'Возрастные ограничения:',
|
||||
value: 'hide',
|
||||
options: [
|
||||
{ label: "Скрыть 18+", value: "hide" },
|
||||
{ label: "Показать +18", value: "show" },
|
||||
{ label: 'Скрыть 18+', value: 'hide' },
|
||||
{ label: 'Показать +18', value: 'show' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
+159
-153
@@ -1,39 +1,39 @@
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { defaultCover } from '@libs/defaultCover';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const apiUrl = "https://api.author.today/";
|
||||
const token = "Bearer guest";
|
||||
const apiUrl = 'https://api.author.today/';
|
||||
const token = 'Bearer guest';
|
||||
|
||||
class AuthorToday implements Plugin.PluginBase {
|
||||
id = "AT";
|
||||
name = "Автор Тудей";
|
||||
icon = "src/ru/authortoday/icon.png";
|
||||
site = "https://author.today";
|
||||
version = "1.0.0";
|
||||
id = 'AT';
|
||||
name = 'Автор Тудей';
|
||||
icon = 'src/ru/authortoday/icon.png';
|
||||
site = 'https://author.today';
|
||||
version = '1.0.0';
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = apiUrl + "v1/catalog/search?page=" + pageNo;
|
||||
let url = apiUrl + 'v1/catalog/search?page=' + pageNo;
|
||||
if (filters?.genre?.value) {
|
||||
url += "&genre=" + filters.genre.value;
|
||||
url += '&genre=' + filters.genre.value;
|
||||
}
|
||||
|
||||
url +=
|
||||
"&sorting=" +
|
||||
(showLatestNovels ? "recent" : filters?.sort?.value || "popular");
|
||||
'&sorting=' +
|
||||
(showLatestNovels ? 'recent' : filters?.sort?.value || 'popular');
|
||||
|
||||
url += "&form=" + (filters?.form?.value || "any");
|
||||
url += "&state=" + (filters?.state?.value || "any");
|
||||
url += "&series=" + (filters?.series?.value || "any");
|
||||
url += "&access=" + (filters?.access?.value || "any");
|
||||
url += "&promo=" + (filters?.promo?.value || "hide");
|
||||
url += '&form=' + (filters?.form?.value || 'any');
|
||||
url += '&state=' + (filters?.state?.value || 'any');
|
||||
url += '&series=' + (filters?.series?.value || 'any');
|
||||
url += '&access=' + (filters?.access?.value || 'any');
|
||||
url += '&promo=' + (filters?.promo?.value || 'hide');
|
||||
|
||||
const result = await fetchApi(url, {
|
||||
headers: {
|
||||
@@ -43,15 +43,15 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
const json = (await result.json()) as response;
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
if (json.code === "NotFound") {
|
||||
if (json.code === 'NotFound') {
|
||||
return novels;
|
||||
}
|
||||
|
||||
json?.searchResults?.forEach((novel) =>
|
||||
json?.searchResults?.forEach(novel =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
cover: novel.coverUrl
|
||||
? "https://cm.author.today/content/" + novel.coverUrl
|
||||
? 'https://cm.author.today/content/' + novel.coverUrl
|
||||
: defaultCover,
|
||||
path: novel.id.toString(),
|
||||
}),
|
||||
@@ -71,24 +71,24 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: workID,
|
||||
name: book.title,
|
||||
cover: book.coverUrl ? book.coverUrl.split("?")[0] : defaultCover,
|
||||
genres: book.tags?.join(", "),
|
||||
summary: "",
|
||||
cover: book.coverUrl ? book.coverUrl.split('?')[0] : defaultCover,
|
||||
genres: book.tags?.join(', '),
|
||||
summary: '',
|
||||
author:
|
||||
book.originalAuthor ||
|
||||
book.authorFIO ||
|
||||
book.coAuthorFIO ||
|
||||
book.secondCoAuthorFIO ||
|
||||
book.translator ||
|
||||
"",
|
||||
'',
|
||||
status: book.isFinished ? NovelStatus.Completed : NovelStatus.Ongoing,
|
||||
};
|
||||
|
||||
if (book.annotation) {
|
||||
novel.summary += book.annotation + "\n";
|
||||
novel.summary += book.annotation + '\n';
|
||||
}
|
||||
if (book.authorNotes) {
|
||||
novel.summary += "Примечания автора:\n" + book.authorNotes;
|
||||
novel.summary += 'Примечания автора:\n' + book.authorNotes;
|
||||
}
|
||||
|
||||
const chaptersRaw = await fetchApi(`${apiUrl}v1/work/${workID}/content`, {
|
||||
@@ -103,11 +103,11 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
chaptersJSON.forEach((chapter, chapterIndex) => {
|
||||
if (chapter.isAvailable && !chapter.isDraft) {
|
||||
chapters.push({
|
||||
name: chapter.title || "Глава " + (chapterIndex + 1),
|
||||
path: workID + "/" + chapter.id,
|
||||
name: chapter.title || 'Глава ' + (chapterIndex + 1),
|
||||
path: workID + '/' + chapter.id,
|
||||
releaseTime: dayjs(
|
||||
chapter.publishTime || chapter.lastModificationTime,
|
||||
).format("LLL"),
|
||||
).format('LLL'),
|
||||
chapterNumber: (chapter.sortOrder || chapterIndex) + 1,
|
||||
});
|
||||
}
|
||||
@@ -118,7 +118,7 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const [workID, chapterID] = chapterPath.split("/");
|
||||
const [workID, chapterID] = chapterPath.split('/');
|
||||
const result = await fetchApi(
|
||||
apiUrl + `v1/work/${workID}/chapter/${chapterID}/text`,
|
||||
{
|
||||
@@ -130,11 +130,11 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
const json = (await result.json()) as encryptedСhapter;
|
||||
|
||||
if (json.code) {
|
||||
return json.code + "\n" + json?.message;
|
||||
return json.code + '\n' + json?.message;
|
||||
}
|
||||
|
||||
const key = json.key.split("").reverse().join("") + "@_@";
|
||||
let text = "";
|
||||
const key = json.key.split('').reverse().join('') + '@_@';
|
||||
let text = '';
|
||||
|
||||
for (let i = 0; i < json.text.length; i++) {
|
||||
text += String.fromCharCode(
|
||||
@@ -143,10 +143,10 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
const loadedCheerio = parseHTML(text);
|
||||
loadedCheerio("img").each((index, element) => {
|
||||
if (!loadedCheerio(element).attr("src")?.startsWith("http")) {
|
||||
const src = loadedCheerio(element).attr("src");
|
||||
loadedCheerio(element).attr("src", this.site + src);
|
||||
loadedCheerio('img').each((index, element) => {
|
||||
if (!loadedCheerio(element).attr('src')?.startsWith('http')) {
|
||||
const src = loadedCheerio(element).attr('src');
|
||||
loadedCheerio(element).attr('src', this.site + src);
|
||||
}
|
||||
});
|
||||
const chapterText = loadedCheerio.html();
|
||||
@@ -158,23 +158,23 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
pageNo: number | undefined = 1,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url =
|
||||
this.site + "/search?category=works&q=" + searchTerm + "&page=" + pageNo;
|
||||
const result = await fetchApi(url).then((res) => res.text());
|
||||
this.site + '/search?category=works&q=' + searchTerm + '&page=' + pageNo;
|
||||
const result = await fetchApi(url).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(result);
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio("a.work-row").each((index, element) => {
|
||||
loadedCheerio('a.work-row').each((index, element) => {
|
||||
const name = loadedCheerio(element)
|
||||
.find('h4[class="work-title"]')
|
||||
.text()
|
||||
.trim();
|
||||
let cover = loadedCheerio(element).find("img").attr("data-src");
|
||||
const path = loadedCheerio(element).attr("href");
|
||||
let cover = loadedCheerio(element).find('img').attr('data-src');
|
||||
const path = loadedCheerio(element).attr('href');
|
||||
|
||||
cover = cover ? cover.split("?")[0] : defaultCover;
|
||||
cover = cover ? cover.split('?')[0] : defaultCover;
|
||||
|
||||
if (!path) return;
|
||||
novels.push({ name, cover, path: path.replace("/work/", "") });
|
||||
novels.push({ name, cover, path: path.replace('/work/', '') });
|
||||
});
|
||||
|
||||
return novels;
|
||||
@@ -182,143 +182,149 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
|
||||
fetchImage = fetchFile;
|
||||
resolveUrl = (path: string, isNovel?: boolean) =>
|
||||
isNovel ? this.site + "/work/" + path : this.site + "/reader/" + path;
|
||||
isNovel ? this.site + '/work/' + path : this.site + '/reader/' + path;
|
||||
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Сортировка",
|
||||
value: "popular",
|
||||
label: 'Сортировка',
|
||||
value: 'popular',
|
||||
options: [
|
||||
{ label: "По популярности", value: "popular" },
|
||||
{ label: "По количеству лайков", value: "likes" },
|
||||
{ label: "По комментариям", value: "comments" },
|
||||
{ label: "По новизне", value: "recent" },
|
||||
{ label: "По просмотрам", value: "views" },
|
||||
{ label: "Набирающие популярность", value: "trending" },
|
||||
{ label: 'По популярности', value: 'popular' },
|
||||
{ label: 'По количеству лайков', value: 'likes' },
|
||||
{ label: 'По комментариям', value: 'comments' },
|
||||
{ label: 'По новизне', value: 'recent' },
|
||||
{ label: 'По просмотрам', value: 'views' },
|
||||
{ label: 'Набирающие популярность', value: 'trending' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
genre: {
|
||||
label: "Жанры",
|
||||
value: "",
|
||||
label: 'Жанры',
|
||||
value: '',
|
||||
options: [
|
||||
{ label: "Все", value: "" },
|
||||
{ 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" },
|
||||
{ label: 'Все', value: '' },
|
||||
{ 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' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
form: {
|
||||
label: "Форма произведения",
|
||||
value: "any",
|
||||
label: 'Форма произведения',
|
||||
value: 'any',
|
||||
options: [
|
||||
{ label: "Любой", value: "any" },
|
||||
{ label: "Перевод", value: "translation" },
|
||||
{ label: "Повесть", value: "tale" },
|
||||
{ label: "Рассказ", value: "story" },
|
||||
{ label: "Роман", value: "novel" },
|
||||
{ label: "Сборник поэзии", value: "poetry" },
|
||||
{ label: "Сборник рассказов", value: "story-book" },
|
||||
{ label: 'Любой', value: 'any' },
|
||||
{ label: 'Перевод', value: 'translation' },
|
||||
{ label: 'Повесть', value: 'tale' },
|
||||
{ label: 'Рассказ', value: 'story' },
|
||||
{ label: 'Роман', value: 'novel' },
|
||||
{ label: 'Сборник поэзии', value: 'poetry' },
|
||||
{ label: 'Сборник рассказов', value: 'story-book' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
state: {
|
||||
label: "Статус произведения",
|
||||
value: "any",
|
||||
label: 'Статус произведения',
|
||||
value: 'any',
|
||||
options: [
|
||||
{ label: "Любой статус", value: "any" },
|
||||
{ label: "В процессе", value: "in-progress" },
|
||||
{ label: "Завершено", value: "finished" },
|
||||
{ label: 'Любой статус', value: 'any' },
|
||||
{ label: 'В процессе', value: 'in-progress' },
|
||||
{ label: 'Завершено', value: 'finished' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
series: {
|
||||
label: "Статус цикла",
|
||||
value: "any",
|
||||
label: 'Статус цикла',
|
||||
value: 'any',
|
||||
options: [
|
||||
{ label: "Не важно", value: "any" },
|
||||
{ label: "Вне цикла", value: "out" },
|
||||
{ label: "Цикл завершен", value: "finished" },
|
||||
{ label: "Цикл не завершен", value: "unfinished" },
|
||||
{ label: 'Не важно', value: 'any' },
|
||||
{ label: 'Вне цикла', value: 'out' },
|
||||
{ label: 'Цикл завершен', value: 'finished' },
|
||||
{ label: 'Цикл не завершен', value: 'unfinished' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
access: {
|
||||
label: "Тип доступа",
|
||||
value: "any",
|
||||
label: 'Тип доступа',
|
||||
value: 'any',
|
||||
options: [
|
||||
{ label: "Любой", value: "any" },
|
||||
{ label: "Платный", value: "paid" },
|
||||
{ label: "Бесплатный", value: "free" },
|
||||
{ label: 'Любой', value: 'any' },
|
||||
{ label: 'Платный', value: 'paid' },
|
||||
{ label: 'Бесплатный', value: 'free' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
promo: {
|
||||
label: "Промо-фрагмент",
|
||||
value: "hide",
|
||||
label: 'Промо-фрагмент',
|
||||
value: 'hide',
|
||||
options: [
|
||||
{ label: "Скрывать", value: "hide" },
|
||||
{ label: "Показывать", value: "show" },
|
||||
{ label: 'Скрывать', value: 'hide' },
|
||||
{ label: 'Показывать', value: 'show' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
+106
-87
@@ -1,16 +1,16 @@
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
class Bookriver implements Plugin.PluginBase {
|
||||
id = "bookriver";
|
||||
name = "Bookriver";
|
||||
site = "https://bookriver.ru";
|
||||
version = "1.0.0";
|
||||
icon = "src/ru/bookriver/icon.png";
|
||||
id = 'bookriver';
|
||||
name = 'Bookriver';
|
||||
site = 'https://bookriver.ru';
|
||||
version = '1.0.0';
|
||||
icon = 'src/ru/bookriver/icon.png';
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
@@ -19,23 +19,23 @@ class Bookriver implements Plugin.PluginBase {
|
||||
filters,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + "/genre?page=" + pageNo + "&perPage=24&sortingType=";
|
||||
let url = this.site + '/genre?page=' + pageNo + '&perPage=24&sortingType=';
|
||||
url += showLatestNovels
|
||||
? "last-update"
|
||||
: filters?.sort?.value || "bestseller";
|
||||
? 'last-update'
|
||||
: filters?.sort?.value || 'bestseller';
|
||||
|
||||
if (filters?.genres?.value?.length) {
|
||||
url += "&g=" + filters.genres.value.join(",");
|
||||
url += '&g=' + filters.genres.value.join(',');
|
||||
}
|
||||
|
||||
const result = await fetchApi(url).then((res) => res.text());
|
||||
const result = await fetchApi(url).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(result);
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
|
||||
const jsonRaw = loadedCheerio('#__NEXT_DATA__').html();
|
||||
if (jsonRaw) {
|
||||
const json: response = JSON.parse(jsonRaw);
|
||||
json.props.pageProps.state.pagesFilter?.genre?.books?.forEach((novel) =>
|
||||
json.props.pageProps.state.pagesFilter?.genre?.books?.forEach(novel =>
|
||||
novels.push({
|
||||
name: novel.name,
|
||||
cover: novel.coverImages[0].url,
|
||||
@@ -47,23 +47,24 @@ class Bookriver implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const result = await fetchApi(this.resolveUrl(novelPath, true))
|
||||
.then((res) => res.text());
|
||||
const result = await fetchApi(this.resolveUrl(novelPath, true)).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
const loadedCheerio = parseHTML(result);
|
||||
|
||||
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
|
||||
const json: response = JSON.parse(jsonRaw || "{}");
|
||||
const jsonRaw = loadedCheerio('#__NEXT_DATA__').html();
|
||||
const json: response = JSON.parse(jsonRaw || '{}');
|
||||
const book = json.props.pageProps.state.book?.bookPage;
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: book?.name || "",
|
||||
name: book?.name || '',
|
||||
cover: book?.coverImages[0].url,
|
||||
summary: book?.annotation,
|
||||
author: book?.author?.name,
|
||||
genres: book?.tags?.map((tag) => tag.name).join(", "),
|
||||
genres: book?.tags?.map(tag => tag.name).join(', '),
|
||||
status:
|
||||
book?.statusComplete === "writing"
|
||||
book?.statusComplete === 'writing'
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
@@ -73,10 +74,10 @@ class Bookriver implements Plugin.PluginBase {
|
||||
if (chapter.available) {
|
||||
chapters.push({
|
||||
name: chapter.name,
|
||||
path: book?.slug + "/" + chapter.chapterId,
|
||||
path: book?.slug + '/' + chapter.chapterId,
|
||||
releaseTime: dayjs(
|
||||
chapter.firstPublishedAt || chapter.createdAt || undefined,
|
||||
).format("LLL"),
|
||||
).format('LLL'),
|
||||
chapterNumber: chapterIndex + 1,
|
||||
});
|
||||
}
|
||||
@@ -86,13 +87,13 @@ class Bookriver implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const url = "https://api.bookriver.ru/api/v1/books/chapter/text/";
|
||||
const result = await fetchApi(url + chapterPath.split("/").pop());
|
||||
const url = 'https://api.bookriver.ru/api/v1/books/chapter/text/';
|
||||
const result = await fetchApi(url + chapterPath.split('/').pop());
|
||||
const json = (await result.json()) as responseChapter;
|
||||
|
||||
let chapterText = json.data.content || "Конец произведения";
|
||||
let chapterText = json.data.content || 'Конец произведения';
|
||||
if (json.data?.audio?.available) {
|
||||
chapterText += "\n" + json.data.audio.url;
|
||||
chapterText += '\n' + json.data.audio.url;
|
||||
}
|
||||
|
||||
return chapterText;
|
||||
@@ -103,13 +104,16 @@ class Bookriver implements Plugin.PluginBase {
|
||||
pageNo: number | undefined = 1,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url =
|
||||
"https://api.bookriver.ru/api/v1/search/autocomplete?keyword=" + searchTerm +
|
||||
"&page=" + pageNo + "&perPage=10";
|
||||
'https://api.bookriver.ru/api/v1/search/autocomplete?keyword=' +
|
||||
searchTerm +
|
||||
'&page=' +
|
||||
pageNo +
|
||||
'&perPage=10';
|
||||
const result = await fetchApi(url);
|
||||
const json = (await result.json()) as responseSearch;
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
json.data?.books?.forEach((novel) =>
|
||||
json.data?.books?.forEach(novel =>
|
||||
novels.push({
|
||||
name: novel.name,
|
||||
cover: novel.coverImages[0].url,
|
||||
@@ -122,69 +126,84 @@ class Bookriver implements Plugin.PluginBase {
|
||||
|
||||
fetchImage = fetchFile;
|
||||
resolveUrl = (path: string, isNovel?: boolean) =>
|
||||
this.site + (isNovel ? "/book/" : "/reader/") + path;
|
||||
this.site + (isNovel ? '/book/' : '/reader/') + path;
|
||||
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Сортировка",
|
||||
value: "bestseller",
|
||||
label: 'Сортировка',
|
||||
value: 'bestseller',
|
||||
options: [
|
||||
{ label: "Бестселлеры", value: "bestseller" },
|
||||
{ label: "Дате добавления", value: "newest" },
|
||||
{ label: "Дате обновления", value: "last-update" },
|
||||
{ label: 'Бестселлеры', value: 'bestseller' },
|
||||
{ label: 'Дате добавления', value: 'newest' },
|
||||
{ label: 'Дате обновления', value: 'last-update' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
genres: {
|
||||
label: "жанры",
|
||||
label: 'жанры',
|
||||
value: [],
|
||||
options: [
|
||||
{ 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" },
|
||||
{ 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' },
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
|
||||
+2496
-2482
File diff suppressed because it is too large
Load Diff
+161
-148
@@ -1,16 +1,16 @@
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
class Jaomix implements Plugin.PluginBase {
|
||||
id = "jaomix.ru";
|
||||
name = "Jaomix";
|
||||
site = "https://jaomix.ru";
|
||||
version = "1.0.0";
|
||||
icon = "src/ru/jaomix/icon.png";
|
||||
id = 'jaomix.ru';
|
||||
name = 'Jaomix';
|
||||
site = 'https://jaomix.ru';
|
||||
version = '1.0.0';
|
||||
icon = 'src/ru/jaomix/icon.png';
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
@@ -19,109 +19,116 @@ class Jaomix implements Plugin.PluginBase {
|
||||
filters,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + "/?searchrn";
|
||||
let url = this.site + '/?searchrn';
|
||||
|
||||
if (filters?.lang?.value?.length) {
|
||||
url += filters.lang.value
|
||||
.map((lang, idx) => `&lang[${idx}]=${lang}`)
|
||||
.join("");
|
||||
.join('');
|
||||
}
|
||||
|
||||
if (filters?.genre?.value?.include?.length) {
|
||||
url += filters.genre.value.include
|
||||
.map((genre, idx) => `&genre[${idx}]=${genre}`)
|
||||
.join("");
|
||||
.join('');
|
||||
}
|
||||
|
||||
if (filters?.genre?.value?.exclude?.length) {
|
||||
url += filters.genre.value.exclude
|
||||
.map((genre, idx) => `&delgenre[${idx}]=del ${genre}`)
|
||||
.join("");
|
||||
.join('');
|
||||
}
|
||||
|
||||
url += "&sortcountchapt=" + (filters?.sortcountchapt?.value || "1");
|
||||
url += "&sortdaycreate=" + (filters?.sortdaycreate?.value || "1");
|
||||
url += "&sortby=" + (showLatestNovels ? "upd" : filters?.sortby?.value || "topweek");
|
||||
url += "&gpage=" + pageNo;
|
||||
url += '&sortcountchapt=' + (filters?.sortcountchapt?.value || '1');
|
||||
url += '&sortdaycreate=' + (filters?.sortdaycreate?.value || '1');
|
||||
url +=
|
||||
'&sortby=' +
|
||||
(showLatestNovels ? 'upd' : filters?.sortby?.value || 'topweek');
|
||||
url += '&gpage=' + pageNo;
|
||||
|
||||
const body = await fetchApi(url).then((res) => res.text());
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
loadedCheerio('div[class="block-home"] > div[class="one"]')
|
||||
.each((index, element) => {
|
||||
loadedCheerio('div[class="block-home"] > div[class="one"]').each(
|
||||
(index, element) => {
|
||||
const name = loadedCheerio(element)
|
||||
.find('div[class="img-home"] > a')
|
||||
.attr("title");
|
||||
.attr('title');
|
||||
const cover = loadedCheerio(element)
|
||||
.find('div[class="img-home"] > a > img')
|
||||
.attr("src")
|
||||
?.replace("-150x150", "");
|
||||
.attr('src')
|
||||
?.replace('-150x150', '');
|
||||
const url = loadedCheerio(element)
|
||||
.find('div[class="img-home"] > a')
|
||||
.attr("href");
|
||||
.attr('href');
|
||||
|
||||
if (!name || !url) return;
|
||||
|
||||
novels.push({ name, cover, path: url.replace(this.site, "") });
|
||||
});
|
||||
novels.push({ name, cover, path: url.replace(this.site, '') });
|
||||
},
|
||||
);
|
||||
|
||||
return novels;
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const body = await fetchApi(this.site + novelPath).then((res) => res.text());
|
||||
const body = await fetchApi(this.site + novelPath).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio('div[class="desc-book"] > h1').text().trim(),
|
||||
cover: loadedCheerio('div[class="img-book"] > img').attr("src"),
|
||||
cover: loadedCheerio('div[class="img-book"] > img').attr('src'),
|
||||
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("продолжается")
|
||||
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: Plugin.ChapterItem[] = [];
|
||||
const totalChapters = loadedCheerio(".download-chapter div.title").length;
|
||||
const totalChapters = loadedCheerio('.download-chapter div.title').length;
|
||||
|
||||
loadedCheerio(".download-chapter div.title").each((chapterIndex, element) => {
|
||||
const name = loadedCheerio(element).find("a").attr("title");
|
||||
const url = loadedCheerio(element).find("a").attr("href");
|
||||
if (!name || !url) return;
|
||||
loadedCheerio('.download-chapter div.title').each(
|
||||
(chapterIndex, element) => {
|
||||
const name = loadedCheerio(element).find('a').attr('title');
|
||||
const url = loadedCheerio(element).find('a').attr('href');
|
||||
if (!name || !url) return;
|
||||
|
||||
const releaseDate = loadedCheerio(element).find("time").text();
|
||||
chapters.push({
|
||||
name,
|
||||
path: url.replace(this.site, ""),
|
||||
releaseTime: this.parseDate(releaseDate),
|
||||
chapterNumber: totalChapters - chapterIndex,
|
||||
});
|
||||
});
|
||||
const releaseDate = loadedCheerio(element).find('time').text();
|
||||
chapters.push({
|
||||
name,
|
||||
path: url.replace(this.site, ''),
|
||||
releaseTime: this.parseDate(releaseDate),
|
||||
chapterNumber: totalChapters - chapterIndex,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
novel.chapters = chapters.reverse();
|
||||
return novel;
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const body = await fetchApi(this.site + chapterPath).then((res) => res.text());
|
||||
const body = await fetchApi(this.site + chapterPath).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio('div[class="adblock-service"]').remove();
|
||||
const chapterText = loadedCheerio('div[class="entry-content"]').html();
|
||||
|
||||
return chapterText || "";
|
||||
return chapterText || '';
|
||||
}
|
||||
|
||||
async searchNovels(
|
||||
@@ -129,34 +136,38 @@ class Jaomix implements Plugin.PluginBase {
|
||||
pageNo: number | undefined = 1,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url =
|
||||
this.site + "/?searchrn=" + searchTerm +
|
||||
"&but=Поиск по названию&sortby=upd&gpage=" + pageNo;
|
||||
const body = await fetchApi(url).then((res) => res.text());
|
||||
this.site +
|
||||
'/?searchrn=' +
|
||||
searchTerm +
|
||||
'&but=Поиск по названию&sortby=upd&gpage=' +
|
||||
pageNo;
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
loadedCheerio('div[class="block-home"] > div[class="one"]')
|
||||
.each((index, element) => {
|
||||
loadedCheerio('div[class="block-home"] > div[class="one"]').each(
|
||||
(index, element) => {
|
||||
const name = loadedCheerio(element)
|
||||
.find('div[class="img-home"] > a')
|
||||
.attr("title");
|
||||
.attr('title');
|
||||
const cover = loadedCheerio(element)
|
||||
.find('div[class="img-home"] > a > img')
|
||||
.attr("src")
|
||||
?.replace("-150x150", "");
|
||||
.attr('src')
|
||||
?.replace('-150x150', '');
|
||||
const url = loadedCheerio(element)
|
||||
.find('div[class="img-home"] > a')
|
||||
.attr("href");
|
||||
.attr('href');
|
||||
|
||||
if (!name || !url) return;
|
||||
|
||||
novels.push({ name, cover, path: url.replace(this.site, "") });
|
||||
});
|
||||
novels.push({ name, cover, path: url.replace(this.site, '') });
|
||||
},
|
||||
);
|
||||
|
||||
return novels;
|
||||
}
|
||||
|
||||
parseDate = (dateString: string | undefined = "") => {
|
||||
parseDate = (dateString: string | undefined = '') => {
|
||||
const months: { [key: string]: number } = {
|
||||
Янв: 1,
|
||||
Фев: 2,
|
||||
@@ -172,9 +183,11 @@ class Jaomix implements Plugin.PluginBase {
|
||||
Дек: 12,
|
||||
};
|
||||
|
||||
const [time, day, month, year] = dateString.split(" ");
|
||||
const [time, day, month, year] = dateString.split(' ');
|
||||
if (time && day && months[month] && year) {
|
||||
return dayjs(year + "-" + months[month] + "-" + day + " " + time).format("LLL");
|
||||
return dayjs(year + '-' + months[month] + '-' + day + ' ' + time).format(
|
||||
'LLL',
|
||||
);
|
||||
}
|
||||
|
||||
return dateString || null;
|
||||
@@ -184,110 +197,110 @@ class Jaomix implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
sortby: {
|
||||
label: "Сортировка:",
|
||||
value: "topweek",
|
||||
label: 'Сортировка:',
|
||||
value: 'topweek',
|
||||
options: [
|
||||
{ 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" },
|
||||
{ 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' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
sortdaycreate: {
|
||||
label: "Дата добавления:",
|
||||
value: "1",
|
||||
label: 'Дата добавления:',
|
||||
value: '1',
|
||||
options: [
|
||||
{ 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" },
|
||||
{ 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' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
sortcountchapt: {
|
||||
label: "Количество глав:",
|
||||
value: "1",
|
||||
label: 'Количество глав:',
|
||||
value: '1',
|
||||
options: [
|
||||
{ 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" },
|
||||
{ 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' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
genre: {
|
||||
label: "Жанры:",
|
||||
label: 'Жанры:',
|
||||
value: { include: [], exclude: [] },
|
||||
options: [
|
||||
{ label: "Боевые Искусства", value: "Боевые Искусства" },
|
||||
{ label: "Виртуальный Мир", value: "Виртуальный Мир" },
|
||||
{ label: "Гарем", value: "Гарем" },
|
||||
{ label: "Детектив", value: "Детектив" },
|
||||
{ label: "Драма", value: "Драма" },
|
||||
{ label: "Игра", value: "Игра" },
|
||||
{ label: "Истории из жизни", value: "Истории из жизни" },
|
||||
{ label: "Исторический", value: "Исторический" },
|
||||
{ label: "История", value: "История" },
|
||||
{ label: "Исэкай", value: "Исэкай" },
|
||||
{ label: "Комедия", value: "Комедия" },
|
||||
{ label: "Меха", value: "Меха" },
|
||||
{ label: "Мистика", value: "Мистика" },
|
||||
{ label: "Научная Фантастика", value: "Научная Фантастика" },
|
||||
{ label: "Повседневность", value: "Повседневность" },
|
||||
{ label: "Постапокалипсис", value: "Постапокалипсис" },
|
||||
{ label: "Приключения", value: "Приключения" },
|
||||
{ label: "Психология", value: "Психология" },
|
||||
{ label: "Романтика", value: "Романтика" },
|
||||
{ label: "Сверхъестественное", value: "Сверхъестественное" },
|
||||
{ label: "Сёнэн", value: "Сёнэн" },
|
||||
{ label: "Сёнэн-ай", value: "Сёнэн-ай" },
|
||||
{ label: "Спорт", value: "Спорт" },
|
||||
{ label: "Сэйнэн", value: "Сэйнэн" },
|
||||
{ label: "Сюаньхуа", value: "Сюаньхуа" },
|
||||
{ label: "Трагедия", value: "Трагедия" },
|
||||
{ label: "Триллер", value: "Триллер" },
|
||||
{ label: "Фантастика", value: "Фантастика" },
|
||||
{ label: "Фэнтези", value: "Фэнтези" },
|
||||
{ label: "Хоррор", value: "Хоррор" },
|
||||
{ label: "Школьная жизнь", value: "Школьная жизнь" },
|
||||
{ label: "Шоунен", value: "Шоунен" },
|
||||
{ label: "Экшн", value: "Экшн" },
|
||||
{ label: "Этти", value: "Этти" },
|
||||
{ label: "Юри", value: "Юри" },
|
||||
{ label: "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" },
|
||||
{ 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' },
|
||||
],
|
||||
type: FilterTypes.ExcludableCheckboxGroup,
|
||||
},
|
||||
lang: {
|
||||
label: "Выбрать языки:",
|
||||
label: 'Выбрать языки:',
|
||||
value: [],
|
||||
options: [
|
||||
{ label: "Английский", value: "Английский" },
|
||||
{ label: "Китайский", value: "Китайский" },
|
||||
{ label: "Корейский", value: "Корейский" },
|
||||
{ label: "Японский", value: "Японский" },
|
||||
{ label: 'Английский', value: 'Английский' },
|
||||
{ label: 'Китайский', value: 'Китайский' },
|
||||
{ label: 'Корейский', value: 'Корейский' },
|
||||
{ label: 'Японский', value: 'Японский' },
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
|
||||
+100
-97
@@ -1,61 +1,64 @@
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { defaultCover } from '@libs/defaultCover';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
|
||||
const statusKey: { [key: string]: string } = {
|
||||
"0": NovelStatus.Unknown,
|
||||
"1": NovelStatus.Ongoing,
|
||||
"2": NovelStatus.Completed,
|
||||
"3": NovelStatus.OnHiatus,
|
||||
"4": NovelStatus.Cancelled,
|
||||
'0': NovelStatus.Unknown,
|
||||
'1': NovelStatus.Ongoing,
|
||||
'2': NovelStatus.Completed,
|
||||
'3': NovelStatus.OnHiatus,
|
||||
'4': NovelStatus.Cancelled,
|
||||
};
|
||||
|
||||
class Neobook implements Plugin.PluginBase {
|
||||
id = "neobook";
|
||||
name = "Neobook";
|
||||
site = "https://neobook.org";
|
||||
version = "1.0.0";
|
||||
icon = "src/ru/neobook/icon.png";
|
||||
id = 'neobook';
|
||||
name = 'Neobook';
|
||||
site = 'https://neobook.org';
|
||||
version = '1.0.0';
|
||||
icon = 'src/ru/neobook/icon.png';
|
||||
|
||||
async fetchNovels(
|
||||
page: number,
|
||||
{ filters, showLatestNovels }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
{
|
||||
filters,
|
||||
showLatestNovels,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
searchTerm?: string,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const formData = new FormData();
|
||||
formData.append("version", "4.0");
|
||||
formData.append("uid", "0");
|
||||
formData.append("utoken", "");
|
||||
formData.append("resource", "general");
|
||||
formData.append("action", "get_bundle");
|
||||
formData.append("bundle", "bundle_books");
|
||||
formData.append("target", "feed");
|
||||
formData.append("page", page.toString());
|
||||
formData.append("filter_category_id", filters?.category?.value || "0");
|
||||
formData.append("filter_completed", "-1");
|
||||
formData.append("filter_search", searchTerm || "");
|
||||
formData.append("filter_tags", filters?.tags?.value || "");
|
||||
formData.append('version', '4.0');
|
||||
formData.append('uid', '0');
|
||||
formData.append('utoken', '');
|
||||
formData.append('resource', 'general');
|
||||
formData.append('action', 'get_bundle');
|
||||
formData.append('bundle', 'bundle_books');
|
||||
formData.append('target', 'feed');
|
||||
formData.append('page', page.toString());
|
||||
formData.append('filter_category_id', filters?.category?.value || '0');
|
||||
formData.append('filter_completed', '-1');
|
||||
formData.append('filter_search', searchTerm || '');
|
||||
formData.append('filter_tags', filters?.tags?.value || '');
|
||||
formData.append(
|
||||
"filter_sort",
|
||||
showLatestNovels ? "new" : filters?.sort?.value || "popular",
|
||||
'filter_sort',
|
||||
showLatestNovels ? 'new' : filters?.sort?.value || 'popular',
|
||||
);
|
||||
formData.append("filter_timeread", filters?.timeread?.value || "0-999999");
|
||||
formData.append('filter_timeread', filters?.timeread?.value || '0-999999');
|
||||
|
||||
const result = await fetchApi("https://api.neobook.org/", {
|
||||
method: "post",
|
||||
const result = await fetchApi('https://api.neobook.org/', {
|
||||
method: 'post',
|
||||
body: formData,
|
||||
});
|
||||
const json = (await result.json()) as { bundle_books: BundleBooks };
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
if (json.bundle_books.feed?.length) {
|
||||
json.bundle_books.feed?.forEach((novel) =>
|
||||
json.bundle_books.feed?.forEach(novel =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
cover: novel?.attachment?.image?.m || defaultCover,
|
||||
path: novel.token + "/",
|
||||
path: novel.token + '/',
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -77,12 +80,13 @@ class Neobook implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const body = await fetchApi(this.resolveUrl(novelPath, true))
|
||||
.then((res) => res.text());
|
||||
const body = await fetchApi(this.resolveUrl(novelPath, true)).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: "",
|
||||
name: '',
|
||||
cover: defaultCover,
|
||||
};
|
||||
|
||||
@@ -91,22 +95,22 @@ class Neobook implements Plugin.PluginBase {
|
||||
const book = JSON.parse(bookRaw[1]) as Novels;
|
||||
|
||||
novel.name = book.title;
|
||||
novel.summary = book.text?.replace?.(/<br>/g, "\n") || book.text_fix;
|
||||
novel.summary = book.text?.replace?.(/<br>/g, '\n') || book.text_fix;
|
||||
novel.author =
|
||||
book.user && book.user.firstname && book.user.lastname
|
||||
? book.user.firstname + " " + book.user.lastname
|
||||
: book.user?.initials || "";
|
||||
novel.status = statusKey[book.status || "0"] || NovelStatus.Unknown;
|
||||
? book.user.firstname + ' ' + book.user.lastname
|
||||
: book.user?.initials || '';
|
||||
novel.status = statusKey[book.status || '0'] || NovelStatus.Unknown;
|
||||
|
||||
if (book.attachment?.image?.m) novel.cover = book.attachment.image.m;
|
||||
if (book.tags?.length) novel.genres = book.tags.join(",");
|
||||
if (book.tags?.length) novel.genres = book.tags.join(',');
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
|
||||
book.chapters?.forEach((chapter, chapterIndex) => {
|
||||
if (chapter.access == "1" && chapter.status == "1") {
|
||||
if (chapter.access == '1' && chapter.status == '1') {
|
||||
chapters.push({
|
||||
name: chapter.title || "Глава " + (chapterIndex + 1),
|
||||
name: chapter.title || 'Глава ' + (chapterIndex + 1),
|
||||
path: `?book=${book.token}&chapter=${chapter.token}`,
|
||||
releaseTime: null,
|
||||
chapterNumber: Number(chapter.sort) || chapterIndex + 1,
|
||||
@@ -120,19 +124,18 @@ class Neobook implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const body = await fetchApi(this.resolveUrl(chapterPath))
|
||||
.then((res) => res.text());
|
||||
const body = await fetchApi(this.resolveUrl(chapterPath)).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
|
||||
const bookRaw = body.match(/var data = ({.*?});/);
|
||||
let chapterText = "";
|
||||
let chapterText = '';
|
||||
|
||||
if (bookRaw instanceof Array && bookRaw.length >= 2) {
|
||||
const book = JSON.parse(bookRaw[1]) as Novels;
|
||||
const token = chapterPath.split("=")[2];
|
||||
const chapter = book.chapters?.find?.(
|
||||
(chapter) => chapter.token == token,
|
||||
);
|
||||
chapterText = (chapter?.data?.html || "").replace(/<br>/g, "");
|
||||
const token = chapterPath.split('=')[2];
|
||||
const chapter = book.chapters?.find?.(chapter => chapter.token == token);
|
||||
chapterText = (chapter?.data?.html || '').replace(/<br>/g, '');
|
||||
}
|
||||
|
||||
return chapterText;
|
||||
@@ -140,67 +143,67 @@ class Neobook implements Plugin.PluginBase {
|
||||
|
||||
fetchImage = fetchFile;
|
||||
resolveUrl = (path: string, isNovel?: boolean) =>
|
||||
this.site + (isNovel ? "/book/" : "/reader/") + path;
|
||||
this.site + (isNovel ? '/book/' : '/reader/') + path;
|
||||
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Сортировка:",
|
||||
value: "popular",
|
||||
label: 'Сортировка:',
|
||||
value: 'popular',
|
||||
options: [
|
||||
{ label: "Сначала популярные", value: "popular" },
|
||||
{ label: "Сначала новые", value: "new" },
|
||||
{ label: "В случайном порядке", value: "rand" },
|
||||
{ label: 'Сначала популярные', value: 'popular' },
|
||||
{ label: 'Сначала новые', value: 'new' },
|
||||
{ label: 'В случайном порядке', value: 'rand' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
timeread: {
|
||||
label: "Время прочтения:",
|
||||
value: "0-999999",
|
||||
label: 'Время прочтения:',
|
||||
value: '0-999999',
|
||||
options: [
|
||||
{ label: "Все", value: "0-999999" },
|
||||
{ label: "Менее 15 минут", value: "0-900" },
|
||||
{ label: "15-30 минут", value: "900-1800" },
|
||||
{ label: "30-60 минут", value: "1800-3600" },
|
||||
{ label: "1-2 часа", value: "3600-7200" },
|
||||
{ label: "Более 2 часов", value: "7200-999999" },
|
||||
{ label: 'Все', value: '0-999999' },
|
||||
{ label: 'Менее 15 минут', value: '0-900' },
|
||||
{ label: '15-30 минут', value: '900-1800' },
|
||||
{ label: '30-60 минут', value: '1800-3600' },
|
||||
{ label: '1-2 часа', value: '3600-7200' },
|
||||
{ label: 'Более 2 часов', value: '7200-999999' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
category: {
|
||||
label: "Жанр:",
|
||||
value: "",
|
||||
label: 'Жанр:',
|
||||
value: '',
|
||||
options: [
|
||||
{ label: "Все", value: "" },
|
||||
{ label: "Антиутопия", value: "10" },
|
||||
{ label: "Детектив", value: "13" },
|
||||
{ label: "Детские книги", value: "14" },
|
||||
{ label: "Драма", value: "15" },
|
||||
{ label: "Другое", value: "16" },
|
||||
{ label: "История", value: "18" },
|
||||
{ label: "Мелодрама", value: "21" },
|
||||
{ label: "Мистика", value: "22" },
|
||||
{ label: "Научная фантастика", value: "23" },
|
||||
{ label: "Нон-фикшн", value: "35" },
|
||||
{ label: "Подростки и молодежь", value: "26" },
|
||||
{ label: "Постапокалипсис", value: "27" },
|
||||
{ label: "Поэзия", value: "28" },
|
||||
{ label: "Приключения", value: "29" },
|
||||
{ label: "Рассказ", value: "34" },
|
||||
{ label: "Роман", value: "36" },
|
||||
{ label: "Творчество", value: "40" },
|
||||
{ label: "Триллер", value: "91" },
|
||||
{ label: "Ужасы", value: "90" },
|
||||
{ label: "Фантастика", value: "41" },
|
||||
{ label: "Фанфик", value: "42" },
|
||||
{ label: "Фэнтези", value: "44" },
|
||||
{ label: "Эротика", value: "46" },
|
||||
{ label: "Юмор", value: "47" },
|
||||
{ label: 'Все', value: '' },
|
||||
{ label: 'Антиутопия', value: '10' },
|
||||
{ label: 'Детектив', value: '13' },
|
||||
{ label: 'Детские книги', value: '14' },
|
||||
{ label: 'Драма', value: '15' },
|
||||
{ label: 'Другое', value: '16' },
|
||||
{ label: 'История', value: '18' },
|
||||
{ label: 'Мелодрама', value: '21' },
|
||||
{ label: 'Мистика', value: '22' },
|
||||
{ label: 'Научная фантастика', value: '23' },
|
||||
{ label: 'Нон-фикшн', value: '35' },
|
||||
{ label: 'Подростки и молодежь', value: '26' },
|
||||
{ label: 'Постапокалипсис', value: '27' },
|
||||
{ label: 'Поэзия', value: '28' },
|
||||
{ label: 'Приключения', value: '29' },
|
||||
{ label: 'Рассказ', value: '34' },
|
||||
{ label: 'Роман', value: '36' },
|
||||
{ label: 'Творчество', value: '40' },
|
||||
{ label: 'Триллер', value: '91' },
|
||||
{ label: 'Ужасы', value: '90' },
|
||||
{ label: 'Фантастика', value: '41' },
|
||||
{ label: 'Фанфик', value: '42' },
|
||||
{ label: 'Фэнтези', value: '44' },
|
||||
{ label: 'Эротика', value: '46' },
|
||||
{ label: 'Юмор', value: '47' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
tags: {
|
||||
label: "Тэги:",
|
||||
value: "",
|
||||
label: 'Тэги:',
|
||||
value: '',
|
||||
type: FilterTypes.TextInput,
|
||||
},
|
||||
} satisfies Filters;
|
||||
@@ -401,7 +404,7 @@ interface Price {
|
||||
deadline: string;
|
||||
}
|
||||
|
||||
type Currency = "NEO";
|
||||
type Currency = 'NEO';
|
||||
|
||||
interface Rating {
|
||||
value: string;
|
||||
|
||||
+65
-54
@@ -1,29 +1,33 @@
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import dayjs from "dayjs";
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
class novelOvh implements Plugin.PluginBase {
|
||||
id = "novelovh";
|
||||
name = "НовелОВХ";
|
||||
site = "https://novel.ovh";
|
||||
version = "1.0.0";
|
||||
icon = "src/ru/novelovh/icon.png";
|
||||
id = 'novelovh';
|
||||
name = 'НовелОВХ';
|
||||
site = 'https://novel.ovh';
|
||||
version = '1.0.0';
|
||||
icon = 'src/ru/novelovh/icon.png';
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + "/novel?page=" + (pageNo - 1);
|
||||
url += "&sort=" +
|
||||
(showLatestNovels ? "updatedAt" : filters?.sort?.value || "averageRating") + ",desc";
|
||||
let url = this.site + '/novel?page=' + (pageNo - 1);
|
||||
url +=
|
||||
'&sort=' +
|
||||
(showLatestNovels
|
||||
? 'updatedAt'
|
||||
: filters?.sort?.value || 'averageRating') +
|
||||
',desc';
|
||||
|
||||
const result = await fetchApi(url + "&_data=routes/reader/book/index");
|
||||
const result = await fetchApi(url + '&_data=routes/reader/book/index');
|
||||
const body = (await result.json()) as { books: BooksEntity[] };
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
body.books.forEach((novel) =>
|
||||
body.books.forEach(novel =>
|
||||
novels.push({
|
||||
name: novel.name.ru,
|
||||
cover: novel.poster,
|
||||
@@ -36,7 +40,8 @@ class novelOvh implements Plugin.PluginBase {
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const result = await fetchApi(
|
||||
this.resolveUrl(novelPath, true) + "?_data=routes/reader/book/$slug/index",
|
||||
this.resolveUrl(novelPath, true) +
|
||||
'?_data=routes/reader/book/$slug/index',
|
||||
);
|
||||
const json = (await result.json()) as responseNovel;
|
||||
|
||||
@@ -44,19 +49,19 @@ class novelOvh implements Plugin.PluginBase {
|
||||
path: novelPath,
|
||||
name: json.book.name.ru,
|
||||
cover: json.book.poster,
|
||||
genres: json.book.labels?.map?.((label) => label.name).join(","),
|
||||
genres: json.book.labels?.map?.(label => label.name).join(','),
|
||||
summary: json.book.description,
|
||||
status:
|
||||
json.book.status == "ONGOING"
|
||||
json.book.status == 'ONGOING'
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
|
||||
json.book.relations?.forEach((person) => {
|
||||
json.book.relations?.forEach(person => {
|
||||
switch (person.type) {
|
||||
case "AUTHOR":
|
||||
case 'AUTHOR':
|
||||
novel.author = person.publisher.name;
|
||||
case "ARTIST":
|
||||
case 'ARTIST':
|
||||
novel.artist = person.publisher.name;
|
||||
}
|
||||
});
|
||||
@@ -67,11 +72,14 @@ class novelOvh implements Plugin.PluginBase {
|
||||
chapters.push({
|
||||
name:
|
||||
chapter.title ||
|
||||
"Том " + (chapter.volume || 0) + " " +
|
||||
(chapter.name || "Глава " +
|
||||
(chapter.number || json.chapters.length - chapterIndex)),
|
||||
path: novelPath + "/" + chapter.id + "/0",
|
||||
releaseTime: dayjs(chapter.createdAt).format("LLL"),
|
||||
'Том ' +
|
||||
(chapter.volume || 0) +
|
||||
' ' +
|
||||
(chapter.name ||
|
||||
'Глава ' +
|
||||
(chapter.number || json.chapters.length - chapterIndex)),
|
||||
path: novelPath + '/' + chapter.id + '/0',
|
||||
releaseTime: dayjs(chapter.createdAt).format('LLL'),
|
||||
chapterNumber: json.chapters.length - chapterIndex,
|
||||
}),
|
||||
);
|
||||
@@ -82,7 +90,7 @@ class novelOvh implements Plugin.PluginBase {
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const result = await fetchApi(
|
||||
"https://api.novel.ovh/v2/chapters/" + chapterPath.split("/")[1],
|
||||
'https://api.novel.ovh/v2/chapters/' + chapterPath.split('/')[1],
|
||||
);
|
||||
const book = (await result.json()) as responseChapter;
|
||||
const image = Object.fromEntries(
|
||||
@@ -98,11 +106,11 @@ class novelOvh implements Plugin.PluginBase {
|
||||
page: number | undefined = 1,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}/novel?search=${searchTerm}&page=${page - 1}`;
|
||||
const result = await fetchApi(url + "&_data=routes/reader/book/index");
|
||||
const result = await fetchApi(url + '&_data=routes/reader/book/index');
|
||||
const body = (await result.json()) as { books: BooksEntity[] };
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
body.books.forEach((novel) =>
|
||||
body.books.forEach(novel =>
|
||||
novels.push({
|
||||
name: novel.name.ru,
|
||||
cover: novel.poster,
|
||||
@@ -116,37 +124,39 @@ class novelOvh implements Plugin.PluginBase {
|
||||
jsonToHtml = (
|
||||
json: ContentEntity[],
|
||||
image: { [key: string]: string },
|
||||
html: string = "",
|
||||
html: string = '',
|
||||
) => {
|
||||
json.forEach((element) => {
|
||||
json.forEach(element => {
|
||||
switch (element.type) {
|
||||
case "image":
|
||||
case 'image':
|
||||
if (element.attrs?.pages?.[0]) {
|
||||
html += `<img src="${image[element.attrs.pages[0]]}"/>`;
|
||||
}
|
||||
break;
|
||||
case "hardBreak":
|
||||
html += "<br>";
|
||||
case 'hardBreak':
|
||||
html += '<br>';
|
||||
break;
|
||||
case "horizontalRule":
|
||||
case "delimiter":
|
||||
case 'horizontalRule':
|
||||
case 'delimiter':
|
||||
html += '<h2 style="text-align: center">***</h2>';
|
||||
break;
|
||||
case "paragraph":
|
||||
case 'paragraph':
|
||||
html +=
|
||||
"<p" +
|
||||
'<p' +
|
||||
(element?.attrs?.textAlign
|
||||
? ` style="text-align: ${element.attrs.textAlign}"`
|
||||
: "") +
|
||||
">" +
|
||||
(element.content ? this.jsonToHtml(element.content, image) : "<br>") +
|
||||
"</p>";
|
||||
: '') +
|
||||
'>' +
|
||||
(element.content
|
||||
? this.jsonToHtml(element.content, image)
|
||||
: '<br>') +
|
||||
'</p>';
|
||||
break;
|
||||
case "text":
|
||||
case 'text':
|
||||
html += element.text;
|
||||
break;
|
||||
default:
|
||||
html += JSON.stringify(element, null, "\t"); //maybe I missed something.
|
||||
html += JSON.stringify(element, null, '\t'); //maybe I missed something.
|
||||
break;
|
||||
}
|
||||
});
|
||||
@@ -154,20 +164,21 @@ class novelOvh implements Plugin.PluginBase {
|
||||
};
|
||||
|
||||
fetchImage = fetchFile;
|
||||
resolveUrl = (path: string, isNovel?: boolean) => this.site + "/novel/" + path;
|
||||
resolveUrl = (path: string, isNovel?: boolean) =>
|
||||
this.site + '/novel/' + path;
|
||||
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Сортировка",
|
||||
value: "averageRating",
|
||||
label: 'Сортировка',
|
||||
value: 'averageRating',
|
||||
options: [
|
||||
{ label: "Кол-во просмотров", value: "viewsCount" },
|
||||
{ label: "Кол-во лайков", value: "likesCount" },
|
||||
{ label: "Кол-во глав", value: "chaptersCount" },
|
||||
{ label: "Кол-во закладок", value: "bookmarksCount" },
|
||||
{ label: "Рейтингу", value: "averageRating" },
|
||||
{ label: "Дате создания", value: "createdAt" },
|
||||
{ label: "Дате обновления", value: "updatedAt" },
|
||||
{ label: 'Кол-во просмотров', value: 'viewsCount' },
|
||||
{ label: 'Кол-во лайков', value: 'likesCount' },
|
||||
{ label: 'Кол-во глав', value: 'chaptersCount' },
|
||||
{ label: 'Кол-во закладок', value: 'bookmarksCount' },
|
||||
{ label: 'Рейтингу', value: 'averageRating' },
|
||||
{ label: 'Дате создания', value: 'createdAt' },
|
||||
{ label: 'Дате обновления', value: 'updatedAt' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
+717
-715
File diff suppressed because it is too large
Load Diff
+1004
-977
File diff suppressed because it is too large
Load Diff
+257
-236
@@ -1,9 +1,9 @@
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const statusKey: { [key: number]: string } = {
|
||||
1: NovelStatus.Ongoing,
|
||||
@@ -13,11 +13,11 @@ const statusKey: { [key: number]: string } = {
|
||||
};
|
||||
|
||||
class RLIB implements Plugin.PluginBase {
|
||||
id = "RLIB";
|
||||
name = "RanobeLib";
|
||||
site = "https://ranobelib.me";
|
||||
version = "1.0.0";
|
||||
icon = "src/ru/ranobelib/icon.png";
|
||||
id = 'RLIB';
|
||||
name = 'RanobeLib';
|
||||
site = 'https://ranobelib.me';
|
||||
version = '1.0.0';
|
||||
icon = 'src/ru/ranobelib/icon.png';
|
||||
ui: string | undefined = undefined;
|
||||
|
||||
async popularNovels(
|
||||
@@ -27,63 +27,74 @@ class RLIB implements Plugin.PluginBase {
|
||||
filters,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + "/manga-list?sort=";
|
||||
url += showLatestNovels ? "last_chapter_at" : filters?.sort?.value || "rate";
|
||||
url += "&dir=" + (filters?.order?.value || "desc");
|
||||
url += "&chapters[min]=" + (filters?.require_chapters?.value ? "1" : "0");
|
||||
let url = this.site + '/manga-list?sort=';
|
||||
url += showLatestNovels
|
||||
? 'last_chapter_at'
|
||||
: filters?.sort?.value || 'rate';
|
||||
url += '&dir=' + (filters?.order?.value || 'desc');
|
||||
url += '&chapters[min]=' + (filters?.require_chapters?.value ? '1' : '0');
|
||||
|
||||
Object.entries(filters || {}).forEach(([type, { value }]: any) => {
|
||||
if (value instanceof Array && value.length) {
|
||||
url += "&" + type + "[]=" + value.join("&" + type + "[]=");
|
||||
url += '&' + type + '[]=' + value.join('&' + type + '[]=');
|
||||
}
|
||||
if (value?.include instanceof Array && value.include.length) {
|
||||
url += "&" + type + "[include][]=" +
|
||||
value.include.join("&" + type + "[include][]=");
|
||||
url +=
|
||||
'&' +
|
||||
type +
|
||||
'[include][]=' +
|
||||
value.include.join('&' + type + '[include][]=');
|
||||
}
|
||||
if (value?.exclude instanceof Array && value.exclude.length) {
|
||||
url += "&" + type + "[exclude][]=" +
|
||||
value.exclude.join("&" + type + "[exclude][]=");
|
||||
url +=
|
||||
'&' +
|
||||
type +
|
||||
'[exclude][]=' +
|
||||
value.exclude.join('&' + type + '[exclude][]=');
|
||||
}
|
||||
});
|
||||
|
||||
url += "&page=" + pageNo;
|
||||
url += '&page=' + pageNo;
|
||||
|
||||
const result = await fetchApi(url).then((res) => res.text());
|
||||
const result = await fetchApi(url).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(result);
|
||||
this.ui = loadedCheerio("a.header-right-menu__item")
|
||||
.attr("href")
|
||||
?.replace?.(/[^0-9]/g, "");
|
||||
this.ui = loadedCheerio('a.header-right-menu__item')
|
||||
.attr('href')
|
||||
?.replace?.(/[^0-9]/g, '');
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
loadedCheerio(".media-card-wrap").each((index, element) => {
|
||||
const name = loadedCheerio(element).find(".media-card__title").text();
|
||||
const cover = loadedCheerio(element).find("a.media-card").attr("data-src");
|
||||
const url = loadedCheerio(element).find("a.media-card").attr("href");
|
||||
loadedCheerio('.media-card-wrap').each((index, element) => {
|
||||
const name = loadedCheerio(element).find('.media-card__title').text();
|
||||
const cover = loadedCheerio(element)
|
||||
.find('a.media-card')
|
||||
.attr('data-src');
|
||||
const url = loadedCheerio(element).find('a.media-card').attr('href');
|
||||
if (!url) return;
|
||||
novels.push({ name, cover, path: url.replace(this.site, "") });
|
||||
novels.push({ name, cover, path: url.replace(this.site, '') });
|
||||
});
|
||||
|
||||
return novels;
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const body = await fetchApi(this.resolveUrl(novelPath, true))
|
||||
.then((res) => res.text());
|
||||
const body = await fetchApi(this.resolveUrl(novelPath, true)).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio(".media-name__main").text()?.trim?.() || "",
|
||||
name: loadedCheerio('.media-name__main').text()?.trim?.() || '',
|
||||
};
|
||||
novel.cover = loadedCheerio(".container_responsive img").attr("src");
|
||||
novel.cover = loadedCheerio('.container_responsive img').attr('src');
|
||||
|
||||
novel.summary = loadedCheerio(".media-description__text").text().trim();
|
||||
novel.summary = loadedCheerio('.media-description__text').text().trim();
|
||||
|
||||
novel.genres = loadedCheerio('div[class="media-tags"]')
|
||||
.text()
|
||||
.trim()
|
||||
.replace(/[\n\r]+/g, ", ")
|
||||
.replace(/ /g, "");
|
||||
.replace(/[\n\r]+/g, ', ')
|
||||
.replace(/ /g, '');
|
||||
|
||||
loadedCheerio(
|
||||
'div[class="media-info-list paper"] > [class="media-info-list__item"]',
|
||||
@@ -92,12 +103,12 @@ class RLIB implements Plugin.PluginBase {
|
||||
.find('div[class="media-info-list__title"]')
|
||||
.text();
|
||||
|
||||
if (name === "Автор") {
|
||||
if (name === 'Автор') {
|
||||
novel.author = loadedCheerio(this)
|
||||
.find('div[class="media-info-list__value"]')
|
||||
.text()
|
||||
.trim();
|
||||
} else if (name === "Художник") {
|
||||
} else if (name === 'Художник') {
|
||||
novel.artist = loadedCheerio(this)
|
||||
.find('div[class="media-info-list__value"]')
|
||||
.text()
|
||||
@@ -105,9 +116,9 @@ class RLIB implements Plugin.PluginBase {
|
||||
}
|
||||
});
|
||||
|
||||
this.ui = loadedCheerio("a.header-right-menu__item")
|
||||
.attr("href")
|
||||
?.replace?.(/[^0-9]/g, "");
|
||||
this.ui = loadedCheerio('a.header-right-menu__item')
|
||||
.attr('href')
|
||||
?.replace?.(/[^0-9]/g, '');
|
||||
|
||||
const chaptersRaw = body.match(/window\.__DATA__ = ({.*?});/);
|
||||
if (chaptersRaw instanceof Array && chaptersRaw.length >= 2) {
|
||||
@@ -119,7 +130,8 @@ class RLIB implements Plugin.PluginBase {
|
||||
chaptersJson.manga.engName ||
|
||||
chaptersJson.manga.name;
|
||||
}
|
||||
novel.status = statusKey[chaptersJson.manga.status] || NovelStatus.Unknown;
|
||||
novel.status =
|
||||
statusKey[chaptersJson.manga.status] || NovelStatus.Unknown;
|
||||
this.ui = chaptersJson?.user?.id;
|
||||
|
||||
if (!chaptersJson.chapters.list?.length) return novel;
|
||||
@@ -127,16 +139,19 @@ class RLIB implements Plugin.PluginBase {
|
||||
|
||||
const customPage: { [key: number]: string } = {};
|
||||
const customOrder: { [key: number]: number } = {};
|
||||
if (chaptersJson.chapters.branches?.length && chaptersJson.chapters.branches.length > 1) {
|
||||
if (
|
||||
chaptersJson.chapters.branches?.length &&
|
||||
chaptersJson.chapters.branches.length > 1
|
||||
) {
|
||||
//if the novel is being translated by more than one team
|
||||
chaptersJson.chapters.branches.forEach(({ teams, id }) => {
|
||||
if (teams?.length) {
|
||||
customPage[id || 0] =
|
||||
teams.find((team) => team.is_active)?.name || teams[0].name;
|
||||
teams.find(team => team.is_active)?.name || teams[0].name;
|
||||
}
|
||||
});
|
||||
//fixes the chapter's position.
|
||||
chaptersJson.chapters.list.forEach((chapter) => {
|
||||
chaptersJson.chapters.list.forEach(chapter => {
|
||||
chapter.index = customOrder[chapter.branch_id || 0] || 1;
|
||||
customOrder[chapter.branch_id || 0] =
|
||||
(customOrder[chapter.branch_id || 0] || 1) + 1;
|
||||
@@ -147,19 +162,24 @@ class RLIB implements Plugin.PluginBase {
|
||||
chaptersJson.chapters.list.forEach((chapter, chapterIndex) =>
|
||||
chapters.push({
|
||||
name:
|
||||
"Том " + chapter.chapter_volume +
|
||||
" Глава " + chapter.chapter_number +
|
||||
(chapter.chapter_name ? " " + chapter.chapter_name.trim() : ""),
|
||||
'Том ' +
|
||||
chapter.chapter_volume +
|
||||
' Глава ' +
|
||||
chapter.chapter_number +
|
||||
(chapter.chapter_name ? ' ' + chapter.chapter_name.trim() : ''),
|
||||
path:
|
||||
novelPath +
|
||||
"/v" + chapter.chapter_volume +
|
||||
"/c" + chapter.chapter_number +
|
||||
"?bid=" + (chapter.branch_id || ""),
|
||||
releaseTime: dayjs(chapter.chapter_created_at).format("LLL"),
|
||||
'/v' +
|
||||
chapter.chapter_volume +
|
||||
'/c' +
|
||||
chapter.chapter_number +
|
||||
'?bid=' +
|
||||
(chapter.branch_id || ''),
|
||||
releaseTime: dayjs(chapter.chapter_created_at).format('LLL'),
|
||||
chapterNumber:
|
||||
customOrder[chapter.branch_id || 0] - (chapter.index || 0) ||
|
||||
totalChapters - chapterIndex,
|
||||
page: customPage[chapter.branch_id || 0] || "Основной перевод",
|
||||
page: customPage[chapter.branch_id || 0] || 'Основной перевод',
|
||||
}),
|
||||
);
|
||||
novel.chapters =
|
||||
@@ -176,38 +196,39 @@ class RLIB implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const result = await fetchApi(this.resolveUrl(chapterPath))
|
||||
.then((res) => res.text());
|
||||
const result = await fetchApi(this.resolveUrl(chapterPath)).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
|
||||
const loadedCheerio = parseHTML(result);
|
||||
loadedCheerio(".reader-container img").each((index, element) => {
|
||||
loadedCheerio('.reader-container img').each((index, element) => {
|
||||
const src =
|
||||
loadedCheerio(element).attr("data-src") ||
|
||||
loadedCheerio(element).attr("src");
|
||||
if (!src?.startsWith("http")) {
|
||||
loadedCheerio(element).attr("src", this.site + src);
|
||||
loadedCheerio(element).attr('data-src') ||
|
||||
loadedCheerio(element).attr('src');
|
||||
if (!src?.startsWith('http')) {
|
||||
loadedCheerio(element).attr('src', this.site + src);
|
||||
} else {
|
||||
loadedCheerio(element).attr("src", src);
|
||||
loadedCheerio(element).attr('src', src);
|
||||
}
|
||||
loadedCheerio(element).removeAttr("data-src");
|
||||
loadedCheerio(element).removeAttr('data-src');
|
||||
});
|
||||
|
||||
const chapterText = loadedCheerio(".reader-container").html();
|
||||
return chapterText || "";
|
||||
const chapterText = loadedCheerio('.reader-container').html();
|
||||
return chapterText || '';
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
||||
const result = await fetchApi(
|
||||
this.site + "/search?q=" + searchTerm + "&type=manga",
|
||||
this.site + '/search?q=' + searchTerm + '&type=manga',
|
||||
);
|
||||
const body = (await result.json()) as Manga[];
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
body.forEach((novel) =>
|
||||
body.forEach(novel =>
|
||||
novels.push({
|
||||
name: novel.rus_name || novel.name,
|
||||
cover: novel.coverImage,
|
||||
path: "/" + novel.slug,
|
||||
path: '/' + novel.slug,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -216,223 +237,223 @@ class RLIB implements Plugin.PluginBase {
|
||||
|
||||
fetchImage = fetchFile;
|
||||
resolveUrl = (path: string, isNovel?: boolean) =>
|
||||
this.site + path + (this.ui ? (isNovel ? "?" : "&") + "ui=" + this.ui : "");
|
||||
this.site + path + (this.ui ? (isNovel ? '?' : '&') + 'ui=' + this.ui : '');
|
||||
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Сортировка",
|
||||
value: "rate",
|
||||
label: 'Сортировка',
|
||||
value: 'rate',
|
||||
options: [
|
||||
{ label: "Рейтинг", value: "rate" },
|
||||
{ label: "Имя", value: "name" },
|
||||
{ label: "Просмотры", value: "views" },
|
||||
{ label: "Дате добавления", value: "created_at" },
|
||||
{ label: "Дате обновления", value: "last_chapter_at" },
|
||||
{ label: "Количество глав", value: "chap_count" },
|
||||
{ label: 'Рейтинг', value: 'rate' },
|
||||
{ label: 'Имя', value: 'name' },
|
||||
{ label: 'Просмотры', value: 'views' },
|
||||
{ label: 'Дате добавления', value: 'created_at' },
|
||||
{ label: 'Дате обновления', value: 'last_chapter_at' },
|
||||
{ label: 'Количество глав', value: 'chap_count' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
order: {
|
||||
label: "Порядок",
|
||||
value: "desc",
|
||||
label: 'Порядок',
|
||||
value: 'desc',
|
||||
options: [
|
||||
{ label: "По убыванию", value: "desc" },
|
||||
{ label: "По возрастанию", value: "asc" },
|
||||
{ label: 'По убыванию', value: 'desc' },
|
||||
{ label: 'По возрастанию', value: 'asc' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
type: {
|
||||
label: "Тип",
|
||||
label: 'Тип',
|
||||
value: [],
|
||||
options: [
|
||||
{ label: "Авторский", value: "14" },
|
||||
{ label: "Английский", value: "13" },
|
||||
{ label: "Китай", value: "12" },
|
||||
{ label: "Корея", value: "11" },
|
||||
{ label: "Фанфик", value: "15" },
|
||||
{ label: "Япония", value: "10" },
|
||||
{ label: 'Авторский', value: '14' },
|
||||
{ label: 'Английский', value: '13' },
|
||||
{ label: 'Китай', value: '12' },
|
||||
{ label: 'Корея', value: '11' },
|
||||
{ label: 'Фанфик', value: '15' },
|
||||
{ label: 'Япония', value: '10' },
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
format: {
|
||||
label: "Формат выпуска",
|
||||
label: 'Формат выпуска',
|
||||
value: { include: [], exclude: [] },
|
||||
options: [
|
||||
{ label: "4-кома (Ёнкома)", value: "1" },
|
||||
{ label: "В цвете", value: "4" },
|
||||
{ label: "Веб", value: "6" },
|
||||
{ label: "Вебтун", value: "7" },
|
||||
{ label: "Додзинси", value: "3" },
|
||||
{ label: "Сборник", value: "2" },
|
||||
{ label: "Сингл", value: "5" },
|
||||
{ label: '4-кома (Ёнкома)', value: '1' },
|
||||
{ label: 'В цвете', value: '4' },
|
||||
{ label: 'Веб', value: '6' },
|
||||
{ label: 'Вебтун', value: '7' },
|
||||
{ label: 'Додзинси', value: '3' },
|
||||
{ label: 'Сборник', value: '2' },
|
||||
{ label: 'Сингл', value: '5' },
|
||||
],
|
||||
type: FilterTypes.ExcludableCheckboxGroup,
|
||||
},
|
||||
status: {
|
||||
label: "Статус перевода",
|
||||
label: 'Статус перевода',
|
||||
value: [],
|
||||
options: [
|
||||
{ label: "Продолжается", value: "1" },
|
||||
{ label: "Завершен", value: "2" },
|
||||
{ label: "Заморожен", value: "3" },
|
||||
{ label: "Заброшен", value: "4" },
|
||||
{ label: 'Продолжается', value: '1' },
|
||||
{ label: 'Завершен', value: '2' },
|
||||
{ label: 'Заморожен', value: '3' },
|
||||
{ label: 'Заброшен', value: '4' },
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
manga_status: {
|
||||
label: "Статус тайтла",
|
||||
label: 'Статус тайтла',
|
||||
value: [],
|
||||
options: [
|
||||
{ label: "Онгоинг", value: "1" },
|
||||
{ label: "Завершён", value: "2" },
|
||||
{ label: "Анонс", value: "3" },
|
||||
{ label: "Приостановлен", value: "4" },
|
||||
{ label: "Выпуск прекращён", value: "5" },
|
||||
{ label: 'Онгоинг', value: '1' },
|
||||
{ label: 'Завершён', value: '2' },
|
||||
{ label: 'Анонс', value: '3' },
|
||||
{ label: 'Приостановлен', value: '4' },
|
||||
{ label: 'Выпуск прекращён', value: '5' },
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
genres: {
|
||||
label: "Жанры",
|
||||
label: 'Жанры',
|
||||
value: { include: [], exclude: [] },
|
||||
options: [
|
||||
{ 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" },
|
||||
{ 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' },
|
||||
],
|
||||
type: FilterTypes.ExcludableCheckboxGroup,
|
||||
},
|
||||
tags: {
|
||||
label: "Теги",
|
||||
label: 'Теги',
|
||||
value: { include: [], exclude: [] },
|
||||
options: [
|
||||
{ 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" },
|
||||
{ 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' },
|
||||
],
|
||||
type: FilterTypes.ExcludableCheckboxGroup,
|
||||
},
|
||||
require_chapters: {
|
||||
label: "Только проекты с главами",
|
||||
label: 'Только проекты с главами',
|
||||
value: true,
|
||||
type: FilterTypes.Switch,
|
||||
},
|
||||
|
||||
+43
-43
@@ -1,43 +1,43 @@
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { defaultCover } from '@libs/defaultCover';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
class RNRF implements Plugin.PluginBase {
|
||||
id = "RNRF";
|
||||
name = "РанобэРФ";
|
||||
site = "https://ранобэ.рф";
|
||||
version = "1.0.0";
|
||||
icon = "src/ru/ranoberf/icon.png";
|
||||
id = 'RNRF';
|
||||
name = 'РанобэРФ';
|
||||
site = 'https://ранобэ.рф';
|
||||
version = '1.0.0';
|
||||
icon = 'src/ru/ranoberf/icon.png';
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + "/books?order=";
|
||||
let url = this.site + '/books?order=';
|
||||
url += showLatestNovels
|
||||
? "lastPublishedChapter"
|
||||
: filters?.sort?.value || "popular";
|
||||
url += "&page=" + pageNo;
|
||||
? 'lastPublishedChapter'
|
||||
: filters?.sort?.value || 'popular';
|
||||
url += '&page=' + pageNo;
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
|
||||
const json: response = JSON.parse(jsonRaw || "{}");
|
||||
const jsonRaw = loadedCheerio('#__NEXT_DATA__').html();
|
||||
const json: response = JSON.parse(jsonRaw || '{}');
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
json.props.pageProps?.totalData?.items?.forEach((novel) =>
|
||||
json.props.pageProps?.totalData?.items?.forEach(novel =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
cover: novel?.verticalImage?.url
|
||||
? this.site + novel.verticalImage.url
|
||||
: defaultCover,
|
||||
path: "/" + novel.slug,
|
||||
path: '/' + novel.slug,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -49,19 +49,19 @@ class RNRF implements Plugin.PluginBase {
|
||||
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;
|
||||
const jsonRaw = loadedCheerio('#__NEXT_DATA__').html();
|
||||
const book = (JSON.parse(jsonRaw || '{}') as response).props.pageProps.book;
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: book?.title || "",
|
||||
name: book?.title || '',
|
||||
cover: book?.verticalImage?.url
|
||||
? this.site + book.verticalImage.url
|
||||
: defaultCover,
|
||||
summary: book?.description,
|
||||
author: book?.author || "",
|
||||
genres: book?.genres.map((item) => item.title).join(", "),
|
||||
status: book?.additionalInfo.includes("Активен")
|
||||
author: book?.author || '',
|
||||
genres: book?.genres.map(item => item.title).join(', '),
|
||||
status: book?.additionalInfo.includes('Активен')
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
@@ -73,7 +73,7 @@ class RNRF implements Plugin.PluginBase {
|
||||
chapters.push({
|
||||
name: chapter.title,
|
||||
path: chapter.url,
|
||||
releaseTime: dayjs(chapter.publishedAt).format("LLL"),
|
||||
releaseTime: dayjs(chapter.publishedAt).format('LLL'),
|
||||
chapterNumber: book.chapters.length - chapterIndex,
|
||||
});
|
||||
}
|
||||
@@ -84,22 +84,22 @@ class RNRF implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const result = await fetchApi(this.site + chapterPath).then((res) =>
|
||||
const result = await fetchApi(this.site + chapterPath).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
|
||||
let loadedCheerio = parseHTML(result);
|
||||
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
|
||||
const json: response = JSON.parse(jsonRaw || "{}");
|
||||
const jsonRaw = loadedCheerio('#__NEXT_DATA__').html();
|
||||
const json: response = JSON.parse(jsonRaw || '{}');
|
||||
|
||||
loadedCheerio = parseHTML(
|
||||
json.props.pageProps?.chapter?.content?.text || "",
|
||||
json.props.pageProps?.chapter?.content?.text || '',
|
||||
);
|
||||
|
||||
loadedCheerio("img").each((index, element) => {
|
||||
if (!loadedCheerio(element).attr("src")?.startsWith("http")) {
|
||||
const src = loadedCheerio(element).attr("src");
|
||||
loadedCheerio(element).attr("src", this.site + src);
|
||||
loadedCheerio('img').each((index, element) => {
|
||||
if (!loadedCheerio(element).attr('src')?.startsWith('http')) {
|
||||
const src = loadedCheerio(element).attr('src');
|
||||
loadedCheerio(element).attr('src', this.site + src);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -113,13 +113,13 @@ class RNRF implements Plugin.PluginBase {
|
||||
const body = (await result.json()) as { items: Item[] };
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
body.items.forEach((novel) =>
|
||||
body.items.forEach(novel =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
cover: novel?.verticalImage?.url
|
||||
? this.site + novel.verticalImage.url
|
||||
: defaultCover,
|
||||
path: "/" + novel.slug,
|
||||
path: '/' + novel.slug,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -130,13 +130,13 @@ class RNRF implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Сортировка",
|
||||
value: "popular",
|
||||
label: 'Сортировка',
|
||||
value: 'popular',
|
||||
options: [
|
||||
{ label: "Рейтинг", value: "popular" },
|
||||
{ label: "Дате добавления", value: "new" },
|
||||
{ label: "Дате обновления", value: "lastPublishedChapter" },
|
||||
{ label: "Законченные", value: "completed" },
|
||||
{ label: 'Рейтинг', value: 'popular' },
|
||||
{ label: 'Дате добавления', value: 'new' },
|
||||
{ label: 'Дате обновления', value: 'lastPublishedChapter' },
|
||||
{ label: 'Законченные', value: 'completed' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
+799
-791
File diff suppressed because it is too large
Load Diff
+244
-241
@@ -1,17 +1,17 @@
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
import qs from "qs";
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import dayjs from 'dayjs';
|
||||
import qs from 'qs';
|
||||
|
||||
class Zelluloza implements Plugin.PluginBase {
|
||||
id = "zelluloza";
|
||||
name = "Целлюлоза";
|
||||
site = "https://zelluloza.ru";
|
||||
version = "1.0.0";
|
||||
icon = "src/ru/zelluloza/icon.png";
|
||||
id = 'zelluloza';
|
||||
name = 'Целлюлоза';
|
||||
site = 'https://zelluloza.ru';
|
||||
version = '1.0.0';
|
||||
icon = 'src/ru/zelluloza/icon.png';
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
@@ -20,36 +20,36 @@ class Zelluloza implements Plugin.PluginBase {
|
||||
showLatestNovels,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const sort = showLatestNovels ? "0.0" : filters?.sort?.value || "6.0";
|
||||
const genres = filters?.genres?.value || "";
|
||||
const sort = showLatestNovels ? '0.0' : filters?.sort?.value || '6.0';
|
||||
const genres = filters?.genres?.value || '';
|
||||
|
||||
const body = await fetchApi(this.site + "/ajaxcall/", {
|
||||
const body = await fetchApi(this.site + '/ajaxcall/', {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Referer: this.site + "/top/freebooks/",
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Referer: this.site + '/top/freebooks/',
|
||||
Origin: this.site,
|
||||
},
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
body: qs.stringify({
|
||||
op: "morebooks",
|
||||
par1: "",
|
||||
op: 'morebooks',
|
||||
par1: '',
|
||||
par2: `125:0:${genres}:0.${sort}.0.0.0.0.0.0.0.0.0.1.s.1..:${pageNo}`,
|
||||
par4: "",
|
||||
par4: '',
|
||||
}),
|
||||
}).then((res) => res.text());
|
||||
}).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio('div[style="display: flex"]').each((index, element) => {
|
||||
novels.push({
|
||||
name: loadedCheerio(element).find('span[itemprop="name"]').text() || "",
|
||||
name: loadedCheerio(element).find('span[itemprop="name"]').text() || '',
|
||||
cover:
|
||||
this.site +
|
||||
loadedCheerio(element).find('img[class="shadow"]').attr("src"),
|
||||
loadedCheerio(element).find('img[class="shadow"]').attr('src'),
|
||||
path: (
|
||||
loadedCheerio(element).find('img[class="shadow"]').attr("onclick") ||
|
||||
""
|
||||
).replace(/\D/g, ""),
|
||||
loadedCheerio(element).find('img[class="shadow"]').attr('onclick') ||
|
||||
''
|
||||
).replace(/\D/g, ''),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -57,20 +57,21 @@ class Zelluloza implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const body = await fetchApi(this.resolveUrl(novelPath, true))
|
||||
.then((res) => res.text());
|
||||
const body = await fetchApi(this.resolveUrl(novelPath, true)).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio('span[itemprop="name"]').text(),
|
||||
cover: this.site + loadedCheerio('img[class="shadow"]').attr("src"),
|
||||
genres: loadedCheerio(".gnres").text()?.split?.(": ")?.[1],
|
||||
cover: this.site + loadedCheerio('img[class="shadow"]').attr('src'),
|
||||
genres: loadedCheerio('.gnres').text()?.split?.(': ')?.[1],
|
||||
summary:
|
||||
loadedCheerio("#bann_full").text() ||
|
||||
loadedCheerio("#bann_short").text(),
|
||||
author: loadedCheerio(".author_link").text(),
|
||||
status: loadedCheerio(".tech_decription").text().includes("Пишется")
|
||||
loadedCheerio('#bann_full').text() ||
|
||||
loadedCheerio('#bann_short').text(),
|
||||
author: loadedCheerio('.author_link').text(),
|
||||
status: loadedCheerio('.tech_decription').text().includes('Пишется')
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
@@ -78,7 +79,9 @@ class Zelluloza implements Plugin.PluginBase {
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
loadedCheerio('ul[class="g0"] div[class="w800_m"]').each(
|
||||
(chapterIndex, element) => {
|
||||
const isFree = loadedCheerio(element).find('div[class="chaptfree"]').length;
|
||||
const isFree = loadedCheerio(element).find(
|
||||
'div[class="chaptfree"]',
|
||||
).length;
|
||||
if (isFree) {
|
||||
const chapter = loadedCheerio(element).find('a[class="chptitle"]');
|
||||
const releaseDate = loadedCheerio(element)
|
||||
@@ -86,7 +89,7 @@ class Zelluloza implements Plugin.PluginBase {
|
||||
.text();
|
||||
chapters.push({
|
||||
name: chapter.text().trim(),
|
||||
path: (chapter.attr("href") || "").split("/").slice(2, 4).join("/"),
|
||||
path: (chapter.attr('href') || '').split('/').slice(2, 4).join('/'),
|
||||
releaseTime: this.parseDate(releaseDate),
|
||||
chapterNumber: chapterIndex + 1,
|
||||
});
|
||||
@@ -99,33 +102,33 @@ class Zelluloza implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const [bookID, chapterID] = chapterPath.split("/");
|
||||
const body = await fetchApi(this.site + "/ajaxcall/", {
|
||||
const [bookID, chapterID] = chapterPath.split('/');
|
||||
const body = await fetchApi(this.site + '/ajaxcall/', {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Referer: this.resolveUrl(chapterPath),
|
||||
Origin: this.site,
|
||||
},
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
body: qs.stringify({
|
||||
op: "getbook",
|
||||
op: 'getbook',
|
||||
par1: bookID,
|
||||
par2: chapterID,
|
||||
}),
|
||||
}).then((res) => res.text());
|
||||
}).then(res => res.text());
|
||||
|
||||
const encrypted = body.split("<END>")[0].split("\n");
|
||||
const encrypted = body.split('<END>')[0].split('\n');
|
||||
const decrypted = encrypted
|
||||
.map((str) => decrypt(str))
|
||||
.join("")
|
||||
.replace(/\r/g, "")
|
||||
.map(str => decrypt(str))
|
||||
.join('')
|
||||
.replace(/\r/g, '')
|
||||
.trim();
|
||||
|
||||
const chapterText = decrypted //cosmetic filters
|
||||
.replace(/\[\*]([\s\S]*?)\[\/]/g, "<b>$1</b>")
|
||||
.replace(/\[_]([\s\S]*?)\[\/]/g, "<u>$1</u>")
|
||||
.replace(/\[-]([\s\S]*?)\[\/]/g, "<s>$1</s>")
|
||||
.replace(/\[~]([\s\S]*?)\[\/]/g, "<i>$1</i>"); /*
|
||||
.replace(/\[\*]([\s\S]*?)\[\/]/g, '<b>$1</b>')
|
||||
.replace(/\[_]([\s\S]*?)\[\/]/g, '<u>$1</u>')
|
||||
.replace(/\[-]([\s\S]*?)\[\/]/g, '<s>$1</s>')
|
||||
.replace(/\[~]([\s\S]*?)\[\/]/g, '<i>$1</i>'); /*
|
||||
.replace(/\[!]([\s\S]*?)\[\/]/g, '<span style="font-family: Times New Roman">$1</span>')
|
||||
.replace(/\[ctr]([\s\S]*?)\[\/]/g, "<center>$1</center>")
|
||||
.replace(/\[rht]([\s\S]*?)\[\/]/g, '<span style="text-align: right">$1</span>')
|
||||
@@ -143,47 +146,47 @@ class Zelluloza implements Plugin.PluginBase {
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const body = await fetchApi(this.site + "/ajaxcall/", {
|
||||
const body = await fetchApi(this.site + '/ajaxcall/', {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Referer: this.site + "/search/done/#result",
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Referer: this.site + '/search/done/#result',
|
||||
Origin: this.site,
|
||||
},
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
body: qs.stringify({
|
||||
op: "morebooks",
|
||||
op: 'morebooks',
|
||||
par1: searchTerm,
|
||||
par2: "206:0:0:0.0.0.0.0.0.0.10.0.0.0.0.0..0..:" + pageNo,
|
||||
par4: "",
|
||||
par2: '206:0:0:0.0.0.0.0.0.0.10.0.0.0.0.0..0..:' + pageNo,
|
||||
par4: '',
|
||||
}),
|
||||
}).then((res) => res.text());
|
||||
}).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio('div[style="display: flex"]').each((index, element) => {
|
||||
novels.push({
|
||||
name: loadedCheerio(element).find('span[itemprop="name"]').text() || "",
|
||||
name: loadedCheerio(element).find('span[itemprop="name"]').text() || '',
|
||||
cover:
|
||||
this.site +
|
||||
loadedCheerio(element).find('img[class="shadow"]').attr("src"),
|
||||
loadedCheerio(element).find('img[class="shadow"]').attr('src'),
|
||||
path: (
|
||||
loadedCheerio(element).find('img[class="shadow"]').attr("onclick") ||
|
||||
""
|
||||
).replace(/\D/g, ""),
|
||||
loadedCheerio(element).find('img[class="shadow"]').attr('onclick') ||
|
||||
''
|
||||
).replace(/\D/g, ''),
|
||||
});
|
||||
});
|
||||
|
||||
return novels;
|
||||
}
|
||||
|
||||
parseDate = (dateString: string | undefined = "") => {
|
||||
parseDate = (dateString: string | undefined = '') => {
|
||||
const [, day, month, year, hour, minute] =
|
||||
dateString.match(/(\d{2})\.(\d{2})\.(\d{4})г (\d{2}):(\d{2})/) || [];
|
||||
|
||||
if (day && month && year && hour && minute) {
|
||||
return dayjs(
|
||||
year + "-" + month + "-" + day + " " + hour + ":" + minute,
|
||||
).format("LLL");
|
||||
year + '-' + month + '-' + day + ' ' + hour + ':' + minute,
|
||||
).format('LLL');
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -191,167 +194,167 @@ class Zelluloza implements Plugin.PluginBase {
|
||||
|
||||
fetchImage = fetchFile;
|
||||
resolveUrl = (path: string, isNovel?: boolean) =>
|
||||
this.site + "/books/" + path;
|
||||
this.site + '/books/' + path;
|
||||
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Сортировка",
|
||||
value: "6.0",
|
||||
label: 'Сортировка',
|
||||
value: '6.0',
|
||||
options: [
|
||||
{ label: "Топ-недели", value: "6.0" },
|
||||
{ label: "Бестселеры", value: "5.0" },
|
||||
{ label: "Популярные у читателей", value: "2.0" },
|
||||
{ label: "Книжные новинки", value: "0.0" },
|
||||
{ label: "Законченные книги", value: "0.1" },
|
||||
{ label: 'Топ-недели', value: '6.0' },
|
||||
{ label: 'Бестселеры', value: '5.0' },
|
||||
{ label: 'Популярные у читателей', value: '2.0' },
|
||||
{ label: 'Книжные новинки', value: '0.0' },
|
||||
{ label: 'Законченные книги', value: '0.1' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
genres: {
|
||||
label: "Жанры",
|
||||
value: "",
|
||||
label: 'Жанры',
|
||||
value: '',
|
||||
options: [
|
||||
{ label: "Все", value: "" },
|
||||
{ label: "16+", value: "204" },
|
||||
{ label: "18+", value: "100" },
|
||||
{ label: "Авантюра", value: "347" },
|
||||
{ label: "Альтернативная история", value: "55" },
|
||||
{ label: "Ангст", value: "463" },
|
||||
{ label: "Аниме", value: "405" },
|
||||
{ label: "Антиутопия", value: "21" },
|
||||
{ label: "Апокалипсис", value: "368" },
|
||||
{ label: "Аудиокнига", value: "391" },
|
||||
{ label: "Байки", value: "435" },
|
||||
{ label: "Бизнес-книги", value: "522" },
|
||||
{ label: "Биография", value: "52" },
|
||||
{ label: "Биопанк", value: "379" },
|
||||
{ label: "Боевая фантастика", value: "512" },
|
||||
{ label: "Боевик", value: "61" },
|
||||
{ label: "Боевые искусства", value: "142" },
|
||||
{ label: "Бояръ-аниме", value: "433" },
|
||||
{ label: "Вбоквелл", value: "372" },
|
||||
{ label: "Вестерн", value: "96" },
|
||||
{ label: "Война миров", value: "137" },
|
||||
{ label: "Восточное фэнтези", value: "505" },
|
||||
{ label: "Городское фентези", value: "63" },
|
||||
{ label: "Городское фэнтези", value: "520" },
|
||||
{ label: "Готика", value: "83" },
|
||||
{ label: "Детективы", value: "4" },
|
||||
{ label: "Детские книги", value: "8" },
|
||||
{ label: "Документальные книги", value: "351" },
|
||||
{ label: "Дом и дача", value: "534" },
|
||||
{ label: "Дорожные истории", value: "41" },
|
||||
{ label: "Драма", value: "47" },
|
||||
{ label: "Древняя литература", value: "529" },
|
||||
{ label: "Женский психологический роман", value: "186" },
|
||||
{ label: "Женский роман", value: "141" },
|
||||
{ label: "Зарубежная классика", value: "524" },
|
||||
{ label: "Здоровье и красота", value: "533" },
|
||||
{ label: "Изучение языков", value: "537" },
|
||||
{ label: "Иронический детектив", value: "85" },
|
||||
{ label: "Исследования", value: "156" },
|
||||
{ label: "Историческая литература", value: "3" },
|
||||
{ label: "Исторический любовный роман", value: "518" },
|
||||
{ label: "Историческое фэнтези", value: "90" },
|
||||
{ label: "Киберпанк", value: "7" },
|
||||
{ label: "Классическая литература", value: "9" },
|
||||
{ label: "Книга-игра", value: "371" },
|
||||
{ label: "Книги для родителей", value: "535" },
|
||||
{ label: "Комедия", value: "80" },
|
||||
{ label: "Конкурсные сборники", value: "349" },
|
||||
{ label: "Короткий любовный роман", value: "517" },
|
||||
{ label: "Космическая фантастика", value: "513" },
|
||||
{ label: "Космоопера", value: "58" },
|
||||
{ label: "Криминальная проза", value: "35" },
|
||||
{ label: "Криптоистория", value: "400" },
|
||||
{ label: "Ксенофантастика", value: "102" },
|
||||
{ label: "Культура и искусство", value: "536" },
|
||||
{ label: "Легенды", value: "161" },
|
||||
{ label: "Лирика", value: "49" },
|
||||
{ label: "ЛитРПГ", value: "62" },
|
||||
{ label: "Любовная фантастика", value: "516" },
|
||||
{ label: "Любовное фэнтези", value: "515" },
|
||||
{ label: "Любовный роман", value: "46" },
|
||||
{ label: "Магический реализм", value: "42" },
|
||||
{ label: "Матриархат", value: "385" },
|
||||
{ label: "Медицина", value: "57" },
|
||||
{ label: "Мелодрама", value: "45" },
|
||||
{ label: "Мемуары", value: "532" },
|
||||
{ label: "Метафизика", value: "430" },
|
||||
{ label: "Милитари", value: "126" },
|
||||
{ label: "Мистика", value: "22" },
|
||||
{ label: "Морские приключения", value: "211" },
|
||||
{ label: "Мотивация", value: "135" },
|
||||
{ label: "Научная фантастика", value: "122" },
|
||||
{ label: "Научно-популярная", value: "43" },
|
||||
{ label: "Ненаучная фантастика", value: "387" },
|
||||
{ label: "Новелла", value: "498" },
|
||||
{ label: "Нон-фикшн", value: "507" },
|
||||
{ label: "Нуар", value: "121" },
|
||||
{ label: "О войне", value: "136" },
|
||||
{ label: "Оккультизм", value: "424" },
|
||||
{ label: "Параллельные миры", value: "438" },
|
||||
{ label: "Пародия", value: "108" },
|
||||
{ label: "Подростковая фантастика", value: "27" },
|
||||
{ label: "Подростковый роман", value: "388" },
|
||||
{ label: "Подростковый ужастик", value: "110" },
|
||||
{ label: "Постапокалипсис", value: "82" },
|
||||
{ label: "Постапокалиптика", value: "67" },
|
||||
{ label: "Поэзия", value: "44" },
|
||||
{ label: "Превращение", value: "170" },
|
||||
{ label: "Приключение", value: "497" },
|
||||
{ label: "Приключения", value: "5" },
|
||||
{ label: "Природа и животные", value: "538" },
|
||||
{ label: "Проза", value: "95" },
|
||||
{ label: "Производственный роман", value: "148" },
|
||||
{ label: "Психология", value: "31" },
|
||||
{ label: "Путешествия", value: "383" },
|
||||
{ label: "Рассказы", value: "101" },
|
||||
{ label: "Реализм", value: "422" },
|
||||
{ label: "РеалРПГ", value: "442" },
|
||||
{ label: "Религия", value: "188" },
|
||||
{ label: "Робинзонада", value: "434" },
|
||||
{ label: "Романтика", value: "130" },
|
||||
{ label: "Романтическая фантастика", value: "501" },
|
||||
{ label: "Русская классика", value: "523" },
|
||||
{ label: "Рыцарский роман", value: "112" },
|
||||
{ label: "Символизм", value: "439" },
|
||||
{ label: "Сказки", value: "93" },
|
||||
{ label: "Славянское фэнтези", value: "508" },
|
||||
{ label: "Сновидения", value: "428" },
|
||||
{ label: "Современный любовный роман", value: "519" },
|
||||
{ label: "Социально-психологическая фантастика", value: "98" },
|
||||
{ label: "Спорт", value: "147" },
|
||||
{ label: "Справочники", value: "528" },
|
||||
{ label: "Стимпанк", value: "56" },
|
||||
{ label: "Субкультура", value: "209" },
|
||||
{ label: "Сюрреализм", value: "26" },
|
||||
{ label: "Тёмное фэнтези", value: "437" },
|
||||
{ label: "Техномагия", value: "159" },
|
||||
{ label: "Триллер", value: "6" },
|
||||
{ label: "Трэш", value: "162" },
|
||||
{ label: "Ужасы", value: "531" },
|
||||
{ label: "Утопия", value: "191" },
|
||||
{ label: "Учебник", value: "411" },
|
||||
{ label: "Фантастика", value: "1" },
|
||||
{ label: "Фантастический боевик", value: "38" },
|
||||
{ label: "Фанфик", value: "111" },
|
||||
{ label: "Философия", value: "461" },
|
||||
{ label: "Фурри", value: "394" },
|
||||
{ label: "Фэнтези", value: "2" },
|
||||
{ label: "Хентай", value: "441" },
|
||||
{ label: "Хобби и досуг", value: "530" },
|
||||
{ label: "Хоррор", value: "86" },
|
||||
{ label: "Чёрный юмор", value: "73" },
|
||||
{ label: "Эзотерика", value: "30" },
|
||||
{ label: "Экшн", value: "94" },
|
||||
{ label: "Эпическое фэнтези", value: "499" },
|
||||
{ label: "Эротика", value: "19" },
|
||||
{ label: "Эссе", value: "353" },
|
||||
{ label: "ЭТТИ", value: "502" },
|
||||
{ label: "Юмористическая литература", value: "20" },
|
||||
{ label: "Юмористическая фантастика", value: "514" },
|
||||
{ label: "Юмористическое фэнтези", value: "521" },
|
||||
{ label: "EVE - Миры Содружества", value: "99" },
|
||||
{ label: 'Все', value: '' },
|
||||
{ label: '16+', value: '204' },
|
||||
{ label: '18+', value: '100' },
|
||||
{ label: 'Авантюра', value: '347' },
|
||||
{ label: 'Альтернативная история', value: '55' },
|
||||
{ label: 'Ангст', value: '463' },
|
||||
{ label: 'Аниме', value: '405' },
|
||||
{ label: 'Антиутопия', value: '21' },
|
||||
{ label: 'Апокалипсис', value: '368' },
|
||||
{ label: 'Аудиокнига', value: '391' },
|
||||
{ label: 'Байки', value: '435' },
|
||||
{ label: 'Бизнес-книги', value: '522' },
|
||||
{ label: 'Биография', value: '52' },
|
||||
{ label: 'Биопанк', value: '379' },
|
||||
{ label: 'Боевая фантастика', value: '512' },
|
||||
{ label: 'Боевик', value: '61' },
|
||||
{ label: 'Боевые искусства', value: '142' },
|
||||
{ label: 'Бояръ-аниме', value: '433' },
|
||||
{ label: 'Вбоквелл', value: '372' },
|
||||
{ label: 'Вестерн', value: '96' },
|
||||
{ label: 'Война миров', value: '137' },
|
||||
{ label: 'Восточное фэнтези', value: '505' },
|
||||
{ label: 'Городское фентези', value: '63' },
|
||||
{ label: 'Городское фэнтези', value: '520' },
|
||||
{ label: 'Готика', value: '83' },
|
||||
{ label: 'Детективы', value: '4' },
|
||||
{ label: 'Детские книги', value: '8' },
|
||||
{ label: 'Документальные книги', value: '351' },
|
||||
{ label: 'Дом и дача', value: '534' },
|
||||
{ label: 'Дорожные истории', value: '41' },
|
||||
{ label: 'Драма', value: '47' },
|
||||
{ label: 'Древняя литература', value: '529' },
|
||||
{ label: 'Женский психологический роман', value: '186' },
|
||||
{ label: 'Женский роман', value: '141' },
|
||||
{ label: 'Зарубежная классика', value: '524' },
|
||||
{ label: 'Здоровье и красота', value: '533' },
|
||||
{ label: 'Изучение языков', value: '537' },
|
||||
{ label: 'Иронический детектив', value: '85' },
|
||||
{ label: 'Исследования', value: '156' },
|
||||
{ label: 'Историческая литература', value: '3' },
|
||||
{ label: 'Исторический любовный роман', value: '518' },
|
||||
{ label: 'Историческое фэнтези', value: '90' },
|
||||
{ label: 'Киберпанк', value: '7' },
|
||||
{ label: 'Классическая литература', value: '9' },
|
||||
{ label: 'Книга-игра', value: '371' },
|
||||
{ label: 'Книги для родителей', value: '535' },
|
||||
{ label: 'Комедия', value: '80' },
|
||||
{ label: 'Конкурсные сборники', value: '349' },
|
||||
{ label: 'Короткий любовный роман', value: '517' },
|
||||
{ label: 'Космическая фантастика', value: '513' },
|
||||
{ label: 'Космоопера', value: '58' },
|
||||
{ label: 'Криминальная проза', value: '35' },
|
||||
{ label: 'Криптоистория', value: '400' },
|
||||
{ label: 'Ксенофантастика', value: '102' },
|
||||
{ label: 'Культура и искусство', value: '536' },
|
||||
{ label: 'Легенды', value: '161' },
|
||||
{ label: 'Лирика', value: '49' },
|
||||
{ label: 'ЛитРПГ', value: '62' },
|
||||
{ label: 'Любовная фантастика', value: '516' },
|
||||
{ label: 'Любовное фэнтези', value: '515' },
|
||||
{ label: 'Любовный роман', value: '46' },
|
||||
{ label: 'Магический реализм', value: '42' },
|
||||
{ label: 'Матриархат', value: '385' },
|
||||
{ label: 'Медицина', value: '57' },
|
||||
{ label: 'Мелодрама', value: '45' },
|
||||
{ label: 'Мемуары', value: '532' },
|
||||
{ label: 'Метафизика', value: '430' },
|
||||
{ label: 'Милитари', value: '126' },
|
||||
{ label: 'Мистика', value: '22' },
|
||||
{ label: 'Морские приключения', value: '211' },
|
||||
{ label: 'Мотивация', value: '135' },
|
||||
{ label: 'Научная фантастика', value: '122' },
|
||||
{ label: 'Научно-популярная', value: '43' },
|
||||
{ label: 'Ненаучная фантастика', value: '387' },
|
||||
{ label: 'Новелла', value: '498' },
|
||||
{ label: 'Нон-фикшн', value: '507' },
|
||||
{ label: 'Нуар', value: '121' },
|
||||
{ label: 'О войне', value: '136' },
|
||||
{ label: 'Оккультизм', value: '424' },
|
||||
{ label: 'Параллельные миры', value: '438' },
|
||||
{ label: 'Пародия', value: '108' },
|
||||
{ label: 'Подростковая фантастика', value: '27' },
|
||||
{ label: 'Подростковый роман', value: '388' },
|
||||
{ label: 'Подростковый ужастик', value: '110' },
|
||||
{ label: 'Постапокалипсис', value: '82' },
|
||||
{ label: 'Постапокалиптика', value: '67' },
|
||||
{ label: 'Поэзия', value: '44' },
|
||||
{ label: 'Превращение', value: '170' },
|
||||
{ label: 'Приключение', value: '497' },
|
||||
{ label: 'Приключения', value: '5' },
|
||||
{ label: 'Природа и животные', value: '538' },
|
||||
{ label: 'Проза', value: '95' },
|
||||
{ label: 'Производственный роман', value: '148' },
|
||||
{ label: 'Психология', value: '31' },
|
||||
{ label: 'Путешествия', value: '383' },
|
||||
{ label: 'Рассказы', value: '101' },
|
||||
{ label: 'Реализм', value: '422' },
|
||||
{ label: 'РеалРПГ', value: '442' },
|
||||
{ label: 'Религия', value: '188' },
|
||||
{ label: 'Робинзонада', value: '434' },
|
||||
{ label: 'Романтика', value: '130' },
|
||||
{ label: 'Романтическая фантастика', value: '501' },
|
||||
{ label: 'Русская классика', value: '523' },
|
||||
{ label: 'Рыцарский роман', value: '112' },
|
||||
{ label: 'Символизм', value: '439' },
|
||||
{ label: 'Сказки', value: '93' },
|
||||
{ label: 'Славянское фэнтези', value: '508' },
|
||||
{ label: 'Сновидения', value: '428' },
|
||||
{ label: 'Современный любовный роман', value: '519' },
|
||||
{ label: 'Социально-психологическая фантастика', value: '98' },
|
||||
{ label: 'Спорт', value: '147' },
|
||||
{ label: 'Справочники', value: '528' },
|
||||
{ label: 'Стимпанк', value: '56' },
|
||||
{ label: 'Субкультура', value: '209' },
|
||||
{ label: 'Сюрреализм', value: '26' },
|
||||
{ label: 'Тёмное фэнтези', value: '437' },
|
||||
{ label: 'Техномагия', value: '159' },
|
||||
{ label: 'Триллер', value: '6' },
|
||||
{ label: 'Трэш', value: '162' },
|
||||
{ label: 'Ужасы', value: '531' },
|
||||
{ label: 'Утопия', value: '191' },
|
||||
{ label: 'Учебник', value: '411' },
|
||||
{ label: 'Фантастика', value: '1' },
|
||||
{ label: 'Фантастический боевик', value: '38' },
|
||||
{ label: 'Фанфик', value: '111' },
|
||||
{ label: 'Философия', value: '461' },
|
||||
{ label: 'Фурри', value: '394' },
|
||||
{ label: 'Фэнтези', value: '2' },
|
||||
{ label: 'Хентай', value: '441' },
|
||||
{ label: 'Хобби и досуг', value: '530' },
|
||||
{ label: 'Хоррор', value: '86' },
|
||||
{ label: 'Чёрный юмор', value: '73' },
|
||||
{ label: 'Эзотерика', value: '30' },
|
||||
{ label: 'Экшн', value: '94' },
|
||||
{ label: 'Эпическое фэнтези', value: '499' },
|
||||
{ label: 'Эротика', value: '19' },
|
||||
{ label: 'Эссе', value: '353' },
|
||||
{ label: 'ЭТТИ', value: '502' },
|
||||
{ label: 'Юмористическая литература', value: '20' },
|
||||
{ label: 'Юмористическая фантастика', value: '514' },
|
||||
{ label: 'Юмористическое фэнтези', value: '521' },
|
||||
{ label: 'EVE - Миры Содружества', value: '99' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
@@ -359,26 +362,26 @@ class Zelluloza implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
const alphabet: { [key: string]: string } = {
|
||||
"~": "0",
|
||||
"H": "1",
|
||||
"^": "2",
|
||||
"@": "3",
|
||||
"f": "4",
|
||||
"0": "5",
|
||||
"5": "6",
|
||||
"n": "7",
|
||||
"r": "8",
|
||||
"=": "9",
|
||||
"W": "a",
|
||||
"L": "b",
|
||||
"7": "c",
|
||||
" ": "d",
|
||||
"u": "e",
|
||||
"c": "f",
|
||||
'~': '0',
|
||||
'H': '1',
|
||||
'^': '2',
|
||||
'@': '3',
|
||||
'f': '4',
|
||||
'0': '5',
|
||||
'5': '6',
|
||||
'n': '7',
|
||||
'r': '8',
|
||||
'=': '9',
|
||||
'W': 'a',
|
||||
'L': 'b',
|
||||
'7': 'c',
|
||||
' ': 'd',
|
||||
'u': 'e',
|
||||
'c': 'f',
|
||||
};
|
||||
|
||||
function decrypt(encrypt: string) {
|
||||
if (!encrypt) return "";
|
||||
if (!encrypt) return '';
|
||||
const hexArray = [];
|
||||
|
||||
for (let j = 0; j < encrypt.length; j += 2) {
|
||||
@@ -387,12 +390,12 @@ function decrypt(encrypt: string) {
|
||||
hexArray.push(alphabet[firstChar] + alphabet[secondChar]);
|
||||
}
|
||||
|
||||
return "<p>" + hexToUtf8(hexArray) + "</p>";
|
||||
return '<p>' + hexToUtf8(hexArray) + '</p>';
|
||||
}
|
||||
|
||||
function hexToUtf8(hexArray: string[]) {
|
||||
let index = 0;
|
||||
let result = "";
|
||||
let result = '';
|
||||
|
||||
while (index < hexArray.length) {
|
||||
const currentHex = parseInt(hexArray[index], 16) & 0xff;
|
||||
@@ -401,13 +404,13 @@ function hexToUtf8(hexArray: string[]) {
|
||||
if (currentHex < 16) {
|
||||
switch (currentHex) {
|
||||
case 9:
|
||||
result += " ";
|
||||
result += ' ';
|
||||
break;
|
||||
case 13:
|
||||
result += "\r";
|
||||
result += '\r';
|
||||
break;
|
||||
case 10:
|
||||
result += "\n";
|
||||
result += '\n';
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user