* fix

* test

* sync

* nitromanga -> madara

* test wuxiaworld

* xm

* add chapterNumber

* fix

chapterNumber > 0

* test filters

* nice

* tunovelaligera -> madara

* Squashed commit of the following:

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

    fix: use english lang in plugin item consistent

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

    Chinese: support showLatestNovels (#733)

    * improve: better chapterText

    * feat: support latest page

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

    Refactor: Using English key (#728)

    * fix: use english key instead of native

    * fix: ukrainian

    * fix: web test with new plugins map

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

    Fix search box styles (#727)

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

    Add 69shu and 69xinshu (#726)

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

    * feat(69xinshu): add 69xinshu

    * feat(69shu): add 69shu

    * remove: empty userAgent

* updated the filters and removed dead sites.

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

* test

* Squashed commit of the following:

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

    kiniga added (#739)

    * kiniga added

    * kiniga updated icon

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

    (add src) LNMTL & ScribbleHub (#738)

    * quick

    * quick panda fix

    * remove console.log

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

    fix: use english lang in plugin item consistent

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

    Chinese: support showLatestNovels (#733)

    * improve: better chapterText

    * feat: support latest page

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

    Refactor: Using English key (#728)

    * fix: use english key instead of native

    * fix: ukrainian

    * fix: web test with new plugins map

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

    Fix search box styles (#727)

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

    Add 69shu and 69xinshu (#726)

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

    * feat(69xinshu): add 69xinshu

    * feat(69shu): add 69shu

    * remove: empty userAgent

* add wuxiamtl, fannovel

* fix

* Update ranobelib.ts

* parseMadaraDate deleted

* remove multisrc name from Plugin item

---------

Co-authored-by: nyagami <hoangquan05112002@gmail.com>
This commit is contained in:
Rider21
2024-02-04 08:47:12 +03:00
committed by GitHub
parent 26bdb94100
commit fc52a6e665
36 changed files with 34403 additions and 15118 deletions
+1
View File
@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

-26
View File
@@ -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
};
+55 -36
View File
@@ -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: [],
};
@@ -69,21 +63,21 @@ class ComradeMaoPlugin implements Plugin.PluginBase {
novel.cover = loadedCheerio("div.thumbook > div > img").attr("src");
loadedCheerio(".infox .wd-full").each((i,el) => {
loadedCheerio(".infox .wd-full").each((i, el) => {
const detailName = loadedCheerio(el).find("b").text();
const 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);
-165
View File
@@ -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();
+12 -7
View File
@@ -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();
-153
View File
@@ -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();
-154
View File
@@ -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();
+13 -12
View File
@@ -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" },
+49 -58
View File
@@ -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;
+14 -20
View File
@@ -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" },
+24 -31
View File
@@ -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" },
+15 -16
View File
@@ -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();
+24 -18
View File
@@ -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;
+44 -59
View File
@@ -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;
+58 -86
View File
@@ -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;
+10 -12
View File
@@ -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" },
+31 -55
View File
@@ -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,
},
+46 -131
View File
@@ -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,
+21 -21
View File
@@ -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]);
}
}
});
+3
View File
@@ -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];
File diff suppressed because it is too large Load Diff
+107 -61
View File
@@ -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");
};
}
-4
View File
@@ -76,10 +76,6 @@
"label": "Fantasy Romance",
"value": "fantasy-romance"
},
{
"label": "Game",
"value": "game"
},
{
"label": "Gender Bender",
"value": "gender-bender"
File diff suppressed because it is too large Load Diff
+50 -21
View File
@@ -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);
+2 -2
View File
@@ -1552,7 +1552,7 @@
],
"type": "Picker"
},
"trash": {
"extra": {
"label": "Другое:",
"value": [],
"options": [
@@ -1612,7 +1612,7 @@
},
"adult": {
"label": "Возрастные ограничения:",
"value": "0",
"value": "1",
"options": [
{
"label": "Все",
+1 -1
View File
@@ -1,6 +1,6 @@
[
{
"id": "tl.rulate",
"id": "rulate",
"sourceSite": "https://tl.rulate.ru",
"sourceName": "Rulate",
"filters": {
+27 -67
View File
@@ -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);
}
});
+7 -3
View File
@@ -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);
+1 -1
View File
@@ -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,