remove this.cookieString attribute

This commit is contained in:
Nyagami
2024-01-14 22:26:34 +07:00
parent 8e3909c1d2
commit f4734f2dda
106 changed files with 11421 additions and 11695 deletions
+241 -242
View File
@@ -1,242 +1,241 @@
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";
class freedlit implements Plugin.PluginBase {
id = "freedlit.space";
name = "LitSpace";
site = "https://freedlit.space";
version = "1.0.0";
icon = "src/ru/freedlit/icon.png";
userAgent = "";
cookieString = "";
async popularNovels(
page: number,
{ showLatestNovels, 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");
if (filters?.genre?.value?.include?.length) {
url += filters.genre.value.include
.map(id => '&genres_included[]=' + id).join("");
}
if (filters?.genre?.value?.exclude?.length) {
url += filters.genre.value.exclude
.map(id => '&genres_excluded[]=' + id).join("");
}
const body = await fetchApi(url).then((res) => res.text());
const loadedCheerio = parseHTML(body);
const novels: Plugin.NovelItem[] = [];
loadedCheerio("#bookListBlock > div > div").each(function () {
const name = loadedCheerio(this).find("div > h4 > a").text()?.trim();
const cover = loadedCheerio(this)
.find("div > a > img")
.attr("src")
?.trim();
const url = loadedCheerio(this).find("div > h4 > a").attr("href")?.trim();
if (!name || !url) return;
novels.push({ name, cover, url });
});
return novels;
}
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
const body = await fetchApi(novelUrl).then((res) => res.text());
const loadedCheerio = parseHTML(body);
const novel: Plugin.SourceNovel = {
url: novelUrl,
name: loadedCheerio(".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(","),
};
const chapters: Plugin.ChapterItem[] = [];
loadedCheerio("a.chapter-line").each(function () {
const name = loadedCheerio(this).find("h6").text();
const releaseTime = loadedCheerio(this).find('span[class="date"]').text();
const url = loadedCheerio(this).attr("href");
if (!name || !url) return;
chapters.push({ name, releaseTime, url });
});
novel.chapters = chapters;
return novel;
}
async parseChapter(chapterUrl: string): Promise<string> {
const body = await fetchApi(chapterUrl).then((res) => res.text());
const loadedCheerio = parseHTML(body);
loadedCheerio("div.mobile-block").remove();
loadedCheerio("div.standart-block").remove();
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 loadedCheerio = parseHTML(body);
const novels: Plugin.NovelItem[] = [];
loadedCheerio("#bookListBlock > div").each(function () {
const name = loadedCheerio(this).find("h4 > a").text()?.trim();
const cover = loadedCheerio(this).find("a > img").attr("src")?.trim();
const url = loadedCheerio(this).find("h4 > a").attr("href")?.trim();
if (!name || !url) return;
novels.push({ name, cover, url });
});
return novels;
}
fetchImage = fetchFile;
filters = {
sort: {
label: "Сортировка:",
value: "",
options: [
{ label: "По популярности", value: "popular" },
{ label: "По количеству комментариев", value: "comments" },
{ label: "По количеству лайков", value: "likes" },
{ label: "По новизне", value: "recent" },
{ label: "По просмотрам", value: "views" },
],
type: FilterTypes.Picker,
},
genre: {
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" },
],
type: FilterTypes.ExcludableCheckboxGroup,
},
status: {
label: "Статус:",
value: "",
options: [
{ label: "Любой статус", value: "all" },
{ label: "В процессе", value: "in-process" },
{ label: "Завершено", value: "finished" },
],
type: FilterTypes.Picker,
},
access: {
label: "Доступ:",
value: "",
options: [
{ label: "Любой доступ", value: "all" },
{ label: "Бесплатные", value: "free" },
{ label: "Платные", value: "paid" },
],
type: FilterTypes.Picker,
},
adult: {
label: "Возрастные ограничения:",
value: "",
options: [
{ label: "Скрыть 18+", value: "hide" },
{ label: "Показать +18", value: "show" },
],
type: FilterTypes.Picker,
},
} satisfies Filters;
}
export default new freedlit();
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";
class freedlit implements Plugin.PluginBase {
id = "freedlit.space";
name = "LitSpace";
site = "https://freedlit.space";
version = "1.0.0";
icon = "src/ru/freedlit/icon.png";
userAgent = "";
async popularNovels(
page: number,
{ showLatestNovels, 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");
if (filters?.genre?.value?.include?.length) {
url += filters.genre.value.include
.map(id => '&genres_included[]=' + id).join("");
}
if (filters?.genre?.value?.exclude?.length) {
url += filters.genre.value.exclude
.map(id => '&genres_excluded[]=' + id).join("");
}
const body = await fetchApi(url).then((res) => res.text());
const loadedCheerio = parseHTML(body);
const novels: Plugin.NovelItem[] = [];
loadedCheerio("#bookListBlock > div > div").each(function () {
const name = loadedCheerio(this).find("div > h4 > a").text()?.trim();
const cover = loadedCheerio(this)
.find("div > a > img")
.attr("src")
?.trim();
const url = loadedCheerio(this).find("div > h4 > a").attr("href")?.trim();
if (!name || !url) return;
novels.push({ name, cover, url });
});
return novels;
}
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
const body = await fetchApi(novelUrl).then((res) => res.text());
const loadedCheerio = parseHTML(body);
const novel: Plugin.SourceNovel = {
url: novelUrl,
name: loadedCheerio(".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(","),
};
const chapters: Plugin.ChapterItem[] = [];
loadedCheerio("a.chapter-line").each(function () {
const name = loadedCheerio(this).find("h6").text();
const releaseTime = loadedCheerio(this).find('span[class="date"]').text();
const url = loadedCheerio(this).attr("href");
if (!name || !url) return;
chapters.push({ name, releaseTime, url });
});
novel.chapters = chapters;
return novel;
}
async parseChapter(chapterUrl: string): Promise<string> {
const body = await fetchApi(chapterUrl).then((res) => res.text());
const loadedCheerio = parseHTML(body);
loadedCheerio("div.mobile-block").remove();
loadedCheerio("div.standart-block").remove();
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 loadedCheerio = parseHTML(body);
const novels: Plugin.NovelItem[] = [];
loadedCheerio("#bookListBlock > div").each(function () {
const name = loadedCheerio(this).find("h4 > a").text()?.trim();
const cover = loadedCheerio(this).find("a > img").attr("src")?.trim();
const url = loadedCheerio(this).find("h4 > a").attr("href")?.trim();
if (!name || !url) return;
novels.push({ name, cover, url });
});
return novels;
}
fetchImage = fetchFile;
filters = {
sort: {
label: "Сортировка:",
value: "",
options: [
{ label: "По популярности", value: "popular" },
{ label: "По количеству комментариев", value: "comments" },
{ label: "По количеству лайков", value: "likes" },
{ label: "По новизне", value: "recent" },
{ label: "По просмотрам", value: "views" },
],
type: FilterTypes.Picker,
},
genre: {
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" },
],
type: FilterTypes.ExcludableCheckboxGroup,
},
status: {
label: "Статус:",
value: "",
options: [
{ label: "Любой статус", value: "all" },
{ label: "В процессе", value: "in-process" },
{ label: "Завершено", value: "finished" },
],
type: FilterTypes.Picker,
},
access: {
label: "Доступ:",
value: "",
options: [
{ label: "Любой доступ", value: "all" },
{ label: "Бесплатные", value: "free" },
{ label: "Платные", value: "paid" },
],
type: FilterTypes.Picker,
},
adult: {
label: "Возрастные ограничения:",
value: "",
options: [
{ label: "Скрыть 18+", value: "hide" },
{ label: "Показать +18", value: "show" },
],
type: FilterTypes.Picker,
},
} satisfies Filters;
}
File diff suppressed because it is too large Load Diff
+258 -259
View File
@@ -1,259 +1,258 @@
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";
userAgent = "";
cookieString = "";
async popularNovels(
pageNo: number,
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
): Promise<Plugin.NovelItem[]> {
let url = this.site + `/genre?page=${pageNo}&perPage=24&sortingType=`;
url += showLatestNovels
? "last-update"
: filters?.sort?.value || "bestseller";
if (filters?.genres?.value instanceof Array && filters?.genres?.value.length) {
url += "&g=" + filters.genres.value.join(",");
}
const result = await fetchApi(url);
const body = await result.text();
const loadedCheerio = parseHTML(body);
const novels: Plugin.NovelItem[] = [];
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
if (jsonRaw) {
const json: response = JSON.parse(jsonRaw);
json.props.pageProps.state.pagesFilter?.genre?.books?.forEach((novel) =>
novels.push({
name: novel.name,
cover: novel.coverImages[0].url,
url: this.site + "/book/" + novel.slug,
}),
);
}
return novels;
}
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
const result = await fetchApi(novelUrl);
const body = await result.text();
const loadedCheerio = parseHTML(body);
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
const json: response = JSON.parse(jsonRaw || "{}");
const book = json.props.pageProps.state.book?.bookPage;
const novel: Plugin.SourceNovel = {
url: novelUrl,
name: book?.name,
cover: book?.coverImages[0].url,
summary: book?.annotation,
author: book?.author?.name,
genres: book?.tags?.map((item) => item.name).join(", "),
status:
book?.statusComplete === "writing"
? NovelStatus.Ongoing
: NovelStatus.Completed,
};
const chapters: Plugin.ChapterItem[] = [];
book?.ebook?.chapters?.forEach((chapter) => {
if (chapter.available) {
chapters.push({
name: chapter.name,
releaseTime: dayjs(
chapter?.firstPublishedAt || chapter.createdAt,
).format("LLL"),
url: this.site + "/reader/" + book?.slug + "/" + chapter.chapterId,
});
}
});
novel.chapters = chapters;
return novel;
}
async parseChapter(chapterUrl: string): Promise<string> {
const url = "https://api.bookriver.ru/api/v1/books/chapter/text/";
const result = await fetchApi(url + chapterUrl.split("/").pop());
const json = (await result.json()) as responseChapter;
let chapterText = json.data.content || "Конец произведения";
if (json.data?.audio?.available) {
chapterText += "\n" + json.data.audio.url;
}
return chapterText;
}
async searchNovels(
searchTerm: string,
pageNo: number | undefined = 1,
): Promise<Plugin.NovelItem[]> {
const url = `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) =>
novels.push({
name: novel.name,
cover: novel.coverImages[0].url,
url: this.site + "/book/" + novel.slug,
}),
);
return novels;
}
fetchImage = fetchFile;
filters = {
sort: {
label: "Сортировка",
value: "",
options: [
{ label: "Бестселлеры", value: "bestseller" },
{ label: "Дате добавления", value: "newest" },
{ label: "Дате обновления", value: "last-update" },
],
type: FilterTypes.Picker,
},
genres: {
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" },
],
type: FilterTypes.CheckboxGroup,
},
} satisfies Filters;
}
export default new Bookriver();
interface response {
props: {
pageProps: {
state: {
book?: {
bookPage: BooksEntity;
};
pagesFilter?: {
genre: {
books: BooksEntity[];
};
};
};
};
};
}
interface BooksEntity {
name: string;
coverImages: { url: string }[];
slug: string;
annotation?: string;
author?: {
name: string;
};
tags?: { name: string }[];
ebook?: {
chapters: Chapter[];
};
statusComplete: string;
}
interface Chapter {
name: string;
available: boolean;
firstPublishedAt?: Date | null;
createdAt?: Date | null;
chapterId: number | string;
}
interface responseChapter {
data: {
id: number;
bookId: number;
name: string;
content: string;
symbols: number;
number: number;
authorPages: number;
createdAt: string;
updatedAt: string;
firstPublishedAt: string;
status: string;
protected: boolean;
free: boolean;
audio?: {
available: boolean;
url: string;
duration: number;
};
publicationScheduledFor?: null;
};
}
interface responseSearch {
data: Data;
total: number;
}
interface Data {
books?: BooksEntity[] | null;
}
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";
userAgent = "";
async popularNovels(
pageNo: number,
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
): Promise<Plugin.NovelItem[]> {
let url = this.site + `/genre?page=${pageNo}&perPage=24&sortingType=`;
url += showLatestNovels
? "last-update"
: filters?.sort?.value || "bestseller";
if (filters?.genres?.value instanceof Array && filters?.genres?.value.length) {
url += "&g=" + filters.genres.value.join(",");
}
const result = await fetchApi(url);
const body = await result.text();
const loadedCheerio = parseHTML(body);
const novels: Plugin.NovelItem[] = [];
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
if (jsonRaw) {
const json: response = JSON.parse(jsonRaw);
json.props.pageProps.state.pagesFilter?.genre?.books?.forEach((novel) =>
novels.push({
name: novel.name,
cover: novel.coverImages[0].url,
url: this.site + "/book/" + novel.slug,
}),
);
}
return novels;
}
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
const result = await fetchApi(novelUrl);
const body = await result.text();
const loadedCheerio = parseHTML(body);
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
const json: response = JSON.parse(jsonRaw || "{}");
const book = json.props.pageProps.state.book?.bookPage;
const novel: Plugin.SourceNovel = {
url: novelUrl,
name: book?.name,
cover: book?.coverImages[0].url,
summary: book?.annotation,
author: book?.author?.name,
genres: book?.tags?.map((item) => item.name).join(", "),
status:
book?.statusComplete === "writing"
? NovelStatus.Ongoing
: NovelStatus.Completed,
};
const chapters: Plugin.ChapterItem[] = [];
book?.ebook?.chapters?.forEach((chapter) => {
if (chapter.available) {
chapters.push({
name: chapter.name,
releaseTime: dayjs(
chapter?.firstPublishedAt || chapter.createdAt,
).format("LLL"),
url: this.site + "/reader/" + book?.slug + "/" + chapter.chapterId,
});
}
});
novel.chapters = chapters;
return novel;
}
async parseChapter(chapterUrl: string): Promise<string> {
const url = "https://api.bookriver.ru/api/v1/books/chapter/text/";
const result = await fetchApi(url + chapterUrl.split("/").pop());
const json = (await result.json()) as responseChapter;
let chapterText = json.data.content || "Конец произведения";
if (json.data?.audio?.available) {
chapterText += "\n" + json.data.audio.url;
}
return chapterText;
}
async searchNovels(
searchTerm: string,
pageNo: number | undefined = 1,
): Promise<Plugin.NovelItem[]> {
const url = `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) =>
novels.push({
name: novel.name,
cover: novel.coverImages[0].url,
url: this.site + "/book/" + novel.slug,
}),
);
return novels;
}
fetchImage = fetchFile;
filters = {
sort: {
label: "Сортировка",
value: "",
options: [
{ label: "Бестселлеры", value: "bestseller" },
{ label: "Дате добавления", value: "newest" },
{ label: "Дате обновления", value: "last-update" },
],
type: FilterTypes.Picker,
},
genres: {
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" },
],
type: FilterTypes.CheckboxGroup,
},
} satisfies Filters;
}
export default new Bookriver();
interface response {
props: {
pageProps: {
state: {
book?: {
bookPage: BooksEntity;
};
pagesFilter?: {
genre: {
books: BooksEntity[];
};
};
};
};
};
}
interface BooksEntity {
name: string;
coverImages: { url: string }[];
slug: string;
annotation?: string;
author?: {
name: string;
};
tags?: { name: string }[];
ebook?: {
chapters: Chapter[];
};
statusComplete: string;
}
interface Chapter {
name: string;
available: boolean;
firstPublishedAt?: Date | null;
createdAt?: Date | null;
chapterId: number | string;
}
interface responseChapter {
data: {
id: number;
bookId: number;
name: string;
content: string;
symbols: number;
number: number;
authorPages: number;
createdAt: string;
updatedAt: string;
firstPublishedAt: string;
status: string;
protected: boolean;
free: boolean;
audio?: {
available: boolean;
url: string;
duration: number;
};
publicationScheduledFor?: null;
};
}
interface responseSearch {
data: Data;
total: number;
}
interface Data {
books?: BooksEntity[] | null;
+2643 -2644
View File
File diff suppressed because it is too large Load Diff
+264 -265
View File
@@ -1,265 +1,264 @@
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";
class Jaomix implements Plugin.PluginBase {
id = "jaomix.ru";
name = "Jaomix";
site = "https://jaomix.ru";
version = "1.0.0";
icon = "src/ru/jaomix/icon.png";
userAgent = "";
cookieString = "";
async popularNovels(
pageNo: number,
{
showLatestNovels,
filters,
}: Plugin.PopularNovelsOptions<typeof this.filters>,
): Promise<Plugin.NovelItem[]> {
let url = this.site + "/?searchrn";
if (filters?.lang?.value?.length) {
url += filters.lang.value
.map((lang, idx) => `&lang[${idx}]=${lang}`).join("");
}
if (filters?.genre?.value?.include?.length) {
url += filters.genre.value.include
.map((genre, idx) => `&genre[${idx}]=${genre}`).join("");
}
if (filters?.genre?.value?.exclude?.length) {
url += filters.genre.value.exclude
.map((genre, idx) => `&delgenre[${idx}]=del ${genre}`).join("");
}
url += "&sortcountchapt=" + (filters?.sortcountchapt?.value || "1");
url += "&sortdaycreate=" + (filters?.sortdaycreate?.value || "1");
url +=
"&sortby=" + (showLatestNovels ? "upd" : filters?.sortby?.value || "topweek");
url += "&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(
function () {
const name = loadedCheerio(this)
.find('div[class="img-home"] > a')
.attr("title");
const cover = loadedCheerio(this)
.find('div[class="img-home"] > a > img')
.attr("src")
?.replace("-150x150", "");
const url = loadedCheerio(this)
.find('div[class="img-home"] > a')
.attr("href");
if (!name || !url) return;
novels.push({ name, cover, url });
},
);
return novels;
}
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
const body = await fetchApi(novelUrl).then((res) => res.text());
const loadedCheerio = parseHTML(body);
const novel: Plugin.SourceNovel = {
url: novelUrl,
name: loadedCheerio('div[class="desc-book"] > h1').text().trim(),
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("продолжается")
? NovelStatus.Ongoing
: NovelStatus.Completed;
}
});
const chapters: Plugin.ChapterItem[] = [];
loadedCheerio(".download-chapter div.title").each(function () {
const name = loadedCheerio(this).find("a").attr("title");
const releaseTime = loadedCheerio(this).find("time").text();
const url = loadedCheerio(this).find("a").attr("href");
if (!name || !url) return;
chapters.push({ name, releaseTime, url });
});
novel.chapters = chapters.reverse();
return novel;
}
async parseChapter(chapterUrl: string): Promise<string> {
const body = await fetchApi(chapterUrl).then((res) => res.text());
const loadedCheerio = parseHTML(body);
loadedCheerio('div[class="adblock-service"]').remove();
const chapterText = loadedCheerio('div[class="entry-content"]').html();
return chapterText || "";
}
async searchNovels(
searchTerm: string,
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());
const loadedCheerio = parseHTML(body);
const novels: Plugin.NovelItem[] = [];
loadedCheerio('div[class="block-home"] > div[class="one"]').each(
function () {
const name = loadedCheerio(this)
.find('div[class="img-home"] > a')
.attr("title");
const cover = loadedCheerio(this)
.find('div[class="img-home"] > a > img')
.attr("src")
?.replace("-150x150", "");
const url = loadedCheerio(this)
.find('div[class="img-home"] > a')
.attr("href");
if (!name || !url) return;
novels.push({ name, cover, url });
},
);
return novels;
}
fetchImage = fetchFile;
filters = {
sortby: {
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" },
],
type: FilterTypes.Picker,
},
sortdaycreate: {
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" },
],
type: FilterTypes.Picker,
},
sortcountchapt: {
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" },
],
type: FilterTypes.Picker,
},
genre: {
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" },
],
type: FilterTypes.ExcludableCheckboxGroup,
},
lang: {
label: "Выбрать языки:",
value: [],
options: [
{ label: "Английский", value: "Английский" },
{ label: "Китайский", value: "Китайский" },
{ label: "Корейский", value: "Корейский" },
{ label: "Японский", value: "Японский" },
],
type: FilterTypes.CheckboxGroup,
},
} satisfies Filters;
}
export default new Jaomix();
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";
class Jaomix implements Plugin.PluginBase {
id = "jaomix.ru";
name = "Jaomix";
site = "https://jaomix.ru";
version = "1.0.0";
icon = "src/ru/jaomix/icon.png";
userAgent = "";
async popularNovels(
pageNo: number,
{
showLatestNovels,
filters,
}: Plugin.PopularNovelsOptions<typeof this.filters>,
): Promise<Plugin.NovelItem[]> {
let url = this.site + "/?searchrn";
if (filters?.lang?.value?.length) {
url += filters.lang.value
.map((lang, idx) => `&lang[${idx}]=${lang}`).join("");
}
if (filters?.genre?.value?.include?.length) {
url += filters.genre.value.include
.map((genre, idx) => `&genre[${idx}]=${genre}`).join("");
}
if (filters?.genre?.value?.exclude?.length) {
url += filters.genre.value.exclude
.map((genre, idx) => `&delgenre[${idx}]=del ${genre}`).join("");
}
url += "&sortcountchapt=" + (filters?.sortcountchapt?.value || "1");
url += "&sortdaycreate=" + (filters?.sortdaycreate?.value || "1");
url +=
"&sortby=" + (showLatestNovels ? "upd" : filters?.sortby?.value || "topweek");
url += "&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(
function () {
const name = loadedCheerio(this)
.find('div[class="img-home"] > a')
.attr("title");
const cover = loadedCheerio(this)
.find('div[class="img-home"] > a > img')
.attr("src")
?.replace("-150x150", "");
const url = loadedCheerio(this)
.find('div[class="img-home"] > a')
.attr("href");
if (!name || !url) return;
novels.push({ name, cover, url });
},
);
return novels;
}
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
const body = await fetchApi(novelUrl).then((res) => res.text());
const loadedCheerio = parseHTML(body);
const novel: Plugin.SourceNovel = {
url: novelUrl,
name: loadedCheerio('div[class="desc-book"] > h1').text().trim(),
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("продолжается")
? NovelStatus.Ongoing
: NovelStatus.Completed;
}
});
const chapters: Plugin.ChapterItem[] = [];
loadedCheerio(".download-chapter div.title").each(function () {
const name = loadedCheerio(this).find("a").attr("title");
const releaseTime = loadedCheerio(this).find("time").text();
const url = loadedCheerio(this).find("a").attr("href");
if (!name || !url) return;
chapters.push({ name, releaseTime, url });
});
novel.chapters = chapters.reverse();
return novel;
}
async parseChapter(chapterUrl: string): Promise<string> {
const body = await fetchApi(chapterUrl).then((res) => res.text());
const loadedCheerio = parseHTML(body);
loadedCheerio('div[class="adblock-service"]').remove();
const chapterText = loadedCheerio('div[class="entry-content"]').html();
return chapterText || "";
}
async searchNovels(
searchTerm: string,
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());
const loadedCheerio = parseHTML(body);
const novels: Plugin.NovelItem[] = [];
loadedCheerio('div[class="block-home"] > div[class="one"]').each(
function () {
const name = loadedCheerio(this)
.find('div[class="img-home"] > a')
.attr("title");
const cover = loadedCheerio(this)
.find('div[class="img-home"] > a > img')
.attr("src")
?.replace("-150x150", "");
const url = loadedCheerio(this)
.find('div[class="img-home"] > a')
.attr("href");
if (!name || !url) return;
novels.push({ name, cover, url });
},
);
return novels;
}
fetchImage = fetchFile;
filters = {
sortby: {
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" },
],
type: FilterTypes.Picker,
},
sortdaycreate: {
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" },
],
type: FilterTypes.Picker,
},
sortcountchapt: {
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" },
],
type: FilterTypes.Picker,
},
genre: {
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" },
],
type: FilterTypes.ExcludableCheckboxGroup,
},
lang: {
label: "Выбрать языки:",
value: [],
options: [
{ label: "Английский", value: "Английский" },
{ label: "Китайский", value: "Китайский" },
{ label: "Корейский", value: "Корейский" },
{ label: "Японский", value: "Японский" },
],
type: FilterTypes.CheckboxGroup,
},
} satisfies Filters;
}
+957 -958
View File
File diff suppressed because it is too large Load Diff
+1284 -1285
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+408 -409
View File
@@ -1,409 +1,408 @@
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";
userAgent = "";
cookieString = "";
async popularNovels(
pageNo: number,
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
): Promise<Plugin.NovelItem[]> {
let url = this.site + "/books?order=";
url += showLatestNovels
? "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 novels: Plugin.NovelItem[] = [];
json.props.pageProps?.totalData?.items?.forEach((novel) =>
novels.push({
name: novel.title,
cover: novel?.verticalImage?.url
? this.site + novel.verticalImage.url
: defaultCover,
url: this.site + "/" + novel.slug,
}),
);
return novels;
}
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
const result = await fetchApi(novelUrl);
const body = await result.text();
const loadedCheerio = parseHTML(body);
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
const book = (JSON.parse(jsonRaw || "{}") as response).props.pageProps.book;
const novel: Plugin.SourceNovel = {
url: novelUrl,
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("Активен")
? NovelStatus.Ongoing
: NovelStatus.Completed,
};
const chapters: Plugin.ChapterItem[] = [];
book?.chapters?.forEach((chapter) => {
if (!chapter.isDonate || chapter.isUserPaid) {
chapters.push({
name: chapter.title,
releaseTime: dayjs(chapter.publishedAt).format("LLL"),
url: this.site + chapter.url,
});
}
});
novel.chapters = chapters.reverse();
return novel;
}
async parseChapter(chapterUrl: string): Promise<string> {
const result = await fetchApi(chapterUrl);
const body = await result.text();
let loadedCheerio = parseHTML(body);
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
const json: response = JSON.parse(jsonRaw || "{}");
loadedCheerio = parseHTML(
json.props.pageProps?.chapter?.content?.text || "",
);
const baseUrl = this.site;
loadedCheerio("img").each(function () {
if (!loadedCheerio(this).attr("src")?.startsWith("http")) {
const src = loadedCheerio(this).attr("src");
loadedCheerio(this).attr("src", baseUrl + src);
}
});
const chapterText = loadedCheerio.html();
return chapterText;
}
async searchNovels(
searchTerm: string,
//pageNo: number | undefined = 1,
): Promise<Plugin.NovelItem[]> {
const url = `${this.site}/v3/books?filter[or][0][title][like]=${searchTerm}&filter[or][1][titleEn][like]=${searchTerm}&filter[or][2][fullTitle][like]=${searchTerm}&filter[status][]=active&filter[status][]=abandoned&filter[status][]=completed&expand=verticalImage`;
const result = await fetchApi(url);
const body = (await result.json()) as { items: Item[] };
const novels: Plugin.NovelItem[] = [];
body.items.forEach((novel) =>
novels.push({
name: novel.title,
cover: novel?.verticalImage?.url
? this.site + novel.verticalImage.url
: defaultCover,
url: this.site + "/" + novel.slug,
}),
);
return novels;
}
fetchImage = fetchFile;
filters = {
sort: {
label: "Сортировка",
value: "",
options: [
{ label: "Рейтинг", value: "popular" },
{ label: "Дате добавления", value: "new" },
{ label: "Дате обновления", value: "lastPublishedChapter" },
{ label: "Законченные", value: "completed" },
],
type: FilterTypes.Picker,
},
} satisfies Filters;
}
export default new RNRF();
interface response {
props: Props;
page: string;
query: Query;
buildId: string;
isFallback: boolean;
gssp: boolean;
appGip: boolean;
dynamicIds?: string[];
}
interface Props {
pageProps: PageProps;
isMobile: boolean;
theme: string;
__N_SSP: boolean;
}
interface PageProps {
initialReduxState: InitialReduxState;
book?: PagePropsBook;
totalData?: TotalData;
layoutViewCookies?: string;
displayBanner?: boolean;
chapter?: PagePropsChapter;
}
interface PagePropsBook {
id: number;
countryId: number;
creatorId: number;
lastChapterId: number;
sourceId: number;
imgVerticalId: number;
imgHorizontalId: number;
imgSquareId: number;
imageVerticalId: number;
imageHorizontalId: number;
title: string;
titleEn: string;
fullTitle: string;
fullTitleEn: string;
description: string;
slug: string;
author: string;
additionalInfo: string;
importantInfo: string;
status: string;
views: number;
likes: number;
dislikes: number;
intervalSec: number;
intervalForNextFreeChapterSec: number;
chapterCost: number;
chapterPrice: number;
chapterPriceCollected: number;
maxSubscriptionChapters: number;
maxDonateChapters: number;
isDisplayOnMainPage: boolean;
withoutSchedule: boolean;
nextFreeChapterPublishedAt: null;
createdAt: Date;
updatedAt: Date;
userRating: null;
userBookmark: null;
country: Country;
genres: Genre[];
timer: Timer;
chapters: ChapterElement[];
verticalImage: Image;
horizontalImage: Image;
squareImage: Image;
}
interface ChapterElement {
id: number;
bookId: number;
title: string;
url: string;
numberChapter: string;
tom: number | null;
chapterShortNumber: number | null;
isDonate: boolean;
isSponsored: boolean;
isSubscription: boolean;
isEdited: boolean;
publishedAt: Date;
price: number;
views: number;
isUserPaid?: boolean;
}
interface Country {
id: number;
title: string;
code: string;
}
interface Genre {
id: number;
title: string;
slug: string;
}
interface Image {
id: number;
alt: string;
path: string;
name: string;
url: string;
}
interface Timer {
value: null;
for: null;
message: string;
}
interface PagePropsChapter {
id: number;
bookId: number;
editorId: null;
translatorId: number;
parserChapterId: null;
title: string;
slug: string;
status: string;
tom: null;
views: number;
isSponsored: boolean;
isSubscription: boolean;
isDonate: boolean;
isIndexing: boolean;
publishedAt: Date;
changeAvailabilityAt: null;
createdAt: Date;
updatedAt: Date;
numberChapter: string;
price: number;
content: Content;
book: ChapterBook;
userBookmark: null;
isUserPaid: boolean;
nextChapter: null;
previousChapter: PreviousChapter;
corrections: null;
}
interface ChapterBook {
id: number;
title: string;
titleEn: string;
url: string;
}
interface Content {
id: number;
text: string;
symbolsCount: number;
}
interface PreviousChapter {
id: number;
title: string;
views: number;
publishedAt: Date;
url: string;
numberChapter: string;
}
interface InitialReduxState {
data?: Data;
auth: Auth;
banners?: Banners;
corrections?: Corrections;
readerSettings?: ReaderSettings;
}
interface Auth {
isAuth: boolean;
user: null;
}
interface Banners {
displaySale: boolean;
}
interface Corrections {
items: null;
data: null;
}
interface Data {
bookPage: BookPage | null;
chapterPage: ChapterPage | null;
}
interface BookPage {
bookId: number;
bookmark: null;
}
interface ChapterPage {
chapterId: number;
bookId: number;
bookmark: null;
}
interface ReaderSettings {
fontSize: number;
lineHeight: number;
textIndent: number;
sidebarHide: number;
textAlign: string;
}
interface TotalData {
items: Item[];
_links: Links;
pagesData: PagesData;
}
interface Links {
self: First;
first: First;
last: First;
next: First;
}
interface First {
href: string;
}
interface Item {
id: number;
title: string;
description: string;
slug: string;
url: string;
status: string;
dislikes: number;
likes: number;
userRating: null;
lastChapter: ChapterElement;
verticalImage: Image;
horizontalImage: Image;
squareImage: Image;
}
interface PagesData {
totalCount: number;
pageCount: number;
currentPage: number;
perPage: number;
}
interface Query {
book?: string;
chapter?: string;
}
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";
userAgent = "";
async popularNovels(
pageNo: number,
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
): Promise<Plugin.NovelItem[]> {
let url = this.site + "/books?order=";
url += showLatestNovels
? "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 novels: Plugin.NovelItem[] = [];
json.props.pageProps?.totalData?.items?.forEach((novel) =>
novels.push({
name: novel.title,
cover: novel?.verticalImage?.url
? this.site + novel.verticalImage.url
: defaultCover,
url: this.site + "/" + novel.slug,
}),
);
return novels;
}
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
const result = await fetchApi(novelUrl);
const body = await result.text();
const loadedCheerio = parseHTML(body);
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
const book = (JSON.parse(jsonRaw || "{}") as response).props.pageProps.book;
const novel: Plugin.SourceNovel = {
url: novelUrl,
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("Активен")
? NovelStatus.Ongoing
: NovelStatus.Completed,
};
const chapters: Plugin.ChapterItem[] = [];
book?.chapters?.forEach((chapter) => {
if (!chapter.isDonate || chapter.isUserPaid) {
chapters.push({
name: chapter.title,
releaseTime: dayjs(chapter.publishedAt).format("LLL"),
url: this.site + chapter.url,
});
}
});
novel.chapters = chapters.reverse();
return novel;
}
async parseChapter(chapterUrl: string): Promise<string> {
const result = await fetchApi(chapterUrl);
const body = await result.text();
let loadedCheerio = parseHTML(body);
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
const json: response = JSON.parse(jsonRaw || "{}");
loadedCheerio = parseHTML(
json.props.pageProps?.chapter?.content?.text || "",
);
const baseUrl = this.site;
loadedCheerio("img").each(function () {
if (!loadedCheerio(this).attr("src")?.startsWith("http")) {
const src = loadedCheerio(this).attr("src");
loadedCheerio(this).attr("src", baseUrl + src);
}
});
const chapterText = loadedCheerio.html();
return chapterText;
}
async searchNovels(
searchTerm: string,
//pageNo: number | undefined = 1,
): Promise<Plugin.NovelItem[]> {
const url = `${this.site}/v3/books?filter[or][0][title][like]=${searchTerm}&filter[or][1][titleEn][like]=${searchTerm}&filter[or][2][fullTitle][like]=${searchTerm}&filter[status][]=active&filter[status][]=abandoned&filter[status][]=completed&expand=verticalImage`;
const result = await fetchApi(url);
const body = (await result.json()) as { items: Item[] };
const novels: Plugin.NovelItem[] = [];
body.items.forEach((novel) =>
novels.push({
name: novel.title,
cover: novel?.verticalImage?.url
? this.site + novel.verticalImage.url
: defaultCover,
url: this.site + "/" + novel.slug,
}),
);
return novels;
}
fetchImage = fetchFile;
filters = {
sort: {
label: "Сортировка",
value: "",
options: [
{ label: "Рейтинг", value: "popular" },
{ label: "Дате добавления", value: "new" },
{ label: "Дате обновления", value: "lastPublishedChapter" },
{ label: "Законченные", value: "completed" },
],
type: FilterTypes.Picker,
},
} satisfies Filters;
}
export default new RNRF();
interface response {
props: Props;
page: string;
query: Query;
buildId: string;
isFallback: boolean;
gssp: boolean;
appGip: boolean;
dynamicIds?: string[];
}
interface Props {
pageProps: PageProps;
isMobile: boolean;
theme: string;
__N_SSP: boolean;
}
interface PageProps {
initialReduxState: InitialReduxState;
book?: PagePropsBook;
totalData?: TotalData;
layoutViewCookies?: string;
displayBanner?: boolean;
chapter?: PagePropsChapter;
}
interface PagePropsBook {
id: number;
countryId: number;
creatorId: number;
lastChapterId: number;
sourceId: number;
imgVerticalId: number;
imgHorizontalId: number;
imgSquareId: number;
imageVerticalId: number;
imageHorizontalId: number;
title: string;
titleEn: string;
fullTitle: string;
fullTitleEn: string;
description: string;
slug: string;
author: string;
additionalInfo: string;
importantInfo: string;
status: string;
views: number;
likes: number;
dislikes: number;
intervalSec: number;
intervalForNextFreeChapterSec: number;
chapterCost: number;
chapterPrice: number;
chapterPriceCollected: number;
maxSubscriptionChapters: number;
maxDonateChapters: number;
isDisplayOnMainPage: boolean;
withoutSchedule: boolean;
nextFreeChapterPublishedAt: null;
createdAt: Date;
updatedAt: Date;
userRating: null;
userBookmark: null;
country: Country;
genres: Genre[];
timer: Timer;
chapters: ChapterElement[];
verticalImage: Image;
horizontalImage: Image;
squareImage: Image;
}
interface ChapterElement {
id: number;
bookId: number;
title: string;
url: string;
numberChapter: string;
tom: number | null;
chapterShortNumber: number | null;
isDonate: boolean;
isSponsored: boolean;
isSubscription: boolean;
isEdited: boolean;
publishedAt: Date;
price: number;
views: number;
isUserPaid?: boolean;
}
interface Country {
id: number;
title: string;
code: string;
}
interface Genre {
id: number;
title: string;
slug: string;
}
interface Image {
id: number;
alt: string;
path: string;
name: string;
url: string;
}
interface Timer {
value: null;
for: null;
message: string;
}
interface PagePropsChapter {
id: number;
bookId: number;
editorId: null;
translatorId: number;
parserChapterId: null;
title: string;
slug: string;
status: string;
tom: null;
views: number;
isSponsored: boolean;
isSubscription: boolean;
isDonate: boolean;
isIndexing: boolean;
publishedAt: Date;
changeAvailabilityAt: null;
createdAt: Date;
updatedAt: Date;
numberChapter: string;
price: number;
content: Content;
book: ChapterBook;
userBookmark: null;
isUserPaid: boolean;
nextChapter: null;
previousChapter: PreviousChapter;
corrections: null;
}
interface ChapterBook {
id: number;
title: string;
titleEn: string;
url: string;
}
interface Content {
id: number;
text: string;
symbolsCount: number;
}
interface PreviousChapter {
id: number;
title: string;
views: number;
publishedAt: Date;
url: string;
numberChapter: string;
}
interface InitialReduxState {
data?: Data;
auth: Auth;
banners?: Banners;
corrections?: Corrections;
readerSettings?: ReaderSettings;
}
interface Auth {
isAuth: boolean;
user: null;
}
interface Banners {
displaySale: boolean;
}
interface Corrections {
items: null;
data: null;
}
interface Data {
bookPage: BookPage | null;
chapterPage: ChapterPage | null;
}
interface BookPage {
bookId: number;
bookmark: null;
}
interface ChapterPage {
chapterId: number;
bookId: number;
bookmark: null;
}
interface ReaderSettings {
fontSize: number;
lineHeight: number;
textIndent: number;
sidebarHide: number;
textAlign: string;
}
interface TotalData {
items: Item[];
_links: Links;
pagesData: PagesData;
}
interface Links {
self: First;
first: First;
last: First;
next: First;
}
interface First {
href: string;
}
interface Item {
id: number;
title: string;
description: string;
slug: string;
url: string;
status: string;
dislikes: number;
likes: number;
userRating: null;
lastChapter: ChapterElement;
verticalImage: Image;
horizontalImage: Image;
squareImage: Image;
}
interface PagesData {
totalCount: number;
pageCount: number;
currentPage: number;
perPage: number;
}
interface Query {
book?: string;
chapter?: string;
+1105 -1106
View File
File diff suppressed because it is too large Load Diff