Added ranboes, indowebnovel & Fixed Pawread, NovelUpdates (#829)

* pwaread

* novelupdates search

* ranobes

* quick fix + indowebnovel
This commit is contained in:
K1ngfish3r
2024-03-18 15:48:01 +08:00
committed by GitHub
parent 2ed1c4b764
commit f14dad2808
6 changed files with 327 additions and 160 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+14 -5
View File
@@ -6,7 +6,7 @@ import { Plugin } from "@typings/plugin";
class NovelUpdates implements Plugin.PluginBase { class NovelUpdates implements Plugin.PluginBase {
id = "novelupdates"; id = "novelupdates";
name = "Novel Updates"; name = "Novel Updates";
version = "0.5.0"; version = "0.5.1";
icon = "src/en/novelupdates/icon.png"; icon = "src/en/novelupdates/icon.png";
site = "https://www.novelupdates.com/"; site = "https://www.novelupdates.com/";
@@ -90,11 +90,15 @@ class NovelUpdates implements Plugin.PluginBase {
const novel: Plugin.SourceNovel = { const novel: Plugin.SourceNovel = {
path: novelPath, path: novelPath,
name: loadedCheerio(".seriestitlenu").text() || "Untitled", name: loadedCheerio(".seriestitlenu").text() || "Untitled",
cover: loadedCheerio(".seriesimg > img").attr("src"), cover: loadedCheerio('.wpb_wrapper img').attr('src'),
author: loadedCheerio("#showauthors").text().trim(),
chapters: [], chapters: [],
}; };
novel.author = loadedCheerio('#authtag')
.map((i, el) => loadedCheerio(el).text().trim())
.toArray()
.join(', ');
novel.genres = loadedCheerio("#seriesgenre") novel.genres = loadedCheerio("#seriesgenre")
.children("a") .children("a")
.map((i, el) => loadedCheerio(el).text()) .map((i, el) => loadedCheerio(el).text())
@@ -180,6 +184,8 @@ class NovelUpdates implements Plugin.PluginBase {
let isAnomalously = result.url.toLowerCase().includes('anotivereads'); let isAnomalously = result.url.toLowerCase().includes('anotivereads');
let kofi = result.url.toLowerCase().includes('ko-fi');
let isBlossomTranslation = result.url let isBlossomTranslation = result.url
.toLowerCase() .toLowerCase()
.includes('blossomtranslation'); .includes('blossomtranslation');
@@ -248,6 +254,10 @@ class NovelUpdates implements Plugin.PluginBase {
chapterText = loadedCheerio('.manga-child-content').html()!; chapterText = loadedCheerio('.manga-child-content').html()!;
} else if (isAnomalously) { } else if (isAnomalously) {
chapterText = loadedCheerio('#comic').html()!; chapterText = loadedCheerio('#comic').html()!;
} else if (kofi) {
chapterText = loadedCheerio('script:contains("shadowDom.innerHTML")')
.html()
?.match(/shadowDom\.innerHTML \+= '(<div.*?)';/)![1]!;
} else if (isiNovelTranslation) { } else if (isiNovelTranslation) {
const link = 'https://api.' + result.url.slice(8); const link = 'https://api.' + result.url.slice(8);
const json = await fetchApi(link).then(r => r.json()); const json = await fetchApi(link).then(r => r.json());
@@ -320,10 +330,9 @@ class NovelUpdates implements Plugin.PluginBase {
} }
async searchNovels(searchTerm: string, page: number): Promise<Plugin.NovelItem[]> { async searchNovels(searchTerm: string, page: number): Promise<Plugin.NovelItem[]> {
const url =`${this.site}page/${page}/?s=${searchTerm}&post_type=seriesplans`; const url = this.site + '?s=' + searchTerm.replace(/\s+/g, '+') + '&post_type=seriesplans';
const result = await fetchApi(url); const result = await fetchApi(url);
const body = await result.text(); const body = await result.text();
const loadedCheerio = parseHTML(body); const loadedCheerio = parseHTML(body);
return this.parseNovels(loadedCheerio); return this.parseNovels(loadedCheerio);
} }
+9 -8
View File
@@ -1,4 +1,4 @@
import { CheerioAPI, load, load as parseHTML } from "cheerio"; import { CheerioAPI, load as parseHTML } from "cheerio";
import { fetchApi, fetchFile } from "@libs/fetch"; import { fetchApi, fetchFile } from "@libs/fetch";
import { Plugin } from "@typings/plugin"; import { Plugin } from "@typings/plugin";
import { Filters, FilterTypes } from "@libs/filterInputs"; import { Filters, FilterTypes } from "@libs/filterInputs";
@@ -6,7 +6,7 @@ import { Filters, FilterTypes } from "@libs/filterInputs";
class PawRead implements Plugin.PluginBase { class PawRead implements Plugin.PluginBase {
id = "pawread"; id = "pawread";
name = "PawRead"; name = "PawRead";
version = "1.1.0"; version = "2.0.0";
icon = "src/en/pawread/icon.png"; icon = "src/en/pawread/icon.png";
site = "https://m.pawread.com/"; site = "https://m.pawread.com/";
@@ -15,7 +15,7 @@ class PawRead implements Plugin.PluginBase {
loadedCheerio(".list-comic, .itemBox").each((idx, ele) => { loadedCheerio(".list-comic, .itemBox").each((idx, ele) => {
loadedCheerio(ele).find('.serialise').remove(); loadedCheerio(ele).find('.serialise').remove();
const novelName = loadedCheerio(ele).find('a').text(); const novelName = loadedCheerio(ele).find('a').text().trim();
const novelCover = loadedCheerio(ele).find('img').attr('src'); const novelCover = loadedCheerio(ele).find('img').attr('src');
const novelUrl = loadedCheerio(ele).find('a').attr('href')?.slice(1); const novelUrl = loadedCheerio(ele).find('a').attr('href')?.slice(1);
@@ -91,16 +91,17 @@ class PawRead implements Plugin.PluginBase {
const chapter: Plugin.ChapterItem[] = []; const chapter: Plugin.ChapterItem[] = [];
loadedCheerio(".item-box").each((idx, ele) => { loadedCheerio(".item-box").each((idx, ele) => {
const releaseDate = loadedCheerio(ele).find('span:last').text().trim(); const smallText = loadedCheerio(ele).find('span:last').text().trim();
if (releaseDate === "Advanced Chapter") return; if (smallText === "Advanced Chapter") return;
const releaseDate = smallText.split('.').map(x => Number(x));
const chapterName = loadedCheerio(ele).find("span:first").text().trim(); const chapterName = loadedCheerio(ele).find("span:first").text().trim();
const chapterUrl = loadedCheerio(ele).attr("onclick")?.match(/\d+/); const chapterUrl = loadedCheerio(ele).attr("onclick")?.match(/\d+/)![0];
if (!chapterUrl) return; if (!chapterUrl) return;
chapter.push({ chapter.push({
name: chapterName, name: chapterName,
path: `${novelPath}${chapterUrl[0]}.html`, path: `${novelPath}${chapterUrl}.html`,
releaseTime: new Date(releaseDate).toISOString(), releaseTime: new Date(releaseDate[0], releaseDate[1], releaseDate[2]).toISOString(),
}); });
}); });
+184
View File
@@ -0,0 +1,184 @@
import { CheerioAPI, load as parseHTML } from "cheerio";
import { fetchApi, fetchFile } from "@libs/fetch";
import { FilterTypes, Filters } from "@libs/filterInputs";
import { Plugin } from "@typings/plugin";
interface ChapterEntry {
id: number;
title: string;
date: string;
link: string;
}
class RanobesPlugin implements Plugin.PagePlugin {
id = 'ranobes';
name = 'Ranobes';
icon = 'src/en/ranobes/icon.png';
site = 'https://ranobes.top';
filters?: Filters | undefined;
version = '1.0.0';
async sleep (ms: number) {
return new Promise(resolve => setTimeout(resolve, ms))
}
parseNovels(loadedCheerio: CheerioAPI){
const novels: Plugin.NovelItem[] = [];
loadedCheerio('.short-cont').each((i,el) => {
const novelName = loadedCheerio(el).find('h2.title').text();
const novelUrl = loadedCheerio(el).find('h2.title a').attr('href');
const novelCover = loadedCheerio(el)
.find('figure')
.attr('style')
?.replace(/.*url\((.*?)\)./g, '$1');
if (!novelUrl) return;
const novel = {
name: novelName,
cover: novelCover,
path: novelUrl?.replace(this.site, ''),
}
novels.push(novel);
});
return novels;
}
parseChapters(data: {chapters: ChapterEntry[]}){
const chapter: Plugin.ChapterItem[] = [];
data.chapters.map((item: ChapterEntry) => {
chapter.push({
name: item.title,
releaseTime: new Date(item.date).toISOString(),
path: item.link.replace(this.site, ''),
});
});
return chapter.reverse();
}
async popularNovels(
page: number,
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions<typeof this.filters>
): Promise<Plugin.NovelItem[]> {
let link = `${this.site}/novels/page/${page}/`;
const body = await fetchApi(link).then((result) =>
result.text()
);
const loadedCheerio = parseHTML(body);
return this.parseNovels(loadedCheerio);
}
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel & { totalPages: number; }> {
const url = this.site + novelPath;
const result = await fetchApi(url);
const body = await result.text();
let loadedCheerio = parseHTML(body);
const novel: Plugin.SourceNovel & { totalPages: number; } = {
path: novelPath,
name: loadedCheerio('.poster img').attr('alt') || "Untitled",
cover: this.site + loadedCheerio('.poster img').attr('src'),
author: loadedCheerio('[itemprop="creator"]').text(),
chapters: [],
totalPages: 1,
};
novel.summary = loadedCheerio('.moreless')
.find('br')
.replaceWith('\n')
.end()
.text()
.trim();
novel.status = loadedCheerio('li:contains("Status") span')
.text() === 'Ongoing'
? 'Ongoing'
: 'Completed';
novel.genres = loadedCheerio('div#mc-fs-genre').text().trim();
let chapterListUrl = loadedCheerio('.read-continue:last').attr('href');
if (!chapterListUrl?.startsWith('http')) {
chapterListUrl = this.site + chapterListUrl;
}
const chaptersHtmlToString = await fetchApi(chapterListUrl).then(r => r.text());
const pageCheerio = parseHTML(chaptersHtmlToString);
const json = pageCheerio('#dle-content main').next().html()!;
const data = JSON.parse(json.replace('window.__DATA__ =', ''));
novel.totalPages = Number(data.pages_count);
novel.chapters = this.parseChapters(data);
return novel;
};
async parsePage(novelPath: string, page: string): Promise<Plugin.SourcePage> {
const pagePath = novelPath.split('-')[0];
const firstUrl = this.site +
'/chapters' +
pagePath.replace('novels/', '');
const pageUrl = firstUrl + '/page/' + page;
const body = await fetchApi(firstUrl).then(r => r.text());
let loadedCheerio = parseHTML(body);
let json = loadedCheerio('#dle-content main').next().html()!;
let data = JSON.parse(json.replace('window.__DATA__ =', ''));
const latestChapterUrl = data.chapters[0].link;
const latestChapterName = data.chapters[0].title;
const latestChapterDate = data.chapters[0].date;
const latestChapter: Plugin.ChapterItem | undefined = latestChapterUrl ? {
path: latestChapterUrl.replace(this.site, ''),
name: latestChapterName,
releaseTime: new Date(latestChapterDate).toISOString(),
} : undefined
await this.sleep(1000);
const pageBody = await fetchApi(pageUrl).then(r => r.text());
loadedCheerio = parseHTML(pageBody);
json = loadedCheerio('#dle-content main').next().html()!;
data = JSON.parse(json.replace('window.__DATA__ =', ''));
const chapters = this.parseChapters(data);
return {
chapters,
latestChapter
}
}
async parseChapter(chapterPath: string): Promise<string> {
const result = await fetchApi(this.site + chapterPath);
const body = await result.text();
const loadedCheerio = parseHTML(body);
const chapterText = loadedCheerio('#arrticle').html() || "";
return chapterText;
};
async searchNovels(
searchTerm: string,
page: number
): Promise<Plugin.NovelItem[]> {
let link = `${this.site}/search/${searchTerm}/page/${page}`;
const body = await fetchApi(link).then(r => r.text());
const loadedCheerio = parseHTML(body);
return this.parseNovels(loadedCheerio)
};
async fetchImage(url: string): Promise<string | undefined> {
return await fetchFile(url);
}
}
export default new RanobesPlugin();
-146
View File
@@ -1,146 +0,0 @@
import { load as parseHTML } from "cheerio";
import { fetchApi, fetchFile } from "@libs/fetch";
import { Plugin } from "@typings/plugin";
import { defaultCover } from "@libs/defaultCover";
import { Filters } from "@libs/filterInputs";
class IndoWebNovel implements Plugin.PluginBase {
id = "IDWN.id";
name = "IndoWebNovel";
icon = "src/id/indowebnovel/icon.png";
site = "https://indowebnovel.id/id/";
filters?: Filters | undefined;
version = "1.0.0";
baseUrl = this.site
async popularNovels(pageNo: number, options: Plugin.PopularNovelsOptions<Filters>): Promise<Plugin.NovelItem[]> {
const url = `${this.baseUrl}daftar-novel/`;
const result = await fetchApi(url);
const body = await result.text();
const loadedCheerio = parseHTML(body);
let novels: Plugin.NovelItem[] = [];
loadedCheerio(".novellist-blc li").each(function () {
const novelName = loadedCheerio(this).find("a").text();
const novelCover = defaultCover;
const novelUrl = loadedCheerio(this).find("a").attr("href");
if (!novelUrl || !novelName) 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);
let novel: Plugin.SourceNovel = {
url,
name: "",
cover: "",
author: "",
status: "",
genres: "",
summary: "",
chapters: [],
};
novel.name = loadedCheerio(".series-title").text().trim();
novel.cover = loadedCheerio(".series-thumb > img").attr("src");
novel.summary = loadedCheerio(".series-synops").text().trim();
novel.status = loadedCheerio(".status").text().trim();
const genres: string[] = [];
loadedCheerio(".series-genres").each(function () {
genres.push(loadedCheerio(this).find("a").text().trim());
});
novel.genres = genres.join(",");
let chapters: Plugin.ChapterItem[] = [];
loadedCheerio(".series-chapterlist li").each(function () {
const chapterName = loadedCheerio(this).find("a").text().trim();
const releaseDate = null;
const chapterUrl = loadedCheerio(this).find("a").attr("href");
if (!chapterUrl) return;
chapters.push({
name: chapterName,
releaseTime: releaseDate,
url: chapterUrl,
});
});
novel.chapters = chapters.reverse();
return novel;
}
async parseChapter(chapterUrl: string): Promise<string> {
const url = chapterUrl;
const result = await fetchApi(url);
const body = await result.text();
let loadedCheerio = parseHTML(body);
const chapterText = loadedCheerio(".readerss").html() || '';
return chapterText;
}
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
const url = `${this.baseUrl}daftar-novel/`;
const result = await fetchApi(url);
const body = await result.text();
const loadedCheerio = parseHTML(body);
let novels: Plugin.NovelItem[] = [];
loadedCheerio(".novellist-blc li").each(function () {
const novelName = loadedCheerio(this).find("a").text();
const novelCover = defaultCover;
const novelUrl = loadedCheerio(this).find("a").attr("href");
if (!novelUrl) return;
const novel = {
name: novelName,
cover: novelCover,
url: novelUrl,
};
if (novelName.toLowerCase().includes(searchTerm.toLowerCase())) {
novels.push(novel);
}
});
return novels;
}
async fetchImage(url: string): Promise<string | undefined> {
return fetchFile(url);
}
}
export default new IndoWebNovel();
+119
View File
@@ -0,0 +1,119 @@
import { CheerioAPI, load, load as parseHTML } from "cheerio";
import { fetchApi, fetchFile } from "@libs/fetch";
import { Plugin } from "@typings/plugin";
import { defaultCover } from "@libs/defaultCover";
import { Filters } from "@libs/filterInputs";
class IndoWebNovel implements Plugin.PluginBase {
id = "IDWN.id";
name = "IndoWebNovel";
icon = "src/id/indowebnovel/icon.png";
site = "https://indowebnovel.id/id/";
filters?: Filters | undefined;
version = "1.0.0";
parseNovels(loadedCheerio:CheerioAPI, searchTerm?: string) {
let novels: Plugin.NovelItem[] = [];
loadedCheerio(".novellist-blc li").each((i,el) => {
const novelName = loadedCheerio(el).find("a").text();
const novelCover = defaultCover;
const novelUrl = loadedCheerio(el).find("a").attr("href");
if (!novelUrl || !novelName) return;
const novel = {
name: novelName,
cover: novelCover,
path: novelUrl.replace(this.site, ''),
};
novels.push(novel);
});
if (searchTerm) {
novels = novels.filter((novel) =>
novel.name.toLowerCase().includes(searchTerm.toLowerCase())
);
}
return novels;
};
async popularNovels(pageNo: number, options: Plugin.PopularNovelsOptions<Filters>): Promise<Plugin.NovelItem[]> {
const url = `${this.site}daftar-novel/`;
const result = await fetchApi(url);
const body = await result.text();
const loadedCheerio = parseHTML(body);
return this.parseNovels(loadedCheerio)
}
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
const result = await fetchApi(this.site + novelPath);
const body = await result.text();
const loadedCheerio = parseHTML(body);
loadedCheerio(".series-synops div").remove();
const novel: Plugin.SourceNovel = {
path: novelPath,
name: loadedCheerio(".series-title").text().trim() || "Untitled",
cover: loadedCheerio(".series-thumb img").attr("src"),
author: loadedCheerio("ul.series-infolist b:contains('Author') +").text().trim(),
status: loadedCheerio(".status").text().trim(),
summary: loadedCheerio(".series-synops").text().trim(),
chapters: [],
};
novel.genres = loadedCheerio(".series-genres a")
.map((i,el) => loadedCheerio(el).text().trim())
.toArray()
.join(',');
const chapters: Plugin.ChapterItem[] = [];
loadedCheerio(".series-chapterlist li").each((i,el) => {
const chapterName = loadedCheerio(el).find("a").text().trim();
const chapterUrl = loadedCheerio(el).find("a").attr("href");
if (!chapterUrl) return;
chapters.push({
name: chapterName,
path: chapterUrl.replace(this.site, ''),
});
});
novel.chapters = chapters.reverse();
return novel;
}
async parseChapter(chapterPath: string): Promise<string> {
const result = await fetchApi(this.site + chapterPath);
const body = await result.text();
const loadedCheerio = parseHTML(body);
const chapterText = loadedCheerio(".readerss").html() || '';
return chapterText;
}
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
const url = `${this.site}daftar-novel/`;
const result = await fetchApi(url);
const body = await result.text();
const loadedCheerio = parseHTML(body);
return this.parseNovels(loadedCheerio, searchTerm)
}
async fetchImage(url: string): Promise<string | undefined> {
return fetchFile(url);
}
}
export default new IndoWebNovel();