Added ranboes, indowebnovel & Fixed Pawread, NovelUpdates (#829)
* pwaread * novelupdates search * ranobes * quick fix + indowebnovel
This commit is contained in:
@@ -6,7 +6,7 @@ import { Plugin } from "@typings/plugin";
|
||||
class NovelUpdates implements Plugin.PluginBase {
|
||||
id = "novelupdates";
|
||||
name = "Novel Updates";
|
||||
version = "0.5.0";
|
||||
version = "0.5.1";
|
||||
icon = "src/en/novelupdates/icon.png";
|
||||
site = "https://www.novelupdates.com/";
|
||||
|
||||
@@ -90,11 +90,15 @@ class NovelUpdates implements Plugin.PluginBase {
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio(".seriestitlenu").text() || "Untitled",
|
||||
cover: loadedCheerio(".seriesimg > img").attr("src"),
|
||||
author: loadedCheerio("#showauthors").text().trim(),
|
||||
cover: loadedCheerio('.wpb_wrapper img').attr('src'),
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
novel.author = loadedCheerio('#authtag')
|
||||
.map((i, el) => loadedCheerio(el).text().trim())
|
||||
.toArray()
|
||||
.join(', ');
|
||||
|
||||
novel.genres = loadedCheerio("#seriesgenre")
|
||||
.children("a")
|
||||
.map((i, el) => loadedCheerio(el).text())
|
||||
@@ -180,6 +184,8 @@ class NovelUpdates implements Plugin.PluginBase {
|
||||
|
||||
let isAnomalously = result.url.toLowerCase().includes('anotivereads');
|
||||
|
||||
let kofi = result.url.toLowerCase().includes('ko-fi');
|
||||
|
||||
let isBlossomTranslation = result.url
|
||||
.toLowerCase()
|
||||
.includes('blossomtranslation');
|
||||
@@ -248,6 +254,10 @@ class NovelUpdates implements Plugin.PluginBase {
|
||||
chapterText = loadedCheerio('.manga-child-content').html()!;
|
||||
} else if (isAnomalously) {
|
||||
chapterText = loadedCheerio('#comic').html()!;
|
||||
} else if (kofi) {
|
||||
chapterText = loadedCheerio('script:contains("shadowDom.innerHTML")')
|
||||
.html()
|
||||
?.match(/shadowDom\.innerHTML \+= '(<div.*?)';/)![1]!;
|
||||
} else if (isiNovelTranslation) {
|
||||
const link = 'https://api.' + result.url.slice(8);
|
||||
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[]> {
|
||||
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 body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio);
|
||||
}
|
||||
|
||||
@@ -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 { Plugin } from "@typings/plugin";
|
||||
import { Filters, FilterTypes } from "@libs/filterInputs";
|
||||
@@ -6,7 +6,7 @@ import { Filters, FilterTypes } from "@libs/filterInputs";
|
||||
class PawRead implements Plugin.PluginBase {
|
||||
id = "pawread";
|
||||
name = "PawRead";
|
||||
version = "1.1.0";
|
||||
version = "2.0.0";
|
||||
icon = "src/en/pawread/icon.png";
|
||||
site = "https://m.pawread.com/";
|
||||
|
||||
@@ -15,7 +15,7 @@ class PawRead implements Plugin.PluginBase {
|
||||
|
||||
loadedCheerio(".list-comic, .itemBox").each((idx, ele) => {
|
||||
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 novelUrl = loadedCheerio(ele).find('a').attr('href')?.slice(1);
|
||||
|
||||
@@ -55,7 +55,7 @@ class PawRead implements Plugin.PluginBase {
|
||||
link += filters.sort.value;
|
||||
link += `/?page=${page}`;
|
||||
|
||||
const body = await fetchApi(link).then((r) => r.text());
|
||||
const body = await fetchApi(link).then((r) => r.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio);
|
||||
@@ -91,16 +91,17 @@ class PawRead implements Plugin.PluginBase {
|
||||
const chapter: Plugin.ChapterItem[] = [];
|
||||
|
||||
loadedCheerio(".item-box").each((idx, ele) => {
|
||||
const releaseDate = loadedCheerio(ele).find('span:last').text().trim();
|
||||
if (releaseDate === "Advanced Chapter") return;
|
||||
const smallText = loadedCheerio(ele).find('span:last').text().trim();
|
||||
if (smallText === "Advanced Chapter") return;
|
||||
const releaseDate = smallText.split('.').map(x => Number(x));
|
||||
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;
|
||||
|
||||
chapter.push({
|
||||
name: chapterName,
|
||||
path: `${novelPath}${chapterUrl[0]}.html`,
|
||||
releaseTime: new Date(releaseDate).toISOString(),
|
||||
path: `${novelPath}${chapterUrl}.html`,
|
||||
releaseTime: new Date(releaseDate[0], releaseDate[1], releaseDate[2]).toISOString(),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
Reference in New Issue
Block a user