diff --git a/icons/src/en/ranobes/icon.png b/icons/src/en/ranobes/icon.png
new file mode 100644
index 0000000..55f4b9f
Binary files /dev/null and b/icons/src/en/ranobes/icon.png differ
diff --git a/plugins/english/novelupdates.ts b/plugins/english/novelupdates.ts
index 88ba4fa..5be0dfc 100644
--- a/plugins/english/novelupdates.ts
+++ b/plugins/english/novelupdates.ts
@@ -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 \+= '(
r.json());
@@ -320,10 +330,9 @@ class NovelUpdates implements Plugin.PluginBase {
}
async searchNovels(searchTerm: string, page: number): Promise {
- 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);
}
diff --git a/plugins/english/pawread.ts b/plugins/english/pawread.ts
index a890523..6f6c75f 100644
--- a/plugins/english/pawread.ts
+++ b/plugins/english/pawread.ts
@@ -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(),
});
});
diff --git a/plugins/english/ranobes.ts b/plugins/english/ranobes.ts
new file mode 100644
index 0000000..13e82e9
--- /dev/null
+++ b/plugins/english/ranobes.ts
@@ -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
+ ): Promise {
+ 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 {
+ 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 {
+ 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 {
+ 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 {
+ 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 {
+ return await fetchFile(url);
+ }
+
+}
+export default new RanobesPlugin();
diff --git a/plugins/indonesian/indowebnovel.broken.ts b/plugins/indonesian/indowebnovel.broken.ts
deleted file mode 100644
index 0126e06..0000000
--- a/plugins/indonesian/indowebnovel.broken.ts
+++ /dev/null
@@ -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): Promise {
- 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 {
- 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 {
- 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 {
- 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 {
- return fetchFile(url);
- }
-
-}
-
-export default new IndoWebNovel();
diff --git a/plugins/indonesian/indowebnovel.ts b/plugins/indonesian/indowebnovel.ts
new file mode 100644
index 0000000..2f5e838
--- /dev/null
+++ b/plugins/indonesian/indowebnovel.ts
@@ -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): Promise {
+ 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 {
+ 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 {
+ 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 {
+ 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 {
+ return fetchFile(url);
+ }
+
+}
+
+export default new IndoWebNovel();