* fix

* test

* sync

* nitromanga -> madara

* test wuxiaworld

* xm

* add chapterNumber

* fix

chapterNumber > 0

* test filters

* nice

* tunovelaligera -> madara

* Squashed commit of the following:

commit 4c7bee5855
Author: nyagami <hoangquan05112002@gmail.com>
Date:   Mon Jan 29 17:56:23 2024 +0700

    fix: use english lang in plugin item consistent

commit b26d1129de
Author: uy/sun <hmy0119@hotmail.com>
Date:   Sun Jan 28 20:22:43 2024 +0800

    Chinese: support showLatestNovels (#733)

    * improve: better chapterText

    * feat: support latest page

commit 6ff206eab3
Author: nyagami <hoangquan05112002@gmail.com>
Date:   Sat Jan 27 09:26:19 2024 +0700

    Refactor: Using English key (#728)

    * fix: use english key instead of native

    * fix: ukrainian

    * fix: web test with new plugins map

commit 62fc988019
Author: Marcin Majewski <skilletssgames@gmail.com>
Date:   Fri Jan 26 14:42:54 2024 +0100

    Fix search box styles (#727)

commit 3a1e0c43eb
Author: uy/sun <hmy0119@hotmail.com>
Date:   Fri Jan 26 19:46:47 2024 +0800

    Add 69shu and 69xinshu (#726)

    * fix(linovelib):  use NovelStatus and translate filter label

    * feat(69xinshu): add 69xinshu

    * feat(69shu): add 69shu

    * remove: empty userAgent

* updated the filters and removed dead sites.

Error: net::ERR_NAME_NOT_RESOLVED at https://cratenovel.com/novel
Error: net::ERR_CONNECTION_REFUSED at https://clicknovel.net/novel
Error: net::ERR_NAME_NOT_RESOLVED at https://noobchan.xyz/novel
Error: net::ERR_CONNECTION_TIMED_OUT at https://sugarbbscan.com/series

* test

* Squashed commit of the following:

commit cadf7a72fe
Author: Felipe Bonfim <felipebonfim2006@gmail.com>
Date:   Tue Jan 30 14:55:34 2024 -0400

    kiniga added (#739)

    * kiniga added

    * kiniga updated icon

commit 38fc1860da
Author: K1ngfish3r <trulyihu@gmail.com>
Date:   Mon Jan 29 23:27:42 2024 +0800

    (add src) LNMTL & ScribbleHub (#738)

    * quick

    * quick panda fix

    * remove console.log

commit 4c7bee5855
Author: nyagami <hoangquan05112002@gmail.com>
Date:   Mon Jan 29 17:56:23 2024 +0700

    fix: use english lang in plugin item consistent

commit b26d1129de
Author: uy/sun <hmy0119@hotmail.com>
Date:   Sun Jan 28 20:22:43 2024 +0800

    Chinese: support showLatestNovels (#733)

    * improve: better chapterText

    * feat: support latest page

commit 6ff206eab3
Author: nyagami <hoangquan05112002@gmail.com>
Date:   Sat Jan 27 09:26:19 2024 +0700

    Refactor: Using English key (#728)

    * fix: use english key instead of native

    * fix: ukrainian

    * fix: web test with new plugins map

commit 62fc988019
Author: Marcin Majewski <skilletssgames@gmail.com>
Date:   Fri Jan 26 14:42:54 2024 +0100

    Fix search box styles (#727)

commit 3a1e0c43eb
Author: uy/sun <hmy0119@hotmail.com>
Date:   Fri Jan 26 19:46:47 2024 +0800

    Add 69shu and 69xinshu (#726)

    * fix(linovelib):  use NovelStatus and translate filter label

    * feat(69xinshu): add 69xinshu

    * feat(69shu): add 69shu

    * remove: empty userAgent

* add wuxiamtl, fannovel

* fix

* Update ranobelib.ts

* parseMadaraDate deleted

* remove multisrc name from Plugin item

---------

Co-authored-by: nyagami <hoangquan05112002@gmail.com>
This commit is contained in:
Rider21
2024-02-04 08:47:12 +03:00
committed by GitHub
parent 26bdb94100
commit fc52a6e665
36 changed files with 34403 additions and 15118 deletions
+242 -241
View File
@@ -1,241 +1,242 @@
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";
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;
}
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";
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((chapterIndex, element) => {
const name = loadedCheerio(element).find("h6").text();
const url = loadedCheerio(element).attr("href");
if (!name || !url) return;
const releaseTime = loadedCheerio(element).find('span[class="date"]').text();
chapters.push({ name, url, releaseTime, chapterNumber: chapterIndex + 1 });
});
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: "popular",
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: "all",
options: [
{ label: "Любой статус", value: "all" },
{ label: "В процессе", value: "in-process" },
{ label: "Завершено", value: "finished" },
],
type: FilterTypes.Picker,
},
access: {
label: "Доступ:",
value: "all",
options: [
{ label: "Любой доступ", value: "all" },
{ label: "Бесплатные", value: "free" },
{ label: "Платные", value: "paid" },
],
type: FilterTypes.Picker,
},
adult: {
label: "Возрастные ограничения:",
value: "hide",
options: [
{ label: "Скрыть 18+", value: "hide" },
{ label: "Показать +18", value: "show" },
],
type: FilterTypes.Picker,
},
} satisfies Filters;
}
export default new freedlit();