Plugins (#718)
* fix * test * sync * nitromanga -> madara * test wuxiaworld * xm * add chapterNumber * fix chapterNumber > 0 * test filters * nice * tunovelaligera -> madara * Squashed commit of the following: commit4c7bee5855Author: nyagami <hoangquan05112002@gmail.com> Date: Mon Jan 29 17:56:23 2024 +0700 fix: use english lang in plugin item consistent commitb26d1129deAuthor: 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 commit6ff206eab3Author: 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 commit62fc988019Author: Marcin Majewski <skilletssgames@gmail.com> Date: Fri Jan 26 14:42:54 2024 +0100 Fix search box styles (#727) commit3a1e0c43ebAuthor: 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: commitcadf7a72feAuthor: Felipe Bonfim <felipebonfim2006@gmail.com> Date: Tue Jan 30 14:55:34 2024 -0400 kiniga added (#739) * kiniga added * kiniga updated icon commit38fc1860daAuthor: 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 commit4c7bee5855Author: nyagami <hoangquan05112002@gmail.com> Date: Mon Jan 29 17:56:23 2024 +0700 fix: use english lang in plugin item consistent commitb26d1129deAuthor: 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 commit6ff206eab3Author: 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 commit62fc988019Author: Marcin Majewski <skilletssgames@gmail.com> Date: Fri Jan 26 14:42:54 2024 +0100 Fix search box styles (#727) commit3a1e0c43ebAuthor: 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>
@@ -17,6 +17,7 @@ fi
|
|||||||
|
|
||||||
git merge $current --strategy-option theirs
|
git merge $current --strategy-option theirs
|
||||||
|
|
||||||
|
npm run clearMulti
|
||||||
npm run generate
|
npm run generate
|
||||||
npm run json
|
npm run json
|
||||||
git add .
|
git add .
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -1,26 +0,0 @@
|
|||||||
import dayjs from "dayjs";
|
|
||||||
|
|
||||||
export const parseMadaraDate = (date: string) => {
|
|
||||||
if (date.includes("ago")) {
|
|
||||||
const dayJSDate = dayjs(new Date()); // today
|
|
||||||
const timeAgo = date.match(/\d+/)?.[0] || "";
|
|
||||||
const timeAgoInt = parseInt(timeAgo, 10);
|
|
||||||
|
|
||||||
if (!timeAgo) return date; // there is no number!
|
|
||||||
|
|
||||||
if (date.includes("hours ago") || date.includes("hour ago")) {
|
|
||||||
dayJSDate.subtract(timeAgoInt, "hours"); // go back N hours
|
|
||||||
}
|
|
||||||
|
|
||||||
if (date.includes("days ago") || date.includes("day ago")) {
|
|
||||||
dayJSDate.subtract(timeAgoInt, "days"); // go back N days
|
|
||||||
}
|
|
||||||
|
|
||||||
if (date.includes("months ago") || date.includes("month ago")) {
|
|
||||||
dayJSDate.subtract(timeAgoInt, "months"); // go back N months
|
|
||||||
}
|
|
||||||
|
|
||||||
return dayJSDate.format("LL");
|
|
||||||
}
|
|
||||||
return date; // there is no "ago" so don't mess with the date
|
|
||||||
};
|
|
||||||
@@ -2,7 +2,7 @@ import { CheerioAPI, load as parseHTML } from "cheerio";
|
|||||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||||
import { Plugin } from "@typings/plugin";
|
import { Plugin } from "@typings/plugin";
|
||||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
class ComradeMaoPlugin implements Plugin.PluginBase {
|
class ComradeMaoPlugin implements Plugin.PluginBase {
|
||||||
id = "comrademao";
|
id = "comrademao";
|
||||||
@@ -20,14 +20,14 @@ class ComradeMaoPlugin implements Plugin.PluginBase {
|
|||||||
const novelName = loadedCheerio(this).find(".tt").text().trim();
|
const novelName = loadedCheerio(this).find(".tt").text().trim();
|
||||||
const novelCover = loadedCheerio(this).find("img").attr("src");
|
const novelCover = loadedCheerio(this).find("img").attr("src");
|
||||||
const novelUrl = loadedCheerio(this).find("a").attr("href");
|
const novelUrl = loadedCheerio(this).find("a").attr("href");
|
||||||
|
|
||||||
if (!novelUrl) return;
|
if (!novelUrl) return;
|
||||||
|
|
||||||
const novel = {
|
const novel = {
|
||||||
name: novelName,
|
name: novelName,
|
||||||
cover: novelCover,
|
cover: novelCover,
|
||||||
url: novelUrl,
|
url: novelUrl,
|
||||||
}
|
};
|
||||||
novels.push(novel);
|
novels.push(novel);
|
||||||
});
|
});
|
||||||
return novels;
|
return novels;
|
||||||
@@ -35,7 +35,7 @@ class ComradeMaoPlugin implements Plugin.PluginBase {
|
|||||||
|
|
||||||
async popularNovels(
|
async popularNovels(
|
||||||
pageNo: number,
|
pageNo: number,
|
||||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>
|
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||||
): Promise<Plugin.NovelItem[]> {
|
): Promise<Plugin.NovelItem[]> {
|
||||||
let link = this.site;
|
let link = this.site;
|
||||||
|
|
||||||
@@ -43,25 +43,19 @@ class ComradeMaoPlugin implements Plugin.PluginBase {
|
|||||||
|
|
||||||
link += "page/" + pageNo + "/?post_type=novel";
|
link += "page/" + pageNo + "/?post_type=novel";
|
||||||
|
|
||||||
const headers = new Headers();
|
const body = await fetchApi(link).then((result) => result.text());
|
||||||
const body = await fetchApi(link, { headers }).then((result) =>
|
|
||||||
result.text()
|
|
||||||
);
|
|
||||||
|
|
||||||
const loadedCheerio = parseHTML(body);
|
const loadedCheerio = parseHTML(body);
|
||||||
return this.parseNovels(loadedCheerio);
|
return this.parseNovels(loadedCheerio);
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
||||||
const url = novelUrl;
|
const body = await fetchApi(novelUrl).then((result) => result.text());
|
||||||
const headers = new Headers();
|
|
||||||
const result = await fetchApi(url, { headers });
|
|
||||||
const body = await result.text();
|
|
||||||
|
|
||||||
let loadedCheerio = parseHTML(body);
|
let loadedCheerio = parseHTML(body);
|
||||||
|
|
||||||
const novel: Plugin.SourceNovel = {
|
const novel: Plugin.SourceNovel = {
|
||||||
url,
|
url: novelUrl,
|
||||||
chapters: [],
|
chapters: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -69,23 +63,23 @@ class ComradeMaoPlugin implements Plugin.PluginBase {
|
|||||||
|
|
||||||
novel.cover = loadedCheerio("div.thumbook > div > img").attr("src");
|
novel.cover = loadedCheerio("div.thumbook > div > img").attr("src");
|
||||||
|
|
||||||
loadedCheerio(".infox .wd-full").each((i,el) => {
|
loadedCheerio(".infox .wd-full").each((i, el) => {
|
||||||
const detailName = loadedCheerio(el).find("b").text();
|
const detailName = loadedCheerio(el).find("b").text();
|
||||||
const detail = loadedCheerio(el).find('span').text();
|
const detail = loadedCheerio(el).find("span").text();
|
||||||
|
|
||||||
switch (detailName) {
|
switch (detailName) {
|
||||||
case 'Publisher: ':
|
case "Publisher: ":
|
||||||
novel.author = detail.trim();
|
novel.author = detail.trim();
|
||||||
break;
|
break;
|
||||||
case 'Status: ':
|
case "Status: ":
|
||||||
novel.status = detail.trim();
|
novel.status = detail.trim();
|
||||||
break;
|
break;
|
||||||
case 'Genres:':
|
case "Genres:":
|
||||||
novel.genres = detail.trim().replace(/ , /g, ',');
|
novel.genres = detail.trim().replace(/ , /g, ",");
|
||||||
break;
|
break;
|
||||||
case 'Description: ':
|
case "Description: ":
|
||||||
novel.summary = detail.trim();
|
novel.summary = detail.trim();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -94,53 +88,78 @@ class ComradeMaoPlugin implements Plugin.PluginBase {
|
|||||||
loadedCheerio("#chapterlist")
|
loadedCheerio("#chapterlist")
|
||||||
.find("li")
|
.find("li")
|
||||||
.each(function () {
|
.each(function () {
|
||||||
const releaseDate = parseMadaraDate(
|
const chapterUrl = loadedCheerio(this).find("a").attr("href");
|
||||||
loadedCheerio(this).find(".chapterdate").text()
|
if (!chapterUrl) return;
|
||||||
);
|
|
||||||
const chapterName = loadedCheerio(this)
|
const chapterName = loadedCheerio(this)
|
||||||
.find(".chapternum")
|
.find(".chapternum")
|
||||||
.text();
|
.text();
|
||||||
const chapterUrl = loadedCheerio(this).find("a").attr("href");
|
let releaseTime = loadedCheerio(this)
|
||||||
|
.find(".chapterdate")
|
||||||
|
.text();
|
||||||
|
|
||||||
if (!chapterUrl) return;
|
if (releaseTime?.includes?.("ago")) {
|
||||||
|
const timeAgo = releaseTime.match(/\d+/)?.[0] || "0";
|
||||||
|
const timeAgoInt = parseInt(timeAgo, 10);
|
||||||
|
|
||||||
|
if (timeAgoInt) {
|
||||||
|
const dayJSDate = dayjs(); // today
|
||||||
|
if (
|
||||||
|
releaseTime.includes("hours ago") ||
|
||||||
|
releaseTime.includes("hour ago")
|
||||||
|
) {
|
||||||
|
dayJSDate.subtract(timeAgoInt, "hours"); // go back N hours
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
releaseTime.includes("days ago") ||
|
||||||
|
releaseTime.includes("day ago")
|
||||||
|
) {
|
||||||
|
dayJSDate.subtract(timeAgoInt, "days"); // go back N days
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
releaseTime.includes("months ago") ||
|
||||||
|
releaseTime.includes("month ago")
|
||||||
|
) {
|
||||||
|
dayJSDate.subtract(timeAgoInt, "months"); // go back N months
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseTime = dayJSDate.format("LL");
|
||||||
|
}
|
||||||
|
}
|
||||||
chapter.push({
|
chapter.push({
|
||||||
name: chapterName,
|
name: chapterName,
|
||||||
url: chapterUrl,
|
url: chapterUrl,
|
||||||
releaseTime: releaseDate,
|
releaseTime,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
novel.chapters = chapter.reverse();
|
novel.chapters = chapter.reverse();
|
||||||
|
|
||||||
return novel;
|
return novel;
|
||||||
};
|
}
|
||||||
|
|
||||||
async parseChapter(chapterUrl: string): Promise<string> {
|
async parseChapter(chapterUrl: string): Promise<string> {
|
||||||
const headers = new Headers();
|
const body = await fetchApi(chapterUrl).then((result) => result.text());
|
||||||
const result = await fetchApi(chapterUrl, { headers });
|
|
||||||
const body = await result.text();
|
|
||||||
|
|
||||||
const loadedCheerio = parseHTML(body);
|
const loadedCheerio = parseHTML(body);
|
||||||
|
|
||||||
const chapterText = loadedCheerio("#chaptercontent").html()?.trim() || "";
|
const chapterText = loadedCheerio("#chaptercontent").html()?.trim() || "";
|
||||||
|
|
||||||
return chapterText;
|
return chapterText;
|
||||||
};
|
}
|
||||||
|
|
||||||
async searchNovels(
|
async searchNovels(
|
||||||
searchTerm: string,
|
searchTerm: string,
|
||||||
pageNo: number
|
pageNo: number,
|
||||||
): Promise<Plugin.NovelItem[]> {
|
): Promise<Plugin.NovelItem[]> {
|
||||||
const url = `${this.site}page/${pageNo}/?s=${searchTerm}&post_type=novel`;
|
const url = `${this.site}page/${pageNo}/?s=${searchTerm}&post_type=novel`;
|
||||||
|
|
||||||
const headers = new Headers();
|
const body = await fetchApi(url).then((result) => result.text());
|
||||||
const result = await fetchApi(url, { headers });
|
|
||||||
const body = await result.text();
|
|
||||||
|
|
||||||
const loadedCheerio = parseHTML(body);
|
const loadedCheerio = parseHTML(body);
|
||||||
return this.parseNovels(loadedCheerio);
|
return this.parseNovels(loadedCheerio);
|
||||||
};
|
}
|
||||||
|
|
||||||
async fetchImage(url: string): Promise<string | undefined> {
|
async fetchImage(url: string): Promise<string | undefined> {
|
||||||
return await fetchFile(url);
|
return await fetchFile(url);
|
||||||
|
|||||||
@@ -1,165 +0,0 @@
|
|||||||
import { load as parseHTML } from "cheerio";
|
|
||||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
|
||||||
import { Plugin } from "@typings/plugin";
|
|
||||||
import { Filters } from "@libs/filterInputs";
|
|
||||||
|
|
||||||
class Foxaholic implements Plugin.PluginBase {
|
|
||||||
id = "foxaholic";
|
|
||||||
name = "Foxaholic";
|
|
||||||
icon = "src/en/foxaholic/icon.png";
|
|
||||||
site = "https://www.foxaholic.com/";
|
|
||||||
filters?: Filters | undefined;
|
|
||||||
version = "1.0.0";
|
|
||||||
baseUrl = this.site;
|
|
||||||
async popularNovels(pageNo: number, options: Plugin.PopularNovelsOptions<Filters>): Promise<Plugin.NovelItem[]> {
|
|
||||||
const link = `${this.baseUrl}novel/page/${pageNo}/?m_orderby=rating`;
|
|
||||||
|
|
||||||
const result = await fetchApi(link);
|
|
||||||
const body = await result.text();
|
|
||||||
|
|
||||||
const loadedCheerio = parseHTML(body);
|
|
||||||
|
|
||||||
const novels: Plugin.NovelItem[] = [];
|
|
||||||
|
|
||||||
loadedCheerio(".page-item-detail").each(function () {
|
|
||||||
const novelName = loadedCheerio(this).find(".h5 > a").text();
|
|
||||||
const novelCover = loadedCheerio(this).find("img").attr("data-src");
|
|
||||||
const novelUrl = loadedCheerio(this).find(".h5 > a").attr("href");
|
|
||||||
|
|
||||||
if (!novelUrl) return;
|
|
||||||
|
|
||||||
const novel = {
|
|
||||||
name: novelName,
|
|
||||||
cover: novelCover,
|
|
||||||
url: novelUrl,
|
|
||||||
};
|
|
||||||
|
|
||||||
novels.push(novel);
|
|
||||||
});
|
|
||||||
|
|
||||||
return novels;
|
|
||||||
}
|
|
||||||
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
|
||||||
const url = novelUrl;
|
|
||||||
const result = await fetchApi(url);
|
|
||||||
const body = await result.text();
|
|
||||||
|
|
||||||
let loadedCheerio = parseHTML(body);
|
|
||||||
|
|
||||||
const novel: Plugin.SourceNovel = {
|
|
||||||
url,
|
|
||||||
chapters: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
novel.name = loadedCheerio(".post-title > h1").text().trim();
|
|
||||||
|
|
||||||
novel.cover = loadedCheerio(".summary_image > a > img").attr(
|
|
||||||
"data-src"
|
|
||||||
);
|
|
||||||
|
|
||||||
loadedCheerio(".post-content_item").each(function () {
|
|
||||||
const detailName = loadedCheerio(this)
|
|
||||||
.find(".summary-heading > h5")
|
|
||||||
.text()
|
|
||||||
.trim();
|
|
||||||
const detail = loadedCheerio(this).find(".summary-content").html();
|
|
||||||
|
|
||||||
if (!detail) return;
|
|
||||||
|
|
||||||
switch (detailName) {
|
|
||||||
case "Genre":
|
|
||||||
novel.genres = loadedCheerio(detail)
|
|
||||||
.children("a")
|
|
||||||
.map((i, el) => loadedCheerio(el).text())
|
|
||||||
.toArray()
|
|
||||||
.join(",");
|
|
||||||
break;
|
|
||||||
case "Author":
|
|
||||||
novel.author = loadedCheerio(detail)
|
|
||||||
.children("a")
|
|
||||||
.map((i, el) => loadedCheerio(el).text())
|
|
||||||
.toArray()
|
|
||||||
.join(", ");
|
|
||||||
break;
|
|
||||||
case "Novel":
|
|
||||||
novel.status = detail?.trim().replace(/G/g, "g");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
loadedCheerio(
|
|
||||||
".description-summary > div.summary__content > div"
|
|
||||||
).remove();
|
|
||||||
|
|
||||||
novel.summary = loadedCheerio(
|
|
||||||
".description-summary > div.summary__content"
|
|
||||||
)
|
|
||||||
.text()
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
let chapter: Plugin.ChapterItem[] = [];
|
|
||||||
loadedCheerio(".wp-manga-chapter").each(function () {
|
|
||||||
const chapterName = loadedCheerio(this)
|
|
||||||
.find("a")
|
|
||||||
.text()
|
|
||||||
.replace(/[\t\n]/g, "")
|
|
||||||
.trim();
|
|
||||||
const releaseDate = loadedCheerio(this).find("span").text().trim();
|
|
||||||
const chapterUrl = loadedCheerio(this).find("a").attr("href");
|
|
||||||
|
|
||||||
if (!chapterUrl) return;
|
|
||||||
|
|
||||||
chapter.push({
|
|
||||||
name: chapterName,
|
|
||||||
releaseTime: releaseDate,
|
|
||||||
url: chapterUrl,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
novel.chapters = chapter.reverse();
|
|
||||||
|
|
||||||
return novel;
|
|
||||||
}
|
|
||||||
async parseChapter(chapterUrl: string): Promise<string> {
|
|
||||||
const result = await fetchApi(chapterUrl);
|
|
||||||
const body = await result.text();
|
|
||||||
|
|
||||||
const loadedCheerio = parseHTML(body);
|
|
||||||
|
|
||||||
loadedCheerio("img").removeAttr("srcset");
|
|
||||||
let chapterText = loadedCheerio(".reading-content").html() || '';
|
|
||||||
|
|
||||||
return chapterText;
|
|
||||||
}
|
|
||||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
|
||||||
const url = `${this.baseUrl}?s=${searchTerm}&post_type=wp-manga`;
|
|
||||||
const result = await fetchApi(url);
|
|
||||||
const body = await result.text();
|
|
||||||
|
|
||||||
const loadedCheerio = parseHTML(body);
|
|
||||||
|
|
||||||
const novels: Plugin.NovelItem[] = [];
|
|
||||||
|
|
||||||
loadedCheerio(".c-tabs-item__content").each(function () {
|
|
||||||
const novelName = loadedCheerio(this).find(".h4 > a").text();
|
|
||||||
const novelCover = loadedCheerio(this).find("img").attr("data-src");
|
|
||||||
const novelUrl = loadedCheerio(this).find(".h4 > a").attr("href");
|
|
||||||
|
|
||||||
if (!novelUrl) return;
|
|
||||||
|
|
||||||
novels.push({
|
|
||||||
name: novelName,
|
|
||||||
url: novelUrl,
|
|
||||||
cover: novelCover,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return novels;
|
|
||||||
}
|
|
||||||
async fetchImage(url: string): Promise<string | undefined> {
|
|
||||||
return fetchFile(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default new Foxaholic();
|
|
||||||
@@ -1,103 +1,108 @@
|
|||||||
import { Plugin } from "@typings/plugin";
|
import { Plugin } from "@typings/plugin";
|
||||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||||
import { NovelStatus } from "@libs/novelStatus";
|
import { NovelStatus } from "@libs/novelStatus";
|
||||||
import { load as parseHTML } from "cheerio";
|
import { load as parseHTML } from "cheerio";
|
||||||
|
|
||||||
class FreeWebNovel implements Plugin.PluginBase {
|
class FreeWebNovel implements Plugin.PluginBase {
|
||||||
id = "FWN.com";
|
id = "FWN.com";
|
||||||
name = "Web NOVEL";
|
name = "Web NOVEL";
|
||||||
site = "https://freewebnovel.com";
|
site = "https://freewebnovel.com";
|
||||||
version = "1.0.0";
|
version = "1.0.0";
|
||||||
icon = "src/en/freewebnovel/icon.png";
|
icon = "src/en/freewebnovel/icon.png";
|
||||||
|
|
||||||
|
async popularNovels(
|
||||||
|
page: number,
|
||||||
async popularNovels(
|
{ showLatestNovels }: Plugin.PopularNovelsOptions,
|
||||||
page: number,
|
): Promise<Plugin.NovelItem[]> {
|
||||||
{ showLatestNovels }: Plugin.PopularNovelsOptions,
|
const sort = showLatestNovels
|
||||||
): Promise<Plugin.NovelItem[]> {
|
? "/latest-release-novels/"
|
||||||
const sort = showLatestNovels
|
: "/completed-novels/";
|
||||||
? "/latest-release-novels/"
|
|
||||||
: "/completed-novels/";
|
const body = await fetchApi(this.site + sort + page).then((res) => res.text());
|
||||||
|
const loadedCheerio = parseHTML(body);
|
||||||
const body = await fetchApi(this.site + sort + page).then((res) => res.text());
|
|
||||||
const loadedCheerio = parseHTML(body);
|
const novels: Plugin.NovelItem[] = loadedCheerio(".li-row")
|
||||||
|
.map((index, element) => ({
|
||||||
const novels: Plugin.NovelItem[] = loadedCheerio(".li-row")
|
name: loadedCheerio(element).find(".tit").text(),
|
||||||
.map((index, element) => ({
|
cover: loadedCheerio(element).find("img").attr("src"),
|
||||||
name: loadedCheerio(element).find(".tit").text(),
|
url: this.site + loadedCheerio(element).find("h3 > a").attr("href"),
|
||||||
cover: loadedCheerio(element).find("img").attr("src"),
|
}))
|
||||||
url: this.site + loadedCheerio(element).find("h3 > a").attr("href"),
|
.get();
|
||||||
}))
|
|
||||||
.get();
|
return novels;
|
||||||
|
}
|
||||||
return novels;
|
|
||||||
}
|
async parseNovelAndChapters(url: string): Promise<Plugin.SourceNovel> {
|
||||||
|
const body = await fetchApi(url).then((res) => res.text());
|
||||||
async parseNovelAndChapters(url: string): Promise<Plugin.SourceNovel> {
|
const loadedCheerio = parseHTML(body);
|
||||||
const body = await fetchApi(url).then((res) => res.text());
|
|
||||||
const loadedCheerio = parseHTML(body);
|
const novel: Plugin.SourceNovel = { url };
|
||||||
|
novel.name = loadedCheerio("h1.tit").text();
|
||||||
const novel: Plugin.SourceNovel = { url };
|
novel.cover = loadedCheerio(".pic > img").attr("src");
|
||||||
novel.name = loadedCheerio("h1.tit").text();
|
novel.genres = loadedCheerio("[title=Genre]")
|
||||||
novel.cover = loadedCheerio(".pic > img").attr("src");
|
.next()
|
||||||
novel.genres = loadedCheerio("[title=Genre]")
|
.text()
|
||||||
.next()
|
.replace(/[\t\n]/g, "");
|
||||||
.text()
|
|
||||||
.replace(/[\t\n]/g, "");
|
novel.author = loadedCheerio("[title=Author]")
|
||||||
|
.next()
|
||||||
novel.author = loadedCheerio("[title=Author]")
|
.text()
|
||||||
.next()
|
.replace(/[\t\n]/g, "");
|
||||||
.text()
|
|
||||||
.replace(/[\t\n]/g, "");
|
novel.status = loadedCheerio("[title=Status]")
|
||||||
|
.next()
|
||||||
novel.status = loadedCheerio("[title=Status]")
|
.text()
|
||||||
.next()
|
.replace(/[\t\n]/g, "");
|
||||||
.text()
|
|
||||||
.replace(/[\t\n]/g, "");
|
novel.summary = loadedCheerio(".inner").text().trim();
|
||||||
|
|
||||||
novel.summary = loadedCheerio(".inner").text().trim();
|
const chapters: Plugin.ChapterItem[] = loadedCheerio("#idData > li > a")
|
||||||
|
.map((chapterIndex, element) => ({
|
||||||
const chapters: Plugin.ChapterItem[] = loadedCheerio("#idData > li > a")
|
name: loadedCheerio(element).attr("title") || "Chapter " + (chapterIndex + 1),
|
||||||
.map((index, element) => ({
|
url: this.site + loadedCheerio(element).attr("href"),
|
||||||
name: loadedCheerio(element).attr("title") || "Chapter " + index,
|
releaseTime: null,
|
||||||
releaseTime: null,
|
chapterNumber: chapterIndex + 1,
|
||||||
url: this.site + loadedCheerio(element).attr("href"),
|
}))
|
||||||
}))
|
.get();
|
||||||
.get();
|
|
||||||
|
novel.chapters = chapters;
|
||||||
novel.chapters = chapters;
|
return novel;
|
||||||
return novel;
|
}
|
||||||
}
|
|
||||||
|
async parseChapter(chapterUrl: string): Promise<string> {
|
||||||
async parseChapter(chapterUrl: string): Promise<string> {
|
const body = await fetchApi(chapterUrl).then((res) => res.text());
|
||||||
const body = await fetchApi(chapterUrl).then((res) => res.text());
|
const loadedCheerio = parseHTML(body);
|
||||||
const loadedCheerio = parseHTML(body);
|
|
||||||
|
const chapterText = loadedCheerio("div.txt").html() || "";
|
||||||
const chapterText = loadedCheerio("div.txt").html() || "";
|
return chapterText;
|
||||||
return chapterText;
|
}
|
||||||
}
|
|
||||||
|
async searchNovels(
|
||||||
async searchNovels(
|
searchTerm: string,
|
||||||
searchkey: string,
|
): Promise<Plugin.NovelItem[]> {
|
||||||
): Promise<Plugin.NovelItem[]> {
|
const body = await fetchApi(this.site + "/search/", {
|
||||||
const body = await fetchApi(this.site + "/search/", {
|
headers: {
|
||||||
method: "POST",
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
body: JSON.stringify({ searchkey }),
|
Referer: this.site,
|
||||||
}).then((res) => res.text());
|
Origin: this.site,
|
||||||
|
},
|
||||||
const loadedCheerio = parseHTML(body);
|
method: "POST",
|
||||||
const novels: Plugin.NovelItem[] = loadedCheerio(".li-row > .li > .con")
|
body: "searchkey=" + encodeURIComponent(searchTerm)
|
||||||
.map((index, element) => ({
|
}).then((res) => res.text());
|
||||||
name: loadedCheerio(element).find(".tit").text(),
|
|
||||||
cover: loadedCheerio(element).find(".pic > a > img").attr("data-cfsrc"),
|
const loadedCheerio = parseHTML(body);
|
||||||
url: this.site + loadedCheerio(element).find("h3 > a").attr("href"),
|
const novels: Plugin.NovelItem[] = loadedCheerio(".li-row > .li > .con")
|
||||||
}))
|
.map((index, element) => ({
|
||||||
.get();
|
name: loadedCheerio(element).find(".tit").text(),
|
||||||
|
cover: loadedCheerio(element).find(".pic > a > img").attr("src"),
|
||||||
return novels;
|
url: this.site + loadedCheerio(element).find("h3 > a").attr("href"),
|
||||||
}
|
}))
|
||||||
|
.get();
|
||||||
fetchImage = fetchFile;
|
|
||||||
}
|
return novels;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchImage = fetchFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new FreeWebNovel();
|
||||||
|
|||||||
@@ -1,153 +0,0 @@
|
|||||||
import { load as parseHTML } from "cheerio";
|
|
||||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
|
||||||
import { Plugin } from "@typings/plugin";
|
|
||||||
import { Filters } from "@libs/filterInputs";
|
|
||||||
|
|
||||||
class LightNovelUpdates implements Plugin.PluginBase {
|
|
||||||
id = "LightNovelUpdates";
|
|
||||||
name = "Light Novel Updates";
|
|
||||||
version = "1.0.0";
|
|
||||||
icon = "src/en/lightnovelupdates/icon.png";
|
|
||||||
site = "https://www.lightnovelupdates.com/";
|
|
||||||
baseUrl = this.site;
|
|
||||||
async popularNovels(pageNo: number, options: Plugin.PopularNovelsOptions<Filters>): Promise<Plugin.NovelItem[]> {
|
|
||||||
let url = `${this.baseUrl}novel/page/${pageNo}/?m_orderby=rating`;
|
|
||||||
|
|
||||||
const body = await fetchApi(url).then((r) => r.text());
|
|
||||||
|
|
||||||
const loadedCheerio = parseHTML(body);
|
|
||||||
|
|
||||||
let novels: Plugin.NovelItem[] = [];
|
|
||||||
|
|
||||||
loadedCheerio(".page-item-detail").each(function () {
|
|
||||||
const novelName = loadedCheerio(this).find(".h5 > a").text();
|
|
||||||
const novelCover = loadedCheerio(this).find("img").attr("src");
|
|
||||||
const novelUrl = loadedCheerio(this).find(".h5 > a").attr("href");
|
|
||||||
|
|
||||||
if (!novelUrl) return;
|
|
||||||
|
|
||||||
const novel = {
|
|
||||||
name: novelName,
|
|
||||||
cover: novelCover,
|
|
||||||
url: novelUrl,
|
|
||||||
};
|
|
||||||
|
|
||||||
novels.push(novel);
|
|
||||||
});
|
|
||||||
|
|
||||||
return novels;
|
|
||||||
}
|
|
||||||
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
|
||||||
const url = novelUrl;
|
|
||||||
|
|
||||||
const body = await fetchApi(url).then((r) => r.text());
|
|
||||||
|
|
||||||
let loadedCheerio = parseHTML(body);
|
|
||||||
|
|
||||||
const novel: Plugin.SourceNovel = {
|
|
||||||
url,
|
|
||||||
chapters: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
novel.name = loadedCheerio(".post-title > h1").text().trim();
|
|
||||||
novel.cover =
|
|
||||||
loadedCheerio(".summary_image > a > img").attr("src") || undefined;
|
|
||||||
|
|
||||||
loadedCheerio(".post-content_item").each(function () {
|
|
||||||
const detailName = loadedCheerio(this)
|
|
||||||
.find(".summary-heading > h5")
|
|
||||||
.text()
|
|
||||||
.trim();
|
|
||||||
const detail = loadedCheerio(this)
|
|
||||||
.find(".summary-content")
|
|
||||||
.text()
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
switch (detailName) {
|
|
||||||
case "Genre(s)":
|
|
||||||
novel.genres = detail.trim().replace(/[\t\n]/g, ",");
|
|
||||||
break;
|
|
||||||
case "Author(s)":
|
|
||||||
novel.author = detail.trim();
|
|
||||||
break;
|
|
||||||
case "Status":
|
|
||||||
novel.status = detail.replace(/G/g, "g");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
novel.summary = loadedCheerio("div.summary__content").text();
|
|
||||||
|
|
||||||
const chapter: Plugin.ChapterItem[] = [];
|
|
||||||
|
|
||||||
const novelId = loadedCheerio(".rating-post-id").attr("value")!;
|
|
||||||
|
|
||||||
let formData = new FormData();
|
|
||||||
formData.append("action", "manga_get_chapters");
|
|
||||||
formData.append("manga", novelId);
|
|
||||||
|
|
||||||
const data = await fetchApi(
|
|
||||||
"https://www.lightnovelupdates.com/wp-admin/admin-ajax.php",
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
body: formData,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const text = await data.text();
|
|
||||||
|
|
||||||
loadedCheerio = parseHTML(text);
|
|
||||||
|
|
||||||
loadedCheerio(".wp-manga-chapter").each(function () {
|
|
||||||
const chapterName = loadedCheerio(this).find("a").text().trim();
|
|
||||||
const releaseDate = loadedCheerio(this).find("span").text().trim();
|
|
||||||
const chapterUrl = loadedCheerio(this).find("a").attr("href");
|
|
||||||
|
|
||||||
if (!chapterUrl) return;
|
|
||||||
|
|
||||||
chapter.push({
|
|
||||||
name: chapterName,
|
|
||||||
releaseTime: releaseDate,
|
|
||||||
url: chapterUrl,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
novel.chapters = chapter.reverse();
|
|
||||||
|
|
||||||
return novel;
|
|
||||||
}
|
|
||||||
async parseChapter(chapterUrl: string): Promise<string> {
|
|
||||||
const chapterText = 'unable to load';
|
|
||||||
|
|
||||||
return chapterText;
|
|
||||||
}
|
|
||||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
|
||||||
const url = `${this.baseUrl}?s=${searchTerm}&post_type=wp-manga&m_orderby=rating`;
|
|
||||||
|
|
||||||
const body = await fetchApi(url).then((r) => r.text());
|
|
||||||
|
|
||||||
const loadedCheerio = parseHTML(body);
|
|
||||||
|
|
||||||
let novels: Plugin.NovelItem[] = [];
|
|
||||||
|
|
||||||
loadedCheerio(".c-tabs-item__content").each(function () {
|
|
||||||
const novelName = loadedCheerio(this).find(".h4 > a").text();
|
|
||||||
const novelCover = loadedCheerio(this).find("img").attr("src");
|
|
||||||
const novelUrl = loadedCheerio(this).find(".h4 > a").attr("href");
|
|
||||||
|
|
||||||
if (!novelUrl) return;
|
|
||||||
|
|
||||||
novels.push({
|
|
||||||
url: novelUrl,
|
|
||||||
name: novelName,
|
|
||||||
cover: novelCover,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return novels;
|
|
||||||
}
|
|
||||||
async fetchImage(url: string): Promise<string | undefined> {
|
|
||||||
return fetchFile(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default new LightNovelUpdates();
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
import { load as parseHTML } from "cheerio";
|
|
||||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
|
||||||
import { Plugin } from "@typings/plugin";
|
|
||||||
import { Filters } from "@libs/filterInputs";
|
|
||||||
|
|
||||||
class NitroManga implements Plugin.PluginBase {
|
|
||||||
id = "nitromanga";
|
|
||||||
name = "Nitro Manga";
|
|
||||||
version = "1.0.0";
|
|
||||||
icon = "src/en/nitromanga/icon.png";
|
|
||||||
filters?: Filters | undefined;
|
|
||||||
site = "https://nitromanga.com/";
|
|
||||||
baseUrl = this.site;
|
|
||||||
async popularNovels(pageNo: number, options: Plugin.PopularNovelsOptions<Filters>): Promise<Plugin.NovelItem[]> {
|
|
||||||
const url = this.baseUrl + "mangas-genre/novel/?m_orderby=trending";
|
|
||||||
|
|
||||||
const body = await fetchApi(url).then((r) => r.text());
|
|
||||||
|
|
||||||
const loadedCheerio = parseHTML(body);
|
|
||||||
|
|
||||||
let novels: Plugin.NovelItem[] = [];
|
|
||||||
|
|
||||||
loadedCheerio(".page-item-detail").each(function () {
|
|
||||||
const novelName = loadedCheerio(this).find("h3 > a").text();
|
|
||||||
const novelCover = loadedCheerio(this).find("img").attr("data-src");
|
|
||||||
const novelUrl = loadedCheerio(this).find("h3 > a").attr("href");
|
|
||||||
|
|
||||||
if (!novelUrl) return;
|
|
||||||
|
|
||||||
const novel = {
|
|
||||||
name: novelName,
|
|
||||||
cover: novelCover,
|
|
||||||
url: novelUrl,
|
|
||||||
};
|
|
||||||
|
|
||||||
novels.push(novel);
|
|
||||||
});
|
|
||||||
|
|
||||||
return novels;
|
|
||||||
}
|
|
||||||
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
|
||||||
const url = novelUrl;
|
|
||||||
|
|
||||||
const body = await fetchApi(url).then((r) => r.text());
|
|
||||||
|
|
||||||
let loadedCheerio = parseHTML(body);
|
|
||||||
|
|
||||||
const novel: Plugin.SourceNovel = {
|
|
||||||
url,
|
|
||||||
chapters: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
loadedCheerio(".manga-title-badges.custom.novel").remove();
|
|
||||||
|
|
||||||
novel.name = loadedCheerio(".post-title > h1").text().trim();
|
|
||||||
novel.cover = loadedCheerio(".summary_image")
|
|
||||||
.find("img")
|
|
||||||
.attr("data-src");
|
|
||||||
|
|
||||||
novel.summary = loadedCheerio(".summary__content").text()?.trim();
|
|
||||||
|
|
||||||
novel.genres = loadedCheerio(".genres-content")
|
|
||||||
.children("a")
|
|
||||||
.map((i, el) => loadedCheerio(el).text())
|
|
||||||
.toArray()
|
|
||||||
.join(",");
|
|
||||||
|
|
||||||
loadedCheerio(".post-content_item").each(function () {
|
|
||||||
const detailName = loadedCheerio(this)
|
|
||||||
.find(".summary-heading")
|
|
||||||
.text()
|
|
||||||
.trim();
|
|
||||||
const detail = loadedCheerio(this)
|
|
||||||
.find(".summary-content")
|
|
||||||
.text()
|
|
||||||
.trim();
|
|
||||||
switch (detailName) {
|
|
||||||
case "Author(s)":
|
|
||||||
novel.author = detail;
|
|
||||||
break;
|
|
||||||
case "Status":
|
|
||||||
novel.status = detail.replace(/G/g, "g");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let chapter: Plugin.ChapterItem[] = [];
|
|
||||||
|
|
||||||
let chapterlisturl = novelUrl + "ajax/chapters/";
|
|
||||||
|
|
||||||
const data = await fetchApi(chapterlisturl, { method: "POST" });
|
|
||||||
const text = await data.text();
|
|
||||||
|
|
||||||
loadedCheerio = parseHTML(text);
|
|
||||||
|
|
||||||
loadedCheerio(".wp-manga-chapter").each(function () {
|
|
||||||
const chapterName = loadedCheerio(this).find("a").text().trim();
|
|
||||||
const releaseDate = loadedCheerio(this).find("span").text().trim();
|
|
||||||
const chapterUrl = loadedCheerio(this).find("a").attr("href");
|
|
||||||
|
|
||||||
if (!chapterUrl) return;
|
|
||||||
|
|
||||||
chapter.push({
|
|
||||||
name: chapterName,
|
|
||||||
releaseTime: releaseDate,
|
|
||||||
url: chapterUrl,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
novel.chapters = chapter.reverse();
|
|
||||||
|
|
||||||
return novel;
|
|
||||||
}
|
|
||||||
async parseChapter(chapterUrl: string): Promise<string> {
|
|
||||||
const body = await fetchApi(chapterUrl).then((r) => r.text());
|
|
||||||
|
|
||||||
let loadedCheerio = parseHTML(body);
|
|
||||||
|
|
||||||
const chapterText = loadedCheerio(".text-left").html() || '';
|
|
||||||
|
|
||||||
return chapterText;
|
|
||||||
}
|
|
||||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
|
||||||
const url = this.baseUrl + '?s=' + searchTerm + '&post_type=wp-manga&genre%5B%5D=novel';
|
|
||||||
|
|
||||||
const body = await fetchApi(url).then((r) => r.text());
|
|
||||||
|
|
||||||
let loadedCheerio = parseHTML(body);
|
|
||||||
|
|
||||||
let novels: Plugin.NovelItem[] = [];
|
|
||||||
|
|
||||||
loadedCheerio(".c-tabs-item__content").each(function () {
|
|
||||||
const novelName = loadedCheerio(this).find("h3 > a").text();
|
|
||||||
const novelCover = loadedCheerio(this).find("img").attr("data-src");
|
|
||||||
const novelUrl = loadedCheerio(this).find("h3 > a").attr("href");
|
|
||||||
|
|
||||||
if (!novelUrl) return;
|
|
||||||
|
|
||||||
const novel = {
|
|
||||||
name: novelName,
|
|
||||||
cover: novelCover,
|
|
||||||
url: novelUrl,
|
|
||||||
};
|
|
||||||
|
|
||||||
novels.push(novel);
|
|
||||||
});
|
|
||||||
|
|
||||||
return novels;
|
|
||||||
}
|
|
||||||
async fetchImage(url: string): Promise<string | undefined> {
|
|
||||||
return fetchFile(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default new NitroManga();
|
|
||||||
@@ -1,241 +1,242 @@
|
|||||||
import { Plugin } from "@typings/plugin";
|
import { Plugin } from "@typings/plugin";
|
||||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||||
import { NovelStatus } from "@libs/novelStatus";
|
import { NovelStatus } from "@libs/novelStatus";
|
||||||
import { load as parseHTML } from "cheerio";
|
import { load as parseHTML } from "cheerio";
|
||||||
|
|
||||||
class freedlit implements Plugin.PluginBase {
|
class freedlit implements Plugin.PluginBase {
|
||||||
id = "freedlit.space";
|
id = "freedlit.space";
|
||||||
name = "LitSpace";
|
name = "LitSpace";
|
||||||
site = "https://freedlit.space";
|
site = "https://freedlit.space";
|
||||||
version = "1.0.0";
|
version = "1.0.0";
|
||||||
icon = "src/ru/freedlit/icon.png";
|
icon = "src/ru/freedlit/icon.png";
|
||||||
|
|
||||||
|
async popularNovels(
|
||||||
|
page: number,
|
||||||
async popularNovels(
|
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||||
page: number,
|
): Promise<Plugin.NovelItem[]> {
|
||||||
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
let url = this.site + "/get-books/all/list/" + page + "?sort=";
|
||||||
): Promise<Plugin.NovelItem[]> {
|
url += showLatestNovels ? "recent" : filters?.sort?.value || "popular";
|
||||||
let url = this.site + "/get-books/all/list/" + page + "?sort=";
|
url += "&status=" + (filters?.status?.value || "all");
|
||||||
url += showLatestNovels ? "recent" : filters?.sort?.value || "popular";
|
url += "&access=" + (filters?.access?.value || "all");
|
||||||
url += "&status=" + (filters?.status?.value || "all");
|
url += "&adult=" + (filters?.adult?.value || "hide");
|
||||||
url += "&access=" + (filters?.access?.value || "all");
|
|
||||||
url += "&adult=" + (filters?.adult?.value || "hide");
|
if (filters?.genre?.value?.include?.length) {
|
||||||
|
url += filters.genre.value.include
|
||||||
if (filters?.genre?.value?.include?.length) {
|
.map((id) => "&genres_included[]=" + id)
|
||||||
url += filters.genre.value.include
|
.join("");
|
||||||
.map(id => '&genres_included[]=' + id).join("");
|
}
|
||||||
}
|
|
||||||
|
if (filters?.genre?.value?.exclude?.length) {
|
||||||
if (filters?.genre?.value?.exclude?.length) {
|
url += filters.genre.value.exclude
|
||||||
url += filters.genre.value.exclude
|
.map((id) => "&genres_excluded[]=" + id)
|
||||||
.map(id => '&genres_excluded[]=' + id).join("");
|
.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
const body = await fetchApi(url).then((res) => res.text());
|
const body = await fetchApi(url).then((res) => res.text());
|
||||||
const loadedCheerio = parseHTML(body);
|
const loadedCheerio = parseHTML(body);
|
||||||
|
|
||||||
const novels: Plugin.NovelItem[] = [];
|
const novels: Plugin.NovelItem[] = [];
|
||||||
loadedCheerio("#bookListBlock > div > div").each(function () {
|
loadedCheerio("#bookListBlock > div > div").each(function () {
|
||||||
const name = loadedCheerio(this).find("div > h4 > a").text()?.trim();
|
const name = loadedCheerio(this).find("div > h4 > a").text()?.trim();
|
||||||
const cover = loadedCheerio(this)
|
const cover = loadedCheerio(this)
|
||||||
.find("div > a > img")
|
.find("div > a > img")
|
||||||
.attr("src")
|
.attr("src")
|
||||||
?.trim();
|
?.trim();
|
||||||
const url = loadedCheerio(this).find("div > h4 > a").attr("href")?.trim();
|
const url = loadedCheerio(this).find("div > h4 > a").attr("href")?.trim();
|
||||||
if (!name || !url) return;
|
if (!name || !url) return;
|
||||||
|
|
||||||
novels.push({ name, cover, url });
|
novels.push({ name, cover, url });
|
||||||
});
|
});
|
||||||
|
|
||||||
return novels;
|
return novels;
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
||||||
const body = await fetchApi(novelUrl).then((res) => res.text());
|
const body = await fetchApi(novelUrl).then((res) => res.text());
|
||||||
const loadedCheerio = parseHTML(body);
|
const loadedCheerio = parseHTML(body);
|
||||||
|
|
||||||
const novel: Plugin.SourceNovel = {
|
const novel: Plugin.SourceNovel = {
|
||||||
url: novelUrl,
|
url: novelUrl,
|
||||||
name: loadedCheerio(".book-info h4").text(),
|
name: loadedCheerio(".book-info h4").text(),
|
||||||
cover: loadedCheerio(".book-cover > div > img").attr("src")?.trim(),
|
cover: loadedCheerio(".book-cover > div > img").attr("src")?.trim(),
|
||||||
summary: loadedCheerio("#nav-home").text()?.trim(),
|
summary: loadedCheerio("#nav-home").text()?.trim(),
|
||||||
author: loadedCheerio(".book-info h5 > a").text(),
|
author: loadedCheerio(".book-info h5 > a").text(),
|
||||||
genres: loadedCheerio(".genre-list > a")
|
genres: loadedCheerio(".genre-list > a")
|
||||||
.map((index, element) => loadedCheerio(element).text())
|
.map((index, element) => loadedCheerio(element).text())
|
||||||
.get()
|
.get()
|
||||||
.join(","),
|
.join(","),
|
||||||
};
|
};
|
||||||
|
|
||||||
const chapters: Plugin.ChapterItem[] = [];
|
const chapters: Plugin.ChapterItem[] = [];
|
||||||
|
|
||||||
loadedCheerio("a.chapter-line").each(function () {
|
loadedCheerio("a.chapter-line").each((chapterIndex, element) => {
|
||||||
const name = loadedCheerio(this).find("h6").text();
|
const name = loadedCheerio(element).find("h6").text();
|
||||||
const releaseTime = loadedCheerio(this).find('span[class="date"]').text();
|
const url = loadedCheerio(element).attr("href");
|
||||||
const url = loadedCheerio(this).attr("href");
|
if (!name || !url) return;
|
||||||
if (!name || !url) return;
|
|
||||||
|
const releaseTime = loadedCheerio(element).find('span[class="date"]').text();
|
||||||
chapters.push({ name, releaseTime, url });
|
chapters.push({ name, url, releaseTime, chapterNumber: chapterIndex + 1 });
|
||||||
});
|
});
|
||||||
|
|
||||||
novel.chapters = chapters;
|
novel.chapters = chapters;
|
||||||
return novel;
|
return novel;
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseChapter(chapterUrl: string): Promise<string> {
|
async parseChapter(chapterUrl: string): Promise<string> {
|
||||||
const body = await fetchApi(chapterUrl).then((res) => res.text());
|
const body = await fetchApi(chapterUrl).then((res) => res.text());
|
||||||
const loadedCheerio = parseHTML(body);
|
const loadedCheerio = parseHTML(body);
|
||||||
|
|
||||||
loadedCheerio("div.mobile-block").remove();
|
loadedCheerio("div.mobile-block").remove();
|
||||||
loadedCheerio("div.standart-block").remove();
|
loadedCheerio("div.standart-block").remove();
|
||||||
|
|
||||||
const chapterText = loadedCheerio('div[class="chapter"]').html() || "";
|
const chapterText = loadedCheerio('div[class="chapter"]').html() || "";
|
||||||
return chapterText;
|
return chapterText;
|
||||||
}
|
}
|
||||||
|
|
||||||
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
||||||
const url = `${this.site}/search?query=${searchTerm}&type=all`;
|
const url = `${this.site}/search?query=${searchTerm}&type=all`;
|
||||||
const body = await fetchApi(url).then((res) => res.text());
|
const body = await fetchApi(url).then((res) => res.text());
|
||||||
const loadedCheerio = parseHTML(body);
|
const loadedCheerio = parseHTML(body);
|
||||||
|
|
||||||
const novels: Plugin.NovelItem[] = [];
|
const novels: Plugin.NovelItem[] = [];
|
||||||
loadedCheerio("#bookListBlock > div").each(function () {
|
loadedCheerio("#bookListBlock > div").each(function () {
|
||||||
const name = loadedCheerio(this).find("h4 > a").text()?.trim();
|
const name = loadedCheerio(this).find("h4 > a").text()?.trim();
|
||||||
const cover = loadedCheerio(this).find("a > img").attr("src")?.trim();
|
const cover = loadedCheerio(this).find("a > img").attr("src")?.trim();
|
||||||
const url = loadedCheerio(this).find("h4 > a").attr("href")?.trim();
|
const url = loadedCheerio(this).find("h4 > a").attr("href")?.trim();
|
||||||
if (!name || !url) return;
|
if (!name || !url) return;
|
||||||
|
|
||||||
novels.push({ name, cover, url });
|
novels.push({ name, cover, url });
|
||||||
});
|
});
|
||||||
|
|
||||||
return novels;
|
return novels;
|
||||||
}
|
}
|
||||||
fetchImage = fetchFile;
|
fetchImage = fetchFile;
|
||||||
|
|
||||||
filters = {
|
filters = {
|
||||||
sort: {
|
sort: {
|
||||||
label: "Сортировка:",
|
label: "Сортировка:",
|
||||||
value: "",
|
value: "popular",
|
||||||
options: [
|
options: [
|
||||||
{ label: "По популярности", value: "popular" },
|
{ label: "По популярности", value: "popular" },
|
||||||
{ label: "По количеству комментариев", value: "comments" },
|
{ label: "По количеству комментариев", value: "comments" },
|
||||||
{ label: "По количеству лайков", value: "likes" },
|
{ label: "По количеству лайков", value: "likes" },
|
||||||
{ label: "По новизне", value: "recent" },
|
{ label: "По новизне", value: "recent" },
|
||||||
{ label: "По просмотрам", value: "views" },
|
{ label: "По просмотрам", value: "views" },
|
||||||
],
|
],
|
||||||
type: FilterTypes.Picker,
|
type: FilterTypes.Picker,
|
||||||
},
|
},
|
||||||
genre: {
|
genre: {
|
||||||
label: "Жанры:",
|
label: "Жанры:",
|
||||||
value: { include: [], exclude: [] },
|
value: { include: [], exclude: [] },
|
||||||
options: [
|
options: [
|
||||||
{ label: "Любой жанр", value: "all" },
|
{ label: "Любой жанр", value: "all" },
|
||||||
{ label: "Альтернативная история", value: "alternative-history" },
|
{ label: "Альтернативная история", value: "alternative-history" },
|
||||||
{ label: "Антиутопия", value: "dystopia" },
|
{ label: "Антиутопия", value: "dystopia" },
|
||||||
{ label: "Бизнес-литература", value: "business-literature" },
|
{ label: "Бизнес-литература", value: "business-literature" },
|
||||||
{ label: "Боевая фантастика", value: "combat-fiction" },
|
{ label: "Боевая фантастика", value: "combat-fiction" },
|
||||||
{ label: "Боевик", value: "action" },
|
{ label: "Боевик", value: "action" },
|
||||||
{ label: "Боевое фэнтези", value: "combat-fantasy" },
|
{ label: "Боевое фэнтези", value: "combat-fantasy" },
|
||||||
{ label: "Бояръ-Аниме", value: "boyar-anime" },
|
{ label: "Бояръ-Аниме", value: "boyar-anime" },
|
||||||
{ label: "Героическая фантастика", value: "heroic-fiction" },
|
{ label: "Героическая фантастика", value: "heroic-fiction" },
|
||||||
{ label: "Героическое фэнтези", value: "heroic-fantasy" },
|
{ label: "Героическое фэнтези", value: "heroic-fantasy" },
|
||||||
{ label: "Городское фэнтези", value: "urban-fantasy" },
|
{ label: "Городское фэнтези", value: "urban-fantasy" },
|
||||||
{ label: "Гримдарк", value: "grimdark" },
|
{ label: "Гримдарк", value: "grimdark" },
|
||||||
{ label: "Детектив", value: "mystery" },
|
{ label: "Детектив", value: "mystery" },
|
||||||
{ label: "Детская литература", value: "kids-literature" },
|
{ label: "Детская литература", value: "kids-literature" },
|
||||||
{ label: "Документальная проза", value: "biography" },
|
{ label: "Документальная проза", value: "biography" },
|
||||||
{ label: "Историческая проза", value: "historical-fiction" },
|
{ label: "Историческая проза", value: "historical-fiction" },
|
||||||
{ label: "Исторический детектив", value: "historical-mystery" },
|
{ label: "Исторический детектив", value: "historical-mystery" },
|
||||||
{ label: "Исторический любовный роман", value: "historical-romantic-novel" },
|
{ label: "Исторический любовный роман", value: "historical-romantic-novel" },
|
||||||
{ label: "Историческое фэнтези", value: "historical-fantasy" },
|
{ label: "Историческое фэнтези", value: "historical-fantasy" },
|
||||||
{ label: "Киберпанк", value: "cyberpunk" },
|
{ label: "Киберпанк", value: "cyberpunk" },
|
||||||
{ label: "Космическая фантастика", value: "cosmic-fiction" },
|
{ label: "Космическая фантастика", value: "cosmic-fiction" },
|
||||||
{ label: "ЛитРПГ", value: "litrpg" },
|
{ label: "ЛитРПГ", value: "litrpg" },
|
||||||
{ label: "Лоу / Низкое фэнтези", value: "low-fantasy" },
|
{ label: "Лоу / Низкое фэнтези", value: "low-fantasy" },
|
||||||
{ label: "Любовное фэнтези", value: "romantic-fantasy" },
|
{ label: "Любовное фэнтези", value: "romantic-fantasy" },
|
||||||
{ label: "Любовный роман", value: "romantic-novel" },
|
{ label: "Любовный роман", value: "romantic-novel" },
|
||||||
{ label: "Мистика", value: "mystic" },
|
{ label: "Мистика", value: "mystic" },
|
||||||
{ label: "Мистический детектив", value: "mystic-detective" },
|
{ label: "Мистический детектив", value: "mystic-detective" },
|
||||||
{ label: "Научная фантастика", value: "science-fiction" },
|
{ label: "Научная фантастика", value: "science-fiction" },
|
||||||
{ label: "Подростковая проза", value: "young-adult" },
|
{ label: "Подростковая проза", value: "young-adult" },
|
||||||
{ label: "Политический роман", value: "political-romance" },
|
{ label: "Политический роман", value: "political-romance" },
|
||||||
{ label: "Попаданцы", value: "accidental-travel" },
|
{ label: "Попаданцы", value: "accidental-travel" },
|
||||||
{ label: "Попаданцы в магические миры", value: "magic-worlds-travel" },
|
{ label: "Попаданцы в магические миры", value: "magic-worlds-travel" },
|
||||||
{ label: "Попаданцы во времени", value: "time-travel" },
|
{ label: "Попаданцы во времени", value: "time-travel" },
|
||||||
{ label: "Порнотика", value: "pornotica" },
|
{ label: "Порнотика", value: "pornotica" },
|
||||||
{ label: "Постапокалипсис", value: "post-apocalypse" },
|
{ label: "Постапокалипсис", value: "post-apocalypse" },
|
||||||
{ label: "Поэзия", value: "poetry" },
|
{ label: "Поэзия", value: "poetry" },
|
||||||
{ label: "Приключения", value: "adventure" },
|
{ label: "Приключения", value: "adventure" },
|
||||||
{ label: "Публицистика", value: "journalism" },
|
{ label: "Публицистика", value: "journalism" },
|
||||||
{ label: "Пьеса", value: "play" },
|
{ label: "Пьеса", value: "play" },
|
||||||
{ label: "Развитие личности", value: "how-to-book" },
|
{ label: "Развитие личности", value: "how-to-book" },
|
||||||
{ label: "Разное", value: "other" },
|
{ label: "Разное", value: "other" },
|
||||||
{ label: "Реализм", value: "Realism" },
|
{ label: "Реализм", value: "Realism" },
|
||||||
{ label: "РеалРПГ", value: "realrpg" },
|
{ label: "РеалРПГ", value: "realrpg" },
|
||||||
{ label: "Репликация", value: "replication" },
|
{ label: "Репликация", value: "replication" },
|
||||||
{ label: "Романтическая эротика", value: "romantic-erotic-fiction" },
|
{ label: "Романтическая эротика", value: "romantic-erotic-fiction" },
|
||||||
{ label: "Сказка", value: "fairy-tale" },
|
{ label: "Сказка", value: "fairy-tale" },
|
||||||
{ label: "Слэш", value: "slash" },
|
{ label: "Слэш", value: "slash" },
|
||||||
{ label: "Современная проза", value: "modern-prose" },
|
{ label: "Современная проза", value: "modern-prose" },
|
||||||
{ label: "Современный любовный роман", value: "modern-romantic-novel" },
|
{ label: "Современный любовный роман", value: "modern-romantic-novel" },
|
||||||
{ label: "Социальная фантастика", value: "social-fiction" },
|
{ label: "Социальная фантастика", value: "social-fiction" },
|
||||||
{ label: "Стимпанк", value: "steampunk" },
|
{ label: "Стимпанк", value: "steampunk" },
|
||||||
{ label: "Сценарий", value: "scenario" },
|
{ label: "Сценарий", value: "scenario" },
|
||||||
{ label: "Сюаньхуань", value: "xuanhuan" },
|
{ label: "Сюаньхуань", value: "xuanhuan" },
|
||||||
{ label: "Сянься", value: "xianxia" },
|
{ label: "Сянься", value: "xianxia" },
|
||||||
{ label: "Тёмное фэнтези", value: "dark-fantasy" },
|
{ label: "Тёмное фэнтези", value: "dark-fantasy" },
|
||||||
{ label: "Триллер", value: "thriller" },
|
{ label: "Триллер", value: "thriller" },
|
||||||
{ label: "Уся", value: "wuxia" },
|
{ label: "Уся", value: "wuxia" },
|
||||||
{ label: "Фантастика", value: "fiction" },
|
{ label: "Фантастика", value: "fiction" },
|
||||||
{ label: "Фантастический детектив", value: "mystery-fiction" },
|
{ label: "Фантастический детектив", value: "mystery-fiction" },
|
||||||
{ label: "Фанфик", value: "fan-fiction" },
|
{ label: "Фанфик", value: "fan-fiction" },
|
||||||
{ label: "Фемслэш", value: "femslash" },
|
{ label: "Фемслэш", value: "femslash" },
|
||||||
{ label: "Фэнтези", value: "fantasy" },
|
{ label: "Фэнтези", value: "fantasy" },
|
||||||
{ label: "Хоррор", value: "horror" },
|
{ label: "Хоррор", value: "horror" },
|
||||||
{ label: "Шпионский детектив", value: "spy-crime" },
|
{ label: "Шпионский детектив", value: "spy-crime" },
|
||||||
{ label: "Эпическое фэнтези", value: "epic-fantasy" },
|
{ label: "Эпическое фэнтези", value: "epic-fantasy" },
|
||||||
{ label: "Эротика", value: "erotic-fiction" },
|
{ label: "Эротика", value: "erotic-fiction" },
|
||||||
{ label: "Эротическая фантастика", value: "erotic-fiction" },
|
{ label: "Эротическая фантастика", value: "erotic-fiction" },
|
||||||
{ label: "Эротический фанфик", value: "erotic-fan-fiction" },
|
{ label: "Эротический фанфик", value: "erotic-fan-fiction" },
|
||||||
{ label: "Эротическое фэнтези", value: "erotic-fantasy" },
|
{ label: "Эротическое фэнтези", value: "erotic-fantasy" },
|
||||||
{ label: "Этническое фэнтези", value: "ethnic-fantasy" },
|
{ label: "Этническое фэнтези", value: "ethnic-fantasy" },
|
||||||
{ label: "Юмор", value: "humor" },
|
{ label: "Юмор", value: "humor" },
|
||||||
{ label: "Юмористическая фантастика", value: "humor-fiction" },
|
{ label: "Юмористическая фантастика", value: "humor-fiction" },
|
||||||
{ label: "Юмористическое фэнтези", value: "humor-fantasy" },
|
{ label: "Юмористическое фэнтези", value: "humor-fantasy" },
|
||||||
{ label: "RPS", value: "rps" },
|
{ label: "RPS", value: "rps" },
|
||||||
],
|
],
|
||||||
type: FilterTypes.ExcludableCheckboxGroup,
|
type: FilterTypes.ExcludableCheckboxGroup,
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
label: "Статус:",
|
label: "Статус:",
|
||||||
value: "",
|
value: "all",
|
||||||
options: [
|
options: [
|
||||||
{ label: "Любой статус", value: "all" },
|
{ label: "Любой статус", value: "all" },
|
||||||
{ label: "В процессе", value: "in-process" },
|
{ label: "В процессе", value: "in-process" },
|
||||||
{ label: "Завершено", value: "finished" },
|
{ label: "Завершено", value: "finished" },
|
||||||
],
|
],
|
||||||
type: FilterTypes.Picker,
|
type: FilterTypes.Picker,
|
||||||
},
|
},
|
||||||
access: {
|
access: {
|
||||||
label: "Доступ:",
|
label: "Доступ:",
|
||||||
value: "",
|
value: "all",
|
||||||
options: [
|
options: [
|
||||||
{ label: "Любой доступ", value: "all" },
|
{ label: "Любой доступ", value: "all" },
|
||||||
{ label: "Бесплатные", value: "free" },
|
{ label: "Бесплатные", value: "free" },
|
||||||
{ label: "Платные", value: "paid" },
|
{ label: "Платные", value: "paid" },
|
||||||
],
|
],
|
||||||
type: FilterTypes.Picker,
|
type: FilterTypes.Picker,
|
||||||
},
|
},
|
||||||
adult: {
|
adult: {
|
||||||
label: "Возрастные ограничения:",
|
label: "Возрастные ограничения:",
|
||||||
value: "",
|
value: "hide",
|
||||||
options: [
|
options: [
|
||||||
{ label: "Скрыть 18+", value: "hide" },
|
{ label: "Скрыть 18+", value: "hide" },
|
||||||
{ label: "Показать +18", value: "show" },
|
{ label: "Показать +18", value: "show" },
|
||||||
],
|
],
|
||||||
type: FilterTypes.Picker,
|
type: FilterTypes.Picker,
|
||||||
},
|
},
|
||||||
} satisfies Filters;
|
} satisfies Filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default new freedlit();
|
||||||
|
|||||||
@@ -1,258 +1,252 @@
|
|||||||
import { Plugin } from "@typings/plugin";
|
import { Plugin } from "@typings/plugin";
|
||||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||||
import { NovelStatus } from "@libs/novelStatus";
|
import { NovelStatus } from "@libs/novelStatus";
|
||||||
import { load as parseHTML } from "cheerio";
|
import { load as parseHTML } from "cheerio";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
class Bookriver implements Plugin.PluginBase {
|
class Bookriver implements Plugin.PluginBase {
|
||||||
id = "bookriver";
|
id = "bookriver";
|
||||||
name = "Bookriver";
|
name = "Bookriver";
|
||||||
site = "https://bookriver.ru";
|
site = "https://bookriver.ru";
|
||||||
version = "1.0.0";
|
version = "1.0.0";
|
||||||
icon = "src/ru/bookriver/icon.png";
|
icon = "src/ru/bookriver/icon.png";
|
||||||
|
|
||||||
|
async popularNovels(
|
||||||
|
pageNo: number,
|
||||||
async popularNovels(
|
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||||
pageNo: number,
|
): Promise<Plugin.NovelItem[]> {
|
||||||
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
|
let url = this.site + `/genre?page=${pageNo}&perPage=24&sortingType=`;
|
||||||
): Promise<Plugin.NovelItem[]> {
|
url += showLatestNovels ? "last-update" : filters?.sort?.value || "bestseller";
|
||||||
let url = this.site + `/genre?page=${pageNo}&perPage=24&sortingType=`;
|
|
||||||
url += showLatestNovels
|
if (filters?.genres?.value?.length) {
|
||||||
? "last-update"
|
url += "&g=" + filters.genres.value.join(",");
|
||||||
: filters?.sort?.value || "bestseller";
|
}
|
||||||
|
|
||||||
if (filters?.genres?.value instanceof Array && filters?.genres?.value.length) {
|
const result = await fetchApi(url).then((res) => res.text());
|
||||||
url += "&g=" + filters.genres.value.join(",");
|
const loadedCheerio = parseHTML(result);
|
||||||
}
|
const novels: Plugin.NovelItem[] = [];
|
||||||
|
|
||||||
const result = await fetchApi(url);
|
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
|
||||||
const body = await result.text();
|
if (jsonRaw) {
|
||||||
|
const json: response = JSON.parse(jsonRaw);
|
||||||
const loadedCheerio = parseHTML(body);
|
json.props.pageProps.state.pagesFilter?.genre?.books?.forEach((novel) =>
|
||||||
const novels: Plugin.NovelItem[] = [];
|
novels.push({
|
||||||
|
name: novel.name,
|
||||||
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
|
cover: novel.coverImages[0].url,
|
||||||
if (jsonRaw) {
|
url: this.site + "/book/" + novel.slug,
|
||||||
const json: response = JSON.parse(jsonRaw);
|
}),
|
||||||
json.props.pageProps.state.pagesFilter?.genre?.books?.forEach((novel) =>
|
);
|
||||||
novels.push({
|
}
|
||||||
name: novel.name,
|
return novels;
|
||||||
cover: novel.coverImages[0].url,
|
}
|
||||||
url: this.site + "/book/" + novel.slug,
|
|
||||||
}),
|
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
||||||
);
|
const result = await fetchApi(novelUrl).then((res) => res.text());
|
||||||
}
|
const loadedCheerio = parseHTML(result);
|
||||||
return novels;
|
|
||||||
}
|
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
|
||||||
|
const json: response = JSON.parse(jsonRaw || "{}");
|
||||||
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
const book = json.props.pageProps.state.book?.bookPage;
|
||||||
const result = await fetchApi(novelUrl);
|
|
||||||
const body = await result.text();
|
const novel: Plugin.SourceNovel = {
|
||||||
|
url: novelUrl,
|
||||||
const loadedCheerio = parseHTML(body);
|
name: book?.name,
|
||||||
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
|
cover: book?.coverImages[0].url,
|
||||||
const json: response = JSON.parse(jsonRaw || "{}");
|
summary: book?.annotation,
|
||||||
const book = json.props.pageProps.state.book?.bookPage;
|
author: book?.author?.name,
|
||||||
const novel: Plugin.SourceNovel = {
|
genres: book?.tags?.map((tag) => tag.name).join(", "),
|
||||||
url: novelUrl,
|
status:
|
||||||
name: book?.name,
|
book?.statusComplete === "writing"
|
||||||
cover: book?.coverImages[0].url,
|
? NovelStatus.Ongoing
|
||||||
summary: book?.annotation,
|
: NovelStatus.Completed,
|
||||||
author: book?.author?.name,
|
};
|
||||||
genres: book?.tags?.map((item) => item.name).join(", "),
|
|
||||||
status:
|
const chapters: Plugin.ChapterItem[] = [];
|
||||||
book?.statusComplete === "writing"
|
book?.ebook?.chapters?.forEach((chapter, chapterIndex) => {
|
||||||
? NovelStatus.Ongoing
|
if (chapter.available) {
|
||||||
: NovelStatus.Completed,
|
chapters.push({
|
||||||
};
|
name: chapter.name,
|
||||||
|
url: this.site + "/reader/" + book?.slug + "/" + chapter.chapterId,
|
||||||
const chapters: Plugin.ChapterItem[] = [];
|
releaseTime: dayjs(chapter?.firstPublishedAt || chapter.createdAt).format("LLL"),
|
||||||
book?.ebook?.chapters?.forEach((chapter) => {
|
chapterNumber: chapterIndex + 1,
|
||||||
if (chapter.available) {
|
});
|
||||||
chapters.push({
|
}
|
||||||
name: chapter.name,
|
});
|
||||||
releaseTime: dayjs(
|
novel.chapters = chapters;
|
||||||
chapter?.firstPublishedAt || chapter.createdAt,
|
return novel;
|
||||||
).format("LLL"),
|
}
|
||||||
url: this.site + "/reader/" + book?.slug + "/" + chapter.chapterId,
|
|
||||||
});
|
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());
|
||||||
novel.chapters = chapters;
|
const json = (await result.json()) as responseChapter;
|
||||||
return novel;
|
|
||||||
}
|
let chapterText = json.data.content || "Конец произведения";
|
||||||
|
if (json.data?.audio?.available) {
|
||||||
async parseChapter(chapterUrl: string): Promise<string> {
|
chapterText += "\n" + json.data.audio.url;
|
||||||
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;
|
return chapterText;
|
||||||
|
}
|
||||||
let chapterText = json.data.content || "Конец произведения";
|
|
||||||
if (json.data?.audio?.available) {
|
async searchNovels(
|
||||||
chapterText += "\n" + json.data.audio.url;
|
searchTerm: string,
|
||||||
}
|
pageNo: number | undefined = 1,
|
||||||
|
): Promise<Plugin.NovelItem[]> {
|
||||||
return chapterText;
|
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;
|
||||||
async searchNovels(
|
|
||||||
searchTerm: string,
|
const novels: Plugin.NovelItem[] = [];
|
||||||
pageNo: number | undefined = 1,
|
json.data?.books?.forEach((novel) =>
|
||||||
): Promise<Plugin.NovelItem[]> {
|
novels.push({
|
||||||
const url = `https://api.bookriver.ru/api/v1/search/autocomplete?keyword=${searchTerm}&page=${pageNo}&perPage=10`;
|
name: novel.name,
|
||||||
const result = await fetchApi(url);
|
cover: novel.coverImages[0].url,
|
||||||
const json = (await result.json()) as responseSearch;
|
url: this.site + "/book/" + novel.slug,
|
||||||
|
}),
|
||||||
const novels: Plugin.NovelItem[] = [];
|
);
|
||||||
json?.data?.books?.forEach((novel) =>
|
|
||||||
novels.push({
|
return novels;
|
||||||
name: novel.name,
|
}
|
||||||
cover: novel.coverImages[0].url,
|
fetchImage = fetchFile;
|
||||||
url: this.site + "/book/" + novel.slug,
|
|
||||||
}),
|
filters = {
|
||||||
);
|
sort: {
|
||||||
|
label: "Сортировка",
|
||||||
return novels;
|
value: "bestseller",
|
||||||
}
|
options: [
|
||||||
fetchImage = fetchFile;
|
{ label: "Бестселлеры", value: "bestseller" },
|
||||||
|
{ label: "Дате добавления", value: "newest" },
|
||||||
filters = {
|
{ label: "Дате обновления", value: "last-update" },
|
||||||
sort: {
|
],
|
||||||
label: "Сортировка",
|
type: FilterTypes.Picker,
|
||||||
value: "",
|
},
|
||||||
options: [
|
genres: {
|
||||||
{ label: "Бестселлеры", value: "bestseller" },
|
label: "жанры",
|
||||||
{ label: "Дате добавления", value: "newest" },
|
value: [],
|
||||||
{ label: "Дате обновления", value: "last-update" },
|
options: [
|
||||||
],
|
{ label: "Альтернативная история", value: "alternativnaya-istoriya" },
|
||||||
type: FilterTypes.Picker,
|
{ label: "Боевая фантастика", value: "boevaya-fantastika" },
|
||||||
},
|
{ label: "Боевое фэнтези", value: "boevoe-fentezi" },
|
||||||
genres: {
|
{ label: "Бытовое фэнтези", value: "bytovoe-fentezi" },
|
||||||
label: "жанры",
|
{ label: "Героическая фантастика", value: "geroicheskaya-fantastika" },
|
||||||
value: [],
|
{ label: "Героическое фэнтези", value: "geroicheskoe-fentezi" },
|
||||||
options: [
|
{ label: "Городское фэнтези", value: "gorodskoe-fentezi" },
|
||||||
{ label: "Альтернативная история", value: "alternativnaya-istoriya" },
|
{ label: "Детектив", value: "detektiv" },
|
||||||
{ label: "Боевая фантастика", value: "boevaya-fantastika" },
|
{ label: "Детективная фантастика", value: "detektivnaya-fantastika" },
|
||||||
{ label: "Боевое фэнтези", value: "boevoe-fentezi" },
|
{ label: "Жёсткая эротика", value: "zhyostkaya-erotika" },
|
||||||
{ label: "Бытовое фэнтези", value: "bytovoe-fentezi" },
|
{ label: "Исторический детектив", value: "istoricheskii-detektiv" },
|
||||||
{ label: "Героическая фантастика", value: "geroicheskaya-fantastika" },
|
{ label: "Исторический любовный роман", value: "istoricheskii-lyubovnyi-roman" },
|
||||||
{ label: "Героическое фэнтези", value: "geroicheskoe-fentezi" },
|
{ label: "Историческое фэнтези", value: "istoricheskoe-fentezi" },
|
||||||
{ label: "Городское фэнтези", value: "gorodskoe-fentezi" },
|
{ label: "Киберпанк", value: "kiberpank" },
|
||||||
{ label: "Детектив", value: "detektiv" },
|
{ label: "Классический детектив", value: "klassicheskii-detektiv" },
|
||||||
{ label: "Детективная фантастика", value: "detektivnaya-fantastika" },
|
{ label: "Короткий любовный роман", value: "korotkii-lyubovnyi-roman" },
|
||||||
{ label: "Жёсткая эротика", value: "zhyostkaya-erotika" },
|
{ label: "Космическая фантастика", value: "kosmicheskaya-fantastika" },
|
||||||
{ label: "Исторический детектив", value: "istoricheskii-detektiv" },
|
{ label: "Криминальный детектив", value: "kriminalnyi-detektiv" },
|
||||||
{ label: "Исторический любовный роман", value: "istoricheskii-lyubovnyi-roman" },
|
{ label: "ЛитРПГ", value: "litrpg" },
|
||||||
{ label: "Историческое фэнтези", value: "istoricheskoe-fentezi" },
|
{ label: "Любовная фантастика", value: "lyubovnaya-fantastika" },
|
||||||
{ label: "Киберпанк", value: "kiberpank" },
|
{ label: "Любовное фэнтези", value: "lyubovnoe-fentezi" },
|
||||||
{ label: "Классический детектив", value: "klassicheskii-detektiv" },
|
{ label: "Любовный роман", value: "lyubovnyi-roman" },
|
||||||
{ label: "Короткий любовный роман", value: "korotkii-lyubovnyi-roman" },
|
{ label: "Мистика", value: "mistika" },
|
||||||
{ label: "Космическая фантастика", value: "kosmicheskaya-fantastika" },
|
{ label: "Молодежная проза", value: "molodezhnaya-proza" },
|
||||||
{ label: "Криминальный детектив", value: "kriminalnyi-detektiv" },
|
{ label: "Научная фантастика", value: "nauchnaya-fantastika" },
|
||||||
{ label: "ЛитРПГ", value: "litrpg" },
|
{ label: "Остросюжетный любовный роман", value: "ostrosyuzhetnyi-lyubovnyi-roman" },
|
||||||
{ label: "Любовная фантастика", value: "lyubovnaya-fantastika" },
|
{ label: "Политический детектив", value: "politicheskii-detektiv" },
|
||||||
{ label: "Любовное фэнтези", value: "lyubovnoe-fentezi" },
|
{ label: "Попаданцы", value: "popadantsy" },
|
||||||
{ label: "Любовный роман", value: "lyubovnyi-roman" },
|
{ label: "Постапокалипсис", value: "postapokalipsis" },
|
||||||
{ label: "Мистика", value: "mistika" },
|
{ label: "Приключенческое фэнтези", value: "priklyuchencheskoe-fentezi" },
|
||||||
{ label: "Молодежная проза", value: "molodezhnaya-proza" },
|
{ label: "Романтическая эротика", value: "romanticheskaya-erotika" },
|
||||||
{ label: "Научная фантастика", value: "nauchnaya-fantastika" },
|
{ label: "С элементами эротики", value: "s-elementami-erotiki" },
|
||||||
{ label: "Остросюжетный любовный роман", value: "ostrosyuzhetnyi-lyubovnyi-roman" },
|
{ label: "Славянское фэнтези", value: "slavyanskoe-fentezi" },
|
||||||
{ label: "Политический детектив", value: "politicheskii-detektiv" },
|
{ label: "Современный любовный роман", value: "sovremennyi-lyubovnyi-roman" },
|
||||||
{ label: "Попаданцы", value: "popadantsy" },
|
{ label: "Социальная фантастика", value: "sotsialnaya-fantastika" },
|
||||||
{ label: "Постапокалипсис", value: "postapokalipsis" },
|
{ label: "Тёмное фэнтези", value: "temnoe-fentezi" },
|
||||||
{ label: "Приключенческое фэнтези", value: "priklyuchencheskoe-fentezi" },
|
{ label: "Фантастика", value: "fantastika" },
|
||||||
{ label: "Романтическая эротика", value: "romanticheskaya-erotika" },
|
{ label: "Фэнтези", value: "fentezi" },
|
||||||
{ label: "С элементами эротики", value: "s-elementami-erotiki" },
|
{ label: "Шпионский детектив", value: "shpionskii-detektiv" },
|
||||||
{ label: "Славянское фэнтези", value: "slavyanskoe-fentezi" },
|
{ label: "Эпическое фэнтези", value: "epicheskoe-fentezi" },
|
||||||
{ label: "Современный любовный роман", value: "sovremennyi-lyubovnyi-roman" },
|
{ label: "Эротика", value: "erotika" },
|
||||||
{ label: "Социальная фантастика", value: "sotsialnaya-fantastika" },
|
{ label: "Эротическая фантастика", value: "eroticheskaya-fantastika" },
|
||||||
{ label: "Тёмное фэнтези", value: "temnoe-fentezi" },
|
{ label: "Эротический фанфик", value: "eroticheskii-fanfik" },
|
||||||
{ label: "Фантастика", value: "fantastika" },
|
{ label: "Эротическое фэнтези", value: "eroticheskoe-fentezi" },
|
||||||
{ label: "Фэнтези", value: "fentezi" },
|
{ label: "Юмористический детектив", value: "yumoristicheskii-detektiv" },
|
||||||
{ label: "Шпионский детектив", value: "shpionskii-detektiv" },
|
{ label: "Юмористическое фэнтези", value: "yumoristicheskoe-fentezi" },
|
||||||
{ label: "Эпическое фэнтези", value: "epicheskoe-fentezi" },
|
],
|
||||||
{ label: "Эротика", value: "erotika" },
|
type: FilterTypes.CheckboxGroup,
|
||||||
{ label: "Эротическая фантастика", value: "eroticheskaya-fantastika" },
|
},
|
||||||
{ label: "Эротический фанфик", value: "eroticheskii-fanfik" },
|
} satisfies Filters;
|
||||||
{ label: "Эротическое фэнтези", value: "eroticheskoe-fentezi" },
|
}
|
||||||
{ label: "Юмористический детектив", value: "yumoristicheskii-detektiv" },
|
export default new Bookriver();
|
||||||
{ label: "Юмористическое фэнтези", value: "yumoristicheskoe-fentezi" },
|
|
||||||
],
|
interface response {
|
||||||
type: FilterTypes.CheckboxGroup,
|
props: {
|
||||||
},
|
pageProps: {
|
||||||
} satisfies Filters;
|
state: {
|
||||||
}
|
book?: {
|
||||||
export default new Bookriver();
|
bookPage: BooksEntity;
|
||||||
|
};
|
||||||
interface response {
|
pagesFilter?: {
|
||||||
props: {
|
genre: {
|
||||||
pageProps: {
|
books: BooksEntity[];
|
||||||
state: {
|
};
|
||||||
book?: {
|
};
|
||||||
bookPage: BooksEntity;
|
};
|
||||||
};
|
};
|
||||||
pagesFilter?: {
|
};
|
||||||
genre: {
|
}
|
||||||
books: BooksEntity[];
|
|
||||||
};
|
interface BooksEntity {
|
||||||
};
|
name: string;
|
||||||
};
|
coverImages: { url: string }[];
|
||||||
};
|
slug: string;
|
||||||
};
|
annotation?: string;
|
||||||
}
|
author?: {
|
||||||
|
name: string;
|
||||||
interface BooksEntity {
|
};
|
||||||
name: string;
|
tags?: { name: string }[];
|
||||||
coverImages: { url: string }[];
|
ebook?: {
|
||||||
slug: string;
|
chapters: Chapter[];
|
||||||
annotation?: string;
|
};
|
||||||
author?: {
|
statusComplete: string;
|
||||||
name: string;
|
}
|
||||||
};
|
|
||||||
tags?: { name: string }[];
|
interface Chapter {
|
||||||
ebook?: {
|
name: string;
|
||||||
chapters: Chapter[];
|
available: boolean;
|
||||||
};
|
firstPublishedAt?: Date | null;
|
||||||
statusComplete: string;
|
createdAt?: Date | null;
|
||||||
}
|
chapterId: number | string;
|
||||||
|
}
|
||||||
interface Chapter {
|
|
||||||
name: string;
|
interface responseChapter {
|
||||||
available: boolean;
|
data: {
|
||||||
firstPublishedAt?: Date | null;
|
id: number;
|
||||||
createdAt?: Date | null;
|
bookId: number;
|
||||||
chapterId: number | string;
|
name: string;
|
||||||
}
|
content: string;
|
||||||
|
symbols: number;
|
||||||
interface responseChapter {
|
number: number;
|
||||||
data: {
|
authorPages: number;
|
||||||
id: number;
|
createdAt: string;
|
||||||
bookId: number;
|
updatedAt: string;
|
||||||
name: string;
|
firstPublishedAt: string;
|
||||||
content: string;
|
status: string;
|
||||||
symbols: number;
|
protected: boolean;
|
||||||
number: number;
|
free: boolean;
|
||||||
authorPages: number;
|
audio?: {
|
||||||
createdAt: string;
|
available: boolean;
|
||||||
updatedAt: string;
|
url: string;
|
||||||
firstPublishedAt: string;
|
duration: number;
|
||||||
status: string;
|
};
|
||||||
protected: boolean;
|
publicationScheduledFor?: null;
|
||||||
free: boolean;
|
};
|
||||||
audio?: {
|
}
|
||||||
available: boolean;
|
|
||||||
url: string;
|
interface responseSearch {
|
||||||
duration: number;
|
data: Data;
|
||||||
};
|
total: number;
|
||||||
publicationScheduledFor?: null;
|
}
|
||||||
};
|
interface Data {
|
||||||
}
|
books?: BooksEntity[] | null;
|
||||||
|
}
|
||||||
interface responseSearch {
|
|
||||||
data: Data;
|
|
||||||
total: number;
|
|
||||||
}
|
|
||||||
interface Data {
|
|
||||||
books?: BooksEntity[] | null;
|
|
||||||
|
|||||||
@@ -1,264 +1,263 @@
|
|||||||
import { Plugin } from "@typings/plugin";
|
import { Plugin } from "@typings/plugin";
|
||||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||||
import { NovelStatus } from "@libs/novelStatus";
|
import { NovelStatus } from "@libs/novelStatus";
|
||||||
import { load as parseHTML } from "cheerio";
|
import { load as parseHTML } from "cheerio";
|
||||||
|
|
||||||
class Jaomix implements Plugin.PluginBase {
|
class Jaomix implements Plugin.PluginBase {
|
||||||
id = "jaomix.ru";
|
id = "jaomix.ru";
|
||||||
name = "Jaomix";
|
name = "Jaomix";
|
||||||
site = "https://jaomix.ru";
|
site = "https://jaomix.ru";
|
||||||
version = "1.0.0";
|
version = "1.0.0";
|
||||||
icon = "src/ru/jaomix/icon.png";
|
icon = "src/ru/jaomix/icon.png";
|
||||||
|
|
||||||
|
async popularNovels(
|
||||||
|
pageNo: number,
|
||||||
async popularNovels(
|
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||||
pageNo: number,
|
): Promise<Plugin.NovelItem[]> {
|
||||||
{
|
let url = this.site + "/?searchrn";
|
||||||
showLatestNovels,
|
|
||||||
filters,
|
if (filters?.lang?.value?.length) {
|
||||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
url += filters.lang.value
|
||||||
): Promise<Plugin.NovelItem[]> {
|
.map((lang, idx) => `&lang[${idx}]=${lang}`)
|
||||||
let url = this.site + "/?searchrn";
|
.join("");
|
||||||
|
}
|
||||||
if (filters?.lang?.value?.length) {
|
|
||||||
url += filters.lang.value
|
if (filters?.genre?.value?.include?.length) {
|
||||||
.map((lang, idx) => `&lang[${idx}]=${lang}`).join("");
|
url += filters.genre.value.include
|
||||||
}
|
.map((genre, idx) => `&genre[${idx}]=${genre}`)
|
||||||
|
.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}`)
|
||||||
if (filters?.genre?.value?.exclude?.length) {
|
.join("");
|
||||||
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 += "&sortcountchapt=" + (filters?.sortcountchapt?.value || "1");
|
url += "&sortby=" + (showLatestNovels ? "upd" : filters?.sortby?.value || "topweek");
|
||||||
url += "&sortdaycreate=" + (filters?.sortdaycreate?.value || "1");
|
url += "&gpage=" + pageNo;
|
||||||
url +=
|
|
||||||
"&sortby=" + (showLatestNovels ? "upd" : filters?.sortby?.value || "topweek");
|
const body = await fetchApi(url).then((res) => res.text());
|
||||||
url += "&gpage=" + pageNo;
|
const loadedCheerio = parseHTML(body);
|
||||||
|
|
||||||
const body = await fetchApi(url).then((res) => res.text());
|
const novels: Plugin.NovelItem[] = [];
|
||||||
const loadedCheerio = parseHTML(body);
|
loadedCheerio('div[class="block-home"] > div[class="one"]').each(
|
||||||
|
function () {
|
||||||
const novels: Plugin.NovelItem[] = [];
|
const name = loadedCheerio(this)
|
||||||
loadedCheerio('div[class="block-home"] > div[class="one"]').each(
|
.find('div[class="img-home"] > a')
|
||||||
function () {
|
.attr("title");
|
||||||
const name = loadedCheerio(this)
|
const cover = loadedCheerio(this)
|
||||||
.find('div[class="img-home"] > a')
|
.find('div[class="img-home"] > a > img')
|
||||||
.attr("title");
|
.attr("src")
|
||||||
const cover = loadedCheerio(this)
|
?.replace("-150x150", "");
|
||||||
.find('div[class="img-home"] > a > img')
|
const url = loadedCheerio(this)
|
||||||
.attr("src")
|
.find('div[class="img-home"] > a')
|
||||||
?.replace("-150x150", "");
|
.attr("href");
|
||||||
const url = loadedCheerio(this)
|
|
||||||
.find('div[class="img-home"] > a')
|
if (!name || !url) return;
|
||||||
.attr("href");
|
|
||||||
|
novels.push({ name, cover, url });
|
||||||
if (!name || !url) return;
|
},
|
||||||
|
);
|
||||||
novels.push({ name, cover, url });
|
|
||||||
},
|
return novels;
|
||||||
);
|
}
|
||||||
|
|
||||||
return novels;
|
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
||||||
}
|
const body = await fetchApi(novelUrl).then((res) => res.text());
|
||||||
|
const loadedCheerio = parseHTML(body);
|
||||||
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
|
||||||
const body = await fetchApi(novelUrl).then((res) => res.text());
|
const novel: Plugin.SourceNovel = {
|
||||||
const loadedCheerio = parseHTML(body);
|
url: novelUrl,
|
||||||
|
name: loadedCheerio('div[class="desc-book"] > h1').text().trim(),
|
||||||
const novel: Plugin.SourceNovel = {
|
cover: loadedCheerio('div[class="img-book"] > img').attr("src"),
|
||||||
url: novelUrl,
|
summary: loadedCheerio('div[id="desc-tab"]').text().trim(),
|
||||||
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] === "Автор:") {
|
||||||
loadedCheerio("#info-book > p").each(function () {
|
novel.author = text.splice(1).join(" ");
|
||||||
let text = loadedCheerio(this).text().replace(/,/g, "").split(" ");
|
} else if (text[0] === "Жанры:") {
|
||||||
if (text[0] === "Автор:") {
|
novel.genres = text.splice(1).join(",");
|
||||||
novel.author = text.splice(1).join(" ");
|
} else if (text[0] === "Статус:") {
|
||||||
} else if (text[0] === "Жанры:") {
|
novel.status = text.includes("продолжается")
|
||||||
novel.genres = text.splice(1).join(",");
|
? NovelStatus.Ongoing
|
||||||
} else if (text[0] === "Статус:") {
|
: NovelStatus.Completed;
|
||||||
novel.status = text.includes("продолжается")
|
}
|
||||||
? NovelStatus.Ongoing
|
});
|
||||||
: NovelStatus.Completed;
|
|
||||||
}
|
const chapters: Plugin.ChapterItem[] = [];
|
||||||
});
|
const totalChapters = loadedCheerio(".download-chapter div.title").length;
|
||||||
|
|
||||||
const chapters: Plugin.ChapterItem[] = [];
|
loadedCheerio(".download-chapter div.title").each((chapterIndex, element) => {
|
||||||
|
const name = loadedCheerio(element).find("a").attr("title");
|
||||||
loadedCheerio(".download-chapter div.title").each(function () {
|
const url = loadedCheerio(element).find("a").attr("href");
|
||||||
const name = loadedCheerio(this).find("a").attr("title");
|
if (!name || !url) return;
|
||||||
const releaseTime = loadedCheerio(this).find("time").text();
|
|
||||||
const url = loadedCheerio(this).find("a").attr("href");
|
const releaseTime = loadedCheerio(element).find("time").text();
|
||||||
if (!name || !url) return;
|
chapters.push({ name, url, releaseTime, chapterNumber: totalChapters - chapterIndex });
|
||||||
|
});
|
||||||
chapters.push({ name, releaseTime, url });
|
|
||||||
});
|
novel.chapters = chapters.reverse();
|
||||||
|
return novel;
|
||||||
novel.chapters = chapters.reverse();
|
}
|
||||||
return novel;
|
|
||||||
}
|
async parseChapter(chapterUrl: string): Promise<string> {
|
||||||
|
const body = await fetchApi(chapterUrl).then((res) => res.text());
|
||||||
async parseChapter(chapterUrl: string): Promise<string> {
|
const loadedCheerio = parseHTML(body);
|
||||||
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();
|
||||||
loadedCheerio('div[class="adblock-service"]').remove();
|
|
||||||
const chapterText = loadedCheerio('div[class="entry-content"]').html();
|
return chapterText || "";
|
||||||
|
}
|
||||||
return chapterText || "";
|
|
||||||
}
|
async searchNovels(
|
||||||
|
searchTerm: string,
|
||||||
async searchNovels(
|
pageNo: number | undefined = 1,
|
||||||
searchTerm: string,
|
): Promise<Plugin.NovelItem[]> {
|
||||||
pageNo: number | undefined = 1,
|
const url = `${this.site}/?searchrn=${searchTerm}&but=Поиск по названию&sortby=upd&gpage=${pageNo}`;
|
||||||
): Promise<Plugin.NovelItem[]> {
|
const body = await fetchApi(url).then((res) => res.text());
|
||||||
const url = `${this.site}/?searchrn=${searchTerm}&but=Поиск по названию&sortby=upd&gpage=${pageNo}`;
|
const loadedCheerio = parseHTML(body);
|
||||||
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(
|
||||||
const novels: Plugin.NovelItem[] = [];
|
function () {
|
||||||
loadedCheerio('div[class="block-home"] > div[class="one"]').each(
|
const name = loadedCheerio(this)
|
||||||
function () {
|
.find('div[class="img-home"] > a')
|
||||||
const name = loadedCheerio(this)
|
.attr("title");
|
||||||
.find('div[class="img-home"] > a')
|
const cover = loadedCheerio(this)
|
||||||
.attr("title");
|
.find('div[class="img-home"] > a > img')
|
||||||
const cover = loadedCheerio(this)
|
.attr("src")
|
||||||
.find('div[class="img-home"] > a > img')
|
?.replace("-150x150", "");
|
||||||
.attr("src")
|
const url = loadedCheerio(this)
|
||||||
?.replace("-150x150", "");
|
.find('div[class="img-home"] > a')
|
||||||
const url = loadedCheerio(this)
|
.attr("href");
|
||||||
.find('div[class="img-home"] > a')
|
|
||||||
.attr("href");
|
if (!name || !url) return;
|
||||||
|
|
||||||
if (!name || !url) return;
|
novels.push({ name, cover, url });
|
||||||
|
},
|
||||||
novels.push({ name, cover, url });
|
);
|
||||||
},
|
|
||||||
);
|
return novels;
|
||||||
|
}
|
||||||
return novels;
|
fetchImage = fetchFile;
|
||||||
}
|
|
||||||
fetchImage = fetchFile;
|
filters = {
|
||||||
|
sortby: {
|
||||||
filters = {
|
label: "Сортировка:",
|
||||||
sortby: {
|
value: "topweek",
|
||||||
label: "Сортировка:",
|
options: [
|
||||||
value: "topweek",
|
{ label: "Топ недели", value: "topweek" },
|
||||||
options: [
|
{ label: "По алфавиту", value: "alphabet" },
|
||||||
{ label: "Топ недели", value: "topweek" },
|
{ label: "По дате обновления", value: "upd" },
|
||||||
{ label: "По алфавиту", value: "alphabet" },
|
{ label: "По дате создания", value: "new" },
|
||||||
{ label: "По дате обновления", value: "upd" },
|
{ label: "По просмотрам", value: "count" },
|
||||||
{ label: "По дате создания", value: "new" },
|
{ label: "Топ года", value: "topyear" },
|
||||||
{ label: "По просмотрам", value: "count" },
|
{ label: "Топ дня", value: "topday" },
|
||||||
{ label: "Топ года", value: "topyear" },
|
{ label: "Топ за все время", value: "alltime" },
|
||||||
{ label: "Топ дня", value: "topday" },
|
{ label: "Топ месяца", value: "topmonth" },
|
||||||
{ label: "Топ за все время", value: "alltime" },
|
],
|
||||||
{ label: "Топ месяца", value: "topmonth" },
|
type: FilterTypes.Picker,
|
||||||
],
|
},
|
||||||
type: FilterTypes.Picker,
|
sortdaycreate: {
|
||||||
},
|
label: "Дата добавления:",
|
||||||
sortdaycreate: {
|
value: "1",
|
||||||
label: "Дата добавления:",
|
options: [
|
||||||
value: "1",
|
{ label: "Любое", value: "1" },
|
||||||
options: [
|
{ label: "От 120 до 180 дней", value: "1218" },
|
||||||
{ label: "Любое", value: "1" },
|
{ label: "От 180 до 365 дней", value: "1836" },
|
||||||
{ label: "От 120 до 180 дней", value: "1218" },
|
{ label: "От 30 до 60 дней", value: "3060" },
|
||||||
{ label: "От 180 до 365 дней", value: "1836" },
|
{ label: "От 365 дней", value: "365" },
|
||||||
{ label: "От 30 до 60 дней", value: "3060" },
|
{ label: "От 60 до 90 дней", value: "6090" },
|
||||||
{ label: "От 365 дней", value: "365" },
|
{ label: "От 90 до 120 дней", value: "9012" },
|
||||||
{ label: "От 60 до 90 дней", value: "6090" },
|
{ label: "Послед. 30 дней", value: "30" },
|
||||||
{ label: "От 90 до 120 дней", value: "9012" },
|
],
|
||||||
{ label: "Послед. 30 дней", value: "30" },
|
type: FilterTypes.Picker,
|
||||||
],
|
},
|
||||||
type: FilterTypes.Picker,
|
sortcountchapt: {
|
||||||
},
|
label: "Количество глав:",
|
||||||
sortcountchapt: {
|
value: "1",
|
||||||
label: "Количество глав:",
|
options: [
|
||||||
value: "1",
|
{ label: "Любое кол-во глав", value: "1" },
|
||||||
options: [
|
{ label: "До 500", value: "500" },
|
||||||
{ label: "Любое кол-во глав", value: "1" },
|
{ label: "От 1000 до 2000", value: "1020" },
|
||||||
{ label: "До 500", value: "500" },
|
{ label: "От 2000 до 3000", value: "2030" },
|
||||||
{ label: "От 1000 до 2000", value: "1020" },
|
{ label: "От 3000 до 4000", value: "3040" },
|
||||||
{ label: "От 2000 до 3000", value: "2030" },
|
{ label: "От 4000", value: "400" },
|
||||||
{ label: "От 3000 до 4000", value: "3040" },
|
{ label: "От 500 до 1000", value: "510" },
|
||||||
{ label: "От 4000", value: "400" },
|
],
|
||||||
{ label: "От 500 до 1000", value: "510" },
|
type: FilterTypes.Picker,
|
||||||
],
|
},
|
||||||
type: FilterTypes.Picker,
|
genre: {
|
||||||
},
|
label: "Жанры:",
|
||||||
genre: {
|
value: { include: [], exclude: [] },
|
||||||
label: "Жанры:",
|
options: [
|
||||||
value: { include: [], exclude: [] },
|
{ label: "Боевые Искусства", value: "Боевые Искусства" },
|
||||||
options: [
|
{ label: "Виртуальный Мир", value: "Виртуальный Мир" },
|
||||||
{ label: "Боевые Искусства", value: "Боевые Искусства" },
|
{ label: "Гарем", value: "Гарем" },
|
||||||
{ label: "Виртуальный Мир", value: "Виртуальный Мир" },
|
{ label: "Детектив", value: "Детектив" },
|
||||||
{ label: "Гарем", value: "Гарем" },
|
{ label: "Драма", value: "Драма" },
|
||||||
{ label: "Детектив", value: "Детектив" },
|
{ label: "Игра", value: "Игра" },
|
||||||
{ label: "Драма", value: "Драма" },
|
{ label: "Истории из жизни", value: "Истории из жизни" },
|
||||||
{ label: "Игра", value: "Игра" },
|
{ label: "Исторический", value: "Исторический" },
|
||||||
{ label: "Истории из жизни", value: "Истории из жизни" },
|
{ label: "История", value: "История" },
|
||||||
{ label: "Исторический", value: "Исторический" },
|
{ label: "Исэкай", value: "Исэкай" },
|
||||||
{ label: "История", value: "История" },
|
{ label: "Комедия", value: "Комедия" },
|
||||||
{ label: "Исэкай", value: "Исэкай" },
|
{ label: "Меха", value: "Меха" },
|
||||||
{ label: "Комедия", value: "Комедия" },
|
{ label: "Мистика", value: "Мистика" },
|
||||||
{ label: "Меха", value: "Меха" },
|
{ label: "Научная Фантастика", value: "Научная Фантастика" },
|
||||||
{ label: "Мистика", value: "Мистика" },
|
{ label: "Повседневность", value: "Повседневность" },
|
||||||
{ label: "Научная Фантастика", value: "Научная Фантастика" },
|
{ label: "Постапокалипсис", value: "Постапокалипсис" },
|
||||||
{ label: "Повседневность", value: "Повседневность" },
|
{ label: "Приключения", value: "Приключения" },
|
||||||
{ label: "Постапокалипсис", value: "Постапокалипсис" },
|
{ label: "Психология", value: "Психология" },
|
||||||
{ label: "Приключения", value: "Приключения" },
|
{ label: "Романтика", value: "Романтика" },
|
||||||
{ label: "Психология", value: "Психология" },
|
{ label: "Сверхъестественное", value: "Сверхъестественное" },
|
||||||
{ label: "Романтика", value: "Романтика" },
|
{ label: "Сёнэн", value: "Сёнэн" },
|
||||||
{ label: "Сверхъестественное", value: "Сверхъестественное" },
|
{ label: "Сёнэн-ай", 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: "Юри", value: "Юри" },
|
{ label: "Ecchi", value: "Ecchi" },
|
||||||
{ label: "Adult", value: "Adult" },
|
{ label: "Josei", value: "Josei" },
|
||||||
{ label: "Ecchi", value: "Ecchi" },
|
{ label: "Lolicon", value: "Lolicon" },
|
||||||
{ label: "Josei", value: "Josei" },
|
{ label: "Mature", value: "Mature" },
|
||||||
{ label: "Lolicon", value: "Lolicon" },
|
{ label: "Shoujo", value: "Shoujo" },
|
||||||
{ label: "Mature", value: "Mature" },
|
{ label: "Wuxia", value: "Wuxia" },
|
||||||
{ label: "Shoujo", value: "Shoujo" },
|
{ label: "Xianxia", value: "Xianxia" },
|
||||||
{ label: "Wuxia", value: "Wuxia" },
|
{ label: "Xuanhuan", value: "Xuanhuan" },
|
||||||
{ label: "Xianxia", value: "Xianxia" },
|
{ label: "Yaoi", value: "Yaoi" },
|
||||||
{ label: "Xuanhuan", value: "Xuanhuan" },
|
],
|
||||||
{ label: "Yaoi", value: "Yaoi" },
|
type: FilterTypes.ExcludableCheckboxGroup,
|
||||||
],
|
},
|
||||||
type: FilterTypes.ExcludableCheckboxGroup,
|
lang: {
|
||||||
},
|
label: "Выбрать языки:",
|
||||||
lang: {
|
value: [],
|
||||||
label: "Выбрать языки:",
|
options: [
|
||||||
value: [],
|
{ label: "Английский", value: "Английский" },
|
||||||
options: [
|
{ label: "Китайский", value: "Китайский" },
|
||||||
{ label: "Английский", value: "Английский" },
|
{ label: "Корейский", value: "Корейский" },
|
||||||
{ label: "Китайский", value: "Китайский" },
|
{ label: "Японский", value: "Японский" },
|
||||||
{ label: "Корейский", value: "Корейский" },
|
],
|
||||||
{ label: "Японский", value: "Японский" },
|
type: FilterTypes.CheckboxGroup,
|
||||||
],
|
},
|
||||||
type: FilterTypes.CheckboxGroup,
|
} satisfies Filters;
|
||||||
},
|
}
|
||||||
} satisfies Filters;
|
|
||||||
}
|
export default new Jaomix();
|
||||||
|
|
||||||
|
|||||||
@@ -1,512 +1,484 @@
|
|||||||
import { Plugin } from "@typings/plugin";
|
import { Plugin } from "@typings/plugin";
|
||||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||||
import { NovelStatus } from "@libs/novelStatus";
|
import { NovelStatus } from "@libs/novelStatus";
|
||||||
import { load as parseHTML } from "cheerio";
|
import { load as parseHTML } from "cheerio";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
class RLIB implements Plugin.PluginBase {
|
const statusKey = [
|
||||||
id = "RLIB";
|
NovelStatus.Ongoing,
|
||||||
name = "RanobeLib";
|
NovelStatus.Completed,
|
||||||
site = "https://ranobelib.me";
|
NovelStatus.OnHiatus,
|
||||||
version = "1.0.0";
|
NovelStatus.Cancelled,
|
||||||
icon = "src/ru/ranobelib/icon.png";
|
];
|
||||||
ui: string | number | undefined = undefined;
|
|
||||||
|
class RLIB implements Plugin.PluginBase {
|
||||||
|
id = "RLIB";
|
||||||
|
name = "RanobeLib";
|
||||||
async popularNovels(
|
site = "https://ranobelib.me";
|
||||||
pageNo: number,
|
version = "1.0.0";
|
||||||
{
|
icon = "src/ru/ranobelib/icon.png";
|
||||||
showLatestNovels,
|
ui: string | undefined = undefined;
|
||||||
filters,
|
|
||||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
async popularNovels(
|
||||||
): Promise<Plugin.NovelItem[]> {
|
pageNo: number,
|
||||||
let url = `${this.site}/manga-list?sort=`;
|
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||||
url += showLatestNovels
|
): Promise<Plugin.NovelItem[]> {
|
||||||
? "last_chapter_at"
|
let url = this.site + "/manga-list?sort=";
|
||||||
: filters?.sort?.value || "rate";
|
url += showLatestNovels ? "last_chapter_at" : filters?.sort?.value || "rate";
|
||||||
url += "&dir=" + (filters?.order?.value || "desc");
|
url += "&dir=" + (filters?.order?.value || "desc");
|
||||||
|
|
||||||
if (filters.type?.value?.length) {
|
Object.entries(filters || {}).forEach(([type, { value }]: any) => {
|
||||||
url += filters.type.value.map((i) => "&types[]=" + i).join("");
|
if (value instanceof Array && value.length) {
|
||||||
}
|
url += "&" + type + "[]=" + value.join("&" + type + "[]=");
|
||||||
|
}
|
||||||
if (filters.format?.value?.include?.length) {
|
if (value?.include instanceof Array && value.include.length) {
|
||||||
url += filters.format.value.include
|
url += "&" + type + "[include][]=" +
|
||||||
.map((i) => "&format[include][]=" + i).join("");
|
value.include.join("&" + type + "[include][]=");
|
||||||
}
|
}
|
||||||
|
if (value?.exclude instanceof Array && value.exclude.length) {
|
||||||
if (filters.format?.value?.exclude?.length) {
|
url += "&" + type + "[exclude][]=" +
|
||||||
url += filters.format.value.exclude
|
value.exclude.join("&" + type + "[exclude][]=");
|
||||||
.map((i) => "&format[exclude][]=" + i).join("");
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
if (filters.status?.value?.length) {
|
url += "&page=" + pageNo;
|
||||||
url += filters.status.value.map((i) => "&status[]=" + i).join("");
|
|
||||||
}
|
const result = await fetchApi(url).then((res) => res.text());
|
||||||
|
const loadedCheerio = parseHTML(result);
|
||||||
if (filters.statuss?.value?.length) {
|
this.ui = loadedCheerio("a.header-right-menu__item")
|
||||||
url += filters.statuss.value.map((i) => "&manga_status[]=" + i).join("");
|
.attr("href")
|
||||||
}
|
?.replace?.(/[^0-9]/g, "");
|
||||||
|
|
||||||
if (filters.genres?.value?.include?.length) {
|
const novels: Plugin.NovelItem[] = [];
|
||||||
url += filters.genres.value.include
|
loadedCheerio(".media-card-wrap").each(function () {
|
||||||
.map((i) => "&genres[include][]=" + i).join("");
|
const name = loadedCheerio(this).find(".media-card__title").text();
|
||||||
}
|
const cover = loadedCheerio(this).find("a.media-card").attr("data-src");
|
||||||
|
const url = loadedCheerio(this).find("a.media-card").attr("href");
|
||||||
if (filters.genres?.value?.exclude?.length) {
|
if (!url) return;
|
||||||
url += filters.genres.value.exclude
|
novels.push({ name, cover, url });
|
||||||
.map((i) => "&genres[exclude][]=" + i).join("");
|
});
|
||||||
}
|
|
||||||
|
return novels;
|
||||||
if (filters.tags?.value?.include?.length) {
|
}
|
||||||
url += filters.tags.value.include
|
|
||||||
.map((i) => "&tags[include][]=" + i).join("");
|
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
||||||
}
|
const body = await fetchApi(novelUrl).then((res) => res.text());
|
||||||
|
const loadedCheerio = parseHTML(body);
|
||||||
if (filters.tags?.value?.exclude?.length) {
|
|
||||||
url += filters.tags.value.exclude
|
const novel: Plugin.SourceNovel = {
|
||||||
.map((i) => "&tags[exclude][]=" + i).join("");
|
url: novelUrl,
|
||||||
}
|
};
|
||||||
|
|
||||||
url += "&page=" + pageNo;
|
novel.name = loadedCheerio(".media-name__main").text().trim();
|
||||||
|
|
||||||
const result = await fetchApi(url);
|
novel.cover = loadedCheerio(".container_responsive img").attr("src");
|
||||||
const body = await result.text();
|
|
||||||
|
novel.summary = loadedCheerio(".media-description__text").text().trim();
|
||||||
const loadedCheerio = parseHTML(body);
|
|
||||||
this.ui = loadedCheerio("a.header-right-menu__item")
|
novel.genres = loadedCheerio('div[class="media-tags"]')
|
||||||
.attr("href")
|
.text()
|
||||||
?.replace?.(/[^0-9]/g, "");
|
.trim()
|
||||||
|
.replace(/[\n\r]+/g, ", ")
|
||||||
const novels: Plugin.NovelItem[] = [];
|
.replace(/ /g, "");
|
||||||
loadedCheerio(".media-card-wrap").each(function () {
|
|
||||||
const name = loadedCheerio(this).find(".media-card__title").text();
|
loadedCheerio(
|
||||||
const cover = loadedCheerio(this).find("a.media-card").attr("data-src");
|
'div[class="media-info-list paper"] > [class="media-info-list__item"]',
|
||||||
const url = loadedCheerio(this).find("a.media-card").attr("href");
|
).each(function () {
|
||||||
if (!url) return;
|
let name = loadedCheerio(this)
|
||||||
novels.push({ name, cover, url });
|
.find('div[class="media-info-list__title"]')
|
||||||
});
|
.text();
|
||||||
|
|
||||||
return novels;
|
if (name === "Автор") {
|
||||||
}
|
novel.author = loadedCheerio(this)
|
||||||
|
.find('div[class="media-info-list__value"]')
|
||||||
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
.text()
|
||||||
const result = await fetchApi(novelUrl);
|
.trim();
|
||||||
const body = await result.text();
|
} else if (name === "Художник") {
|
||||||
|
novel.artist = loadedCheerio(this)
|
||||||
const loadedCheerio = parseHTML(body);
|
.find('div[class="media-info-list__value"]')
|
||||||
|
.text()
|
||||||
const novel: Plugin.SourceNovel = {
|
.trim();
|
||||||
url: novelUrl,
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
novel.name = loadedCheerio(".media-name__main").text().trim();
|
const chapters: Plugin.ChapterItem[] = [];
|
||||||
|
|
||||||
novel.cover = loadedCheerio(".media-sidebar__cover img").attr("src");
|
let chaptersRaw: any = body.match(
|
||||||
|
/window.__DATA__ = [\s\S]*?window._SITE_COLOR_/gm,
|
||||||
novel.summary = loadedCheerio(".media-description__text").text().trim();
|
);
|
||||||
|
|
||||||
novel.genres = loadedCheerio('div[class="media-tags"]')
|
chaptersRaw = chaptersRaw?.[0]
|
||||||
.text()
|
?.replace("window.__DATA__ = ", "")
|
||||||
.trim()
|
?.replace("window._SITE_COLOR_", "")
|
||||||
.replace(/[\n\r]+/g, ", ")
|
?.trim()
|
||||||
.replace(/ /g, "");
|
?.slice(0, -1);
|
||||||
|
|
||||||
loadedCheerio(
|
const chaptersJson: responseBook = JSON.parse(chaptersRaw);
|
||||||
'div[class="media-info-list paper"] > [class="media-info-list__item"]',
|
const totalChapters = chaptersJson.chapters.list?.length || 0;
|
||||||
).each(function () {
|
novel.status = statusKey[chaptersJson.manga.status - 1] || NovelStatus.Unknown;
|
||||||
let name = loadedCheerio(this)
|
this.ui = chaptersJson?.user?.id;
|
||||||
.find('div[class="media-info-list__title"]')
|
|
||||||
.text();
|
chaptersJson.chapters?.list?.forEach((chapter, chapterIndex) =>
|
||||||
|
chapters.push({
|
||||||
if (name === "Статус перевода") {
|
name:
|
||||||
novel.status =
|
"Том " + chapter.chapter_volume +
|
||||||
loadedCheerio(this).find("div:nth-child(2)").text().trim() ===
|
" Глава " + chapter.chapter_number +
|
||||||
"Продолжается"
|
(chapter.chapter_name ? " " + chapter.chapter_name.trim() : ""),
|
||||||
? NovelStatus.Ongoing
|
url:
|
||||||
: NovelStatus.Completed;
|
this.site + "/" + chaptersJson.manga.slug +
|
||||||
} else if (name === "Автор") {
|
"/v" + chapter.chapter_volume + "/c" + chapter.chapter_number +
|
||||||
novel.author = loadedCheerio(this)
|
"?bid=" + (chapter.branch_id || ""),
|
||||||
.find("div:nth-child(2)")
|
releaseTime: dayjs(chapter.chapter_created_at).format("LLL"),
|
||||||
.text()
|
chapterNumber: totalChapters - chapterIndex,
|
||||||
.trim();
|
}),
|
||||||
}
|
);
|
||||||
});
|
|
||||||
|
novel.chapters = chapters.reverse();
|
||||||
const chapters: Plugin.ChapterItem[] = [];
|
return novel;
|
||||||
|
}
|
||||||
let chaptersRaw: any = body.match(
|
|
||||||
/window.__DATA__ = [\s\S]*?window._SITE_COLOR_/gm,
|
async parseChapter(chapterUrl: string): Promise<string> {
|
||||||
);
|
const result = await fetchApi(
|
||||||
|
chapterUrl + (this.ui ? "&ui=" + this.ui : ""),
|
||||||
chaptersRaw = chaptersRaw?.[0]
|
).then((res) => res.text());
|
||||||
?.replace("window.__DATA__ = ", "")
|
|
||||||
?.replace("window._SITE_COLOR_", "")
|
const loadedCheerio = parseHTML(result);
|
||||||
?.trim()
|
loadedCheerio(".reader-container img").each((index, element) => {
|
||||||
?.slice(0, -1);
|
const src =
|
||||||
|
loadedCheerio(element).attr("data-src") ||
|
||||||
let chaptersJson: responseBook = JSON.parse(chaptersRaw);
|
loadedCheerio(element).attr("src");
|
||||||
this.ui = chaptersJson?.user?.id;
|
if (!src?.startsWith("http")) {
|
||||||
|
loadedCheerio(element).attr("src", this.site + src);
|
||||||
chaptersJson.chapters?.list?.forEach((chapter) =>
|
} else {
|
||||||
chapters.push({
|
loadedCheerio(element).attr("src", src);
|
||||||
name: `Том ${chapter.chapter_volume} Глава ${chapter.chapter_number} ${chapter.chapter_name}`?.trim(),
|
}
|
||||||
releaseTime: dayjs(chapter.chapter_created_at).format("LLL"),
|
loadedCheerio(element).removeAttr("data-src");
|
||||||
url:
|
});
|
||||||
`${this.site}/${chaptersJson.manga.slug}/v${chapter.chapter_volume}/c${chapter.chapter_number}?bid=` +
|
|
||||||
(chapter?.branch_id || ""),
|
const chapterText = loadedCheerio(".reader-container").html();
|
||||||
}),
|
return chapterText || "";
|
||||||
);
|
}
|
||||||
|
|
||||||
novel.chapters = chapters.reverse();
|
async searchNovels(
|
||||||
return novel;
|
searchTerm: string,
|
||||||
}
|
//pageNo: number | undefined = 1,
|
||||||
|
): Promise<Plugin.NovelItem[]> {
|
||||||
async parseChapter(chapterUrl: string): Promise<string> {
|
const result = await fetchApi(
|
||||||
const result = await fetchApi(
|
`${this.site}/search?q=${searchTerm}&type=manga`,
|
||||||
chapterUrl + (this.ui ? "&ui=" + this.ui : ""),
|
);
|
||||||
);
|
const body = (await result.json()) as Manga[];
|
||||||
const body = await result.text();
|
const novels: Plugin.NovelItem[] = [];
|
||||||
|
|
||||||
const loadedCheerio = parseHTML(body);
|
body.forEach((novel) =>
|
||||||
|
novels.push({
|
||||||
const baseUrl = this.site;
|
name: novel?.rus_name || novel.name,
|
||||||
loadedCheerio(".reader-container img").each(function () {
|
cover: novel?.coverImage,
|
||||||
const src =
|
url: novel?.href || this.site + "/" + novel.slug,
|
||||||
loadedCheerio(this).attr("data-src") || loadedCheerio(this).attr("src");
|
}),
|
||||||
if (!src?.startsWith("http")) {
|
);
|
||||||
loadedCheerio(this).attr("src", baseUrl + src);
|
|
||||||
} else {
|
return novels;
|
||||||
loadedCheerio(this).attr("src", src);
|
}
|
||||||
}
|
|
||||||
loadedCheerio(this).removeAttr("data-src");
|
fetchImage = fetchFile;
|
||||||
});
|
|
||||||
|
filters = {
|
||||||
const chapterText = loadedCheerio(".reader-container").html();
|
sort: {
|
||||||
|
label: "Сортировка",
|
||||||
return chapterText || "";
|
value: "rate",
|
||||||
}
|
options: [
|
||||||
|
{ label: "Рейтинг", value: "rate" },
|
||||||
async searchNovels(
|
{ label: "Имя", value: "name" },
|
||||||
searchTerm: string,
|
{ label: "Просмотры", value: "views" },
|
||||||
//pageNo: number | undefined = 1,
|
{ label: "Дате добавления", value: "created_at" },
|
||||||
): Promise<Plugin.NovelItem[]> {
|
{ label: "Дате обновления", value: "last_chapter_at" },
|
||||||
const result = await fetchApi(
|
{ label: "Количество глав", value: "chap_count" },
|
||||||
`${this.site}/search?q=${searchTerm}&type=manga`,
|
],
|
||||||
);
|
type: FilterTypes.Picker,
|
||||||
const body = (await result.json()) as Manga[];
|
},
|
||||||
const novels: Plugin.NovelItem[] = [];
|
order: {
|
||||||
|
label: "Порядок",
|
||||||
body.forEach((novel) =>
|
value: "desc",
|
||||||
novels.push({
|
options: [
|
||||||
name: novel?.rus_name || novel.name,
|
{ label: "По убыванию", value: "desc" },
|
||||||
cover: novel?.coverImage,
|
{ label: "По возрастанию", value: "asc" },
|
||||||
url: novel?.href || this.site + "/" + novel.slug,
|
],
|
||||||
}),
|
type: FilterTypes.Picker,
|
||||||
);
|
},
|
||||||
|
type: {
|
||||||
return novels;
|
label: "Тип",
|
||||||
}
|
value: [],
|
||||||
|
options: [
|
||||||
fetchImage = fetchFile;
|
{ label: "Авторский", value: "14" },
|
||||||
|
{ label: "Английский", value: "13" },
|
||||||
filters = {
|
{ label: "Китай", value: "12" },
|
||||||
sort: {
|
{ label: "Корея", value: "11" },
|
||||||
label: "Сортировка",
|
{ label: "Фанфик", value: "15" },
|
||||||
value: "",
|
{ label: "Япония", value: "10" },
|
||||||
options: [
|
],
|
||||||
{ label: "Рейтинг", value: "rate" },
|
type: FilterTypes.CheckboxGroup,
|
||||||
{ label: "Имя", value: "name" },
|
},
|
||||||
{ label: "Просмотры", value: "views" },
|
format: {
|
||||||
{ label: "Дате добавления", value: "created_at" },
|
label: "Формат выпуска",
|
||||||
{ label: "Дате обновления", value: "last_chapter_at" },
|
value: { include: [], exclude: [] },
|
||||||
{ label: "Количество глав", value: "chap_count" },
|
options: [
|
||||||
],
|
{ label: "4-кома (Ёнкома)", value: "1" },
|
||||||
type: FilterTypes.Picker,
|
{ label: "В цвете", value: "4" },
|
||||||
},
|
{ label: "Веб", value: "6" },
|
||||||
order: {
|
{ label: "Вебтун", value: "7" },
|
||||||
label: "Порядок",
|
{ label: "Додзинси", value: "3" },
|
||||||
value: "",
|
{ label: "Сборник", value: "2" },
|
||||||
options: [
|
{ label: "Сингл", value: "5" },
|
||||||
{ label: "По убыванию", value: "desc" },
|
],
|
||||||
{ label: "По возрастанию", value: "asc" },
|
type: FilterTypes.ExcludableCheckboxGroup,
|
||||||
],
|
},
|
||||||
type: FilterTypes.Picker,
|
status: {
|
||||||
},
|
label: "Статус перевода",
|
||||||
type: {
|
value: [],
|
||||||
label: "Тип",
|
options: [
|
||||||
value: [],
|
{ label: "Продолжается", value: "1" },
|
||||||
options: [
|
{ label: "Завершен", value: "2" },
|
||||||
{ label: "Авторский", value: "14" },
|
{ label: "Заморожен", value: "3" },
|
||||||
{ label: "Английский", value: "13" },
|
{ label: "Заброшен", value: "4" },
|
||||||
{ label: "Китай", value: "12" },
|
],
|
||||||
{ label: "Корея", value: "11" },
|
type: FilterTypes.CheckboxGroup,
|
||||||
{ label: "Фанфик", value: "15" },
|
},
|
||||||
{ label: "Япония", value: "10" },
|
manga_status: {
|
||||||
],
|
label: "Статус тайтла",
|
||||||
type: FilterTypes.CheckboxGroup,
|
value: [],
|
||||||
},
|
options: [
|
||||||
format: {
|
{ label: "Онгоинг", value: "1" },
|
||||||
label: "Формат выпуска",
|
{ label: "Завершён", value: "2" },
|
||||||
value: { include: [], exclude: [] },
|
{ label: "Анонс", value: "3" },
|
||||||
options: [
|
{ label: "Приостановлен", value: "4" },
|
||||||
{ label: "4-кома (Ёнкома)", value: "1" },
|
{ label: "Выпуск прекращён", value: "5" },
|
||||||
{ label: "В цвете", value: "4" },
|
],
|
||||||
{ label: "Веб", value: "6" },
|
type: FilterTypes.CheckboxGroup,
|
||||||
{ label: "Вебтун", value: "7" },
|
},
|
||||||
{ label: "Додзинси", value: "3" },
|
genres: {
|
||||||
{ label: "Сборник", value: "2" },
|
label: "Жанры",
|
||||||
{ label: "Сингл", value: "5" },
|
value: { include: [], exclude: [] },
|
||||||
],
|
options: [
|
||||||
type: FilterTypes.ExcludableCheckboxGroup,
|
{ label: "Арт", value: "32" },
|
||||||
},
|
{ label: "Безумие", value: "91" },
|
||||||
status: {
|
{ label: "Боевик", value: "34" },
|
||||||
label: "Статус перевода",
|
{ label: "Боевые искусства", value: "35" },
|
||||||
value: [],
|
{ label: "Вампиры", value: "36" },
|
||||||
options: [
|
{ label: "Военное", value: "89" },
|
||||||
{ label: "Продолжается", value: "1" },
|
{ label: "Гарем", value: "37" },
|
||||||
{ label: "Завершен", value: "2" },
|
{ label: "Гендерная интрига", value: "38" },
|
||||||
{ label: "Заморожен", value: "3" },
|
{ label: "Героическое фэнтези", value: "39" },
|
||||||
{ label: "Заброшен", value: "4" },
|
{ label: "Демоны", value: "81" },
|
||||||
],
|
{ label: "Детектив", value: "40" },
|
||||||
type: FilterTypes.CheckboxGroup,
|
{ label: "Детское", value: "88" },
|
||||||
},
|
{ label: "Дзёсэй", value: "41" },
|
||||||
statuss: {
|
{ label: "Драма", value: "43" },
|
||||||
label: "Статус тайтла",
|
{ label: "Игра", value: "44" },
|
||||||
value: [],
|
{ label: "Исекай", value: "79" },
|
||||||
options: [
|
{ label: "История", value: "45" },
|
||||||
{ label: "Онгоинг", value: "1" },
|
{ label: "Киберпанк", value: "46" },
|
||||||
{ label: "Завершён", value: "2" },
|
{ label: "Кодомо", value: "76" },
|
||||||
{ label: "Анонс", value: "3" },
|
{ label: "Комедия", value: "47" },
|
||||||
{ label: "Приостановлен", value: "4" },
|
{ label: "Космос", value: "83" },
|
||||||
{ label: "Выпуск прекращён", value: "5" },
|
{ label: "Магия", value: "85" },
|
||||||
],
|
{ label: "Махо-сёдзё", value: "48" },
|
||||||
type: FilterTypes.CheckboxGroup,
|
{ label: "Машины", value: "90" },
|
||||||
},
|
{ label: "Меха", value: "49" },
|
||||||
genres: {
|
{ label: "Мистика", value: "50" },
|
||||||
label: "Жанры",
|
{ label: "Музыка", value: "80" },
|
||||||
value: { include: [], exclude: [] },
|
{ label: "Научная фантастика", value: "51" },
|
||||||
options: [
|
{ label: "Омегаверс", value: "77" },
|
||||||
{ label: "Арт", value: "32" },
|
{ label: "Пародия", value: "86" },
|
||||||
{ label: "Безумие", value: "91" },
|
{ label: "Повседневность", value: "52" },
|
||||||
{ label: "Боевик", value: "34" },
|
{ label: "Полиция", value: "82" },
|
||||||
{ label: "Боевые искусства", value: "35" },
|
{ label: "Постапокалиптика", value: "53" },
|
||||||
{ label: "Вампиры", value: "36" },
|
{ label: "Приключения", value: "54" },
|
||||||
{ label: "Военное", value: "89" },
|
{ label: "Психология", value: "55" },
|
||||||
{ label: "Гарем", value: "37" },
|
{ label: "Романтика", value: "56" },
|
||||||
{ label: "Гендерная интрига", value: "38" },
|
{ label: "Самурайский боевик", value: "57" },
|
||||||
{ label: "Героическое фэнтези", value: "39" },
|
{ label: "Сверхъестественное", value: "58" },
|
||||||
{ label: "Демоны", value: "81" },
|
{ label: "Сёдзё", value: "59" },
|
||||||
{ label: "Детектив", value: "40" },
|
{ label: "Сёдзё-ай", value: "60" },
|
||||||
{ label: "Детское", value: "88" },
|
{ label: "Сёнэн", value: "61" },
|
||||||
{ label: "Дзёсэй", value: "41" },
|
{ label: "Сёнэн-ай", value: "62" },
|
||||||
{ label: "Драма", value: "43" },
|
{ label: "Спорт", value: "63" },
|
||||||
{ label: "Игра", value: "44" },
|
{ label: "Супер сила", value: "87" },
|
||||||
{ label: "Исекай", value: "79" },
|
{ label: "Сэйнэн", value: "64" },
|
||||||
{ label: "История", value: "45" },
|
{ label: "Трагедия", value: "65" },
|
||||||
{ label: "Киберпанк", value: "46" },
|
{ label: "Триллер", value: "66" },
|
||||||
{ label: "Кодомо", value: "76" },
|
{ label: "Ужасы", value: "67" },
|
||||||
{ label: "Комедия", value: "47" },
|
{ label: "Фантастика", value: "68" },
|
||||||
{ label: "Космос", value: "83" },
|
{ label: "Фэнтези", value: "69" },
|
||||||
{ label: "Магия", value: "85" },
|
{ label: "Школа", value: "70" },
|
||||||
{ label: "Махо-сёдзё", value: "48" },
|
{ label: "Эротика", value: "71" },
|
||||||
{ label: "Машины", value: "90" },
|
{ label: "Этти", value: "72" },
|
||||||
{ label: "Меха", value: "49" },
|
{ label: "Юри", value: "73" },
|
||||||
{ label: "Мистика", value: "50" },
|
{ label: "Яой", value: "74" },
|
||||||
{ label: "Музыка", value: "80" },
|
],
|
||||||
{ label: "Научная фантастика", value: "51" },
|
type: FilterTypes.ExcludableCheckboxGroup,
|
||||||
{ label: "Омегаверс", value: "77" },
|
},
|
||||||
{ label: "Пародия", value: "86" },
|
tags: {
|
||||||
{ label: "Повседневность", value: "52" },
|
label: "Теги",
|
||||||
{ label: "Полиция", value: "82" },
|
value: { include: [], exclude: [] },
|
||||||
{ label: "Постапокалиптика", value: "53" },
|
options: [
|
||||||
{ label: "Приключения", value: "54" },
|
{ label: "Авантюристы", value: "328" },
|
||||||
{ label: "Психология", value: "55" },
|
{ label: "Антигерой", value: "176" },
|
||||||
{ label: "Романтика", value: "56" },
|
{ label: "Бессмертные", value: "333" },
|
||||||
{ label: "Самурайский боевик", value: "57" },
|
{ label: "Боги", value: "218" },
|
||||||
{ label: "Сверхъестественное", value: "58" },
|
{ label: "Борьба за власть", value: "309" },
|
||||||
{ label: "Сёдзё", value: "59" },
|
{ label: "Брат и сестра", value: "360" },
|
||||||
{ label: "Сёдзё-ай", value: "60" },
|
{ label: "Ведьма", value: "339" },
|
||||||
{ label: "Сёнэн", value: "61" },
|
{ label: "Видеоигры", value: "204" },
|
||||||
{ label: "Сёнэн-ай", value: "62" },
|
{ label: "Виртуальная реальность", value: "214" },
|
||||||
{ label: "Спорт", value: "63" },
|
{ label: "Владыка демонов", value: "349" },
|
||||||
{ label: "Супер сила", value: "87" },
|
{ label: "Военные", value: "198" },
|
||||||
{ label: "Сэйнэн", value: "64" },
|
{ label: "Воспоминания из другого мира", value: "310" },
|
||||||
{ label: "Трагедия", value: "65" },
|
{ label: "Выживание", value: "212" },
|
||||||
{ label: "Триллер", value: "66" },
|
{ label: "ГГ женщина", value: "294" },
|
||||||
{ label: "Ужасы", value: "67" },
|
{ label: "ГГ имба", value: "292" },
|
||||||
{ label: "Фантастика", value: "68" },
|
{ label: "ГГ мужчина", value: "295" },
|
||||||
{ label: "Фэнтези", value: "69" },
|
{ label: "ГГ не ояш", value: "325" },
|
||||||
{ label: "Школа", value: "70" },
|
{ label: "ГГ не человек", value: "331" },
|
||||||
{ label: "Эротика", value: "71" },
|
{ label: "ГГ ояш", value: "326" },
|
||||||
{ label: "Этти", value: "72" },
|
{ label: "Главный герой бог", value: "324" },
|
||||||
{ label: "Юри", value: "73" },
|
{ label: "Глупый ГГ", value: "298" },
|
||||||
{ label: "Яой", value: "74" },
|
{ label: "Горничные", value: "171" },
|
||||||
],
|
{ label: "Гуро", value: "306" },
|
||||||
type: FilterTypes.ExcludableCheckboxGroup,
|
{ label: "Гяру", value: "197" },
|
||||||
},
|
{ label: "Демоны", value: "157" },
|
||||||
tags: {
|
{ label: "Драконы", value: "313" },
|
||||||
label: "Теги",
|
{ label: "Древний мир", value: "317" },
|
||||||
value: { include: [], exclude: [] },
|
{ label: "Зверолюди", value: "163" },
|
||||||
options: [
|
{ label: "Зомби", value: "155" },
|
||||||
{ label: "Авантюристы", value: "328" },
|
{ label: "Исторические фигуры", value: "323" },
|
||||||
{ label: "Антигерой", value: "176" },
|
{ label: "Кулинария", value: "158" },
|
||||||
{ label: "Бессмертные", value: "333" },
|
{ label: "Культивация", value: "161" },
|
||||||
{ label: "Боги", value: "218" },
|
{ label: "ЛГБТ", value: "344" },
|
||||||
{ label: "Борьба за власть", value: "309" },
|
{ label: "ЛитРПГ", value: "319" },
|
||||||
{ label: "Брат и сестра", value: "360" },
|
{ label: "Лоли", value: "206" },
|
||||||
{ label: "Ведьма", value: "339" },
|
{ label: "Магия", value: "170" },
|
||||||
{ label: "Видеоигры", value: "204" },
|
{ label: "Машинный перевод", value: "345" },
|
||||||
{ label: "Виртуальная реальность", value: "214" },
|
{ label: "Медицина", value: "159" },
|
||||||
{ label: "Владыка демонов", value: "349" },
|
{ label: "Межгалактическая война", value: "330" },
|
||||||
{ label: "Военные", value: "198" },
|
{ label: "Монстр Девушки", value: "207" },
|
||||||
{ label: "Воспоминания из другого мира", value: "310" },
|
{ label: "Монстры", value: "208" },
|
||||||
{ label: "Выживание", value: "212" },
|
{ label: "Мрачный мир", value: "316" },
|
||||||
{ label: "ГГ женщина", value: "294" },
|
{ label: "Музыка", value: "358" },
|
||||||
{ label: "ГГ имба", value: "292" },
|
{ label: "Музыка", value: "209" },
|
||||||
{ label: "ГГ мужчина", value: "295" },
|
{ label: "Ниндзя", value: "199" },
|
||||||
{ label: "ГГ не ояш", value: "325" },
|
{ label: "Обратный Гарем", value: "210" },
|
||||||
{ label: "ГГ не человек", value: "331" },
|
{ label: "Офисные Работники", value: "200" },
|
||||||
{ label: "ГГ ояш", value: "326" },
|
{ label: "Пираты", value: "341" },
|
||||||
{ label: "Главный герой бог", value: "324" },
|
{ label: "Подземелья", value: "314" },
|
||||||
{ label: "Глупый ГГ", value: "298" },
|
{ label: "Политика", value: "311" },
|
||||||
{ label: "Горничные", value: "171" },
|
{ label: "Полиция", value: "201" },
|
||||||
{ label: "Гуро", value: "306" },
|
{ label: "Преступники / Криминал", value: "205" },
|
||||||
{ label: "Гяру", value: "197" },
|
{ label: "Призраки / Духи", value: "196" },
|
||||||
{ label: "Демоны", value: "157" },
|
{ label: "Призыватели", value: "329" },
|
||||||
{ label: "Драконы", value: "313" },
|
{ label: "Прыжки между мирами", value: "321" },
|
||||||
{ label: "Древний мир", value: "317" },
|
{ label: "Путешествие в другой мир", value: "318" },
|
||||||
{ label: "Зверолюди", value: "163" },
|
{ label: "Путешествие во времени", value: "213" },
|
||||||
{ label: "Зомби", value: "155" },
|
{ label: "Рабы", value: "355" },
|
||||||
{ label: "Исторические фигуры", value: "323" },
|
{ label: "Ранги силы", value: "312" },
|
||||||
{ label: "Кулинария", value: "158" },
|
{ label: "Реинкарнация", value: "154" },
|
||||||
{ label: "Культивация", value: "161" },
|
{ label: "Самураи", value: "202" },
|
||||||
{ label: "ЛГБТ", value: "344" },
|
{ label: "Скрытие личности", value: "315" },
|
||||||
{ label: "ЛитРПГ", value: "319" },
|
{ label: "Средневековье", value: "174" },
|
||||||
{ label: "Лоли", value: "206" },
|
{ label: "Традиционные игры", value: "203" },
|
||||||
{ label: "Магия", value: "170" },
|
{ label: "Умный ГГ", value: "303" },
|
||||||
{ label: "Машинный перевод", value: "345" },
|
{ label: "Характерный рост", value: "332" },
|
||||||
{ label: "Медицина", value: "159" },
|
{ label: "Хикикомори", value: "167" },
|
||||||
{ label: "Межгалактическая война", value: "330" },
|
{ label: "Эволюция", value: "322" },
|
||||||
{ label: "Монстр Девушки", value: "207" },
|
{ label: "Элементы РПГ", value: "327" },
|
||||||
{ label: "Монстры", value: "208" },
|
{ label: "Эльфы", value: "217" },
|
||||||
{ label: "Мрачный мир", value: "316" },
|
{ label: "Якудза", value: "165" },
|
||||||
{ label: "Музыка", value: "358" },
|
],
|
||||||
{ label: "Музыка", value: "209" },
|
type: FilterTypes.ExcludableCheckboxGroup,
|
||||||
{ label: "Ниндзя", value: "199" },
|
},
|
||||||
{ label: "Обратный Гарем", value: "210" },
|
} satisfies Filters;
|
||||||
{ label: "Офисные Работники", value: "200" },
|
}
|
||||||
{ label: "Пираты", value: "341" },
|
|
||||||
{ label: "Подземелья", value: "314" },
|
export default new RLIB();
|
||||||
{ label: "Политика", value: "311" },
|
|
||||||
{ label: "Полиция", value: "201" },
|
interface responseBook {
|
||||||
{ label: "Преступники / Криминал", value: "205" },
|
hasStickyPermission: boolean;
|
||||||
{ label: "Призраки / Духи", value: "196" },
|
bookmark?: null;
|
||||||
{ label: "Призыватели", value: "329" },
|
auth: boolean;
|
||||||
{ label: "Прыжки между мирами", value: "321" },
|
comments_version: string;
|
||||||
{ label: "Путешествие в другой мир", value: "318" },
|
manga: Manga;
|
||||||
{ label: "Путешествие во времени", value: "213" },
|
chapters: Chapters;
|
||||||
{ label: "Рабы", value: "355" },
|
user?: User;
|
||||||
{ label: "Ранги силы", value: "312" },
|
}
|
||||||
{ label: "Реинкарнация", value: "154" },
|
interface Manga {
|
||||||
{ label: "Самураи", value: "202" },
|
id: number;
|
||||||
{ label: "Скрытие личности", value: "315" },
|
name: string;
|
||||||
{ label: "Средневековье", value: "174" },
|
rusName?: string;
|
||||||
{ label: "Традиционные игры", value: "203" },
|
rus_name?: string;
|
||||||
{ label: "Умный ГГ", value: "303" },
|
engName?: string;
|
||||||
{ label: "Характерный рост", value: "332" },
|
slug: string;
|
||||||
{ label: "Хикикомори", value: "167" },
|
status: number;
|
||||||
{ label: "Эволюция", value: "322" },
|
chapters_count: number;
|
||||||
{ label: "Элементы РПГ", value: "327" },
|
altNames?: string[] | null;
|
||||||
{ label: "Эльфы", value: "217" },
|
coverImage?: string;
|
||||||
{ label: "Якудза", value: "165" },
|
href?: string;
|
||||||
],
|
}
|
||||||
type: FilterTypes.ExcludableCheckboxGroup,
|
interface Chapters {
|
||||||
},
|
list?: ListEntity[] | null;
|
||||||
} satisfies Filters;
|
teams?: TeamsEntity[] | null;
|
||||||
}
|
branches?: BranchesEntity[] | null;
|
||||||
|
is_paid?: null[] | null;
|
||||||
export default new RLIB();
|
}
|
||||||
|
interface ListEntity {
|
||||||
interface responseBook {
|
chapter_id: number;
|
||||||
hasStickyPermission: boolean;
|
chapter_slug: string;
|
||||||
bookmark?: null;
|
chapter_name: string;
|
||||||
auth: boolean;
|
chapter_number: string;
|
||||||
comments_version: string;
|
chapter_volume: number;
|
||||||
manga: Manga;
|
chapter_moderated: number;
|
||||||
chapters: Chapters;
|
chapter_user_id: number;
|
||||||
user?: User;
|
chapter_expired_at: string;
|
||||||
}
|
chapter_scanlator_id: number;
|
||||||
interface Manga {
|
chapter_created_at: string;
|
||||||
id: number;
|
status?: null;
|
||||||
name: string;
|
price: number;
|
||||||
rusName?: string;
|
branch_id?: number;
|
||||||
rus_name?: string;
|
username: string;
|
||||||
engName?: string;
|
}
|
||||||
slug: string;
|
interface TeamsEntity {
|
||||||
status: number;
|
name: string;
|
||||||
chapters_count: number;
|
alt_name: string;
|
||||||
altNames?: string[] | null;
|
cover: string;
|
||||||
coverImage?: string;
|
slug: string;
|
||||||
href?: string;
|
id: number;
|
||||||
}
|
branch_id: number;
|
||||||
interface Chapters {
|
sale: number;
|
||||||
list?: ListEntity[] | null;
|
href: string;
|
||||||
teams?: TeamsEntity[] | null;
|
pivot: Pivot;
|
||||||
branches?: BranchesEntity[] | null;
|
}
|
||||||
is_paid?: null[] | null;
|
interface Pivot {
|
||||||
}
|
manga_id: number;
|
||||||
interface ListEntity {
|
team_id: number;
|
||||||
chapter_id: number;
|
}
|
||||||
chapter_slug: string;
|
interface BranchesEntity {
|
||||||
chapter_name: string;
|
id: number;
|
||||||
chapter_number: string;
|
manga_id: number;
|
||||||
chapter_volume: number;
|
name: string;
|
||||||
chapter_moderated: number;
|
teams?: TeamsEntity1[] | null;
|
||||||
chapter_user_id: number;
|
is_subscribed: boolean;
|
||||||
chapter_expired_at: string;
|
}
|
||||||
chapter_scanlator_id: number;
|
interface TeamsEntity1 {
|
||||||
chapter_created_at: string;
|
id: number;
|
||||||
status?: null;
|
name: string;
|
||||||
price: number;
|
slug: string;
|
||||||
branch_id: number;
|
cover: string;
|
||||||
username: string;
|
branch_id: number;
|
||||||
}
|
is_active: number;
|
||||||
interface TeamsEntity {
|
}
|
||||||
name: string;
|
interface User {
|
||||||
alt_name: string;
|
id: string;
|
||||||
cover: string;
|
avatar: string;
|
||||||
slug: string;
|
access: boolean;
|
||||||
id: number;
|
isAdmin: boolean;
|
||||||
branch_id: number;
|
paid: boolean;
|
||||||
sale: number;
|
}
|
||||||
href: string;
|
|
||||||
pivot: Pivot;
|
|
||||||
}
|
|
||||||
interface Pivot {
|
|
||||||
manga_id: number;
|
|
||||||
team_id: number;
|
|
||||||
}
|
|
||||||
interface BranchesEntity {
|
|
||||||
id: number;
|
|
||||||
manga_id: number;
|
|
||||||
name: string;
|
|
||||||
teams?: TeamsEntity1[] | null;
|
|
||||||
is_subscribed: boolean;
|
|
||||||
}
|
|
||||||
interface TeamsEntity1 {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
cover: string;
|
|
||||||
branch_id: number;
|
|
||||||
is_active: number;
|
|
||||||
}
|
|
||||||
interface User {
|
|
||||||
id: number;
|
|
||||||
avatar: string;
|
|
||||||
access: boolean;
|
|
||||||
isAdmin: boolean;
|
|
||||||
paid: boolean;
|
|
||||||
|
|||||||
@@ -1,408 +1,406 @@
|
|||||||
import { Plugin } from "@typings/plugin";
|
import { Plugin } from "@typings/plugin";
|
||||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||||
import { defaultCover } from "@libs/defaultCover";
|
import { defaultCover } from "@libs/defaultCover";
|
||||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||||
import { NovelStatus } from "@libs/novelStatus";
|
import { NovelStatus } from "@libs/novelStatus";
|
||||||
import { load as parseHTML } from "cheerio";
|
import { load as parseHTML } from "cheerio";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
class RNRF implements Plugin.PluginBase {
|
class RNRF implements Plugin.PluginBase {
|
||||||
id = "RNRF";
|
id = "RNRF";
|
||||||
name = "РанобэРФ";
|
name = "РанобэРФ";
|
||||||
site = "https://ранобэ.рф";
|
site = "https://ранобэ.рф";
|
||||||
version = "1.0.0";
|
version = "1.0.0";
|
||||||
icon = "src/ru/ranoberf/icon.png";
|
icon = "src/ru/ranoberf/icon.png";
|
||||||
|
|
||||||
|
async popularNovels(
|
||||||
|
pageNo: number,
|
||||||
async popularNovels(
|
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
|
||||||
pageNo: number,
|
): Promise<Plugin.NovelItem[]> {
|
||||||
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
|
let url = this.site + "/books?order=";
|
||||||
): Promise<Plugin.NovelItem[]> {
|
url += showLatestNovels
|
||||||
let url = this.site + "/books?order=";
|
? "lastPublishedChapter"
|
||||||
url += showLatestNovels
|
: filters?.sort?.value || "popular";
|
||||||
? "lastPublishedChapter"
|
url += "&page=" + pageNo;
|
||||||
: filters?.sort?.value || "popular";
|
|
||||||
url += "&page=" + pageNo;
|
const result = await fetchApi(url);
|
||||||
|
const body = await result.text();
|
||||||
const result = await fetchApi(url);
|
|
||||||
const body = await result.text();
|
const loadedCheerio = parseHTML(body);
|
||||||
|
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
|
||||||
const loadedCheerio = parseHTML(body);
|
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) =>
|
||||||
const novels: Plugin.NovelItem[] = [];
|
novels.push({
|
||||||
json.props.pageProps?.totalData?.items?.forEach((novel) =>
|
name: novel.title,
|
||||||
novels.push({
|
cover: novel?.verticalImage?.url
|
||||||
name: novel.title,
|
? this.site + novel.verticalImage.url
|
||||||
cover: novel?.verticalImage?.url
|
: defaultCover,
|
||||||
? this.site + novel.verticalImage.url
|
url: this.site + "/" + novel.slug,
|
||||||
: defaultCover,
|
}),
|
||||||
url: this.site + "/" + novel.slug,
|
);
|
||||||
}),
|
|
||||||
);
|
return novels;
|
||||||
|
}
|
||||||
return novels;
|
|
||||||
}
|
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
||||||
|
const result = await fetchApi(novelUrl);
|
||||||
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
const body = await result.text();
|
||||||
const result = await fetchApi(novelUrl);
|
|
||||||
const body = await result.text();
|
const loadedCheerio = parseHTML(body);
|
||||||
|
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
|
||||||
const loadedCheerio = parseHTML(body);
|
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 = {
|
||||||
|
url: novelUrl,
|
||||||
const novel: Plugin.SourceNovel = {
|
name: book?.title,
|
||||||
url: novelUrl,
|
cover: book?.verticalImage?.url
|
||||||
name: book?.title,
|
? this.site + book.verticalImage.url
|
||||||
cover: book?.verticalImage?.url
|
: defaultCover,
|
||||||
? this.site + book.verticalImage.url
|
summary: book?.description,
|
||||||
: defaultCover,
|
author: book?.author || "",
|
||||||
summary: book?.description,
|
genres: book?.genres.map((item) => item.title).join(", "),
|
||||||
author: book?.author || "",
|
status: book?.additionalInfo.includes("Активен")
|
||||||
genres: book?.genres.map((item) => item.title).join(", "),
|
? NovelStatus.Ongoing
|
||||||
status: book?.additionalInfo.includes("Активен")
|
: NovelStatus.Completed,
|
||||||
? NovelStatus.Ongoing
|
};
|
||||||
: NovelStatus.Completed,
|
|
||||||
};
|
const chapters: Plugin.ChapterItem[] = [];
|
||||||
|
|
||||||
const chapters: Plugin.ChapterItem[] = [];
|
book?.chapters.forEach((chapter, chapterIndex) => {
|
||||||
|
if (!chapter.isDonate || chapter.isUserPaid) {
|
||||||
book?.chapters?.forEach((chapter) => {
|
chapters.push({
|
||||||
if (!chapter.isDonate || chapter.isUserPaid) {
|
name: chapter.title,
|
||||||
chapters.push({
|
url: this.site + chapter.url,
|
||||||
name: chapter.title,
|
releaseTime: dayjs(chapter.publishedAt).format("LLL"),
|
||||||
releaseTime: dayjs(chapter.publishedAt).format("LLL"),
|
chapterNumber: book.chapters.length - chapterIndex,
|
||||||
url: this.site + chapter.url,
|
});
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
novel.chapters = chapters.reverse();
|
||||||
novel.chapters = chapters.reverse();
|
return novel;
|
||||||
return novel;
|
}
|
||||||
}
|
|
||||||
|
async parseChapter(chapterUrl: string): Promise<string> {
|
||||||
async parseChapter(chapterUrl: string): Promise<string> {
|
const result = await fetchApi(chapterUrl).then((res) => res.text());
|
||||||
const result = await fetchApi(chapterUrl);
|
|
||||||
const body = await result.text();
|
let loadedCheerio = parseHTML(result);
|
||||||
|
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
|
||||||
let loadedCheerio = parseHTML(body);
|
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 || "",
|
||||||
loadedCheerio = parseHTML(
|
);
|
||||||
json.props.pageProps?.chapter?.content?.text || "",
|
|
||||||
);
|
loadedCheerio("img").each((index, element) => {
|
||||||
|
if (!loadedCheerio(element).attr("src")?.startsWith("http")) {
|
||||||
const baseUrl = this.site;
|
const src = loadedCheerio(element).attr("src");
|
||||||
loadedCheerio("img").each(function () {
|
loadedCheerio(element).attr("src", this.site + src);
|
||||||
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;
|
||||||
|
}
|
||||||
const chapterText = loadedCheerio.html();
|
|
||||||
return chapterText;
|
async searchNovels(
|
||||||
}
|
searchTerm: string,
|
||||||
|
//pageNo: number | undefined = 1,
|
||||||
async searchNovels(
|
): Promise<Plugin.NovelItem[]> {
|
||||||
searchTerm: string,
|
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`;
|
||||||
//pageNo: number | undefined = 1,
|
const result = await fetchApi(url);
|
||||||
): Promise<Plugin.NovelItem[]> {
|
const body = (await result.json()) as { items: Item[] };
|
||||||
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 novels: Plugin.NovelItem[] = [];
|
||||||
const result = await fetchApi(url);
|
|
||||||
const body = (await result.json()) as { items: Item[] };
|
body.items.forEach((novel) =>
|
||||||
const novels: Plugin.NovelItem[] = [];
|
novels.push({
|
||||||
|
name: novel.title,
|
||||||
body.items.forEach((novel) =>
|
cover: novel?.verticalImage?.url
|
||||||
novels.push({
|
? this.site + novel.verticalImage.url
|
||||||
name: novel.title,
|
: defaultCover,
|
||||||
cover: novel?.verticalImage?.url
|
url: this.site + "/" + novel.slug,
|
||||||
? this.site + novel.verticalImage.url
|
}),
|
||||||
: defaultCover,
|
);
|
||||||
url: this.site + "/" + novel.slug,
|
|
||||||
}),
|
return novels;
|
||||||
);
|
}
|
||||||
|
fetchImage = fetchFile;
|
||||||
return novels;
|
|
||||||
}
|
filters = {
|
||||||
fetchImage = fetchFile;
|
sort: {
|
||||||
|
label: "Сортировка",
|
||||||
filters = {
|
value: "popular",
|
||||||
sort: {
|
options: [
|
||||||
label: "Сортировка",
|
{ label: "Рейтинг", value: "popular" },
|
||||||
value: "",
|
{ label: "Дате добавления", value: "new" },
|
||||||
options: [
|
{ label: "Дате обновления", value: "lastPublishedChapter" },
|
||||||
{ label: "Рейтинг", value: "popular" },
|
{ label: "Законченные", value: "completed" },
|
||||||
{ label: "Дате добавления", value: "new" },
|
],
|
||||||
{ label: "Дате обновления", value: "lastPublishedChapter" },
|
type: FilterTypes.Picker,
|
||||||
{ label: "Законченные", value: "completed" },
|
},
|
||||||
],
|
} satisfies Filters;
|
||||||
type: FilterTypes.Picker,
|
}
|
||||||
},
|
export default new RNRF();
|
||||||
} satisfies Filters;
|
|
||||||
}
|
interface response {
|
||||||
export default new RNRF();
|
props: Props;
|
||||||
|
page: string;
|
||||||
interface response {
|
query: Query;
|
||||||
props: Props;
|
buildId: string;
|
||||||
page: string;
|
isFallback: boolean;
|
||||||
query: Query;
|
gssp: boolean;
|
||||||
buildId: string;
|
appGip: boolean;
|
||||||
isFallback: boolean;
|
dynamicIds?: string[];
|
||||||
gssp: boolean;
|
}
|
||||||
appGip: boolean;
|
|
||||||
dynamicIds?: string[];
|
interface Props {
|
||||||
}
|
pageProps: PageProps;
|
||||||
|
isMobile: boolean;
|
||||||
interface Props {
|
theme: string;
|
||||||
pageProps: PageProps;
|
__N_SSP: boolean;
|
||||||
isMobile: boolean;
|
}
|
||||||
theme: string;
|
|
||||||
__N_SSP: boolean;
|
interface PageProps {
|
||||||
}
|
initialReduxState: InitialReduxState;
|
||||||
|
book?: PagePropsBook;
|
||||||
interface PageProps {
|
totalData?: TotalData;
|
||||||
initialReduxState: InitialReduxState;
|
layoutViewCookies?: string;
|
||||||
book?: PagePropsBook;
|
displayBanner?: boolean;
|
||||||
totalData?: TotalData;
|
chapter?: PagePropsChapter;
|
||||||
layoutViewCookies?: string;
|
}
|
||||||
displayBanner?: boolean;
|
|
||||||
chapter?: PagePropsChapter;
|
interface PagePropsBook {
|
||||||
}
|
id: number;
|
||||||
|
countryId: number;
|
||||||
interface PagePropsBook {
|
creatorId: number;
|
||||||
id: number;
|
lastChapterId: number;
|
||||||
countryId: number;
|
sourceId: number;
|
||||||
creatorId: number;
|
imgVerticalId: number;
|
||||||
lastChapterId: number;
|
imgHorizontalId: number;
|
||||||
sourceId: number;
|
imgSquareId: number;
|
||||||
imgVerticalId: number;
|
imageVerticalId: number;
|
||||||
imgHorizontalId: number;
|
imageHorizontalId: number;
|
||||||
imgSquareId: number;
|
title: string;
|
||||||
imageVerticalId: number;
|
titleEn: string;
|
||||||
imageHorizontalId: number;
|
fullTitle: string;
|
||||||
title: string;
|
fullTitleEn: string;
|
||||||
titleEn: string;
|
description: string;
|
||||||
fullTitle: string;
|
slug: string;
|
||||||
fullTitleEn: string;
|
author: string;
|
||||||
description: string;
|
additionalInfo: string;
|
||||||
slug: string;
|
importantInfo: string;
|
||||||
author: string;
|
status: string;
|
||||||
additionalInfo: string;
|
views: number;
|
||||||
importantInfo: string;
|
likes: number;
|
||||||
status: string;
|
dislikes: number;
|
||||||
views: number;
|
intervalSec: number;
|
||||||
likes: number;
|
intervalForNextFreeChapterSec: number;
|
||||||
dislikes: number;
|
chapterCost: number;
|
||||||
intervalSec: number;
|
chapterPrice: number;
|
||||||
intervalForNextFreeChapterSec: number;
|
chapterPriceCollected: number;
|
||||||
chapterCost: number;
|
maxSubscriptionChapters: number;
|
||||||
chapterPrice: number;
|
maxDonateChapters: number;
|
||||||
chapterPriceCollected: number;
|
isDisplayOnMainPage: boolean;
|
||||||
maxSubscriptionChapters: number;
|
withoutSchedule: boolean;
|
||||||
maxDonateChapters: number;
|
nextFreeChapterPublishedAt: null;
|
||||||
isDisplayOnMainPage: boolean;
|
createdAt: Date;
|
||||||
withoutSchedule: boolean;
|
updatedAt: Date;
|
||||||
nextFreeChapterPublishedAt: null;
|
userRating: null;
|
||||||
createdAt: Date;
|
userBookmark: null;
|
||||||
updatedAt: Date;
|
country: Country;
|
||||||
userRating: null;
|
genres: Genre[];
|
||||||
userBookmark: null;
|
timer: Timer;
|
||||||
country: Country;
|
chapters: ChapterElement[];
|
||||||
genres: Genre[];
|
verticalImage: Image;
|
||||||
timer: Timer;
|
horizontalImage: Image;
|
||||||
chapters: ChapterElement[];
|
squareImage: Image;
|
||||||
verticalImage: Image;
|
}
|
||||||
horizontalImage: Image;
|
|
||||||
squareImage: Image;
|
interface ChapterElement {
|
||||||
}
|
id: number;
|
||||||
|
bookId: number;
|
||||||
interface ChapterElement {
|
title: string;
|
||||||
id: number;
|
url: string;
|
||||||
bookId: number;
|
numberChapter: string;
|
||||||
title: string;
|
tom: number | null;
|
||||||
url: string;
|
chapterShortNumber: number | null;
|
||||||
numberChapter: string;
|
isDonate: boolean;
|
||||||
tom: number | null;
|
isSponsored: boolean;
|
||||||
chapterShortNumber: number | null;
|
isSubscription: boolean;
|
||||||
isDonate: boolean;
|
isEdited: boolean;
|
||||||
isSponsored: boolean;
|
publishedAt: Date;
|
||||||
isSubscription: boolean;
|
price: number;
|
||||||
isEdited: boolean;
|
views: number;
|
||||||
publishedAt: Date;
|
isUserPaid?: boolean;
|
||||||
price: number;
|
}
|
||||||
views: number;
|
|
||||||
isUserPaid?: boolean;
|
interface Country {
|
||||||
}
|
id: number;
|
||||||
|
title: string;
|
||||||
interface Country {
|
code: string;
|
||||||
id: number;
|
}
|
||||||
title: string;
|
|
||||||
code: string;
|
interface Genre {
|
||||||
}
|
id: number;
|
||||||
|
title: string;
|
||||||
interface Genre {
|
slug: string;
|
||||||
id: number;
|
}
|
||||||
title: string;
|
|
||||||
slug: string;
|
interface Image {
|
||||||
}
|
id: number;
|
||||||
|
alt: string;
|
||||||
interface Image {
|
path: string;
|
||||||
id: number;
|
name: string;
|
||||||
alt: string;
|
url: string;
|
||||||
path: string;
|
}
|
||||||
name: string;
|
|
||||||
url: string;
|
interface Timer {
|
||||||
}
|
value: null;
|
||||||
|
for: null;
|
||||||
interface Timer {
|
message: string;
|
||||||
value: null;
|
}
|
||||||
for: null;
|
|
||||||
message: string;
|
interface PagePropsChapter {
|
||||||
}
|
id: number;
|
||||||
|
bookId: number;
|
||||||
interface PagePropsChapter {
|
editorId: null;
|
||||||
id: number;
|
translatorId: number;
|
||||||
bookId: number;
|
parserChapterId: null;
|
||||||
editorId: null;
|
title: string;
|
||||||
translatorId: number;
|
slug: string;
|
||||||
parserChapterId: null;
|
status: string;
|
||||||
title: string;
|
tom: null;
|
||||||
slug: string;
|
views: number;
|
||||||
status: string;
|
isSponsored: boolean;
|
||||||
tom: null;
|
isSubscription: boolean;
|
||||||
views: number;
|
isDonate: boolean;
|
||||||
isSponsored: boolean;
|
isIndexing: boolean;
|
||||||
isSubscription: boolean;
|
publishedAt: Date;
|
||||||
isDonate: boolean;
|
changeAvailabilityAt: null;
|
||||||
isIndexing: boolean;
|
createdAt: Date;
|
||||||
publishedAt: Date;
|
updatedAt: Date;
|
||||||
changeAvailabilityAt: null;
|
numberChapter: string;
|
||||||
createdAt: Date;
|
price: number;
|
||||||
updatedAt: Date;
|
content: Content;
|
||||||
numberChapter: string;
|
book: ChapterBook;
|
||||||
price: number;
|
userBookmark: null;
|
||||||
content: Content;
|
isUserPaid: boolean;
|
||||||
book: ChapterBook;
|
nextChapter: null;
|
||||||
userBookmark: null;
|
previousChapter: PreviousChapter;
|
||||||
isUserPaid: boolean;
|
corrections: null;
|
||||||
nextChapter: null;
|
}
|
||||||
previousChapter: PreviousChapter;
|
|
||||||
corrections: null;
|
interface ChapterBook {
|
||||||
}
|
id: number;
|
||||||
|
title: string;
|
||||||
interface ChapterBook {
|
titleEn: string;
|
||||||
id: number;
|
url: string;
|
||||||
title: string;
|
}
|
||||||
titleEn: string;
|
|
||||||
url: string;
|
interface Content {
|
||||||
}
|
id: number;
|
||||||
|
text: string;
|
||||||
interface Content {
|
symbolsCount: number;
|
||||||
id: number;
|
}
|
||||||
text: string;
|
|
||||||
symbolsCount: number;
|
interface PreviousChapter {
|
||||||
}
|
id: number;
|
||||||
|
title: string;
|
||||||
interface PreviousChapter {
|
views: number;
|
||||||
id: number;
|
publishedAt: Date;
|
||||||
title: string;
|
url: string;
|
||||||
views: number;
|
numberChapter: string;
|
||||||
publishedAt: Date;
|
}
|
||||||
url: string;
|
|
||||||
numberChapter: string;
|
interface InitialReduxState {
|
||||||
}
|
data?: Data;
|
||||||
|
auth: Auth;
|
||||||
interface InitialReduxState {
|
banners?: Banners;
|
||||||
data?: Data;
|
corrections?: Corrections;
|
||||||
auth: Auth;
|
readerSettings?: ReaderSettings;
|
||||||
banners?: Banners;
|
}
|
||||||
corrections?: Corrections;
|
|
||||||
readerSettings?: ReaderSettings;
|
interface Auth {
|
||||||
}
|
isAuth: boolean;
|
||||||
|
user: null;
|
||||||
interface Auth {
|
}
|
||||||
isAuth: boolean;
|
|
||||||
user: null;
|
interface Banners {
|
||||||
}
|
displaySale: boolean;
|
||||||
|
}
|
||||||
interface Banners {
|
|
||||||
displaySale: boolean;
|
interface Corrections {
|
||||||
}
|
items: null;
|
||||||
|
data: null;
|
||||||
interface Corrections {
|
}
|
||||||
items: null;
|
|
||||||
data: null;
|
interface Data {
|
||||||
}
|
bookPage: BookPage | null;
|
||||||
|
chapterPage: ChapterPage | null;
|
||||||
interface Data {
|
}
|
||||||
bookPage: BookPage | null;
|
|
||||||
chapterPage: ChapterPage | null;
|
interface BookPage {
|
||||||
}
|
bookId: number;
|
||||||
|
bookmark: null;
|
||||||
interface BookPage {
|
}
|
||||||
bookId: number;
|
|
||||||
bookmark: null;
|
interface ChapterPage {
|
||||||
}
|
chapterId: number;
|
||||||
|
bookId: number;
|
||||||
interface ChapterPage {
|
bookmark: null;
|
||||||
chapterId: number;
|
}
|
||||||
bookId: number;
|
|
||||||
bookmark: null;
|
interface ReaderSettings {
|
||||||
}
|
fontSize: number;
|
||||||
|
lineHeight: number;
|
||||||
interface ReaderSettings {
|
textIndent: number;
|
||||||
fontSize: number;
|
sidebarHide: number;
|
||||||
lineHeight: number;
|
textAlign: string;
|
||||||
textIndent: number;
|
}
|
||||||
sidebarHide: number;
|
|
||||||
textAlign: string;
|
interface TotalData {
|
||||||
}
|
items: Item[];
|
||||||
|
_links: Links;
|
||||||
interface TotalData {
|
pagesData: PagesData;
|
||||||
items: Item[];
|
}
|
||||||
_links: Links;
|
|
||||||
pagesData: PagesData;
|
interface Links {
|
||||||
}
|
self: First;
|
||||||
|
first: First;
|
||||||
interface Links {
|
last: First;
|
||||||
self: First;
|
next: First;
|
||||||
first: First;
|
}
|
||||||
last: First;
|
|
||||||
next: First;
|
interface First {
|
||||||
}
|
href: string;
|
||||||
|
}
|
||||||
interface First {
|
|
||||||
href: string;
|
interface Item {
|
||||||
}
|
id: number;
|
||||||
|
title: string;
|
||||||
interface Item {
|
description: string;
|
||||||
id: number;
|
slug: string;
|
||||||
title: string;
|
url: string;
|
||||||
description: string;
|
status: string;
|
||||||
slug: string;
|
dislikes: number;
|
||||||
url: string;
|
likes: number;
|
||||||
status: string;
|
userRating: null;
|
||||||
dislikes: number;
|
lastChapter: ChapterElement;
|
||||||
likes: number;
|
verticalImage: Image;
|
||||||
userRating: null;
|
horizontalImage: Image;
|
||||||
lastChapter: ChapterElement;
|
squareImage: Image;
|
||||||
verticalImage: Image;
|
}
|
||||||
horizontalImage: Image;
|
|
||||||
squareImage: Image;
|
interface PagesData {
|
||||||
}
|
totalCount: number;
|
||||||
|
pageCount: number;
|
||||||
interface PagesData {
|
currentPage: number;
|
||||||
totalCount: number;
|
perPage: number;
|
||||||
pageCount: number;
|
}
|
||||||
currentPage: number;
|
|
||||||
perPage: number;
|
interface Query {
|
||||||
}
|
book?: string;
|
||||||
|
chapter?: string;
|
||||||
interface Query {
|
}
|
||||||
book?: string;
|
|
||||||
chapter?: string;
|
|
||||||
|
|||||||
@@ -1,371 +1,286 @@
|
|||||||
import { load as parseHTML } from "cheerio";
|
import { load as parseHTML } from "cheerio";
|
||||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||||
import { Plugin } from "@typings/plugin";
|
import { Plugin } from "@typings/plugin";
|
||||||
import { defaultCover } from "@libs/defaultCover";
|
import { defaultCover } from "@libs/defaultCover";
|
||||||
import { NovelStatus } from "@libs/novelStatus";
|
import { NovelStatus } from "@libs/novelStatus";
|
||||||
|
|
||||||
class TuNovelaLigera implements Plugin.PluginBase {
|
class TuNovelaLigera implements Plugin.PluginBase {
|
||||||
id = "tunovelaligera";
|
id = "tunovelaligera";
|
||||||
name = "TuNovelaLigera";
|
name = "TuNovelaLigera";
|
||||||
icon = "src/es/tunovelaligera/icon.png";
|
icon = "src/es/tunovelaligera/icon.png";
|
||||||
site = "https://tunovelaligera.com/";
|
site = "https://tunovelaligera.com";
|
||||||
version = "1.0.0";
|
version = "1.0.0";
|
||||||
|
|
||||||
|
async popularNovels(
|
||||||
|
pageNo: number,
|
||||||
async popularNovels(
|
{
|
||||||
pageNo: number,
|
showLatestNovels,
|
||||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>
|
filters,
|
||||||
): Promise<Plugin.NovelItem[]> {
|
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||||
let link = `${this.site}`;
|
): Promise<Plugin.NovelItem[]> {
|
||||||
|
let link = this.site
|
||||||
link += filters.genres.value ? `genero/` + filters.genres.value : "novelas";
|
|
||||||
|
link += filters?.genres?.value ? "/genero" + filters.genres.value : "/novelas";
|
||||||
link += `/page/${pageNo}`;
|
link += `/page/${pageNo}?m_orderby=`;
|
||||||
|
link += (showLatestNovels ? "latest" : filters?.order?.value || "rating");
|
||||||
link += filters.order.value;
|
|
||||||
|
const result = await fetchApi(link).then((res) => res.text());
|
||||||
const headers = new Headers();
|
const loadedCheerio = parseHTML(result);
|
||||||
const result = await fetchApi(link, { headers });
|
|
||||||
const body = await result.text();
|
const novels: Plugin.NovelItem[] = [];
|
||||||
|
|
||||||
const loadedCheerio = parseHTML(body);
|
loadedCheerio(".page-item-detail").each((i, el) => {
|
||||||
|
const name = loadedCheerio(el).find(".h5 > a").text();
|
||||||
const novels: Plugin.NovelItem[] = [];
|
const cover = loadedCheerio(el).find("img").attr("src");
|
||||||
|
const url = loadedCheerio(el).find(".h5 > a").attr("href");
|
||||||
loadedCheerio(".page-item-detail").each((i, el) => {
|
if (!url) return;
|
||||||
const novelName = loadedCheerio(el).find(".h5 > a").text();
|
|
||||||
const novelCover = loadedCheerio(el).find("img").attr("src");
|
novels.push({ name, cover, url });
|
||||||
const novelUrl = loadedCheerio(el).find(".h5 > a").attr("href");
|
});
|
||||||
|
|
||||||
if (!novelUrl) return;
|
return novels;
|
||||||
const novel = {
|
}
|
||||||
name: novelName,
|
|
||||||
cover: novelCover,
|
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
||||||
url: novelUrl,
|
const result = await fetchApi(novelUrl);
|
||||||
};
|
const body = await result.text();
|
||||||
|
|
||||||
novels.push(novel);
|
let loadedCheerio = parseHTML(body);
|
||||||
});
|
|
||||||
|
const novel: Plugin.SourceNovel = {
|
||||||
return novels;
|
url: novelUrl,
|
||||||
}
|
chapters: [],
|
||||||
|
};
|
||||||
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
|
||||||
const url = novelUrl;
|
loadedCheerio(".manga-title-badges").remove();
|
||||||
const headers = new Headers();
|
|
||||||
const result = await fetchApi(url, { headers });
|
novel.name = loadedCheerio(".post-title > h1").text().trim();
|
||||||
const body = await result.text();
|
|
||||||
|
let novelCover = loadedCheerio(".summary_image > a > img");
|
||||||
let loadedCheerio = parseHTML(body);
|
|
||||||
|
novel.cover =
|
||||||
const novel: Plugin.SourceNovel = {
|
novelCover.attr("data-src") ||
|
||||||
url,
|
novelCover.attr("src") ||
|
||||||
chapters: [],
|
novelCover.attr("data-cfsrc") ||
|
||||||
};
|
defaultCover;
|
||||||
|
|
||||||
loadedCheerio(".manga-title-badges").remove();
|
loadedCheerio(".post-content_item").each(function () {
|
||||||
|
const detailName = loadedCheerio(this)
|
||||||
novel.name = loadedCheerio(".post-title > h1").text().trim();
|
.find(".summary-heading > h5")
|
||||||
|
.text()
|
||||||
let novelCover = loadedCheerio(".summary_image > a > img");
|
.trim();
|
||||||
|
const detail = loadedCheerio(this).find(".summary-content").text().trim();
|
||||||
novel.cover =
|
|
||||||
novelCover.attr("data-src") ||
|
switch (detailName) {
|
||||||
novelCover.attr("src") ||
|
case "Género(s)":
|
||||||
novelCover.attr("data-cfsrc") ||
|
novel.genres = detail.replace(/, /g, ",");
|
||||||
defaultCover;
|
break;
|
||||||
|
case "Autor(es)":
|
||||||
loadedCheerio(".post-content_item").each(function () {
|
novel.author = detail;
|
||||||
const detailName = loadedCheerio(this)
|
break;
|
||||||
.find(".summary-heading > h5")
|
case "Estado":
|
||||||
.text()
|
novel.status =
|
||||||
.trim();
|
detail.includes("OnGoing") || detail.includes("Updating")
|
||||||
const detail = loadedCheerio(this)
|
? NovelStatus.Ongoing
|
||||||
.find(".summary-content")
|
: NovelStatus.Completed;
|
||||||
.text()
|
break;
|
||||||
.trim();
|
}
|
||||||
|
});
|
||||||
switch (detailName) {
|
|
||||||
case "Generos":
|
novel.summary = loadedCheerio("div.summary__content > p").text().trim();
|
||||||
novel.genres = detail.replace(/, /g, ",");
|
|
||||||
break;
|
let chapter: Plugin.ChapterItem[] = [];
|
||||||
case "Autores":
|
|
||||||
novel.author = detail;
|
const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));
|
||||||
break;
|
let lastPage = 1;
|
||||||
case "Estado":
|
lastPage = +loadedCheerio(".lcp_paginator li:last").prev().text();
|
||||||
novel.status =
|
|
||||||
detail.includes("OnGoing") ||
|
const getChapters = async () => {
|
||||||
detail.includes("Updating")
|
const n = novelUrl.split("/");
|
||||||
? NovelStatus.Ongoing
|
const novelName = n[4];
|
||||||
: NovelStatus.Completed;
|
const formData = new FormData();
|
||||||
break;
|
formData.append("action", "madara_load_more");
|
||||||
}
|
formData.append("page", "0");
|
||||||
});
|
formData.append("template", "html/loop/content");
|
||||||
|
formData.append("vars[category_name]", novelName);
|
||||||
novel.summary = loadedCheerio("div.summary__content > p").text().trim();
|
formData.append("vars[posts_per_page]", "10000");
|
||||||
|
|
||||||
let chapter: Plugin.ChapterItem[] = [];
|
const result = await fetchApi(this.site + '/wp-admin/admin-ajax.php', {
|
||||||
|
method: "POST",
|
||||||
const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));
|
body: formData,
|
||||||
let lastPage = 1;
|
});
|
||||||
lastPage = +loadedCheerio(".lcp_paginator li:last").prev().text();
|
const text = await result.text();
|
||||||
|
|
||||||
const getChapters = async () => {
|
loadedCheerio = parseHTML(text);
|
||||||
const n = url.split("/");
|
|
||||||
const novelName = n[4];
|
loadedCheerio(".heading").each((i, el) => {
|
||||||
const formData = new FormData();
|
const chapterName = loadedCheerio(el)
|
||||||
formData.append("action", "madara_load_more");
|
.text()
|
||||||
formData.append("page", "0");
|
.replace(/[\t\n]/g, "")
|
||||||
formData.append("template", "html/loop/content");
|
.trim();
|
||||||
formData.append("vars[category_name]", novelName);
|
const releaseDate = null;
|
||||||
formData.append("vars[posts_per_page]", "10000");
|
const chapterUrl = loadedCheerio(el).find("a").attr("href") || "";
|
||||||
|
|
||||||
const result = await fetchApi(
|
chapter.push({
|
||||||
`${this.site}wp-admin/admin-ajax.php`,
|
name: chapterName,
|
||||||
{
|
url: chapterUrl,
|
||||||
method: "POST",
|
releaseTime: releaseDate,
|
||||||
body: formData,
|
});
|
||||||
headers,
|
});
|
||||||
}
|
return chapter.reverse();
|
||||||
);
|
};
|
||||||
const text = await result.text();
|
|
||||||
|
const getPageChapters = async () => {
|
||||||
loadedCheerio = parseHTML(text);
|
for (let i = 1; i <= lastPage; i++) {
|
||||||
|
const chaptersUrl = `${novelUrl}?lcp_page0=${i}`;
|
||||||
loadedCheerio(".heading").each((i, el) => {
|
const result = await fetchApi(chaptersUrl);
|
||||||
const chapterName = loadedCheerio(el)
|
const chaptersHTML = await result.text();
|
||||||
.text()
|
|
||||||
.replace(/[\t\n]/g, "")
|
let chapterCheerio = parseHTML(chaptersHTML);
|
||||||
.trim();
|
|
||||||
const releaseDate = null;
|
chapterCheerio("#lcp_instance_0 li").each((i, el) => {
|
||||||
const chapterUrl =
|
const chapterName = chapterCheerio(el)
|
||||||
loadedCheerio(el).find("a").attr("href") || "";
|
.find("a")
|
||||||
|
.text()
|
||||||
chapter.push({
|
.replace(/[\t\n]/g, "")
|
||||||
name: chapterName,
|
.trim();
|
||||||
url: chapterUrl,
|
|
||||||
releaseTime: releaseDate,
|
const releaseDate = chapterCheerio(el).find("span").text().trim();
|
||||||
});
|
|
||||||
});
|
const chapterUrl = chapterCheerio(el).find("a").attr("href") || "";
|
||||||
return chapter.reverse();
|
|
||||||
};
|
chapter.push({
|
||||||
|
name: chapterName,
|
||||||
const getPageChapters = async () => {
|
releaseTime: releaseDate,
|
||||||
for (let i = 1; i <= lastPage; i++) {
|
url: chapterUrl,
|
||||||
const chaptersUrl = `${novelUrl}?lcp_page0=${i}`;
|
});
|
||||||
const result = await fetchApi(chaptersUrl, { headers });
|
});
|
||||||
const chaptersHTML = await result.text();
|
await delay(1000);
|
||||||
|
}
|
||||||
let chapterCheerio = parseHTML(chaptersHTML);
|
return chapter.reverse();
|
||||||
|
};
|
||||||
chapterCheerio(".lcp_catlist li").each((i, el) => {
|
|
||||||
const chapterName = chapterCheerio(el)
|
novel.chapters = await getChapters();
|
||||||
.find("a")
|
|
||||||
.text()
|
if (!novel.chapters.length) {
|
||||||
.replace(/[\t\n]/g, "")
|
await delay(1000);
|
||||||
.trim();
|
novel.chapters = await getPageChapters();
|
||||||
|
}
|
||||||
const releaseDate = chapterCheerio(el)
|
|
||||||
.find("span")
|
return novel;
|
||||||
.text()
|
}
|
||||||
.trim();
|
|
||||||
|
async parseChapter(chapterUrl: string): Promise<string> {
|
||||||
const chapterUrl =
|
const result = await fetchApi(chapterUrl);
|
||||||
chapterCheerio(el).find("a").attr("href") || "";
|
const body = await result.text();
|
||||||
|
|
||||||
chapter.push({
|
const loadedCheerio = parseHTML(body);
|
||||||
name: chapterName,
|
|
||||||
releaseTime: releaseDate,
|
loadedCheerio("#hola_siguiente").next().find("div").remove();
|
||||||
url: chapterUrl,
|
const chapterText = loadedCheerio("#hola_siguiente").next().html() || "";
|
||||||
});
|
|
||||||
});
|
return chapterText;
|
||||||
await delay(1000);
|
}
|
||||||
}
|
|
||||||
return chapter.reverse();
|
async searchNovels(
|
||||||
};
|
searchTerm: string,
|
||||||
|
): Promise<Plugin.NovelItem[]> {
|
||||||
novel.chapters = await getChapters();
|
const url = `${this.site}/?s=${searchTerm}&post_type=wp-manga`;
|
||||||
|
|
||||||
if (!novel.chapters.length) {
|
const result = await fetchApi(url);
|
||||||
await delay(1000);
|
const body = await result.text();
|
||||||
novel.chapters = await getPageChapters();
|
|
||||||
}
|
const loadedCheerio = parseHTML(body);
|
||||||
|
|
||||||
return novel;
|
const novels: Plugin.NovelItem[] = [];
|
||||||
}
|
|
||||||
|
loadedCheerio(".c-tabs-item__content").each((i, el) => {
|
||||||
async parseChapter(chapterUrl: string): Promise<string> {
|
const name = loadedCheerio(el).find(".h4 > a").text();
|
||||||
const headers = new Headers();
|
const cover = loadedCheerio(el).find("img").attr("src");
|
||||||
const result = await fetchApi(chapterUrl);
|
const url = loadedCheerio(el).find(".h4 > a").attr("href");
|
||||||
const body = await result.text();
|
if (!url) return;
|
||||||
|
|
||||||
const loadedCheerio = parseHTML(body);
|
novels.push({ name, cover, url });
|
||||||
|
});
|
||||||
loadedCheerio("#hola_siguiente").next().find("div").remove();
|
|
||||||
const chapterText =
|
return novels;
|
||||||
loadedCheerio("#hola_siguiente").next().html() || "";
|
}
|
||||||
|
|
||||||
return chapterText;
|
async fetchImage(url: string): Promise<string | undefined> {
|
||||||
}
|
return await fetchFile(url);
|
||||||
|
}
|
||||||
async searchNovels(
|
|
||||||
searchTerm: string,
|
filters = {
|
||||||
pageNo?: number | undefined
|
order: {
|
||||||
): Promise<Plugin.NovelItem[]> {
|
value: "rating",
|
||||||
const url = `${this.site}?s=${searchTerm}&post_type=wp-manga`;
|
label: "Ordenado por",
|
||||||
|
options: [
|
||||||
const result = await fetchApi(url);
|
{ label: "Lo mas reciente", value: "latest" },
|
||||||
const body = await result.text();
|
{ label: "A-Z", value: "alphabet" },
|
||||||
|
{ label: "Clasificación", value: "rating" },
|
||||||
const loadedCheerio = parseHTML(body);
|
{ label: "Trending", value: "trending" },
|
||||||
|
{ label: "Mas visto", value: "views" },
|
||||||
const novels: Plugin.NovelItem[] = [];
|
{ label: "Nuevo", value: "new-manga" },
|
||||||
|
],
|
||||||
loadedCheerio(".c-tabs-item__content").each((i, el) => {
|
type: FilterTypes.Picker,
|
||||||
const novelName = loadedCheerio(el).find(".h4 > a").text();
|
},
|
||||||
const novelCover = loadedCheerio(el).find("img").attr("src");
|
genres: {
|
||||||
const novelUrl = loadedCheerio(el).find(".h4 > a").attr("href");
|
value: "",
|
||||||
if (!novelUrl) return;
|
label: "Generos",
|
||||||
novels.push({
|
options: [
|
||||||
name: novelName,
|
{ label: "None", value: "" },
|
||||||
url: novelUrl,
|
{ label: "Acción", value: "accion" },
|
||||||
cover: novelCover,
|
{ label: "Adulto", value: "adulto" },
|
||||||
});
|
{ label: "Artes Marciales", value: "artes-marciales" },
|
||||||
});
|
{ label: "Aventura", value: "aventura" },
|
||||||
|
{ label: "Ciencia Ficción", value: "ciencia-ficcion" },
|
||||||
return novels;
|
{ label: "Comedia", value: "comedia" },
|
||||||
}
|
{ label: "Deportes", value: "deportes" },
|
||||||
|
{ label: "Drama", value: "drama" },
|
||||||
async fetchImage(url: string): Promise<string | undefined> {
|
{ label: "Eastern Fantasy", value: "eastern-fantasy" },
|
||||||
return await fetchFile(url);
|
{ label: "Ecchi", value: "ecchi" },
|
||||||
}
|
{ label: "FanFiction", value: "fan-fiction" },
|
||||||
|
{ label: "Fantasía", value: "fantasia" },
|
||||||
filters = {
|
{ label: "Fantasía oriental", value: "fantasia-oriental" },
|
||||||
order: {
|
{ label: "Ficción Romántica", value: "ficcion-romantica" },
|
||||||
value: "?m_orderby=rating",
|
{ label: "Gender Bender", value: "gender-bender" },
|
||||||
label: "Ordenado por",
|
{ label: "Harem", value: "harem" },
|
||||||
options: [
|
{ label: "Histórico", value: "historico" },
|
||||||
{ label: "Lo mas reciente", value: "?m_orderby=latest" },
|
{ label: "Horror", value: "horror" },
|
||||||
|
{ label: "Josei", value: "josei" },
|
||||||
{ label: "A-Z", value: "?m_orderby=alphabet" },
|
{ label: "Maduro", value: "maduro" },
|
||||||
|
{ label: "Mecha", value: "mecha" },
|
||||||
{ label: "Clasificación", value: "?m_orderby=rating" },
|
{ label: "Misterio", value: "misterio" },
|
||||||
|
{ label: "Novela China", value: "novela-china" },
|
||||||
{ label: "Trending", value: "?m_orderby=trending" },
|
{ label: "Novela FanFiction", value: "novela-fanfiction" },
|
||||||
|
{ label: "Novela Japonesa", value: "novela-japonesa" },
|
||||||
{ label: "Mas visto", value: "?m_orderby=views" },
|
{ label: "Novela Koreana", value: "novela-koreana" },
|
||||||
|
{ label: "Novela ligera", value: "novela-ligera" },
|
||||||
{ label: "Nuevo", value: "?m_orderby=new-manga" },
|
{ label: "Novela original", value: "novela-original" },
|
||||||
],
|
{ label: "Novela Web", value: "web-novel" },
|
||||||
type: FilterTypes.Picker,
|
{ label: "Psicológico", value: "psicologico" },
|
||||||
},
|
{ label: "Realismo Mágico", value: "realismo-magico" },
|
||||||
genres: {
|
{ label: "Recuento de vida", value: "recuento-de-vida" },
|
||||||
value: "",
|
{ label: "Romance", value: "romance" },
|
||||||
label: "Generos",
|
{ label: "Romance contemporáneo", value: "romance-contemporaneo" },
|
||||||
options: [
|
{ label: "Romance Moderno", value: "romance-moderno" },
|
||||||
{ label: "None", value: "" },
|
{ label: "Seinen", value: "seinen" },
|
||||||
{ label: "Acción", value: "accion" },
|
{ label: "Shoujo", value: "shoujo" },
|
||||||
|
{ label: "Shounen", value: "shounen" },
|
||||||
{ label: "Adulto", value: "adulto" },
|
{ label: "Sobrenatural", value: "sobrenatural" },
|
||||||
|
{ label: "Tragedia", value: "tragedia" },
|
||||||
{ label: "Artes Marciales", value: "artes-marciales" },
|
{ label: "Vampiros", value: "vampiros" },
|
||||||
|
{ label: "Vida Escolar", value: "vida-escolar" },
|
||||||
{ label: "Aventura", value: "aventura" },
|
{ label: "Western Fantasy", value: "western-fantasy" },
|
||||||
|
{ label: "Wuxia", value: "wuxia" },
|
||||||
{ label: "Ciencia Ficción", value: "ciencia-ficcion" },
|
{ label: "Xianxia", value: "xianxia" },
|
||||||
|
{ label: "Xuanhuan", value: "xuanhuan" },
|
||||||
{ label: "Comedia", value: "comedia" },
|
{ label: "Yaoi", value: "yaoi" },
|
||||||
|
],
|
||||||
{ label: "Deportes", value: "deportes" },
|
type: FilterTypes.Picker,
|
||||||
|
},
|
||||||
{ label: "Drama", value: "drama" },
|
} satisfies Filters;
|
||||||
|
}
|
||||||
{ label: "Eastern Fantasy", value: "eastern-fantasy" },
|
|
||||||
|
export default new TuNovelaLigera();
|
||||||
{ label: "Ecchi", value: "ecchi" },
|
|
||||||
|
|
||||||
{ label: "FanFiction", value: "fan-fiction" },
|
|
||||||
|
|
||||||
{ label: "Fantasía", value: "fantasia" },
|
|
||||||
|
|
||||||
{ label: "Fantasía oriental", value: "fantasia-oriental" },
|
|
||||||
|
|
||||||
{ label: "Ficción Romántica", value: "ficcion-romantica" },
|
|
||||||
|
|
||||||
{ label: "Gender Bender", value: "gender-bender" },
|
|
||||||
|
|
||||||
{ label: "Harem", value: "harem" },
|
|
||||||
|
|
||||||
{ label: "Histórico", value: "historico" },
|
|
||||||
|
|
||||||
{ label: "Horror", value: "horror" },
|
|
||||||
|
|
||||||
{ label: "Josei", value: "josei" },
|
|
||||||
|
|
||||||
{ label: "Maduro", value: "maduro" },
|
|
||||||
|
|
||||||
{ label: "Mecha", value: "mecha" },
|
|
||||||
|
|
||||||
{ label: "Misterio", value: "misterio" },
|
|
||||||
|
|
||||||
{ label: "Novela China", value: "novela-china" },
|
|
||||||
|
|
||||||
{ label: "Novela FanFiction", value: "novela-fanfiction" },
|
|
||||||
|
|
||||||
{ label: "Novela Japonesa", value: "novela-japonesa" },
|
|
||||||
|
|
||||||
{ label: "Novela Koreana", value: "novela-koreana" },
|
|
||||||
|
|
||||||
{ label: "Novela ligera", value: "novela-ligera" },
|
|
||||||
|
|
||||||
{ label: "Novela original", value: "novela-original" },
|
|
||||||
|
|
||||||
{ label: "Novela Web", value: "web-novel" },
|
|
||||||
|
|
||||||
{ label: "Psicológico", value: "psicologico" },
|
|
||||||
|
|
||||||
{ label: "Realismo Mágico", value: "realismo-magico" },
|
|
||||||
|
|
||||||
{ label: "Recuento de vida", value: "recuento-de-vida" },
|
|
||||||
|
|
||||||
{ label: "Romance", value: "romance" },
|
|
||||||
|
|
||||||
{
|
|
||||||
label: "Romance contemporáneo",
|
|
||||||
value: "romance-contemporaneo",
|
|
||||||
},
|
|
||||||
|
|
||||||
{ label: "Romance Moderno", value: "romance-moderno" },
|
|
||||||
|
|
||||||
{ label: "Seinen", value: "seinen" },
|
|
||||||
|
|
||||||
{ label: "Shoujo", value: "shoujo" },
|
|
||||||
|
|
||||||
{ label: "Shounen", value: "shounen" },
|
|
||||||
|
|
||||||
{ label: "Sobrenatural", value: "sobrenatural" },
|
|
||||||
|
|
||||||
{ label: "Tragedia", value: "tragedia" },
|
|
||||||
|
|
||||||
{ label: "Vampiros", value: "vampiros" },
|
|
||||||
|
|
||||||
{ label: "Vida Escolar", value: "vida-escolar" },
|
|
||||||
|
|
||||||
{ label: "Western Fantasy", value: "western-fantasy" },
|
|
||||||
|
|
||||||
{ label: "Wuxia", value: "wuxia" },
|
|
||||||
|
|
||||||
{ label: "Xianxia", value: "xianxia" },
|
|
||||||
|
|
||||||
{ label: "Xuanhuan", value: "xuanhuan" },
|
|
||||||
|
|
||||||
{ label: "Yaoi", value: "yaoi" },
|
|
||||||
],
|
|
||||||
type: FilterTypes.Picker,
|
|
||||||
},
|
|
||||||
} satisfies Filters;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default new TuNovelaLigera();
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class IfreedomPlugin implements Plugin.PluginBase {
|
|||||||
|
|
||||||
constructor(metadata: IfreedomMetadata) {
|
constructor(metadata: IfreedomMetadata) {
|
||||||
this.id = metadata.id;
|
this.id = metadata.id;
|
||||||
this.name = metadata.sourceName + "[ifreedom]";
|
this.name = metadata.sourceName;
|
||||||
this.icon = `multisrc/ifreedom/icons/${metadata.id}.png`;
|
this.icon = `multisrc/ifreedom/icons/${metadata.id}.png`;
|
||||||
this.site = metadata.sourceSite;
|
this.site = metadata.sourceSite;
|
||||||
this.version = "1.0.0";
|
this.version = "1.0.0";
|
||||||
@@ -31,20 +31,17 @@ class IfreedomPlugin implements Plugin.PluginBase {
|
|||||||
|
|
||||||
async popularNovels(
|
async popularNovels(
|
||||||
page: number,
|
page: number,
|
||||||
{ filters, showLatestNovels }: Plugin.PopularNovelsOptions,
|
{ filters, showLatestNovels }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||||
): Promise<Plugin.NovelItem[]> {
|
): Promise<Plugin.NovelItem[]> {
|
||||||
let url = this.site + "/vse-knigi/?sort=" +
|
let url = this.site + "/vse-knigi/?sort=" +
|
||||||
(showLatestNovels ? "По дате обновления" : filters?.sort?.value || "По рейтингу");
|
(showLatestNovels ? "По дате обновления" : filters?.sort?.value || "По рейтингу");
|
||||||
|
|
||||||
if (filters?.status?.value instanceof Array) {
|
Object.entries(filters || {}).forEach(([type, { value }]) => {
|
||||||
url += filters.status.value.map((i) => "&status[]=" + i).join("");
|
if (value instanceof Array && value.length) {
|
||||||
}
|
url += "&" + type + "[]=" + value.join("&" + type + "[]=");
|
||||||
if (filters?.lang?.value instanceof Array) {
|
}
|
||||||
url += filters.lang.value.map((i) => "&lang[]=" + i).join("");
|
});
|
||||||
}
|
|
||||||
if (filters?.genre?.value instanceof Array) {
|
|
||||||
url += filters.genre.value.map((i) => "&genre[]=" + i).join("");
|
|
||||||
}
|
|
||||||
url += "&bpage=" + page;
|
url += "&bpage=" + page;
|
||||||
|
|
||||||
const body = await fetchApi(url).then((res) => res.text());
|
const body = await fetchApi(url).then((res) => res.text());
|
||||||
@@ -102,14 +99,14 @@ class IfreedomPlugin implements Plugin.PluginBase {
|
|||||||
if (novel.author == "Не указан") delete novel.author;
|
if (novel.author == "Не указан") delete novel.author;
|
||||||
|
|
||||||
const chapters: Plugin.ChapterItem[] = [];
|
const chapters: Plugin.ChapterItem[] = [];
|
||||||
loadedCheerio("div.li-ranobe").each(function () {
|
const totalChapters = loadedCheerio("div.li-ranobe").length;
|
||||||
const name = loadedCheerio(this).find("a").text();
|
|
||||||
const url = loadedCheerio(this).find("a").attr("href");
|
|
||||||
if (!loadedCheerio(this).find("label.buy-ranobe").length && name && url) {
|
|
||||||
const releaseTime = loadedCheerio(this)
|
|
||||||
.find("div.li-col2-ranobe").text().trim();
|
|
||||||
|
|
||||||
chapters.push({ name, releaseTime, url });
|
loadedCheerio("div.li-ranobe").each((chapterIndex, element) => {
|
||||||
|
const name = loadedCheerio(element).find("a").text();
|
||||||
|
const url = loadedCheerio(element).find("a").attr("href");
|
||||||
|
if (!loadedCheerio(element).find("label.buy-ranobe").length && name && url) {
|
||||||
|
const releaseTime = loadedCheerio(element).find("div.li-col2-ranobe").text().trim();
|
||||||
|
chapters.push({ name, url, releaseTime, chapterNumber: totalChapters - chapterIndex });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -121,11 +118,14 @@ class IfreedomPlugin implements Plugin.PluginBase {
|
|||||||
const body = await fetchApi(chapterUrl).then((res) => res.text());
|
const body = await fetchApi(chapterUrl).then((res) => res.text());
|
||||||
const loadedCheerio = parseHTML(body);
|
const loadedCheerio = parseHTML(body);
|
||||||
|
|
||||||
loadedCheerio(".entry-content img").each(function () {
|
loadedCheerio(".entry-content img").each((index, element) => {
|
||||||
const srcset = loadedCheerio(this).attr("srcset")?.split?.(" ");
|
const srcset = loadedCheerio(element).attr("srcset")?.split?.(" ");
|
||||||
if (srcset?.length) {
|
if (srcset?.length) {
|
||||||
const bestlink = srcset.filter((url) => url.startsWith("http")).unshift();
|
loadedCheerio(element).removeAttr("srcset");
|
||||||
if (typeof bestlink == 'string') loadedCheerio(this).attr("src", bestlink);
|
const bestlink: string[] = srcset.filter((url) => url.startsWith("http"));
|
||||||
|
if (bestlink[bestlink.length - 1]) {
|
||||||
|
loadedCheerio(element).attr("src", bestlink[bestlink.length - 1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ export const generateAll: ScrpitGeneratorFunction = function (name) {
|
|||||||
/* This map is changing `type` from string to FilterTypes */
|
/* This map is changing `type` from string to FilterTypes */
|
||||||
.map<MadaraMetadata>((p) => {
|
.map<MadaraMetadata>((p) => {
|
||||||
let d = false;
|
let d = false;
|
||||||
|
if (p?.filters?.genres?.options?.length) {
|
||||||
|
p.filters.genres.options.unshift({ label: "NONE", value: "" })
|
||||||
|
}
|
||||||
const filters: Filters = {};
|
const filters: Filters = {};
|
||||||
for (const k in p.filters) {
|
for (const k in p.filters) {
|
||||||
const f = p.filters[k as keyof typeof p.filters];
|
const f = p.filters[k as keyof typeof p.filters];
|
||||||
|
|||||||
@@ -4,275 +4,321 @@ import { Plugin } from "@typings/plugin";
|
|||||||
import { load as parseHTML } from "cheerio";
|
import { load as parseHTML } from "cheerio";
|
||||||
import { defaultCover } from "@libs/defaultCover";
|
import { defaultCover } from "@libs/defaultCover";
|
||||||
import { NovelStatus } from "@libs/novelStatus";
|
import { NovelStatus } from "@libs/novelStatus";
|
||||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));
|
||||||
|
const includesAny = (str: string, keywords: string[]) =>
|
||||||
|
new RegExp(keywords.join("|")).test(str);
|
||||||
|
|
||||||
interface MadaraOptionPath {
|
interface MadaraOptionPath {
|
||||||
genres?: string;
|
genres?: string;
|
||||||
novels?: string;
|
novels?: string;
|
||||||
novel?: string;
|
novel?: string;
|
||||||
chapter?: string;
|
chapter?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MadaraDefaultPath = {
|
const MadaraDefaultPath = {
|
||||||
genres: "novel-genre",
|
genres: "novel-genre",
|
||||||
novels: "novel",
|
novels: "novel",
|
||||||
novel: "novel",
|
novel: "novel",
|
||||||
chapter: "novel",
|
chapter: "novel",
|
||||||
};
|
};
|
||||||
|
|
||||||
interface MadaraOptions {
|
interface MadaraOptions {
|
||||||
useNewChapterEndpoint?: boolean;
|
useNewChapterEndpoint?: boolean;
|
||||||
path?: MadaraOptionPath;
|
path?: MadaraOptionPath;
|
||||||
lang?: string;
|
lang?: string;
|
||||||
orderBy?: string;
|
orderBy?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MadaraMetadata {
|
export interface MadaraMetadata {
|
||||||
id: string;
|
id: string;
|
||||||
sourceSite: string;
|
sourceSite: string;
|
||||||
sourceName: string;
|
sourceName: string;
|
||||||
options?: MadaraOptions;
|
options?: MadaraOptions;
|
||||||
filters?: Filters;
|
filters?: Filters;
|
||||||
}
|
}
|
||||||
class MadaraPlugin implements Plugin.PluginBase {
|
class MadaraPlugin implements Plugin.PluginBase {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
site: string;
|
site: string;
|
||||||
version: string;
|
version: string;
|
||||||
options?: MadaraOptions;
|
options?: MadaraOptions;
|
||||||
filters?: Filters | undefined;
|
filters?: Filters | undefined;
|
||||||
|
|
||||||
constructor(metadata: MadaraMetadata) {
|
constructor(metadata: MadaraMetadata) {
|
||||||
this.id = metadata.id;
|
this.id = metadata.id;
|
||||||
this.name = metadata.sourceName + "[madara]";
|
this.name = metadata.sourceName;
|
||||||
const iconFileName = metadata.sourceName
|
const iconFileName = metadata.sourceName.replace(/\s+/g, "").toLowerCase();
|
||||||
.replace(/\s+/g, "")
|
this.icon = `multisrc/madara/icons/${iconFileName}.png`;
|
||||||
.toLowerCase();
|
this.site = metadata.sourceSite;
|
||||||
this.icon = `multisrc/madara/icons/${iconFileName}.png`;
|
this.version = "1.0.0";
|
||||||
this.site = metadata.sourceSite;
|
this.options = metadata.options;
|
||||||
this.version = "1.0.0";
|
this.filters = metadata.filters;
|
||||||
this.options = metadata.options;
|
}
|
||||||
this.filters = metadata.filters;
|
async popularNovels(
|
||||||
|
pageNo: number,
|
||||||
|
{
|
||||||
|
filters,
|
||||||
|
showLatestNovels,
|
||||||
|
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||||
|
): Promise<Plugin.NovelItem[]> {
|
||||||
|
const novels: Plugin.NovelItem[] = [];
|
||||||
|
|
||||||
|
let url = this.site;
|
||||||
|
if (filters?.genres?.value) {
|
||||||
|
url +=
|
||||||
|
(this.options?.path?.genres || MadaraDefaultPath.genres) +
|
||||||
|
"/" + filters.genres.value;
|
||||||
|
} else {
|
||||||
|
url += this.options?.path?.novels || MadaraDefaultPath.novels;
|
||||||
}
|
}
|
||||||
async popularNovels(
|
|
||||||
pageNo: number,
|
|
||||||
{
|
|
||||||
filters,
|
|
||||||
showLatestNovels,
|
|
||||||
}: Plugin.PopularNovelsOptions<typeof this.filters>
|
|
||||||
): Promise<Plugin.NovelItem[]> {
|
|
||||||
const novels: Plugin.NovelItem[] = [];
|
|
||||||
|
|
||||||
let url = this.site;
|
url += "/page/" + pageNo + "/?m_orderby=" +
|
||||||
if (filters?.genres?.value) {
|
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
|
||||||
url += (this.options?.path?.genres || MadaraDefaultPath.genres) +
|
|
||||||
"/" +
|
|
||||||
filters.genres.value;
|
|
||||||
} else {
|
|
||||||
url += this.options?.path?.novels || MadaraDefaultPath.novels;
|
|
||||||
}
|
|
||||||
|
|
||||||
url +=
|
const body = await fetchApi(url).then((res) => res.text());
|
||||||
"/page/" +
|
const loadedCheerio = parseHTML(body);
|
||||||
pageNo +
|
|
||||||
"/" +
|
|
||||||
"?m_orderby=" +
|
|
||||||
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
|
|
||||||
|
|
||||||
const body = await fetchApi(url).then((res) => res.text());
|
loadedCheerio(".manga-title-badges").remove();
|
||||||
|
|
||||||
const loadedCheerio = parseHTML(body);
|
loadedCheerio(".page-item-detail").each(function () {
|
||||||
|
const novelName = loadedCheerio(this).find(".post-title").text().trim();
|
||||||
|
const image = loadedCheerio(this).find("img");
|
||||||
|
const novelCover = image.attr("data-src") || image.attr("src");
|
||||||
|
const novelUrl =
|
||||||
|
loadedCheerio(this).find(".post-title").find("a").attr("href") || "";
|
||||||
|
if (!novelName || !novelUrl) return;
|
||||||
|
|
||||||
loadedCheerio(".manga-title-badges").remove();
|
const novel: Plugin.NovelItem = {
|
||||||
|
name: novelName,
|
||||||
|
cover: novelCover,
|
||||||
|
url: novelUrl,
|
||||||
|
};
|
||||||
|
|
||||||
loadedCheerio(".page-item-detail").each(function () {
|
novels.push(novel);
|
||||||
const novelName = loadedCheerio(this)
|
});
|
||||||
.find(".post-title")
|
|
||||||
.text()
|
|
||||||
.trim();
|
|
||||||
let image = loadedCheerio(this).find("img");
|
|
||||||
const novelCover = image.attr("data-src") || image.attr("src");
|
|
||||||
|
|
||||||
let novelUrl =
|
return novels;
|
||||||
loadedCheerio(this)
|
}
|
||||||
.find(".post-title")
|
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
||||||
.find("a")
|
const novel: Plugin.SourceNovel = {
|
||||||
.attr("href") || "";
|
url: novelUrl,
|
||||||
const novel: Plugin.NovelItem = {
|
};
|
||||||
name: novelName,
|
|
||||||
cover: novelCover,
|
|
||||||
url: novelUrl,
|
|
||||||
};
|
|
||||||
|
|
||||||
novels.push(novel);
|
const body = await fetchApi(novelUrl).then((res) => res.text());
|
||||||
});
|
|
||||||
|
|
||||||
return novels;
|
let loadedCheerio = parseHTML(body);
|
||||||
|
|
||||||
|
loadedCheerio(".manga-title-badges, #manga-title span").remove();
|
||||||
|
novel.name =
|
||||||
|
loadedCheerio(".post-title h1").text().trim() ||
|
||||||
|
loadedCheerio("#manga-title h1").text();
|
||||||
|
|
||||||
|
novel.cover =
|
||||||
|
loadedCheerio(".summary_image > a > img").attr("data-lazy-src") ||
|
||||||
|
loadedCheerio(".summary_image > a > img").attr("data-src") ||
|
||||||
|
loadedCheerio(".summary_image > a > img").attr("src") ||
|
||||||
|
loadedCheerio(".summary_image > a > img").attr("src") ||
|
||||||
|
defaultCover;
|
||||||
|
|
||||||
|
loadedCheerio(".post-content_item, .post-content").each(function () {
|
||||||
|
const detailName = loadedCheerio(this).find("h5").text().trim();
|
||||||
|
const detail = loadedCheerio(this).find(".summary-content").text().trim();
|
||||||
|
|
||||||
|
switch (detailName) {
|
||||||
|
case "Genre(s)":
|
||||||
|
case "Género(s)":
|
||||||
|
case "التصنيفات":
|
||||||
|
novel.genres = detail;
|
||||||
|
break;
|
||||||
|
case "Author(s)":
|
||||||
|
case "Autor(es)":
|
||||||
|
case "المؤلف":
|
||||||
|
case "المؤلف (ين)":
|
||||||
|
novel.author = detail;
|
||||||
|
break;
|
||||||
|
case "Status":
|
||||||
|
case "Estado":
|
||||||
|
novel.status =
|
||||||
|
detail.includes("OnGoing") || detail.includes("مستمرة")
|
||||||
|
? NovelStatus.Ongoing
|
||||||
|
: NovelStatus.Completed;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
loadedCheerio("div.summary__content .code-block,script").remove();
|
||||||
|
novel.summary =
|
||||||
|
loadedCheerio("div.summary__content").text().trim() ||
|
||||||
|
loadedCheerio("#tab-manga-about").text().trim() ||
|
||||||
|
loadedCheerio('.post-content_item h5:contains("Summary")')
|
||||||
|
.next()
|
||||||
|
.text()
|
||||||
|
.trim();
|
||||||
|
const chapters: Plugin.ChapterItem[] = [];
|
||||||
|
let html = "";
|
||||||
|
|
||||||
|
if (this.options?.useNewChapterEndpoint) {
|
||||||
|
html = await fetchApi(novelUrl + "ajax/chapters/", {
|
||||||
|
method: "POST",
|
||||||
|
}).then((res) => res.text());
|
||||||
|
} else {
|
||||||
|
const novelId =
|
||||||
|
loadedCheerio(".rating-post-id").attr("value") ||
|
||||||
|
loadedCheerio("#manga-chapters-holder").attr("data-id") ||
|
||||||
|
"";
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("action", "manga_get_chapters");
|
||||||
|
formData.append("manga", novelId);
|
||||||
|
|
||||||
|
html = await fetchApi(this.site + "wp-admin/admin-ajax.php", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
}).then((res) => res.text());
|
||||||
}
|
}
|
||||||
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
|
||||||
const novel: Plugin.SourceNovel = {
|
|
||||||
url: novelUrl,
|
|
||||||
};
|
|
||||||
|
|
||||||
const body = await fetchApi(novelUrl).then((res) => res.text());
|
if (html !== "0") {
|
||||||
|
loadedCheerio = parseHTML(html);
|
||||||
let loadedCheerio = parseHTML(body);
|
|
||||||
|
|
||||||
loadedCheerio(".manga-title-badges, #manga-title span").remove();
|
|
||||||
novel.name =
|
|
||||||
loadedCheerio(".post-title h1").text().trim() ||
|
|
||||||
loadedCheerio("#manga-title h1").text();
|
|
||||||
|
|
||||||
novel.cover =
|
|
||||||
loadedCheerio(".summary_image > a > img").attr("data-lazy-src") ||
|
|
||||||
loadedCheerio(".summary_image > a > img").attr("data-src") ||
|
|
||||||
loadedCheerio(".summary_image > a > img").attr("src") ||
|
|
||||||
defaultCover;
|
|
||||||
|
|
||||||
loadedCheerio(".post-content_item, .post-content").each(function () {
|
|
||||||
const detailName = loadedCheerio(this).find("h5").text().trim();
|
|
||||||
const detail = loadedCheerio(this)
|
|
||||||
.find(".summary-content")
|
|
||||||
.text()
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
switch (detailName) {
|
|
||||||
case "Genre(s)":
|
|
||||||
case "التصنيفات":
|
|
||||||
novel.genres = detail.replace(/[\\t\\n]/g, ",");
|
|
||||||
break;
|
|
||||||
case "Author(s)":
|
|
||||||
case "المؤلف":
|
|
||||||
case "المؤلف (ين)":
|
|
||||||
novel.author = detail;
|
|
||||||
break;
|
|
||||||
case "Status":
|
|
||||||
case "الحالة":
|
|
||||||
novel.status =
|
|
||||||
detail.includes("OnGoing") || detail.includes("مستمرة")
|
|
||||||
? NovelStatus.Ongoing
|
|
||||||
: NovelStatus.Completed;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
loadedCheerio("div.summary__content .code-block,script").remove();
|
|
||||||
novel.summary =
|
|
||||||
loadedCheerio("div.summary__content").text().trim() ||
|
|
||||||
loadedCheerio("#tab-manga-about").text().trim() ||
|
|
||||||
loadedCheerio('.post-content_item h5:contains("Summary")')
|
|
||||||
.next()
|
|
||||||
.text()
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
let html;
|
|
||||||
|
|
||||||
if (this.options?.useNewChapterEndpoint !== true) {
|
|
||||||
const novelId =
|
|
||||||
loadedCheerio(".rating-post-id").attr("value") ||
|
|
||||||
loadedCheerio("#manga-chapters-holder").attr("data-id") ||
|
|
||||||
"";
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append("action", "manga_get_chapters");
|
|
||||||
formData.append("manga", novelId);
|
|
||||||
|
|
||||||
html = await fetchApi(this.site + "wp-admin/admin-ajax.php", {
|
|
||||||
method: "POST",
|
|
||||||
body: formData,
|
|
||||||
}).then((res) => res.text());
|
|
||||||
} else {
|
|
||||||
html = await fetchApi(novelUrl + "ajax/chapters/", {
|
|
||||||
method: "POST",
|
|
||||||
}).then((res) => res.text());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (html !== "0") {
|
|
||||||
loadedCheerio = parseHTML(html);
|
|
||||||
}
|
|
||||||
|
|
||||||
const chapters: Plugin.ChapterItem[] = [];
|
|
||||||
loadedCheerio(".wp-manga-chapter").each(function () {
|
|
||||||
const chapterName = loadedCheerio(this).find("a").text().trim();
|
|
||||||
|
|
||||||
let releaseDate = null;
|
|
||||||
releaseDate = loadedCheerio(this)
|
|
||||||
.find("span.chapter-release-date")
|
|
||||||
.text()
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
if (releaseDate) {
|
|
||||||
releaseDate = parseMadaraDate(releaseDate);
|
|
||||||
} else {
|
|
||||||
/**
|
|
||||||
* Insert current date
|
|
||||||
*/
|
|
||||||
|
|
||||||
releaseDate = dayjs().format("LL");
|
|
||||||
}
|
|
||||||
|
|
||||||
let chapterUrl = loadedCheerio(this).find("a").attr("href") || "";
|
|
||||||
|
|
||||||
chapters.push({
|
|
||||||
name: chapterName,
|
|
||||||
releaseTime: releaseDate,
|
|
||||||
url: chapterUrl,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
const totalChapters = loadedCheerio(".wp-manga-chapter").length;
|
||||||
const chapterText =
|
loadedCheerio(".wp-manga-chapter").each((chapterIndex, element) => {
|
||||||
loadedCheerio(".text-left").html() ||
|
const chapterName = loadedCheerio(element).find("a").text().trim();
|
||||||
loadedCheerio(".text-right").html() ||
|
|
||||||
loadedCheerio(".entry-content").html() ||
|
|
||||||
"";
|
|
||||||
|
|
||||||
return chapterText;
|
let releaseDate = loadedCheerio(element)
|
||||||
|
.find("span.chapter-release-date")
|
||||||
|
.text()
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
if (releaseDate) {
|
||||||
|
releaseDate = this.parseData(releaseDate);
|
||||||
|
} else {
|
||||||
|
releaseDate = dayjs().format("LL");
|
||||||
|
}
|
||||||
|
|
||||||
|
let chapterUrl = loadedCheerio(element).find("a").attr("href") || "";
|
||||||
|
|
||||||
|
chapters.push({
|
||||||
|
name: chapterName,
|
||||||
|
url: chapterUrl,
|
||||||
|
releaseTime: releaseDate || null,
|
||||||
|
chapterNumber: totalChapters - chapterIndex,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
const chapterText =
|
||||||
|
loadedCheerio(".text-left").html() ||
|
||||||
|
loadedCheerio(".text-right").html() ||
|
||||||
|
loadedCheerio(".entry-content").html() ||
|
||||||
|
loadedCheerio(".c-blog-post > div > div:nth-child(2)").html() ||
|
||||||
|
"";
|
||||||
|
|
||||||
|
return chapterText;
|
||||||
|
}
|
||||||
|
async searchNovels(
|
||||||
|
searchTerm: string,
|
||||||
|
pageNo?: number | undefined,
|
||||||
|
): Promise<Plugin.NovelItem[]> {
|
||||||
|
const novels: Plugin.NovelItem[] = [];
|
||||||
|
const url = this.site + "?s=" + searchTerm + "&post_type=wp-manga";
|
||||||
|
|
||||||
|
const body = await fetchApi(url).then((res) => res.text());
|
||||||
|
|
||||||
|
const loadedCheerio = parseHTML(body);
|
||||||
|
|
||||||
|
loadedCheerio(".c-tabs-item__content").each(function () {
|
||||||
|
const novelName = loadedCheerio(this).find(".post-title").text().trim();
|
||||||
|
|
||||||
|
let image = loadedCheerio(this).find("img");
|
||||||
|
const novelCover = image.attr("data-src") || image.attr("src");
|
||||||
|
|
||||||
|
let novelUrl =
|
||||||
|
loadedCheerio(this).find(".post-title").find("a").attr("href") || "";
|
||||||
|
const novel = {
|
||||||
|
name: novelName,
|
||||||
|
cover: novelCover,
|
||||||
|
url: novelUrl,
|
||||||
|
};
|
||||||
|
|
||||||
|
novels.push(novel);
|
||||||
|
});
|
||||||
|
return novels;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchImage = fetchFile;
|
||||||
|
|
||||||
|
parseData = (date: string) => {
|
||||||
|
const dayJSDate = dayjs(); // today
|
||||||
|
const timeAgo = date.match(/\d+/)?.[0] || "";
|
||||||
|
const timeAgoInt = parseInt(timeAgo, 10);
|
||||||
|
|
||||||
|
if (!timeAgo) return date; // there is no number!
|
||||||
|
|
||||||
|
if (includesAny(date, ["detik", "segundo", "second", "วินาที"])) {
|
||||||
|
dayJSDate.subtract(timeAgoInt, "second"); // go back N seconds
|
||||||
|
} else if (
|
||||||
|
includesAny(date, [
|
||||||
|
"menit",
|
||||||
|
"dakika",
|
||||||
|
"min",
|
||||||
|
"minute",
|
||||||
|
"minuto",
|
||||||
|
"นาที",
|
||||||
|
"دقائق",
|
||||||
|
])
|
||||||
|
) {
|
||||||
|
dayJSDate.subtract(timeAgoInt, "minute"); // go back N minute
|
||||||
|
} else if (
|
||||||
|
includesAny(date, [
|
||||||
|
"jam",
|
||||||
|
"saat",
|
||||||
|
"heure",
|
||||||
|
"hora",
|
||||||
|
"hour",
|
||||||
|
"ชั่วโมง",
|
||||||
|
"giờ",
|
||||||
|
"ore",
|
||||||
|
"ساعة",
|
||||||
|
"小时",
|
||||||
|
])
|
||||||
|
) {
|
||||||
|
dayJSDate.subtract(timeAgoInt, "hours"); // go back N hours
|
||||||
|
} else if (
|
||||||
|
includesAny(date, [
|
||||||
|
"hari",
|
||||||
|
"gün",
|
||||||
|
"jour",
|
||||||
|
"día",
|
||||||
|
"dia",
|
||||||
|
"day",
|
||||||
|
"วัน",
|
||||||
|
"ngày",
|
||||||
|
"giorni",
|
||||||
|
"أيام",
|
||||||
|
"天",
|
||||||
|
])
|
||||||
|
) {
|
||||||
|
dayJSDate.subtract(timeAgoInt, "days"); // go back N days
|
||||||
|
} else if (includesAny(date, ["week", "semana"])) {
|
||||||
|
dayJSDate.subtract(timeAgoInt, "week"); // go back N a week
|
||||||
|
} else if (includesAny(date, ["month", "mes"])) {
|
||||||
|
dayJSDate.subtract(timeAgoInt, "month"); // go back N months
|
||||||
|
} else if (includesAny(date, ["year", "año"])) {
|
||||||
|
dayJSDate.subtract(timeAgoInt, "year"); // go back N years
|
||||||
|
} else {
|
||||||
|
return date;
|
||||||
}
|
}
|
||||||
async searchNovels(
|
|
||||||
searchTerm: string,
|
|
||||||
pageNo?: number | undefined
|
|
||||||
): Promise<Plugin.NovelItem[]> {
|
|
||||||
const novels: Plugin.NovelItem[] = [];
|
|
||||||
const url = this.site + "?s=" + searchTerm + "&post_type=wp-manga";
|
|
||||||
|
|
||||||
const body = await fetchApi(url).then((res) => res.text());
|
return dayJSDate.format("LL");
|
||||||
|
};
|
||||||
const loadedCheerio = parseHTML(body);
|
|
||||||
|
|
||||||
loadedCheerio(".c-tabs-item__content").each(function () {
|
|
||||||
const novelName = loadedCheerio(this)
|
|
||||||
.find(".post-title")
|
|
||||||
.text()
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
let image = loadedCheerio(this).find("img");
|
|
||||||
const novelCover = image.attr("data-src") || image.attr("src");
|
|
||||||
|
|
||||||
let novelUrl =
|
|
||||||
loadedCheerio(this)
|
|
||||||
.find(".post-title")
|
|
||||||
.find("a")
|
|
||||||
.attr("href") || "";
|
|
||||||
const novel = {
|
|
||||||
name: novelName,
|
|
||||||
cover: novelCover,
|
|
||||||
url: novelUrl,
|
|
||||||
};
|
|
||||||
|
|
||||||
novels.push(novel);
|
|
||||||
});
|
|
||||||
return novels;
|
|
||||||
}
|
|
||||||
async fetchImage(url: string): Promise<string | undefined> {
|
|
||||||
return await fetchFile(url, {});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,10 +76,6 @@
|
|||||||
"label": "Fantasy Romance",
|
"label": "Fantasy Romance",
|
||||||
"value": "fantasy-romance"
|
"value": "fantasy-romance"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"label": "Game",
|
|
||||||
"value": "game"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"label": "Gender Bender",
|
"label": "Gender Bender",
|
||||||
"value": "gender-bender"
|
"value": "gender-bender"
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ import { Filters, FilterTypes } from "@libs/filterInputs";
|
|||||||
import { Plugin } from "@typings/plugin";
|
import { Plugin } from "@typings/plugin";
|
||||||
import { NovelStatus } from "@libs/novelStatus";
|
import { NovelStatus } from "@libs/novelStatus";
|
||||||
import { load as parseHTML } from "cheerio";
|
import { load as parseHTML } from "cheerio";
|
||||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
import dayjs from "dayjs";
|
||||||
// import { defaultCover } from "@libs/defaultCover";
|
|
||||||
|
|
||||||
export interface ReadwnMetadata {
|
export interface ReadwnMetadata {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -23,9 +22,8 @@ class ReadwnPlugin implements Plugin.PluginBase {
|
|||||||
|
|
||||||
constructor(metadata: ReadwnMetadata) {
|
constructor(metadata: ReadwnMetadata) {
|
||||||
this.id = metadata.id;
|
this.id = metadata.id;
|
||||||
this.name = metadata.sourceName + "[readwn]";
|
this.name = metadata.sourceName;
|
||||||
const iconFileName = metadata.sourceName.replace(/\s+/g, "").toLowerCase();
|
this.icon = `multisrc/readwn/icons/${metadata.id}.png`;
|
||||||
this.icon = `multisrc/readwn/icons/${iconFileName}.png`;
|
|
||||||
this.site = metadata.sourceSite;
|
this.site = metadata.sourceSite;
|
||||||
this.version = "1.0.0";
|
this.version = "1.0.0";
|
||||||
this.filters = metadata.filters;
|
this.filters = metadata.filters;
|
||||||
@@ -70,7 +68,8 @@ class ReadwnPlugin implements Plugin.PluginBase {
|
|||||||
|
|
||||||
novel.name = loadedCheerio("h1.novel-title").text();
|
novel.name = loadedCheerio("h1.novel-title").text();
|
||||||
novel.author = loadedCheerio("span[itemprop=author]").text();
|
novel.author = loadedCheerio("span[itemprop=author]").text();
|
||||||
novel.cover = this.site + loadedCheerio("figure.cover > img").attr("data-src");
|
novel.cover =
|
||||||
|
this.site + loadedCheerio("figure.cover > img").attr("data-src");
|
||||||
|
|
||||||
novel.summary = loadedCheerio(".summary")
|
novel.summary = loadedCheerio(".summary")
|
||||||
.text()
|
.text()
|
||||||
@@ -84,7 +83,10 @@ class ReadwnPlugin implements Plugin.PluginBase {
|
|||||||
|
|
||||||
loadedCheerio("div.header-stats > span").each(function () {
|
loadedCheerio("div.header-stats > span").each(function () {
|
||||||
if (loadedCheerio(this).find("small").text() === "Status") {
|
if (loadedCheerio(this).find("small").text() === "Status") {
|
||||||
novel.status = loadedCheerio(this).find("strong").text();
|
novel.status =
|
||||||
|
loadedCheerio(this).find("strong").text() === "Ongoing"
|
||||||
|
? NovelStatus.Ongoing
|
||||||
|
: NovelStatus.Completed;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -97,17 +99,48 @@ class ReadwnPlugin implements Plugin.PluginBase {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const chapters: Plugin.ChapterItem[] = loadedCheerio(".chapter-list li")
|
const chapters: Plugin.ChapterItem[] = loadedCheerio(".chapter-list li")
|
||||||
.map((index, element) => {
|
.map((chapterIndex, element) => {
|
||||||
const name = loadedCheerio(element).find("a .chapter-title").text().trim();
|
const name = loadedCheerio(element).find("a .chapter-title").text().trim();
|
||||||
const url = loadedCheerio(element).find("a").attr("href")?.trim();
|
const url = loadedCheerio(element).find("a").attr("href")?.trim();
|
||||||
const releaseTime = loadedCheerio(element).find("a .chapter-update").text().trim();
|
|
||||||
|
|
||||||
if (!name || !url) return null;
|
if (!name || !url) return null;
|
||||||
|
|
||||||
|
let releaseTime = loadedCheerio(element).find("a .chapter-update").text().trim();
|
||||||
|
if (releaseTime?.includes?.("ago")) {
|
||||||
|
const timeAgo = releaseTime.match(/\d+/)?.[0] || "0";
|
||||||
|
const timeAgoInt = parseInt(timeAgo, 10);
|
||||||
|
|
||||||
|
if (timeAgoInt) {
|
||||||
|
const dayJSDate = dayjs(); // today
|
||||||
|
if (
|
||||||
|
releaseTime.includes("hours ago") ||
|
||||||
|
releaseTime.includes("hour ago")
|
||||||
|
) {
|
||||||
|
dayJSDate.subtract(timeAgoInt, "hours"); // go back N hours
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
releaseTime.includes("days ago") ||
|
||||||
|
releaseTime.includes("day ago")
|
||||||
|
) {
|
||||||
|
dayJSDate.subtract(timeAgoInt, "days"); // go back N days
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
releaseTime.includes("months ago") ||
|
||||||
|
releaseTime.includes("month ago")
|
||||||
|
) {
|
||||||
|
dayJSDate.subtract(timeAgoInt, "months"); // go back N months
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseTime = dayJSDate.format("LL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
releaseTime: parseMadaraDate(releaseTime),
|
|
||||||
url: this.site + url,
|
url: this.site + url,
|
||||||
|
releaseTime: releaseTime,
|
||||||
|
chapterNumber: chapterIndex + 1,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.get()
|
.get()
|
||||||
@@ -122,8 +155,9 @@ class ReadwnPlugin implements Plugin.PluginBase {
|
|||||||
for (let i = (lastChapterNo || chapters.length) + 1; i <= latestChapterNo; i++) {
|
for (let i = (lastChapterNo || chapters.length) + 1; i <= latestChapterNo; i++) {
|
||||||
chapters.push({
|
chapters.push({
|
||||||
name: "Chapter " + i,
|
name: "Chapter " + i,
|
||||||
releaseTime: null,
|
|
||||||
url: novelUrl.replace(".html", "_" + i + ".html"),
|
url: novelUrl.replace(".html", "_" + i + ".html"),
|
||||||
|
releaseTime: null,
|
||||||
|
chapterNumber: i,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,22 +174,17 @@ class ReadwnPlugin implements Plugin.PluginBase {
|
|||||||
return chapterText;
|
return chapterText;
|
||||||
}
|
}
|
||||||
|
|
||||||
async searchNovels(keyboard: string): Promise<Plugin.NovelItem[]> {
|
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
||||||
const result = await fetchApi(this.site + "/e/search/index.php", {
|
const result = await fetchApi(this.site + "/e/search/index.php", {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
Referer: this.site + "/search.html",
|
Referer: this.site + "/search.html",
|
||||||
Origin: this.site,
|
Origin: this.site,
|
||||||
"user-agent":
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36",
|
|
||||||
},
|
},
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body:
|
||||||
show: "title",
|
"show=title&tempid=1&tbname=news&keyboard=" +
|
||||||
tempid: 1,
|
encodeURIComponent(searchTerm),
|
||||||
tbname: "news",
|
|
||||||
keyboard,
|
|
||||||
}),
|
|
||||||
}).then((res) => res.text());
|
}).then((res) => res.text());
|
||||||
const loadedCheerio = parseHTML(result);
|
const loadedCheerio = parseHTML(result);
|
||||||
|
|
||||||
|
|||||||
@@ -1552,7 +1552,7 @@
|
|||||||
],
|
],
|
||||||
"type": "Picker"
|
"type": "Picker"
|
||||||
},
|
},
|
||||||
"trash": {
|
"extra": {
|
||||||
"label": "Другое:",
|
"label": "Другое:",
|
||||||
"value": [],
|
"value": [],
|
||||||
"options": [
|
"options": [
|
||||||
@@ -1612,7 +1612,7 @@
|
|||||||
},
|
},
|
||||||
"adult": {
|
"adult": {
|
||||||
"label": "Возрастные ограничения:",
|
"label": "Возрастные ограничения:",
|
||||||
"value": "0",
|
"value": "1",
|
||||||
"options": [
|
"options": [
|
||||||
{
|
{
|
||||||
"label": "Все",
|
"label": "Все",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": "tl.rulate",
|
"id": "rulate",
|
||||||
"sourceSite": "https://tl.rulate.ru",
|
"sourceSite": "https://tl.rulate.ru",
|
||||||
"sourceName": "Rulate",
|
"sourceName": "Rulate",
|
||||||
"filters": {
|
"filters": {
|
||||||
|
|||||||
@@ -22,9 +22,8 @@ class RulatePlugin implements Plugin.PluginBase {
|
|||||||
|
|
||||||
constructor(metadata: RulateMetadata) {
|
constructor(metadata: RulateMetadata) {
|
||||||
this.id = metadata.id;
|
this.id = metadata.id;
|
||||||
this.name = metadata.sourceName + "[rulate]";
|
this.name = metadata.sourceName;
|
||||||
const iconFileName = metadata.sourceName.replace(/\s+/g, "").toLowerCase();
|
this.icon = `multisrc/rulate/icons/${metadata.id}.png`;
|
||||||
this.icon = `multisrc/rulate/icons/${iconFileName}.png`;
|
|
||||||
this.site = metadata.sourceSite;
|
this.site = metadata.sourceSite;
|
||||||
this.version = "1.0.0";
|
this.version = "1.0.0";
|
||||||
this.filters = metadata.filters;
|
this.filters = metadata.filters;
|
||||||
@@ -35,8 +34,7 @@ class RulatePlugin implements Plugin.PluginBase {
|
|||||||
{ filters, showLatestNovels }: Plugin.PopularNovelsOptions,
|
{ filters, showLatestNovels }: Plugin.PopularNovelsOptions,
|
||||||
): Promise<Plugin.NovelItem[]> {
|
): Promise<Plugin.NovelItem[]> {
|
||||||
const novels: Plugin.NovelItem[] = [];
|
const novels: Plugin.NovelItem[] = [];
|
||||||
const baseUrl = this.site;
|
let url = this.site + "/search?t=";
|
||||||
let url = baseUrl + "/search?t=";
|
|
||||||
url += "&cat=" + (filters?.cat?.value || "0");
|
url += "&cat=" + (filters?.cat?.value || "0");
|
||||||
url += "&s_lang=" + (filters?.s_lang?.value || "0");
|
url += "&s_lang=" + (filters?.s_lang?.value || "0");
|
||||||
url += "&t_lang=" + (filters?.t_lang?.value || "0");
|
url += "&t_lang=" + (filters?.t_lang?.value || "0");
|
||||||
@@ -45,39 +43,13 @@ class RulatePlugin implements Plugin.PluginBase {
|
|||||||
url += "&atmosphere=" + (filters?.atmosphere?.value || "0");
|
url += "&atmosphere=" + (filters?.atmosphere?.value || "0");
|
||||||
url += "&adult=" + (filters?.adult?.value || "0");
|
url += "&adult=" + (filters?.adult?.value || "0");
|
||||||
|
|
||||||
if (filters?.genres?.value instanceof Array) {
|
Object.entries(filters || {}).forEach(([type, { value }]) => {
|
||||||
url += filters.genres.value.map((i) => "&genres[]=" + i).join("");
|
if (value instanceof Array && value.length) {
|
||||||
}
|
url += "&" + value
|
||||||
|
.map((val) => (type == "extra" ? val + "=1" : type + "[]=" + val))
|
||||||
//if (filters?.genres?.value?.exclude instanceof Array) {
|
.join("&");
|
||||||
// url += filters.genres.value.exclude
|
}
|
||||||
// ?.map((i) => "&genres_ex[]=" + i)
|
});
|
||||||
// .join("");
|
|
||||||
//}
|
|
||||||
|
|
||||||
if (filters?.tags?.value instanceof Array) {
|
|
||||||
url += filters.tags.value.map((i) => "&tags[]=" + i).join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
//if (filters?.tags?.value?.exclude instanceof Array) {
|
|
||||||
// url += filters.tags.value.exclude
|
|
||||||
// ?.map((i) => "&tags_ex[]=" + i)
|
|
||||||
// .join("");
|
|
||||||
//}
|
|
||||||
|
|
||||||
if (filters?.fandoms?.value instanceof Array) {
|
|
||||||
url += filters.fandoms.value.map((i) => "&fandoms[]=" + i).join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (filters?.fandoms?.value?.exclude instanceof Array) {
|
|
||||||
// url += filters.fandoms.value.exclude
|
|
||||||
// .map((i) => "&fandoms_ex[]=" + i)
|
|
||||||
// .join("");
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (filters?.trash?.value instanceof Array) {
|
|
||||||
url += filters.trash.value.map((i) => "&" + i + "=1").join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
url += "&Book_page=" + pageNo;
|
url += "&Book_page=" + pageNo;
|
||||||
|
|
||||||
@@ -86,21 +58,20 @@ class RulatePlugin implements Plugin.PluginBase {
|
|||||||
|
|
||||||
loadedCheerio(
|
loadedCheerio(
|
||||||
'ul[class="search-results"] > li:not([class="ad_type_catalog"])',
|
'ul[class="search-results"] > li:not([class="ad_type_catalog"])',
|
||||||
).each(function () {
|
).each((index, element) => {
|
||||||
loadedCheerio(this).find("p > a").text();
|
loadedCheerio(element).find("p > a").text();
|
||||||
const name = loadedCheerio(this).find("p > a").text();
|
const name = loadedCheerio(element).find("p > a").text();
|
||||||
const cover = loadedCheerio(this).find("img").attr("src");
|
const cover = loadedCheerio(element).find("img").attr("src");
|
||||||
const url = loadedCheerio(this).find("p > a").attr("href");
|
const url = loadedCheerio(element).find("p > a").attr("href");
|
||||||
if (!name || !url) return;
|
if (!name || !url) return;
|
||||||
|
|
||||||
novels.push({ name, cover: baseUrl + cover, url: baseUrl + url });
|
novels.push({ name, cover: this.site + cover, url: this.site + url });
|
||||||
});
|
});
|
||||||
|
|
||||||
return novels;
|
return novels;
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
||||||
const baseUrl = this.site;
|
|
||||||
const novel: Plugin.SourceNovel = {
|
const novel: Plugin.SourceNovel = {
|
||||||
url: novelUrl,
|
url: novelUrl,
|
||||||
};
|
};
|
||||||
@@ -129,7 +100,7 @@ class RulatePlugin implements Plugin.PluginBase {
|
|||||||
novel.name = novel.name.split("[")[0].trim();
|
novel.name = novel.name.split("[")[0].trim();
|
||||||
}
|
}
|
||||||
novel.cover =
|
novel.cover =
|
||||||
baseUrl + loadedCheerio('div[class="images"] > div img').attr("src");
|
this.site + loadedCheerio('div[class="images"] > div img').attr("src");
|
||||||
novel.summary =
|
novel.summary =
|
||||||
loadedCheerio("#Info > div:nth-child(3), .book-description").text().trim();
|
loadedCheerio("#Info > div:nth-child(3), .book-description").text().trim();
|
||||||
novel.author = loadedCheerio(
|
novel.author = loadedCheerio(
|
||||||
@@ -172,27 +143,17 @@ class RulatePlugin implements Plugin.PluginBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const chapters: Plugin.ChapterItem[] = [];
|
const chapters: Plugin.ChapterItem[] = [];
|
||||||
loadedCheerio("table > tbody > tr.chapter_row").each(function () {
|
loadedCheerio("table > tbody > tr.chapter_row").each((chapterIndex, element) => {
|
||||||
const chapterName = loadedCheerio(this)
|
const chapterName = loadedCheerio(element).find('td[class="t"] > a').text().trim();
|
||||||
.find('td[class="t"] > a')
|
const releaseDate = loadedCheerio(element).find("td > span").attr("title")?.trim();
|
||||||
.text()
|
const chapterUrl = loadedCheerio(element).find('td[class="t"] > a').attr("href");
|
||||||
.trim();
|
|
||||||
const releaseDate = loadedCheerio(this)
|
|
||||||
.find("td > span")
|
|
||||||
.attr("title")
|
|
||||||
?.trim();
|
|
||||||
const chapterUrl = loadedCheerio(this)
|
|
||||||
.find('td[class="t"] > a')
|
|
||||||
.attr("href");
|
|
||||||
|
|
||||||
if (
|
if (!loadedCheerio(element).find('td > span[class="disabled"]').length && releaseDate) {
|
||||||
!loadedCheerio(this).find('td > span[class="disabled"]').length &&
|
|
||||||
releaseDate
|
|
||||||
) {
|
|
||||||
chapters.push({
|
chapters.push({
|
||||||
name: chapterName,
|
name: chapterName,
|
||||||
|
url: this.site + chapterUrl,
|
||||||
releaseTime: releaseDate,
|
releaseTime: releaseDate,
|
||||||
url: baseUrl + chapterUrl,
|
chapterNumber: chapterIndex + 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -202,7 +163,6 @@ class RulatePlugin implements Plugin.PluginBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async parseChapter(chapterUrl: string): Promise<string> {
|
async parseChapter(chapterUrl: string): Promise<string> {
|
||||||
const baseUrl = this.site;
|
|
||||||
let result = await fetchApi(chapterUrl);
|
let result = await fetchApi(chapterUrl);
|
||||||
if (result.url.includes("mature?path=")) {
|
if (result.url.includes("mature?path=")) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
@@ -218,10 +178,10 @@ class RulatePlugin implements Plugin.PluginBase {
|
|||||||
const body = await result.text();
|
const body = await result.text();
|
||||||
const loadedCheerio = parseHTML(body);
|
const loadedCheerio = parseHTML(body);
|
||||||
|
|
||||||
loadedCheerio(".content-text img").each(function () {
|
loadedCheerio(".content-text img").each((index, element) => {
|
||||||
if (!loadedCheerio(this).attr("src")?.startsWith("http")) {
|
if (!loadedCheerio(element).attr("src")?.startsWith("http")) {
|
||||||
const src = loadedCheerio(this).attr("src");
|
const src = loadedCheerio(element).attr("src");
|
||||||
loadedCheerio(this).attr("src", baseUrl + src);
|
loadedCheerio(element).attr("src", this.site + src);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class WPmangaStreamPlugin implements Plugin.PluginBase {
|
|||||||
|
|
||||||
constructor(metadata: WPmangaStreamMetadata) {
|
constructor(metadata: WPmangaStreamMetadata) {
|
||||||
this.id = metadata.id;
|
this.id = metadata.id;
|
||||||
this.name = metadata.sourceName + "[wpmangastream]";
|
this.name = metadata.sourceName;
|
||||||
this.icon = `multisrc/wpmangastream/icons/${metadata.id}.png`;
|
this.icon = `multisrc/wpmangastream/icons/${metadata.id}.png`;
|
||||||
this.site = metadata.sourceSite;
|
this.site = metadata.sourceSite;
|
||||||
this.version = "1.0.0";
|
this.version = "1.0.0";
|
||||||
@@ -93,13 +93,17 @@ class WPmangaStreamPlugin implements Plugin.PluginBase {
|
|||||||
novel.summary += loadedCheerio(p).text().trim() + "\\n\\n";
|
novel.summary += loadedCheerio(p).text().trim() + "\\n\\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const totalChapters = loadedCheerio(".eplister li").length;
|
||||||
const chapters: Plugin.ChapterItem[] = loadedCheerio(".eplister li")
|
const chapters: Plugin.ChapterItem[] = loadedCheerio(".eplister li")
|
||||||
.map((index, element) => ({
|
.map((chapterIndex, element) => ({
|
||||||
name:
|
name:
|
||||||
loadedCheerio(element).find(".epl-num").text() +
|
loadedCheerio(element).find(".epl-num").text() +
|
||||||
loadedCheerio(element).find(".epl-title").text(),
|
loadedCheerio(element).find(".epl-title").text(),
|
||||||
releaseTime: loadedCheerio(element).find(".epl-date").text().trim(),
|
|
||||||
url: loadedCheerio(element).find("a").attr("href") || "",
|
url: loadedCheerio(element).find("a").attr("href") || "",
|
||||||
|
releaseTime: loadedCheerio(element).find(".epl-date").text().trim(),
|
||||||
|
chapterNumber: this.options?.reverseChapters
|
||||||
|
? totalChapters - chapterIndex
|
||||||
|
: chapterIndex + 1,
|
||||||
}))
|
}))
|
||||||
.get()
|
.get()
|
||||||
.filter((chapter) => chapter.name && chapter.url);
|
.filter((chapter) => chapter.name && chapter.url);
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const all_plugins = (): PluginList => {
|
|||||||
const { id, name, version, icon } = instance;
|
const { id, name, version, icon } = instance;
|
||||||
const info: Plugin.PluginItem = {
|
const info: Plugin.PluginItem = {
|
||||||
id,
|
id,
|
||||||
name,
|
name: name + (plugin.match(/(\[.+\])/)?.[1] || ''),
|
||||||
lang: languageNative,
|
lang: languageNative,
|
||||||
version,
|
version,
|
||||||
requirePath,
|
requirePath,
|
||||||
|
|||||||