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
|
||||
|
||||
npm run clearMulti
|
||||
npm run generate
|
||||
npm run json
|
||||
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 { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
class ComradeMaoPlugin implements Plugin.PluginBase {
|
||||
id = "comrademao";
|
||||
@@ -27,7 +27,7 @@ class ComradeMaoPlugin implements Plugin.PluginBase {
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
url: novelUrl,
|
||||
}
|
||||
};
|
||||
novels.push(novel);
|
||||
});
|
||||
return novels;
|
||||
@@ -35,7 +35,7 @@ class ComradeMaoPlugin implements Plugin.PluginBase {
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = this.site;
|
||||
|
||||
@@ -43,25 +43,19 @@ class ComradeMaoPlugin implements Plugin.PluginBase {
|
||||
|
||||
link += "page/" + pageNo + "/?post_type=novel";
|
||||
|
||||
const headers = new Headers();
|
||||
const body = await fetchApi(link, { headers }).then((result) =>
|
||||
result.text()
|
||||
);
|
||||
const body = await fetchApi(link).then((result) => result.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio);
|
||||
}
|
||||
|
||||
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
||||
const url = novelUrl;
|
||||
const headers = new Headers();
|
||||
const result = await fetchApi(url, { headers });
|
||||
const body = await result.text();
|
||||
const body = await fetchApi(novelUrl).then((result) => result.text());
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
url,
|
||||
url: novelUrl,
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
@@ -71,19 +65,19 @@ class ComradeMaoPlugin implements Plugin.PluginBase {
|
||||
|
||||
loadedCheerio(".infox .wd-full").each((i, el) => {
|
||||
const detailName = loadedCheerio(el).find("b").text();
|
||||
const detail = loadedCheerio(el).find('span').text();
|
||||
const detail = loadedCheerio(el).find("span").text();
|
||||
|
||||
switch (detailName) {
|
||||
case 'Publisher: ':
|
||||
case "Publisher: ":
|
||||
novel.author = detail.trim();
|
||||
break;
|
||||
case 'Status: ':
|
||||
case "Status: ":
|
||||
novel.status = detail.trim();
|
||||
break;
|
||||
case 'Genres:':
|
||||
novel.genres = detail.trim().replace(/ , /g, ',');
|
||||
case "Genres:":
|
||||
novel.genres = detail.trim().replace(/ , /g, ",");
|
||||
break;
|
||||
case 'Description: ':
|
||||
case "Description: ":
|
||||
novel.summary = detail.trim();
|
||||
break;
|
||||
}
|
||||
@@ -94,53 +88,78 @@ class ComradeMaoPlugin implements Plugin.PluginBase {
|
||||
loadedCheerio("#chapterlist")
|
||||
.find("li")
|
||||
.each(function () {
|
||||
const releaseDate = parseMadaraDate(
|
||||
loadedCheerio(this).find(".chapterdate").text()
|
||||
);
|
||||
const chapterUrl = loadedCheerio(this).find("a").attr("href");
|
||||
if (!chapterUrl) return;
|
||||
|
||||
const chapterName = loadedCheerio(this)
|
||||
.find(".chapternum")
|
||||
.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({
|
||||
name: chapterName,
|
||||
url: chapterUrl,
|
||||
releaseTime: releaseDate,
|
||||
releaseTime,
|
||||
});
|
||||
});
|
||||
|
||||
novel.chapters = chapter.reverse();
|
||||
|
||||
return novel;
|
||||
};
|
||||
}
|
||||
|
||||
async parseChapter(chapterUrl: string): Promise<string> {
|
||||
const headers = new Headers();
|
||||
const result = await fetchApi(chapterUrl, { headers });
|
||||
const body = await result.text();
|
||||
|
||||
const body = await fetchApi(chapterUrl).then((result) => result.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const chapterText = loadedCheerio("#chaptercontent").html()?.trim() || "";
|
||||
|
||||
return chapterText;
|
||||
};
|
||||
}
|
||||
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}page/${pageNo}/?s=${searchTerm}&post_type=novel`;
|
||||
|
||||
const headers = new Headers();
|
||||
const result = await fetchApi(url, { headers });
|
||||
const body = await result.text();
|
||||
const body = await fetchApi(url).then((result) => result.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio);
|
||||
};
|
||||
}
|
||||
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
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();
|
||||
@@ -10,7 +10,6 @@ class FreeWebNovel implements Plugin.PluginBase {
|
||||
version = "1.0.0";
|
||||
icon = "src/en/freewebnovel/icon.png";
|
||||
|
||||
|
||||
async popularNovels(
|
||||
page: number,
|
||||
{ showLatestNovels }: Plugin.PopularNovelsOptions,
|
||||
@@ -58,10 +57,11 @@ class FreeWebNovel implements Plugin.PluginBase {
|
||||
novel.summary = loadedCheerio(".inner").text().trim();
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = loadedCheerio("#idData > li > a")
|
||||
const chapters: Plugin.ChapterItem[] = loadedCheerio("#idData > li > a")
|
||||
.map((index, element) => ({
|
||||
name: loadedCheerio(element).attr("title") || "Chapter " + index,
|
||||
.map((chapterIndex, element) => ({
|
||||
name: loadedCheerio(element).attr("title") || "Chapter " + (chapterIndex + 1),
|
||||
url: this.site + loadedCheerio(element).attr("href"),
|
||||
releaseTime: null,
|
||||
chapterNumber: chapterIndex + 1,
|
||||
}))
|
||||
.get();
|
||||
|
||||
@@ -78,18 +78,23 @@ class FreeWebNovel implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async searchNovels(
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const body = await fetchApi(this.site + "/search/", {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Referer: this.site,
|
||||
Origin: this.site,
|
||||
},
|
||||
method: "POST",
|
||||
method: "POST",
|
||||
body: "searchkey=" + encodeURIComponent(searchTerm)
|
||||
}).then((res) => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const novels: Plugin.NovelItem[] = loadedCheerio(".li-row > .li > .con")
|
||||
.map((index, element) => ({
|
||||
name: loadedCheerio(element).find(".tit").text(),
|
||||
name: loadedCheerio(element).find(".tit").text(),
|
||||
cover: loadedCheerio(element).find(".pic > a > img").attr("src"),
|
||||
url: this.site + loadedCheerio(element).find("h3 > a").attr("href"),
|
||||
}))
|
||||
.get();
|
||||
|
||||
@@ -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();
|
||||
@@ -11,7 +11,6 @@ class freedlit implements Plugin.PluginBase {
|
||||
version = "1.0.0";
|
||||
icon = "src/ru/freedlit/icon.png";
|
||||
|
||||
|
||||
async popularNovels(
|
||||
page: number,
|
||||
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
@@ -24,12 +23,14 @@ class freedlit implements Plugin.PluginBase {
|
||||
|
||||
if (filters?.genre?.value?.include?.length) {
|
||||
url += filters.genre.value.include
|
||||
url += filters.genre.value.include
|
||||
.map((id) => "&genres_included[]=" + id)
|
||||
.join("");
|
||||
}
|
||||
|
||||
if (filters?.genre?.value?.exclude?.length) {
|
||||
url += filters.genre.value.exclude
|
||||
url += filters.genre.value.exclude
|
||||
.map((id) => "&genres_excluded[]=" + id)
|
||||
.join("");
|
||||
}
|
||||
|
||||
const body = await fetchApi(url).then((res) => res.text());
|
||||
@@ -69,13 +70,13 @@ class freedlit implements Plugin.PluginBase {
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
|
||||
|
||||
loadedCheerio("a.chapter-line").each(function () {
|
||||
const name = loadedCheerio(this).find("h6").text();
|
||||
const releaseTime = loadedCheerio(this).find('span[class="date"]').text();
|
||||
loadedCheerio("a.chapter-line").each((chapterIndex, element) => {
|
||||
const name = loadedCheerio(element).find("h6").text();
|
||||
const url = loadedCheerio(element).attr("href");
|
||||
if (!name || !url) return;
|
||||
|
||||
|
||||
const releaseTime = loadedCheerio(element).find('span[class="date"]').text();
|
||||
chapters.push({ name, url, releaseTime, chapterNumber: chapterIndex + 1 });
|
||||
});
|
||||
|
||||
novel.chapters = chapters;
|
||||
@@ -115,7 +116,7 @@ class freedlit implements Plugin.PluginBase {
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Сортировка:",
|
||||
label: "Сортировка:",
|
||||
value: "popular",
|
||||
options: [
|
||||
{ label: "По популярности", value: "popular" },
|
||||
{ label: "По количеству комментариев", value: "comments" },
|
||||
@@ -208,7 +209,7 @@ class freedlit implements Plugin.PluginBase {
|
||||
},
|
||||
status: {
|
||||
label: "Статус:",
|
||||
label: "Статус:",
|
||||
value: "all",
|
||||
options: [
|
||||
{ label: "Любой статус", value: "all" },
|
||||
{ label: "В процессе", value: "in-process" },
|
||||
@@ -218,7 +219,7 @@ class freedlit implements Plugin.PluginBase {
|
||||
},
|
||||
access: {
|
||||
label: "Доступ:",
|
||||
label: "Доступ:",
|
||||
value: "all",
|
||||
options: [
|
||||
{ label: "Любой доступ", value: "all" },
|
||||
{ label: "Бесплатные", value: "free" },
|
||||
@@ -228,7 +229,7 @@ class freedlit implements Plugin.PluginBase {
|
||||
},
|
||||
adult: {
|
||||
label: "Возрастные ограничения:",
|
||||
label: "Возрастные ограничения:",
|
||||
value: "hide",
|
||||
options: [
|
||||
{ label: "Скрыть 18+", value: "hide" },
|
||||
{ label: "Показать +18", value: "show" },
|
||||
|
||||
@@ -16,7 +16,6 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
site = "https://author.today";
|
||||
version = "1.0.0";
|
||||
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
|
||||
@@ -27,7 +26,8 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
url +=
|
||||
url +=
|
||||
"&sorting=" +
|
||||
(showLatestNovels ? "recent" : filters?.sort?.value || "popular");
|
||||
|
||||
url += "&form=" + (filters?.form?.value || "any");
|
||||
url += "&state=" + (filters?.state?.value || "any");
|
||||
@@ -43,14 +43,14 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
const json = (await result.json()) as response;
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
|
||||
if (json.code === "NotFound") {
|
||||
return novels;
|
||||
}
|
||||
|
||||
json?.searchResults?.forEach((novel) =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
name: novel.title,
|
||||
cover: novel.coverUrl
|
||||
? "https://cm.author.today/content/" + novel.coverUrl
|
||||
: defaultCover,
|
||||
url: this.site + "/work/" + novel.id,
|
||||
@@ -67,27 +67,26 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
const book = (await result.json()) as responseBook;
|
||||
const novel: Plugin.SourceNovel = {
|
||||
url: novelUrl,
|
||||
url: novelUrl,
|
||||
name: json.title,
|
||||
name: book.title,
|
||||
cover: book.coverUrl ? book.coverUrl.split("?")[0] : defaultCover,
|
||||
genres: book.tags?.join(", "),
|
||||
summary: "",
|
||||
author:
|
||||
author:
|
||||
json?.originalAuthor ||
|
||||
json?.authorFIO ||
|
||||
json?.coAuthorFIO ||
|
||||
json?.secondCoAuthorFIO ||
|
||||
book.originalAuthor ||
|
||||
book.authorFIO ||
|
||||
book.coAuthorFIO ||
|
||||
book.secondCoAuthorFIO ||
|
||||
book.translator ||
|
||||
"",
|
||||
"",
|
||||
genres: json?.tags?.join(", "),
|
||||
status: book.isFinished ? NovelStatus.Completed : NovelStatus.Ongoing,
|
||||
};
|
||||
|
||||
|
||||
novel.summary = "";
|
||||
novel.summary += json?.annotation ? json.annotation + "\n" : "";
|
||||
novel.summary += json?.authorNotes
|
||||
? "Примечания автора:\n" + json.authorNotes
|
||||
if (book.annotation) novel.summary += book.annotation + "\n";
|
||||
if (book.authorNotes) novel.summary += "Примечания автора:\n" + book.authorNotes;
|
||||
|
||||
// all chapters
|
||||
const chaptersRaw = await fetchApi(`${apiUrl}v1/work/${workID}/content`, {
|
||||
headers: {
|
||||
@@ -97,14 +96,13 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
|
||||
const chaptersJSON = (await chaptersRaw.json()) as ChaptersEntity[];
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
chaptersJSON?.forEach((chapter, index) => {
|
||||
chaptersJSON.forEach((chapter, chapterIndex) => {
|
||||
if (chapter.isAvailable && !chapter.isDraft) {
|
||||
chapters.push({
|
||||
chapters.push({
|
||||
name: chapter?.title || `Глава ${index + 1}`,
|
||||
releaseTime: dayjs(
|
||||
chapter?.publishTime || chapter.lastModificationTime,
|
||||
name: chapter.title || 'Глава ' + (chapterIndex + 1),
|
||||
url: `${apiUrl}v1/work/${workID}/chapter/${chapter.id}/text`,
|
||||
releaseTime: dayjs(chapter.publishTime || chapter.lastModificationTime).format("LLL"),
|
||||
chapterNumber: (chapter.sortOrder || chapterIndex) + 1,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -121,7 +119,7 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
});
|
||||
const json = (await result.json()) as encryptedСhapter;
|
||||
|
||||
|
||||
if (json.code) {
|
||||
return json.code + "\n" + json?.message;
|
||||
}
|
||||
|
||||
@@ -135,11 +133,10 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
const loadedCheerio = parseHTML(text);
|
||||
const loadedCheerio = parseHTML(text);
|
||||
const baseUrl = this.site;
|
||||
loadedCheerio("img").each(function () {
|
||||
if (!loadedCheerio(this).attr("src")?.startsWith("http")) {
|
||||
const src = loadedCheerio(this).attr("src");
|
||||
loadedCheerio("img").each((index, element) => {
|
||||
if (!loadedCheerio(element).attr("src")?.startsWith("http")) {
|
||||
const src = loadedCheerio(element).attr("src");
|
||||
loadedCheerio(element).attr("src", this.site + src);
|
||||
}
|
||||
});
|
||||
const chapterText = loadedCheerio.html();
|
||||
@@ -148,36 +145,29 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
searchTerm: string,
|
||||
pageNo: number | undefined = 1,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const baseUrl = this.site;
|
||||
const url = baseUrl + "/search?category=works&q=" + searchTerm + "&page=";
|
||||
const result = await fetchApi(url + (pageNo || 1));
|
||||
const body = await result.text();
|
||||
const url =
|
||||
this.site + "/search?category=works&q=" + searchTerm + "&page=" + pageNo;
|
||||
const result = await fetchApi(url).then((res) => res.text());
|
||||
const loadedCheerio = parseHTML(result);
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
|
||||
loadedCheerio("div.book-row").each(function () {
|
||||
loadedCheerio("div.book-row").each((index, element) => {
|
||||
const name = loadedCheerio(element)
|
||||
.find('div[class="book-title"] a')
|
||||
.text()
|
||||
.trim();
|
||||
.trim();
|
||||
let cover = loadedCheerio(this).find("img").attr("src");
|
||||
const url =
|
||||
baseUrl +
|
||||
"/work/" +
|
||||
let cover = loadedCheerio(element).find("img").attr("src");
|
||||
const url = loadedCheerio(element)
|
||||
.find('div[class="book-title"] a')
|
||||
.attr("href")
|
||||
.attr("href")
|
||||
?.split("/")?.[2];
|
||||
|
||||
|
||||
if (cover) {
|
||||
cover = cover.split("?")[0];
|
||||
} else {
|
||||
cover = defaultCover;
|
||||
cover = cover ? cover.split("?")[0] : defaultCover;
|
||||
|
||||
|
||||
if (!url) return;
|
||||
novels.push({ name, cover, url: this.site + "/work/" + url });
|
||||
});
|
||||
|
||||
return novels;
|
||||
@@ -188,7 +178,7 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Сортировка",
|
||||
label: "Сортировка",
|
||||
value: "popular",
|
||||
options: [
|
||||
{ label: "По популярности", value: "popular" },
|
||||
{ label: "По количеству лайков", value: "likes" },
|
||||
@@ -203,6 +193,7 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
label: "Жанры",
|
||||
value: "",
|
||||
options: [
|
||||
{ label: "Все", value:"" },
|
||||
{ label: "Альтернативная история", value: "sf-history" },
|
||||
{ label: "Антиутопия", value: "dystopia" },
|
||||
{ label: "Бизнес-литература", value: "biznes-literatura" },
|
||||
@@ -270,7 +261,7 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
},
|
||||
form: {
|
||||
label: "Форма произведения",
|
||||
label: "Форма произведения",
|
||||
value: "any",
|
||||
options: [
|
||||
{ label: "Любой", value: "any" },
|
||||
{ label: "Перевод", value: "translation" },
|
||||
@@ -284,7 +275,7 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
},
|
||||
state: {
|
||||
label: "Статус произведения",
|
||||
label: "Статус произведения",
|
||||
value: "any",
|
||||
options: [
|
||||
{ label: "Любой статус", value: "any" },
|
||||
{ label: "В процессе", value: "in-progress" },
|
||||
@@ -294,7 +285,7 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
},
|
||||
series: {
|
||||
label: "Статус цикла",
|
||||
label: "Статус цикла",
|
||||
value: "any",
|
||||
options: [
|
||||
{ label: "Не важно", value: "any" },
|
||||
{ label: "Вне цикла", value: "out" },
|
||||
@@ -305,7 +296,7 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
},
|
||||
access: {
|
||||
label: "Тип доступа",
|
||||
label: "Тип доступа",
|
||||
value: "any",
|
||||
options: [
|
||||
{ label: "Любой", value: "any" },
|
||||
{ label: "Платный", value: "paid" },
|
||||
@@ -315,7 +306,7 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
},
|
||||
promo: {
|
||||
label: "Промо-фрагмент",
|
||||
label: "Промо-фрагмент",
|
||||
value: "hide",
|
||||
options: [
|
||||
{ label: "Скрывать", value: "hide" },
|
||||
{ label: "Показывать", value: "show" },
|
||||
@@ -483,7 +474,7 @@ interface ChaptersEntity {
|
||||
title: string;
|
||||
isDraft: boolean;
|
||||
sortOrder: number;
|
||||
sortOrder: number;
|
||||
publishTime?: string;
|
||||
lastModificationTime: string;
|
||||
textLength: number;
|
||||
isAvailable: boolean;
|
||||
|
||||
@@ -12,24 +12,19 @@ class Bookriver implements Plugin.PluginBase {
|
||||
version = "1.0.0";
|
||||
icon = "src/ru/bookriver/icon.png";
|
||||
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
pageNo: number,
|
||||
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + `/genre?page=${pageNo}&perPage=24&sortingType=`;
|
||||
let url = this.site + `/genre?page=${pageNo}&perPage=24&sortingType=`;
|
||||
url += showLatestNovels
|
||||
? "last-update"
|
||||
url += showLatestNovels ? "last-update" : filters?.sort?.value || "bestseller";
|
||||
|
||||
|
||||
if (filters?.genres?.value?.length) {
|
||||
url += "&g=" + filters.genres.value.join(",");
|
||||
}
|
||||
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
const result = await fetchApi(url).then((res) => res.text());
|
||||
const loadedCheerio = parseHTML(result);
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
|
||||
@@ -47,20 +42,20 @@ class Bookriver implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
||||
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
||||
const result = await fetchApi(novelUrl);
|
||||
const result = await fetchApi(novelUrl).then((res) => res.text());
|
||||
const loadedCheerio = parseHTML(result);
|
||||
|
||||
|
||||
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
|
||||
const json: response = JSON.parse(jsonRaw || "{}");
|
||||
const book = json.props.pageProps.state.book?.bookPage;
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
url: novelUrl,
|
||||
name: book?.name,
|
||||
cover: book?.coverImages[0].url,
|
||||
summary: book?.annotation,
|
||||
author: book?.author?.name,
|
||||
author: book?.author?.name,
|
||||
genres: book?.tags?.map((tag) => tag.name).join(", "),
|
||||
status:
|
||||
book?.statusComplete === "writing"
|
||||
? NovelStatus.Ongoing
|
||||
@@ -68,14 +63,13 @@ class Bookriver implements Plugin.PluginBase {
|
||||
};
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
book?.ebook?.chapters?.forEach((chapter, chapterIndex) => {
|
||||
if (chapter.available) {
|
||||
chapters.push({
|
||||
name: chapter.name,
|
||||
name: chapter.name,
|
||||
releaseTime: dayjs(
|
||||
chapter?.firstPublishedAt || chapter.createdAt,
|
||||
url: this.site + "/reader/" + book?.slug + "/" + chapter.chapterId,
|
||||
releaseTime: dayjs(chapter?.firstPublishedAt || chapter.createdAt).format("LLL"),
|
||||
chapterNumber: chapterIndex + 1,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -105,7 +99,7 @@ class Bookriver implements Plugin.PluginBase {
|
||||
const json = (await result.json()) as responseSearch;
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
json.data?.books?.forEach((novel) =>
|
||||
novels.push({
|
||||
name: novel.name,
|
||||
cover: novel.coverImages[0].url,
|
||||
@@ -120,7 +114,7 @@ class Bookriver implements Plugin.PluginBase {
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Сортировка",
|
||||
label: "Сортировка",
|
||||
value: "bestseller",
|
||||
options: [
|
||||
{ label: "Бестселлеры", value: "bestseller" },
|
||||
{ label: "Дате добавления", value: "newest" },
|
||||
|
||||
@@ -12,13 +12,11 @@ class ficbook implements Plugin.PluginBase {
|
||||
version = "1.0.0";
|
||||
icon = "src/ru/ficbook/icon.png";
|
||||
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const baseUrl = this.site;
|
||||
let url = this.site;
|
||||
|
||||
if (filters?.directions?.value) {
|
||||
url += "/popular-fanfics/" + filters.directions.value;
|
||||
@@ -28,33 +26,29 @@ class ficbook implements Plugin.PluginBase {
|
||||
url += "/" + (filters?.sort?.value || "fanfiction") + "?p=" + pageNo;
|
||||
}
|
||||
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const result = await fetchApi(url).then((res) => res.text());
|
||||
const loadedCheerio = parseHTML(result);
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
|
||||
loadedCheerio("article.fanfic-inline").each(function () {
|
||||
const name = loadedCheerio(this).find("h3 > a").text().trim();
|
||||
let cover = loadedCheerio(this).find("picture > img").attr("src");
|
||||
loadedCheerio("article.fanfic-inline").each((index, element) => {
|
||||
const name = loadedCheerio(element).find("h3 > a").text().trim();
|
||||
let cover = loadedCheerio(element).find("picture > img").attr("src");
|
||||
const url = loadedCheerio(element).find("h3 > a").attr("href");
|
||||
|
||||
cover = cover
|
||||
? cover.replace(/covers\/m_|covers\/d_/g, "covers/")
|
||||
: defaultCover;
|
||||
if (!name || !url) return;
|
||||
|
||||
|
||||
novels.push({ name, cover, url: this.site + url.replace(/\?.*/g, "") });
|
||||
});
|
||||
|
||||
return novels;
|
||||
}
|
||||
|
||||
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
||||
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
||||
const result = await fetchApi(novelUrl);
|
||||
const body = await result.text();
|
||||
|
||||
const result = await fetchApi(novelUrl).then((res) => res.text());
|
||||
const loadedCheerio = parseHTML(result);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
url: novelUrl,
|
||||
@@ -79,7 +73,8 @@ class ficbook implements Plugin.PluginBase {
|
||||
const tags = loadedCheerio('div[class="tags"] > a')
|
||||
.map((index, element) => loadedCheerio(element).text())
|
||||
.get();
|
||||
.get();
|
||||
|
||||
if (tags.length) {
|
||||
novel.genres = tags.join(",");
|
||||
}
|
||||
|
||||
@@ -90,24 +85,24 @@ class ficbook implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
|
||||
if (loadedCheerio("#content").length == 1) {
|
||||
const name = loadedCheerio(".title-area > h2").text();
|
||||
const releaseTime = loadedCheerio(".part-date > span").attr("title");
|
||||
|
||||
|
||||
if (name) chapters.push({ name, url: novelUrl, releaseTime });
|
||||
} else {
|
||||
} else {
|
||||
loadedCheerio("li.part").each(function () {
|
||||
const name = loadedCheerio(this).find("h3").text();
|
||||
const releaseTime = loadedCheerio(this)
|
||||
.find("div > span")
|
||||
.attr("title");
|
||||
loadedCheerio("li.part").each((chapterIndex, element) => {
|
||||
const name = loadedCheerio(element).find("h3").text();
|
||||
const url = loadedCheerio(element).find("a:nth-child(1)").attr("href");
|
||||
if (!name || !url) return;
|
||||
|
||||
|
||||
const releaseTime = loadedCheerio(element).find("div > span").attr("title");
|
||||
chapters.push({
|
||||
name, url: this.site + url, releaseTime, chapterNumber: chapterIndex + 1,
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
novel.chapters = chapters;
|
||||
@@ -115,10 +110,8 @@ class ficbook implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterUrl: string): Promise<string> {
|
||||
async parseChapter(chapterUrl: string): Promise<string> {
|
||||
const result = await fetchApi(chapterUrl);
|
||||
const body = await result.text();
|
||||
|
||||
const result = await fetchApi(chapterUrl).then((res) => res.text());
|
||||
const loadedCheerio = parseHTML(result);
|
||||
let chapterText = "";
|
||||
|
||||
loadedCheerio("#content")
|
||||
@@ -172,7 +165,7 @@ class ficbook implements Plugin.PluginBase {
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Сортировка:",
|
||||
label: "Сортировка:",
|
||||
value: "fanfiction",
|
||||
options: [
|
||||
{ label: "Горячие работы", value: "fanfiction" },
|
||||
{ label: "Популярные ", value: "popular-fanfics" },
|
||||
|
||||
@@ -11,35 +11,33 @@ class Jaomix implements Plugin.PluginBase {
|
||||
version = "1.0.0";
|
||||
icon = "src/ru/jaomix/icon.png";
|
||||
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
pageNo: number,
|
||||
{
|
||||
showLatestNovels,
|
||||
filters,
|
||||
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + "/?searchrn";
|
||||
|
||||
if (filters?.lang?.value?.length) {
|
||||
url += filters.lang.value
|
||||
url += filters.lang.value
|
||||
.map((lang, idx) => `&lang[${idx}]=${lang}`)
|
||||
.join("");
|
||||
}
|
||||
|
||||
if (filters?.genre?.value?.include?.length) {
|
||||
url += filters.genre.value.include
|
||||
url += filters.genre.value.include
|
||||
.map((genre, idx) => `&genre[${idx}]=${genre}`)
|
||||
.join("");
|
||||
}
|
||||
|
||||
if (filters?.genre?.value?.exclude?.length) {
|
||||
url += filters.genre.value.exclude
|
||||
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 += "&sortdaycreate=" + (filters?.sortdaycreate?.value || "1");
|
||||
url +=
|
||||
url += "&sortby=" + (showLatestNovels ? "upd" : filters?.sortby?.value || "topweek");
|
||||
url += "&gpage=" + pageNo;
|
||||
|
||||
const body = await fetchApi(url).then((res) => res.text());
|
||||
@@ -76,7 +74,7 @@ class Jaomix implements Plugin.PluginBase {
|
||||
url: novelUrl,
|
||||
name: loadedCheerio('div[class="desc-book"] > h1').text().trim(),
|
||||
cover: loadedCheerio('div[class="img-book"] > img').attr("src"),
|
||||
cover: loadedCheerio('div[class="img-book"] > img').attr("src"),
|
||||
summary: loadedCheerio('div[id="desc-tab"]').text().trim(),
|
||||
};
|
||||
|
||||
loadedCheerio("#info-book > p").each(function () {
|
||||
@@ -93,14 +91,15 @@ class Jaomix implements Plugin.PluginBase {
|
||||
});
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
const totalChapters = loadedCheerio(".download-chapter div.title").length;
|
||||
|
||||
|
||||
loadedCheerio(".download-chapter div.title").each(function () {
|
||||
const name = loadedCheerio(this).find("a").attr("title");
|
||||
const releaseTime = loadedCheerio(this).find("time").text();
|
||||
loadedCheerio(".download-chapter div.title").each((chapterIndex, element) => {
|
||||
const name = loadedCheerio(element).find("a").attr("title");
|
||||
const url = loadedCheerio(element).find("a").attr("href");
|
||||
if (!name || !url) return;
|
||||
|
||||
|
||||
const releaseTime = loadedCheerio(element).find("time").text();
|
||||
chapters.push({ name, url, releaseTime, chapterNumber: totalChapters - chapterIndex });
|
||||
});
|
||||
|
||||
novel.chapters = chapters.reverse();
|
||||
|
||||
@@ -13,10 +13,9 @@ class TL implements Plugin.PluginBase {
|
||||
version = "1.0.0";
|
||||
icon = "src/ru/noveltl/icon.png";
|
||||
|
||||
|
||||
async popularNovels(
|
||||
page: number,
|
||||
page: number,
|
||||
{ filters }: Plugin.PopularNovelsOptions,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const result = await fetchApi(this.site + "/api/site/v2/graphql", {
|
||||
method: "post",
|
||||
@@ -104,21 +103,29 @@ class TL implements Plugin.PluginBase {
|
||||
novel.author =
|
||||
person.name.firstName + " " + (person.name?.lastName || "");
|
||||
}
|
||||
if (person.role == "illustrator" && person.name.firstName) {
|
||||
novel.artist =
|
||||
person.name.firstName + " " + (person.name?.lastName || "");
|
||||
}
|
||||
});
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
|
||||
json.data.project?.subprojects?.content?.forEach((work) =>
|
||||
json.data.project?.subprojects?.content?.forEach((work) =>
|
||||
work.volumes.content.forEach((volume) =>
|
||||
volume.chapters.forEach(
|
||||
(chapter) =>
|
||||
work.volumes.content.forEach((volume, volumeIndex) =>
|
||||
volume.chapters.forEach((chapter, chapterIndex) => {
|
||||
if (chapter.published) {
|
||||
chapters.push({
|
||||
chapters.push({
|
||||
name:
|
||||
(volume.shortName || "Том " + (volumeIndex + 1)) + " " +
|
||||
(chapter.title || "Глава " + (chapterIndex + 1)),
|
||||
url: "https://" + chapter.fullUrl,
|
||||
releaseTime: dayjs(chapter.publishDate).format("LLL"),
|
||||
chapterNumber: chapters.length + 1,
|
||||
});
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
novel.chapters = chapters;
|
||||
@@ -138,22 +145,21 @@ class TL implements Plugin.PluginBase {
|
||||
query:
|
||||
"query($url:String){chapter(chapter:{fullUrl:$url}){text{text}}}",
|
||||
variables: {
|
||||
variables: {
|
||||
url: decodeURI(chapterUrl),
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json = (await result.json()) as response;
|
||||
|
||||
|
||||
const loadedCheerio = parseHTML(json.data.chapter?.text?.text || "");
|
||||
const loadedCheerio = parseHTML(json.data.chapter?.text?.text || "");
|
||||
loadedCheerio("p > a[href]").each(function () {
|
||||
loadedCheerio("p > a[href]").each((index, element) => {
|
||||
let src = loadedCheerio(element).attr("href") || "";
|
||||
if (!src.startsWith("http")) {
|
||||
if (!src.startsWith("http")) {
|
||||
src = this.site + src;
|
||||
}
|
||||
}
|
||||
loadedCheerio(this).find("picture").remove();
|
||||
loadedCheerio(this).removeAttr("href");
|
||||
loadedCheerio(element).find("picture").remove();
|
||||
loadedCheerio(element).removeAttr("href");
|
||||
loadedCheerio(`<img src="${src}">`).appendTo(element);
|
||||
});
|
||||
|
||||
const chapterText = loadedCheerio.html();
|
||||
@@ -935,12 +941,12 @@ interface Volumes {
|
||||
}
|
||||
|
||||
interface VolumesContent {
|
||||
interface VolumesContent {
|
||||
shortName: null | string;
|
||||
chapters: ChapterElement[];
|
||||
}
|
||||
|
||||
interface ChapterElement {
|
||||
interface ChapterElement {
|
||||
title: null | string;
|
||||
publishDate: Date;
|
||||
fullUrl: string;
|
||||
published: boolean;
|
||||
|
||||
@@ -9,53 +9,42 @@ class RNBH implements Plugin.PluginBase {
|
||||
id = "RNBH.org";
|
||||
name = "RanobeHub";
|
||||
version = "1.0.0";
|
||||
site = "https://ranobehub.org/";
|
||||
site = "https://ranobehub.org";
|
||||
icon = "src/ru/ranobehub/icon.png";
|
||||
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
pageNo: number,
|
||||
{
|
||||
showLatestNovels,
|
||||
filters,
|
||||
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + "/api/search?page=" + pageNo + "&sort=";
|
||||
url += showLatestNovels
|
||||
? "last_chapter_at"
|
||||
? "last_chapter_at"
|
||||
: filters?.sort?.value || "computed_rating";
|
||||
url += "&status=" + (filters?.status?.value ? filters?.status?.value : "0");
|
||||
|
||||
if (filters) {
|
||||
if (filters.country?.value?.length) {
|
||||
url += "&country=" + filters.country.value.join(",");
|
||||
}
|
||||
}
|
||||
if (
|
||||
filters.tags?.value?.include?.length ||
|
||||
filters.events?.value?.include?.length
|
||||
) {
|
||||
const tagsPositive = [
|
||||
filters.tags.value?.include,
|
||||
filters.events.value?.include,
|
||||
]
|
||||
.flat()
|
||||
.filter((t) => t);
|
||||
url += "&tags:positive=" + tagsPositive.join(",");
|
||||
}
|
||||
if (
|
||||
filters?.tags?.value?.exclude?.length ||
|
||||
filters?.events?.value?.exclude?.length
|
||||
) {
|
||||
const tagsNegative = [
|
||||
filters.tags.value?.exclude,
|
||||
filters.events.value?.exclude,
|
||||
]
|
||||
.flat()
|
||||
.filter((t) => t);
|
||||
url += "&tags:negative=" + tagsNegative.join(",");
|
||||
|
||||
const includeTags = [
|
||||
filters.tags?.value?.include,
|
||||
filters.events?.value?.include,
|
||||
].flat().filter((t) => t);
|
||||
|
||||
if (includeTags.length) {
|
||||
url += "&tags:positive=" + includeTags.join(",");
|
||||
}
|
||||
|
||||
const excludeTags = [
|
||||
filters.tags?.value?.exclude,
|
||||
filters.events?.value?.exclude,
|
||||
].flat().filter((t) => t);
|
||||
|
||||
if (excludeTags.length) {
|
||||
url += "&tags:negative=" + excludeTags.join(",");
|
||||
}
|
||||
}
|
||||
url += "&take=40";
|
||||
const result = await fetchApi(url);
|
||||
const body = (await result.json()) as { resource: responseNovels[] };
|
||||
@@ -63,7 +52,7 @@ class RNBH implements Plugin.PluginBase {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
body.resource.forEach((novel) =>
|
||||
novels.push({
|
||||
novels.push({
|
||||
name: novel.names.rus || novel.names.eng || novel.names.original,
|
||||
cover: novel.poster.medium,
|
||||
url: novel.url,
|
||||
}),
|
||||
@@ -76,7 +65,7 @@ class RNBH implements Plugin.PluginBase {
|
||||
const novelId = novelUrl
|
||||
.substring("https://ranobehub.org/ranobe/".length)
|
||||
.split("-")[0];
|
||||
.split("-")[0];
|
||||
const result = await fetchApi(this.site + "/api/ranobe/" + novelId);
|
||||
const json = (await result.json()) as { data: responseNovel };
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
@@ -100,24 +89,20 @@ class RNBH implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
const fetchChaptersUrl = `${this.site}api/ranobe/${novelId}/contents`;
|
||||
|
||||
const chaptersRaw = await fetchApi(`${this.site}/api/ranobe/${novelId}/contents`);
|
||||
const chaptersJSON = (await chaptersRaw.json()) as {
|
||||
volumes: VolumesEntity[];
|
||||
};
|
||||
|
||||
|
||||
chaptersJSON.volumes.forEach(
|
||||
(volume) =>
|
||||
chaptersJSON.volumes.forEach((volume) =>
|
||||
volume.chapters?.forEach((chapter) => {
|
||||
chapters.push({
|
||||
name: chapter.name,
|
||||
url: chapter.url,
|
||||
url: chapter.url,
|
||||
releaseTime: dayjs(parseInt(chapter.changed_at, 10) * 1000).format(
|
||||
"LLL",
|
||||
releaseTime: dayjs(parseInt(chapter.changed_at, 10) * 1000).format("LLL"),
|
||||
chapterNumber: chapters.length + 1,
|
||||
});
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
novel.chapters = chapters;
|
||||
@@ -129,18 +114,15 @@ class RNBH implements Plugin.PluginBase {
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const loadedCheerio = parseHTML(body);
|
||||
loadedCheerio(".chapter-hoticons").remove();
|
||||
loadedCheerio(".chapter-hoticons").remove();
|
||||
const baseUrl = this.site;
|
||||
loadedCheerio("div.text:nth-child(1) img").each(function () {
|
||||
if (!loadedCheerio(this).attr("src")?.startsWith("http")) {
|
||||
const dataMediaId = loadedCheerio(this).attr("data-media-id");
|
||||
loadedCheerio("div.text:nth-child(1) img").each((index, element) => {
|
||||
if (!loadedCheerio(element).attr("src")?.startsWith("http")) {
|
||||
const dataMediaId = loadedCheerio(element).attr("data-media-id");
|
||||
loadedCheerio(element).attr("src", this.site + "/api/media/" + dataMediaId);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
let chapterText: string =
|
||||
let chapterText = loadedCheerio("div.text:nth-child(1)").html() || "";
|
||||
|
||||
// Remove script tags
|
||||
chapterText = chapterText?.replace(
|
||||
@@ -154,18 +136,21 @@ class RNBH implements Plugin.PluginBase {
|
||||
searchTerm: string,
|
||||
//pageNo: number | undefined = 1,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}api/fulltext/global?query=${searchTerm}&take=10`;
|
||||
const url = `${this.site}/api/fulltext/global?query=${searchTerm}&take=10`;
|
||||
const result = await fetchApi(url);
|
||||
const data = (await result.json()) as responseSearch[];
|
||||
const data = (await result.json()) as responseSearch[];
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
data
|
||||
?.find((item) => item?.meta?.key === "ranobe")
|
||||
?.data?.forEach((novel) =>
|
||||
novels.push({
|
||||
novels.push({
|
||||
name:
|
||||
novel?.names?.rus ||
|
||||
novel?.names?.eng ||
|
||||
novel.name ||
|
||||
novel?.names?.original ||
|
||||
"",
|
||||
url:
|
||||
"https://ranobehub.org/ranobe/" +
|
||||
novel?.url?.match(
|
||||
@@ -183,7 +168,7 @@ class RNBH implements Plugin.PluginBase {
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Сортировка",
|
||||
label: "Сортировка",
|
||||
value: "computed_rating",
|
||||
options: [
|
||||
{ label: "по рейтингу", value: "computed_rating" },
|
||||
{ label: "по дате обновления", value: "last_chapter_at" },
|
||||
@@ -1156,9 +1141,9 @@ interface responseNovels {
|
||||
counts: Counts;
|
||||
}
|
||||
interface Names {
|
||||
interface Names {
|
||||
eng?: string;
|
||||
rus?: string;
|
||||
rus?: string;
|
||||
original: string;
|
||||
}
|
||||
interface Poster {
|
||||
medium: string;
|
||||
|
||||
@@ -5,76 +5,47 @@ import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const statusKey = [
|
||||
NovelStatus.Ongoing,
|
||||
NovelStatus.Completed,
|
||||
NovelStatus.OnHiatus,
|
||||
NovelStatus.Cancelled,
|
||||
];
|
||||
|
||||
class RLIB implements Plugin.PluginBase {
|
||||
id = "RLIB";
|
||||
name = "RanobeLib";
|
||||
site = "https://ranobelib.me";
|
||||
version = "1.0.0";
|
||||
icon = "src/ru/ranobelib/icon.png";
|
||||
ui: string | number | undefined = undefined;
|
||||
|
||||
ui: string | undefined = undefined;
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
pageNo: number,
|
||||
{
|
||||
showLatestNovels,
|
||||
filters,
|
||||
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = `${this.site}/manga-list?sort=`;
|
||||
url += showLatestNovels
|
||||
? "last_chapter_at"
|
||||
let url = this.site + "/manga-list?sort=";
|
||||
url += showLatestNovels ? "last_chapter_at" : filters?.sort?.value || "rate";
|
||||
url += "&dir=" + (filters?.order?.value || "desc");
|
||||
|
||||
|
||||
if (filters.type?.value?.length) {
|
||||
Object.entries(filters || {}).forEach(([type, { value }]: any) => {
|
||||
if (value instanceof Array && value.length) {
|
||||
url += "&" + type + "[]=" + value.join("&" + type + "[]=");
|
||||
}
|
||||
}
|
||||
|
||||
if (filters.format?.value?.include?.length) {
|
||||
url += filters.format.value.include
|
||||
if (value?.include instanceof Array && value.include.length) {
|
||||
url += "&" + type + "[include][]=" +
|
||||
value.include.join("&" + type + "[include][]=");
|
||||
}
|
||||
}
|
||||
|
||||
if (filters.format?.value?.exclude?.length) {
|
||||
url += filters.format.value.exclude
|
||||
.map((i) => "&format[exclude][]=" + i).join("");
|
||||
}
|
||||
|
||||
if (filters.status?.value?.length) {
|
||||
url += filters.status.value.map((i) => "&status[]=" + i).join("");
|
||||
}
|
||||
|
||||
if (filters.statuss?.value?.length) {
|
||||
url += filters.statuss.value.map((i) => "&manga_status[]=" + i).join("");
|
||||
}
|
||||
|
||||
if (filters.genres?.value?.include?.length) {
|
||||
url += filters.genres.value.include
|
||||
.map((i) => "&genres[include][]=" + i).join("");
|
||||
}
|
||||
|
||||
if (filters.genres?.value?.exclude?.length) {
|
||||
url += filters.genres.value.exclude
|
||||
.map((i) => "&genres[exclude][]=" + i).join("");
|
||||
}
|
||||
|
||||
if (filters.tags?.value?.include?.length) {
|
||||
url += filters.tags.value.include
|
||||
.map((i) => "&tags[include][]=" + i).join("");
|
||||
}
|
||||
|
||||
if (filters.tags?.value?.exclude?.length) {
|
||||
url += filters.tags.value.exclude
|
||||
if (value?.exclude instanceof Array && value.exclude.length) {
|
||||
url += "&" + type + "[exclude][]=" +
|
||||
value.exclude.join("&" + type + "[exclude][]=");
|
||||
}
|
||||
});
|
||||
|
||||
url += "&page=" + pageNo;
|
||||
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
const result = await fetchApi(url).then((res) => res.text());
|
||||
const loadedCheerio = parseHTML(result);
|
||||
this.ui = loadedCheerio("a.header-right-menu__item")
|
||||
.attr("href")
|
||||
?.replace?.(/[^0-9]/g, "");
|
||||
@@ -92,9 +63,7 @@ class RLIB implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
||||
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
||||
const result = await fetchApi(novelUrl);
|
||||
const body = await result.text();
|
||||
const body = await fetchApi(novelUrl).then((res) => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
@@ -103,7 +72,7 @@ class RLIB implements Plugin.PluginBase {
|
||||
|
||||
novel.name = loadedCheerio(".media-name__main").text().trim();
|
||||
|
||||
|
||||
novel.cover = loadedCheerio(".container_responsive img").attr("src");
|
||||
|
||||
novel.summary = loadedCheerio(".media-description__text").text().trim();
|
||||
|
||||
@@ -120,15 +89,14 @@ class RLIB implements Plugin.PluginBase {
|
||||
.find('div[class="media-info-list__title"]')
|
||||
.text();
|
||||
|
||||
|
||||
if (name === "Статус перевода") {
|
||||
novel.status =
|
||||
loadedCheerio(this).find("div:nth-child(2)").text().trim() ===
|
||||
"Продолжается"
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed;
|
||||
if (name === "Автор") {
|
||||
novel.author = loadedCheerio(this)
|
||||
novel.author = loadedCheerio(this)
|
||||
.find('div[class="media-info-list__value"]')
|
||||
.text()
|
||||
.trim();
|
||||
} else if (name === "Художник") {
|
||||
novel.artist = loadedCheerio(this)
|
||||
.find('div[class="media-info-list__value"]')
|
||||
.text()
|
||||
.trim();
|
||||
}
|
||||
@@ -146,16 +114,23 @@ class RLIB implements Plugin.PluginBase {
|
||||
?.trim()
|
||||
?.slice(0, -1);
|
||||
|
||||
|
||||
const chaptersJson: responseBook = JSON.parse(chaptersRaw);
|
||||
const totalChapters = chaptersJson.chapters.list?.length || 0;
|
||||
novel.status = statusKey[chaptersJson.manga.status - 1] || NovelStatus.Unknown;
|
||||
this.ui = chaptersJson?.user?.id;
|
||||
|
||||
|
||||
chaptersJson.chapters?.list?.forEach((chapter, chapterIndex) =>
|
||||
chapters.push({
|
||||
chapters.push({
|
||||
name: `Том ${chapter.chapter_volume} Глава ${chapter.chapter_number} ${chapter.chapter_name}`?.trim(),
|
||||
name:
|
||||
"Том " + chapter.chapter_volume +
|
||||
" Глава " + chapter.chapter_number +
|
||||
(chapter.chapter_name ? " " + chapter.chapter_name.trim() : ""),
|
||||
url:
|
||||
url:
|
||||
`${this.site}/${chaptersJson.manga.slug}/v${chapter.chapter_volume}/c${chapter.chapter_number}?bid=` +
|
||||
this.site + "/" + chaptersJson.manga.slug +
|
||||
"/v" + chapter.chapter_volume + "/c" + chapter.chapter_number +
|
||||
"?bid=" + (chapter.branch_id || ""),
|
||||
releaseTime: dayjs(chapter.chapter_created_at).format("LLL"),
|
||||
chapterNumber: totalChapters - chapterIndex,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -166,25 +141,22 @@ class RLIB implements Plugin.PluginBase {
|
||||
async parseChapter(chapterUrl: string): Promise<string> {
|
||||
const result = await fetchApi(
|
||||
chapterUrl + (this.ui ? "&ui=" + this.ui : ""),
|
||||
chapterUrl + (this.ui ? "&ui=" + this.ui : ""),
|
||||
);
|
||||
).then((res) => res.text());
|
||||
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const baseUrl = this.site;
|
||||
const loadedCheerio = parseHTML(result);
|
||||
loadedCheerio(".reader-container img").each((index, element) => {
|
||||
const src =
|
||||
const src =
|
||||
loadedCheerio(element).attr("data-src") ||
|
||||
loadedCheerio(element).attr("src");
|
||||
if (!src?.startsWith("http")) {
|
||||
if (!src?.startsWith("http")) {
|
||||
loadedCheerio(element).attr("src", this.site + src);
|
||||
} else {
|
||||
} else {
|
||||
loadedCheerio(element).attr("src", src);
|
||||
}
|
||||
}
|
||||
loadedCheerio(element).removeAttr("data-src");
|
||||
});
|
||||
|
||||
const chapterText = loadedCheerio(".reader-container").html();
|
||||
const chapterText = loadedCheerio(".reader-container").html();
|
||||
return chapterText || "";
|
||||
}
|
||||
|
||||
@@ -214,7 +186,7 @@ class RLIB implements Plugin.PluginBase {
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Сортировка",
|
||||
label: "Сортировка",
|
||||
value: "rate",
|
||||
options: [
|
||||
{ label: "Рейтинг", value: "rate" },
|
||||
{ label: "Имя", value: "name" },
|
||||
@@ -227,7 +199,7 @@ class RLIB implements Plugin.PluginBase {
|
||||
},
|
||||
order: {
|
||||
label: "Порядок",
|
||||
label: "Порядок",
|
||||
value: "desc",
|
||||
options: [
|
||||
{ label: "По убыванию", value: "desc" },
|
||||
{ label: "По возрастанию", value: "asc" },
|
||||
@@ -272,7 +244,7 @@ class RLIB implements Plugin.PluginBase {
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
},
|
||||
manga_status: {
|
||||
label: "Статус тайтла",
|
||||
value: [],
|
||||
options: [
|
||||
@@ -470,7 +442,7 @@ interface ListEntity {
|
||||
chapter_created_at: string;
|
||||
status?: null;
|
||||
price: number;
|
||||
price: number;
|
||||
branch_id?: number;
|
||||
username: string;
|
||||
}
|
||||
interface TeamsEntity {
|
||||
@@ -504,7 +476,7 @@ interface TeamsEntity1 {
|
||||
is_active: number;
|
||||
}
|
||||
interface User {
|
||||
interface User {
|
||||
id: string;
|
||||
avatar: string;
|
||||
access: boolean;
|
||||
isAdmin: boolean;
|
||||
|
||||
@@ -13,7 +13,6 @@ class RNRF implements Plugin.PluginBase {
|
||||
version = "1.0.0";
|
||||
icon = "src/ru/ranoberf/icon.png";
|
||||
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
|
||||
@@ -69,12 +68,13 @@ class RNRF implements Plugin.PluginBase {
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
|
||||
|
||||
book?.chapters.forEach((chapter, chapterIndex) => {
|
||||
if (!chapter.isDonate || chapter.isUserPaid) {
|
||||
chapters.push({
|
||||
name: chapter.title,
|
||||
name: chapter.title,
|
||||
url: this.site + chapter.url,
|
||||
releaseTime: dayjs(chapter.publishedAt).format("LLL"),
|
||||
chapterNumber: book.chapters.length - chapterIndex,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -84,10 +84,9 @@ class RNRF implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterUrl: string): Promise<string> {
|
||||
async parseChapter(chapterUrl: string): Promise<string> {
|
||||
const result = await fetchApi(chapterUrl);
|
||||
const result = await fetchApi(chapterUrl).then((res) => res.text());
|
||||
|
||||
|
||||
let loadedCheerio = parseHTML(result);
|
||||
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
|
||||
const json: response = JSON.parse(jsonRaw || "{}");
|
||||
|
||||
@@ -95,11 +94,10 @@ class RNRF implements Plugin.PluginBase {
|
||||
json.props.pageProps?.chapter?.content?.text || "",
|
||||
);
|
||||
|
||||
|
||||
const baseUrl = this.site;
|
||||
loadedCheerio("img").each(function () {
|
||||
if (!loadedCheerio(this).attr("src")?.startsWith("http")) {
|
||||
const src = loadedCheerio(this).attr("src");
|
||||
loadedCheerio("img").each((index, element) => {
|
||||
if (!loadedCheerio(element).attr("src")?.startsWith("http")) {
|
||||
const src = loadedCheerio(element).attr("src");
|
||||
loadedCheerio(element).attr("src", this.site + src);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -133,7 +131,7 @@ class RNRF implements Plugin.PluginBase {
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Сортировка",
|
||||
label: "Сортировка",
|
||||
value: "popular",
|
||||
options: [
|
||||
{ label: "Рейтинг", value: "popular" },
|
||||
{ label: "Дате добавления", value: "new" },
|
||||
|
||||
@@ -11,65 +11,39 @@ class ReN implements Plugin.PluginBase {
|
||||
version = "1.0.0";
|
||||
icon = "src/ru/renovels/icon.png";
|
||||
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
pageNo: number,
|
||||
{
|
||||
showLatestNovels,
|
||||
filters,
|
||||
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + "/api/search/catalog/?count=30&ordering=";
|
||||
let url = this.site + "/api/search/catalog/?count=30&ordering=";
|
||||
url += filters?.order?.value
|
||||
? (filters.order.value as string).replace("+", "")
|
||||
url += filters?.order?.value ? "-" : "";
|
||||
url += showLatestNovels ? "chapter_date" : filters?.sort?.value || "rating";
|
||||
|
||||
|
||||
if (filters?.genres?.value?.include?.length) {
|
||||
Object.entries(filters || {}).forEach(([type, { value }]: any) => {
|
||||
if (value instanceof Array && value.length) {
|
||||
url += "&" + type + "=" +
|
||||
value.join("&" + type + "=");
|
||||
}
|
||||
}
|
||||
|
||||
if (filters?.genres?.value?.exclude?.length) {
|
||||
url += filters.genres.value.exclude
|
||||
if (value?.include instanceof Array && value.include.length) {
|
||||
url += "&" + type + "=" +
|
||||
value.include.join("&" + type + "=");
|
||||
}
|
||||
}
|
||||
|
||||
if (filters?.status?.value?.length) {
|
||||
url += filters.status.value.map((i) => "&status=" + i).join("");
|
||||
}
|
||||
|
||||
if (filters?.types?.value?.length) {
|
||||
url += filters.types.value.map((i) => "&types=" + i).join("");
|
||||
}
|
||||
|
||||
if (filters?.categories?.value?.include?.length) {
|
||||
url += filters.categories.value.include
|
||||
.map((i) => "&categories=" + i).join("");
|
||||
}
|
||||
|
||||
if (filters?.categories?.value?.exclude?.length) {
|
||||
url += filters.categories.value.exclude
|
||||
.map((i) => "&exclude_categories=" + i).join("");
|
||||
}
|
||||
|
||||
if (filters?.age_limit?.value?.length) {
|
||||
if (value?.exclude instanceof Array && value.exclude.length) {
|
||||
url += "&exclude_" + type + "=" +
|
||||
value.exclude.join("&exclude_" + type + "=");
|
||||
}
|
||||
});
|
||||
|
||||
url += "&page=" + pageNo;
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = (await result.json()) as { content: responseNovels[] };
|
||||
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
body.content.forEach((novel) =>
|
||||
const novels: Plugin.NovelItem[] = body.content.map((novel) => ({
|
||||
name: novel.main_name || novel.secondary_name,
|
||||
name: novel.main_name || novel.secondary_name,
|
||||
cover: this.site + (novel.img.high || novel.img.mid || novel.img.low),
|
||||
url: this.site + "/novel/" + novel.dir,
|
||||
url: this.site + "/novel/" + novel.dir,
|
||||
}),
|
||||
}));
|
||||
|
||||
return novels;
|
||||
}
|
||||
@@ -81,12 +55,12 @@ class ReN implements Plugin.PluginBase {
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
url: novelUrl,
|
||||
url: novelUrl,
|
||||
name: body.content.main_name || body.content.secondary_name || body.content.another_name,
|
||||
summary: body.content.description,
|
||||
cover:
|
||||
this.site +
|
||||
this.site +
|
||||
(body.content.img?.high ||
|
||||
(body.content.img.high ||
|
||||
body.content.img.mid ||
|
||||
body.content.img.low),
|
||||
status:
|
||||
body.content.status.name === "Продолжается"
|
||||
@@ -103,17 +77,15 @@ class ReN implements Plugin.PluginBase {
|
||||
novel.genres = tags.join(",");
|
||||
}
|
||||
|
||||
|
||||
const all = body.content.count_chapters / 100 + 1;
|
||||
const branch_id = body.content.branches?.[0]?.id || body.content.id;
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
|
||||
for (let i = 0; i < all; i++) {
|
||||
const chapterResult = await fetchApi(
|
||||
this.site +
|
||||
this.site +
|
||||
"/api/titles/chapters/?branch_id=" +
|
||||
branch_id +
|
||||
"&count=100&page=" +
|
||||
"/api/titles/chapters/?branch_id=" + branch_id +
|
||||
"&count=100&page=" + (i + 1),
|
||||
);
|
||||
const volumes = (await chapterResult.json()) as {
|
||||
content: responseСhapters[];
|
||||
@@ -122,9 +94,13 @@ class ReN implements Plugin.PluginBase {
|
||||
volumes.content.forEach((chapter) => {
|
||||
if (!chapter.is_paid || chapter.is_bought) {
|
||||
chapters.push({
|
||||
chapters.push({
|
||||
name: `Том ${chapter.tome} Глава ${chapter.chapter} ${chapter.name}`?.trim(),
|
||||
name:
|
||||
"Том " + chapter.tome +
|
||||
" Глава " + chapter.chapter +
|
||||
(chapter.name ? " " + chapter.name.trim() : ""),
|
||||
url: `${this.site}/novel/${novelID}/${chapter.id}/`,
|
||||
releaseTime: dayjs(chapter.upload_date).format("LLL"),
|
||||
chapterNumber: chapter.index,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -154,7 +130,7 @@ class ReN implements Plugin.PluginBase {
|
||||
body.content.forEach((novel) =>
|
||||
novels.push({
|
||||
name: novel.main_name || novel.secondary_name,
|
||||
name: novel.main_name || novel.secondary_name,
|
||||
cover: this.site + (novel.img.high || novel.img.mid || novel.img.low),
|
||||
url: this.site + "/novel/" + novel.dir,
|
||||
}),
|
||||
);
|
||||
@@ -167,7 +143,7 @@ class ReN implements Plugin.PluginBase {
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Сортировка",
|
||||
label: "Сортировка",
|
||||
value: "rating",
|
||||
options: [
|
||||
{ label: "Рейтинг", value: "rating" },
|
||||
{ label: "Просмотры", value: "views" },
|
||||
@@ -183,7 +159,7 @@ class ReN implements Plugin.PluginBase {
|
||||
value: "",
|
||||
options: [
|
||||
{ label: "По убыванию", value: "-" },
|
||||
{ label: "По убыванию", value: "-" },
|
||||
{ label: "По возрастанию", value: "" },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
@@ -9,59 +9,47 @@ class TuNovelaLigera implements Plugin.PluginBase {
|
||||
id = "tunovelaligera";
|
||||
name = "TuNovelaLigera";
|
||||
icon = "src/es/tunovelaligera/icon.png";
|
||||
site = "https://tunovelaligera.com/";
|
||||
site = "https://tunovelaligera.com";
|
||||
version = "1.0.0";
|
||||
|
||||
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
{
|
||||
showLatestNovels,
|
||||
filters,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = `${this.site}`;
|
||||
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}?m_orderby=`;
|
||||
link += (showLatestNovels ? "latest" : filters?.order?.value || "rating");
|
||||
|
||||
link += `/page/${pageNo}`;
|
||||
|
||||
link += filters.order.value;
|
||||
|
||||
const headers = new Headers();
|
||||
const result = await fetchApi(link, { headers });
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const result = await fetchApi(link).then((res) => res.text());
|
||||
const loadedCheerio = parseHTML(result);
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".page-item-detail").each((i, el) => {
|
||||
const novelName = loadedCheerio(el).find(".h5 > a").text();
|
||||
const novelCover = loadedCheerio(el).find("img").attr("src");
|
||||
const novelUrl = loadedCheerio(el).find(".h5 > a").attr("href");
|
||||
const name = loadedCheerio(el).find(".h5 > a").text();
|
||||
const cover = loadedCheerio(el).find("img").attr("src");
|
||||
const url = loadedCheerio(el).find(".h5 > a").attr("href");
|
||||
if (!url) return;
|
||||
|
||||
if (!novelUrl) return;
|
||||
const novel = {
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
url: novelUrl,
|
||||
};
|
||||
|
||||
novels.push(novel);
|
||||
novels.push({ name, cover, url });
|
||||
});
|
||||
|
||||
return novels;
|
||||
}
|
||||
|
||||
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
||||
const url = novelUrl;
|
||||
const headers = new Headers();
|
||||
const result = await fetchApi(url, { headers });
|
||||
const result = await fetchApi(novelUrl);
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
url,
|
||||
url: novelUrl,
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
@@ -82,22 +70,18 @@ class TuNovelaLigera implements Plugin.PluginBase {
|
||||
.find(".summary-heading > h5")
|
||||
.text()
|
||||
.trim();
|
||||
const detail = loadedCheerio(this)
|
||||
.find(".summary-content")
|
||||
.text()
|
||||
.trim();
|
||||
const detail = loadedCheerio(this).find(".summary-content").text().trim();
|
||||
|
||||
switch (detailName) {
|
||||
case "Generos":
|
||||
case "Género(s)":
|
||||
novel.genres = detail.replace(/, /g, ",");
|
||||
break;
|
||||
case "Autores":
|
||||
case "Autor(es)":
|
||||
novel.author = detail;
|
||||
break;
|
||||
case "Estado":
|
||||
novel.status =
|
||||
detail.includes("OnGoing") ||
|
||||
detail.includes("Updating")
|
||||
detail.includes("OnGoing") || detail.includes("Updating")
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed;
|
||||
break;
|
||||
@@ -113,7 +97,7 @@ class TuNovelaLigera implements Plugin.PluginBase {
|
||||
lastPage = +loadedCheerio(".lcp_paginator li:last").prev().text();
|
||||
|
||||
const getChapters = async () => {
|
||||
const n = url.split("/");
|
||||
const n = novelUrl.split("/");
|
||||
const novelName = n[4];
|
||||
const formData = new FormData();
|
||||
formData.append("action", "madara_load_more");
|
||||
@@ -122,14 +106,10 @@ class TuNovelaLigera implements Plugin.PluginBase {
|
||||
formData.append("vars[category_name]", novelName);
|
||||
formData.append("vars[posts_per_page]", "10000");
|
||||
|
||||
const result = await fetchApi(
|
||||
`${this.site}wp-admin/admin-ajax.php`,
|
||||
{
|
||||
const result = await fetchApi(this.site + '/wp-admin/admin-ajax.php', {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
headers,
|
||||
}
|
||||
);
|
||||
});
|
||||
const text = await result.text();
|
||||
|
||||
loadedCheerio = parseHTML(text);
|
||||
@@ -140,8 +120,7 @@ class TuNovelaLigera implements Plugin.PluginBase {
|
||||
.replace(/[\t\n]/g, "")
|
||||
.trim();
|
||||
const releaseDate = null;
|
||||
const chapterUrl =
|
||||
loadedCheerio(el).find("a").attr("href") || "";
|
||||
const chapterUrl = loadedCheerio(el).find("a").attr("href") || "";
|
||||
|
||||
chapter.push({
|
||||
name: chapterName,
|
||||
@@ -155,25 +134,21 @@ class TuNovelaLigera implements Plugin.PluginBase {
|
||||
const getPageChapters = async () => {
|
||||
for (let i = 1; i <= lastPage; i++) {
|
||||
const chaptersUrl = `${novelUrl}?lcp_page0=${i}`;
|
||||
const result = await fetchApi(chaptersUrl, { headers });
|
||||
const result = await fetchApi(chaptersUrl);
|
||||
const chaptersHTML = await result.text();
|
||||
|
||||
let chapterCheerio = parseHTML(chaptersHTML);
|
||||
|
||||
chapterCheerio(".lcp_catlist li").each((i, el) => {
|
||||
chapterCheerio("#lcp_instance_0 li").each((i, el) => {
|
||||
const chapterName = chapterCheerio(el)
|
||||
.find("a")
|
||||
.text()
|
||||
.replace(/[\t\n]/g, "")
|
||||
.trim();
|
||||
|
||||
const releaseDate = chapterCheerio(el)
|
||||
.find("span")
|
||||
.text()
|
||||
.trim();
|
||||
const releaseDate = chapterCheerio(el).find("span").text().trim();
|
||||
|
||||
const chapterUrl =
|
||||
chapterCheerio(el).find("a").attr("href") || "";
|
||||
const chapterUrl = chapterCheerio(el).find("a").attr("href") || "";
|
||||
|
||||
chapter.push({
|
||||
name: chapterName,
|
||||
@@ -197,24 +172,21 @@ class TuNovelaLigera implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterUrl: string): Promise<string> {
|
||||
const headers = new Headers();
|
||||
const result = await fetchApi(chapterUrl);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio("#hola_siguiente").next().find("div").remove();
|
||||
const chapterText =
|
||||
loadedCheerio("#hola_siguiente").next().html() || "";
|
||||
const chapterText = loadedCheerio("#hola_siguiente").next().html() || "";
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo?: number | undefined
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}?s=${searchTerm}&post_type=wp-manga`;
|
||||
const url = `${this.site}/?s=${searchTerm}&post_type=wp-manga`;
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
@@ -224,15 +196,12 @@ class TuNovelaLigera implements Plugin.PluginBase {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".c-tabs-item__content").each((i, el) => {
|
||||
const novelName = loadedCheerio(el).find(".h4 > a").text();
|
||||
const novelCover = loadedCheerio(el).find("img").attr("src");
|
||||
const novelUrl = loadedCheerio(el).find(".h4 > a").attr("href");
|
||||
if (!novelUrl) return;
|
||||
novels.push({
|
||||
name: novelName,
|
||||
url: novelUrl,
|
||||
cover: novelCover,
|
||||
});
|
||||
const name = loadedCheerio(el).find(".h4 > a").text();
|
||||
const cover = loadedCheerio(el).find("img").attr("src");
|
||||
const url = loadedCheerio(el).find(".h4 > a").attr("href");
|
||||
if (!url) return;
|
||||
|
||||
novels.push({ name, cover, url });
|
||||
});
|
||||
|
||||
return novels;
|
||||
@@ -244,20 +213,15 @@ class TuNovelaLigera implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
order: {
|
||||
value: "?m_orderby=rating",
|
||||
value: "rating",
|
||||
label: "Ordenado por",
|
||||
options: [
|
||||
{ label: "Lo mas reciente", value: "?m_orderby=latest" },
|
||||
|
||||
{ label: "A-Z", value: "?m_orderby=alphabet" },
|
||||
|
||||
{ label: "Clasificación", value: "?m_orderby=rating" },
|
||||
|
||||
{ label: "Trending", value: "?m_orderby=trending" },
|
||||
|
||||
{ label: "Mas visto", value: "?m_orderby=views" },
|
||||
|
||||
{ label: "Nuevo", value: "?m_orderby=new-manga" },
|
||||
{ label: "Lo mas reciente", value: "latest" },
|
||||
{ label: "A-Z", value: "alphabet" },
|
||||
{ label: "Clasificación", value: "rating" },
|
||||
{ label: "Trending", value: "trending" },
|
||||
{ label: "Mas visto", value: "views" },
|
||||
{ label: "Nuevo", value: "new-manga" },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
@@ -267,100 +231,51 @@ class TuNovelaLigera implements Plugin.PluginBase {
|
||||
options: [
|
||||
{ label: "None", value: "" },
|
||||
{ label: "Acción", value: "accion" },
|
||||
|
||||
{ label: "Adulto", value: "adulto" },
|
||||
|
||||
{ label: "Artes Marciales", value: "artes-marciales" },
|
||||
|
||||
{ label: "Aventura", value: "aventura" },
|
||||
|
||||
{ label: "Ciencia Ficción", value: "ciencia-ficcion" },
|
||||
|
||||
{ label: "Comedia", value: "comedia" },
|
||||
|
||||
{ label: "Deportes", value: "deportes" },
|
||||
|
||||
{ label: "Drama", value: "drama" },
|
||||
|
||||
{ label: "Eastern Fantasy", value: "eastern-fantasy" },
|
||||
|
||||
{ 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 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,
|
||||
|
||||
@@ -22,7 +22,7 @@ class IfreedomPlugin implements Plugin.PluginBase {
|
||||
|
||||
constructor(metadata: IfreedomMetadata) {
|
||||
this.id = metadata.id;
|
||||
this.name = metadata.sourceName + "[ifreedom]";
|
||||
this.name = metadata.sourceName;
|
||||
this.icon = `multisrc/ifreedom/icons/${metadata.id}.png`;
|
||||
this.site = metadata.sourceSite;
|
||||
this.version = "1.0.0";
|
||||
@@ -31,20 +31,17 @@ class IfreedomPlugin implements Plugin.PluginBase {
|
||||
|
||||
async popularNovels(
|
||||
page: number,
|
||||
{ filters, showLatestNovels }: Plugin.PopularNovelsOptions,
|
||||
{ filters, showLatestNovels }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + "/vse-knigi/?sort=" +
|
||||
(showLatestNovels ? "По дате обновления" : filters?.sort?.value || "По рейтингу");
|
||||
|
||||
if (filters?.status?.value instanceof Array) {
|
||||
url += filters.status.value.map((i) => "&status[]=" + i).join("");
|
||||
}
|
||||
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("");
|
||||
Object.entries(filters || {}).forEach(([type, { value }]) => {
|
||||
if (value instanceof Array && value.length) {
|
||||
url += "&" + type + "[]=" + value.join("&" + type + "[]=");
|
||||
}
|
||||
});
|
||||
|
||||
url += "&bpage=" + page;
|
||||
|
||||
const body = await fetchApi(url).then((res) => res.text());
|
||||
@@ -102,14 +99,14 @@ class IfreedomPlugin implements Plugin.PluginBase {
|
||||
if (novel.author == "Не указан") delete novel.author;
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
loadedCheerio("div.li-ranobe").each(function () {
|
||||
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();
|
||||
const totalChapters = loadedCheerio("div.li-ranobe").length;
|
||||
|
||||
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 loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio(".entry-content img").each(function () {
|
||||
const srcset = loadedCheerio(this).attr("srcset")?.split?.(" ");
|
||||
loadedCheerio(".entry-content img").each((index, element) => {
|
||||
const srcset = loadedCheerio(element).attr("srcset")?.split?.(" ");
|
||||
if (srcset?.length) {
|
||||
const bestlink = srcset.filter((url) => url.startsWith("http")).unshift();
|
||||
if (typeof bestlink == 'string') loadedCheerio(this).attr("src", bestlink);
|
||||
loadedCheerio(element).removeAttr("srcset");
|
||||
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 */
|
||||
.map<MadaraMetadata>((p) => {
|
||||
let d = false;
|
||||
if (p?.filters?.genres?.options?.length) {
|
||||
p.filters.genres.options.unshift({ label: "NONE", value: "" })
|
||||
}
|
||||
const filters: Filters = {};
|
||||
for (const k in p.filters) {
|
||||
const f = p.filters[k as keyof typeof p.filters];
|
||||
|
||||
@@ -4,9 +4,12 @@ import { Plugin } from "@typings/plugin";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
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 {
|
||||
genres?: string;
|
||||
novels?: string;
|
||||
@@ -46,10 +49,8 @@ class MadaraPlugin implements Plugin.PluginBase {
|
||||
|
||||
constructor(metadata: MadaraMetadata) {
|
||||
this.id = metadata.id;
|
||||
this.name = metadata.sourceName + "[madara]";
|
||||
const iconFileName = metadata.sourceName
|
||||
.replace(/\s+/g, "")
|
||||
.toLowerCase();
|
||||
this.name = metadata.sourceName;
|
||||
const iconFileName = metadata.sourceName.replace(/\s+/g, "").toLowerCase();
|
||||
this.icon = `multisrc/madara/icons/${iconFileName}.png`;
|
||||
this.site = metadata.sourceSite;
|
||||
this.version = "1.0.0";
|
||||
@@ -61,45 +62,35 @@ class MadaraPlugin implements Plugin.PluginBase {
|
||||
{
|
||||
filters,
|
||||
showLatestNovels,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
}: 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;
|
||||
url +=
|
||||
(this.options?.path?.genres || MadaraDefaultPath.genres) +
|
||||
"/" + filters.genres.value;
|
||||
} else {
|
||||
url += this.options?.path?.novels || MadaraDefaultPath.novels;
|
||||
}
|
||||
|
||||
url +=
|
||||
"/page/" +
|
||||
pageNo +
|
||||
"/" +
|
||||
"?m_orderby=" +
|
||||
url += "/page/" + pageNo + "/?m_orderby=" +
|
||||
(showLatestNovels ? "latest" : filters?.sort?.value || "rating");
|
||||
|
||||
const body = await fetchApi(url).then((res) => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio(".manga-title-badges").remove();
|
||||
|
||||
loadedCheerio(".page-item-detail").each(function () {
|
||||
const novelName = loadedCheerio(this)
|
||||
.find(".post-title")
|
||||
.text()
|
||||
.trim();
|
||||
let image = loadedCheerio(this).find("img");
|
||||
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;
|
||||
|
||||
let novelUrl =
|
||||
loadedCheerio(this)
|
||||
.find(".post-title")
|
||||
.find("a")
|
||||
.attr("href") || "";
|
||||
const novel: Plugin.NovelItem = {
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
@@ -129,27 +120,27 @@ class MadaraPlugin implements Plugin.PluginBase {
|
||||
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();
|
||||
const detail = loadedCheerio(this).find(".summary-content").text().trim();
|
||||
|
||||
switch (detailName) {
|
||||
case "Genre(s)":
|
||||
case "Género(s)":
|
||||
case "التصنيفات":
|
||||
novel.genres = detail.replace(/[\\t\\n]/g, ",");
|
||||
novel.genres = detail;
|
||||
break;
|
||||
case "Author(s)":
|
||||
case "Autor(es)":
|
||||
case "المؤلف":
|
||||
case "المؤلف (ين)":
|
||||
novel.author = detail;
|
||||
break;
|
||||
case "Status":
|
||||
case "الحالة":
|
||||
case "Estado":
|
||||
novel.status =
|
||||
detail.includes("OnGoing") || detail.includes("مستمرة")
|
||||
? NovelStatus.Ongoing
|
||||
@@ -166,10 +157,14 @@ class MadaraPlugin implements Plugin.PluginBase {
|
||||
.next()
|
||||
.text()
|
||||
.trim();
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
let html = "";
|
||||
|
||||
let html;
|
||||
|
||||
if (this.options?.useNewChapterEndpoint !== true) {
|
||||
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") ||
|
||||
@@ -183,42 +178,34 @@ class MadaraPlugin implements Plugin.PluginBase {
|
||||
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();
|
||||
const totalChapters = loadedCheerio(".wp-manga-chapter").length;
|
||||
loadedCheerio(".wp-manga-chapter").each((chapterIndex, element) => {
|
||||
const chapterName = loadedCheerio(element).find("a").text().trim();
|
||||
|
||||
let releaseDate = null;
|
||||
releaseDate = loadedCheerio(this)
|
||||
let releaseDate = loadedCheerio(element)
|
||||
.find("span.chapter-release-date")
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
if (releaseDate) {
|
||||
releaseDate = parseMadaraDate(releaseDate);
|
||||
releaseDate = this.parseData(releaseDate);
|
||||
} else {
|
||||
/**
|
||||
* Insert current date
|
||||
*/
|
||||
|
||||
releaseDate = dayjs().format("LL");
|
||||
}
|
||||
|
||||
let chapterUrl = loadedCheerio(this).find("a").attr("href") || "";
|
||||
let chapterUrl = loadedCheerio(element).find("a").attr("href") || "";
|
||||
|
||||
chapters.push({
|
||||
name: chapterName,
|
||||
releaseTime: releaseDate,
|
||||
url: chapterUrl,
|
||||
releaseTime: releaseDate || null,
|
||||
chapterNumber: totalChapters - chapterIndex,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -233,13 +220,14 @@ class MadaraPlugin implements Plugin.PluginBase {
|
||||
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
|
||||
pageNo?: number | undefined,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
const url = this.site + "?s=" + searchTerm + "&post_type=wp-manga";
|
||||
@@ -249,19 +237,13 @@ class MadaraPlugin implements Plugin.PluginBase {
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio(".c-tabs-item__content").each(function () {
|
||||
const novelName = loadedCheerio(this)
|
||||
.find(".post-title")
|
||||
.text()
|
||||
.trim();
|
||||
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") || "";
|
||||
loadedCheerio(this).find(".post-title").find("a").attr("href") || "";
|
||||
const novel = {
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
@@ -272,7 +254,71 @@ class MadaraPlugin implements Plugin.PluginBase {
|
||||
});
|
||||
return novels;
|
||||
}
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
return await fetchFile(url, {});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return dayJSDate.format("LL");
|
||||
};
|
||||
}
|
||||
|
||||
@@ -76,10 +76,6 @@
|
||||
"label": "Fantasy Romance",
|
||||
"value": "fantasy-romance"
|
||||
},
|
||||
{
|
||||
"label": "Game",
|
||||
"value": "game"
|
||||
},
|
||||
{
|
||||
"label": "Gender Bender",
|
||||
"value": "gender-bender"
|
||||
|
||||
@@ -3,8 +3,7 @@ import { Filters, FilterTypes } from "@libs/filterInputs";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
// import { defaultCover } from "@libs/defaultCover";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export interface ReadwnMetadata {
|
||||
id: string;
|
||||
@@ -23,9 +22,8 @@ class ReadwnPlugin implements Plugin.PluginBase {
|
||||
|
||||
constructor(metadata: ReadwnMetadata) {
|
||||
this.id = metadata.id;
|
||||
this.name = metadata.sourceName + "[readwn]";
|
||||
const iconFileName = metadata.sourceName.replace(/\s+/g, "").toLowerCase();
|
||||
this.icon = `multisrc/readwn/icons/${iconFileName}.png`;
|
||||
this.name = metadata.sourceName;
|
||||
this.icon = `multisrc/readwn/icons/${metadata.id}.png`;
|
||||
this.site = metadata.sourceSite;
|
||||
this.version = "1.0.0";
|
||||
this.filters = metadata.filters;
|
||||
@@ -70,7 +68,8 @@ class ReadwnPlugin implements Plugin.PluginBase {
|
||||
|
||||
novel.name = loadedCheerio("h1.novel-title").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")
|
||||
.text()
|
||||
@@ -84,7 +83,10 @@ class ReadwnPlugin implements Plugin.PluginBase {
|
||||
|
||||
loadedCheerio("div.header-stats > span").each(function () {
|
||||
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")
|
||||
.map((index, element) => {
|
||||
.map((chapterIndex, element) => {
|
||||
const name = loadedCheerio(element).find("a .chapter-title").text().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;
|
||||
|
||||
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 {
|
||||
name,
|
||||
releaseTime: parseMadaraDate(releaseTime),
|
||||
url: this.site + url,
|
||||
releaseTime: releaseTime,
|
||||
chapterNumber: chapterIndex + 1,
|
||||
};
|
||||
})
|
||||
.get()
|
||||
@@ -122,8 +155,9 @@ class ReadwnPlugin implements Plugin.PluginBase {
|
||||
for (let i = (lastChapterNo || chapters.length) + 1; i <= latestChapterNo; i++) {
|
||||
chapters.push({
|
||||
name: "Chapter " + i,
|
||||
releaseTime: null,
|
||||
url: novelUrl.replace(".html", "_" + i + ".html"),
|
||||
releaseTime: null,
|
||||
chapterNumber: i,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -140,22 +174,17 @@ class ReadwnPlugin implements Plugin.PluginBase {
|
||||
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", {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Referer: this.site + "/search.html",
|
||||
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",
|
||||
body: JSON.stringify({
|
||||
show: "title",
|
||||
tempid: 1,
|
||||
tbname: "news",
|
||||
keyboard,
|
||||
}),
|
||||
body:
|
||||
"show=title&tempid=1&tbname=news&keyboard=" +
|
||||
encodeURIComponent(searchTerm),
|
||||
}).then((res) => res.text());
|
||||
const loadedCheerio = parseHTML(result);
|
||||
|
||||
|
||||
@@ -1552,7 +1552,7 @@
|
||||
],
|
||||
"type": "Picker"
|
||||
},
|
||||
"trash": {
|
||||
"extra": {
|
||||
"label": "Другое:",
|
||||
"value": [],
|
||||
"options": [
|
||||
@@ -1612,7 +1612,7 @@
|
||||
},
|
||||
"adult": {
|
||||
"label": "Возрастные ограничения:",
|
||||
"value": "0",
|
||||
"value": "1",
|
||||
"options": [
|
||||
{
|
||||
"label": "Все",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{
|
||||
"id": "tl.rulate",
|
||||
"id": "rulate",
|
||||
"sourceSite": "https://tl.rulate.ru",
|
||||
"sourceName": "Rulate",
|
||||
"filters": {
|
||||
|
||||
@@ -22,9 +22,8 @@ class RulatePlugin implements Plugin.PluginBase {
|
||||
|
||||
constructor(metadata: RulateMetadata) {
|
||||
this.id = metadata.id;
|
||||
this.name = metadata.sourceName + "[rulate]";
|
||||
const iconFileName = metadata.sourceName.replace(/\s+/g, "").toLowerCase();
|
||||
this.icon = `multisrc/rulate/icons/${iconFileName}.png`;
|
||||
this.name = metadata.sourceName;
|
||||
this.icon = `multisrc/rulate/icons/${metadata.id}.png`;
|
||||
this.site = metadata.sourceSite;
|
||||
this.version = "1.0.0";
|
||||
this.filters = metadata.filters;
|
||||
@@ -35,8 +34,7 @@ class RulatePlugin implements Plugin.PluginBase {
|
||||
{ filters, showLatestNovels }: Plugin.PopularNovelsOptions,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
const baseUrl = this.site;
|
||||
let url = baseUrl + "/search?t=";
|
||||
let url = this.site + "/search?t=";
|
||||
url += "&cat=" + (filters?.cat?.value || "0");
|
||||
url += "&s_lang=" + (filters?.s_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 += "&adult=" + (filters?.adult?.value || "0");
|
||||
|
||||
if (filters?.genres?.value instanceof Array) {
|
||||
url += filters.genres.value.map((i) => "&genres[]=" + i).join("");
|
||||
}
|
||||
|
||||
//if (filters?.genres?.value?.exclude instanceof Array) {
|
||||
// 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("");
|
||||
Object.entries(filters || {}).forEach(([type, { value }]) => {
|
||||
if (value instanceof Array && value.length) {
|
||||
url += "&" + value
|
||||
.map((val) => (type == "extra" ? val + "=1" : type + "[]=" + val))
|
||||
.join("&");
|
||||
}
|
||||
});
|
||||
|
||||
url += "&Book_page=" + pageNo;
|
||||
|
||||
@@ -86,21 +58,20 @@ class RulatePlugin implements Plugin.PluginBase {
|
||||
|
||||
loadedCheerio(
|
||||
'ul[class="search-results"] > li:not([class="ad_type_catalog"])',
|
||||
).each(function () {
|
||||
loadedCheerio(this).find("p > a").text();
|
||||
const name = loadedCheerio(this).find("p > a").text();
|
||||
const cover = loadedCheerio(this).find("img").attr("src");
|
||||
const url = loadedCheerio(this).find("p > a").attr("href");
|
||||
).each((index, element) => {
|
||||
loadedCheerio(element).find("p > a").text();
|
||||
const name = loadedCheerio(element).find("p > a").text();
|
||||
const cover = loadedCheerio(element).find("img").attr("src");
|
||||
const url = loadedCheerio(element).find("p > a").attr("href");
|
||||
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;
|
||||
}
|
||||
|
||||
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
||||
const baseUrl = this.site;
|
||||
const novel: Plugin.SourceNovel = {
|
||||
url: novelUrl,
|
||||
};
|
||||
@@ -129,7 +100,7 @@ class RulatePlugin implements Plugin.PluginBase {
|
||||
novel.name = novel.name.split("[")[0].trim();
|
||||
}
|
||||
novel.cover =
|
||||
baseUrl + loadedCheerio('div[class="images"] > div img').attr("src");
|
||||
this.site + loadedCheerio('div[class="images"] > div img').attr("src");
|
||||
novel.summary =
|
||||
loadedCheerio("#Info > div:nth-child(3), .book-description").text().trim();
|
||||
novel.author = loadedCheerio(
|
||||
@@ -172,27 +143,17 @@ class RulatePlugin implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
loadedCheerio("table > tbody > tr.chapter_row").each(function () {
|
||||
const chapterName = loadedCheerio(this)
|
||||
.find('td[class="t"] > a')
|
||||
.text()
|
||||
.trim();
|
||||
const releaseDate = loadedCheerio(this)
|
||||
.find("td > span")
|
||||
.attr("title")
|
||||
?.trim();
|
||||
const chapterUrl = loadedCheerio(this)
|
||||
.find('td[class="t"] > a')
|
||||
.attr("href");
|
||||
loadedCheerio("table > tbody > tr.chapter_row").each((chapterIndex, element) => {
|
||||
const chapterName = loadedCheerio(element).find('td[class="t"] > a').text().trim();
|
||||
const releaseDate = loadedCheerio(element).find("td > span").attr("title")?.trim();
|
||||
const chapterUrl = loadedCheerio(element).find('td[class="t"] > a').attr("href");
|
||||
|
||||
if (
|
||||
!loadedCheerio(this).find('td > span[class="disabled"]').length &&
|
||||
releaseDate
|
||||
) {
|
||||
if (!loadedCheerio(element).find('td > span[class="disabled"]').length && releaseDate) {
|
||||
chapters.push({
|
||||
name: chapterName,
|
||||
url: this.site + chapterUrl,
|
||||
releaseTime: releaseDate,
|
||||
url: baseUrl + chapterUrl,
|
||||
chapterNumber: chapterIndex + 1,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -202,7 +163,6 @@ class RulatePlugin implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterUrl: string): Promise<string> {
|
||||
const baseUrl = this.site;
|
||||
let result = await fetchApi(chapterUrl);
|
||||
if (result.url.includes("mature?path=")) {
|
||||
const formData = new FormData();
|
||||
@@ -218,10 +178,10 @@ class RulatePlugin implements Plugin.PluginBase {
|
||||
const body = await result.text();
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio(".content-text img").each(function () {
|
||||
if (!loadedCheerio(this).attr("src")?.startsWith("http")) {
|
||||
const src = loadedCheerio(this).attr("src");
|
||||
loadedCheerio(this).attr("src", baseUrl + src);
|
||||
loadedCheerio(".content-text img").each((index, element) => {
|
||||
if (!loadedCheerio(element).attr("src")?.startsWith("http")) {
|
||||
const src = loadedCheerio(element).attr("src");
|
||||
loadedCheerio(element).attr("src", this.site + src);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ class WPmangaStreamPlugin implements Plugin.PluginBase {
|
||||
|
||||
constructor(metadata: WPmangaStreamMetadata) {
|
||||
this.id = metadata.id;
|
||||
this.name = metadata.sourceName + "[wpmangastream]";
|
||||
this.name = metadata.sourceName;
|
||||
this.icon = `multisrc/wpmangastream/icons/${metadata.id}.png`;
|
||||
this.site = metadata.sourceSite;
|
||||
this.version = "1.0.0";
|
||||
@@ -93,13 +93,17 @@ class WPmangaStreamPlugin implements Plugin.PluginBase {
|
||||
novel.summary += loadedCheerio(p).text().trim() + "\\n\\n";
|
||||
}
|
||||
|
||||
const totalChapters = loadedCheerio(".eplister li").length;
|
||||
const chapters: Plugin.ChapterItem[] = loadedCheerio(".eplister li")
|
||||
.map((index, element) => ({
|
||||
.map((chapterIndex, element) => ({
|
||||
name:
|
||||
loadedCheerio(element).find(".epl-num").text() +
|
||||
loadedCheerio(element).find(".epl-title").text(),
|
||||
releaseTime: loadedCheerio(element).find(".epl-date").text().trim(),
|
||||
url: loadedCheerio(element).find("a").attr("href") || "",
|
||||
releaseTime: loadedCheerio(element).find(".epl-date").text().trim(),
|
||||
chapterNumber: this.options?.reverseChapters
|
||||
? totalChapters - chapterIndex
|
||||
: chapterIndex + 1,
|
||||
}))
|
||||
.get()
|
||||
.filter((chapter) => chapter.name && chapter.url);
|
||||
|
||||
@@ -31,7 +31,7 @@ export const all_plugins = (): PluginList => {
|
||||
const { id, name, version, icon } = instance;
|
||||
const info: Plugin.PluginItem = {
|
||||
id,
|
||||
name,
|
||||
name: name + (plugin.match(/(\[.+\])/)?.[1] || ''),
|
||||
lang: languageNative,
|
||||
version,
|
||||
requirePath,
|
||||
|
||||