From e09c58c5f5fbc687d460a89074fb610de5f10a2f Mon Sep 17 00:00:00 2001 From: Rajarshee Chatterjee Date: Mon, 25 Mar 2024 21:03:18 +0530 Subject: [PATCH] Add ReadNovelFull (#856) * Add ReadNovelFull * Address Review Comments --- .gitignore | 1 + .../multisrc/readnovelfull/readnovelfull.png | Bin 0 -> 4166 bytes scripts/multisrc/readnovelfull/generator.ts | 33 ++++ scripts/multisrc/readnovelfull/sources.json | 7 + scripts/multisrc/readnovelfull/template.ts | 177 ++++++++++++++++++ 5 files changed, 218 insertions(+) create mode 100644 icons/multisrc/readnovelfull/readnovelfull.png create mode 100644 scripts/multisrc/readnovelfull/generator.ts create mode 100644 scripts/multisrc/readnovelfull/sources.json create mode 100644 scripts/multisrc/readnovelfull/template.ts diff --git a/.gitignore b/.gitignore index 10d0c5e..b458710 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ plugins/*/*\[*\]*.ts # ignore dist stuffs .js .dist +.DS_Store diff --git a/icons/multisrc/readnovelfull/readnovelfull.png b/icons/multisrc/readnovelfull/readnovelfull.png new file mode 100644 index 0000000000000000000000000000000000000000..e28d8dbce26a77ce7f247b9c381837245f2c06da GIT binary patch literal 4166 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 Qpa1{>07*qoM6N<$f`{VYr2qf` literal 0 HcmV?d00001 diff --git a/scripts/multisrc/readnovelfull/generator.ts b/scripts/multisrc/readnovelfull/generator.ts new file mode 100644 index 0000000..d9072d2 --- /dev/null +++ b/scripts/multisrc/readnovelfull/generator.ts @@ -0,0 +1,33 @@ +import { readFileSync } from 'fs'; +import path from 'path'; + +import list from './sources.json'; +import { ReadNovelFullMetadata } from './template'; +import { ScrpitGeneratorFunction } from '../generate'; + +export const generateAll: ScrpitGeneratorFunction = function (name) { + return list.map((metadata: ReadNovelFullMetadata) => { + console.log( + `[${name}] Generating: ${metadata.id}${' '.repeat(20 - metadata.id.length)}`, + ); + return generator(metadata); + }); +}; + +const generator = function generator(metadata: ReadNovelFullMetadata) { + const readNovelFullTemplate = readFileSync( + path.join(__dirname, 'template.ts'), + { encoding: 'utf-8' }, + ); + + const pluginScript = ` +${readNovelFullTemplate} +const plugin = new ReadNovelFullPlugin(${JSON.stringify(metadata)}); +export default plugin; + `.trim(); + return { + lang: metadata.options?.lang || 'English', + filename: metadata.sourceName, + pluginScript, + }; +}; diff --git a/scripts/multisrc/readnovelfull/sources.json b/scripts/multisrc/readnovelfull/sources.json new file mode 100644 index 0000000..b244dea --- /dev/null +++ b/scripts/multisrc/readnovelfull/sources.json @@ -0,0 +1,7 @@ +[ + { + "id": "readnovelfull", + "sourceSite": "https://readnovelfull.com", + "sourceName": "ReadNovelFull" + } +] diff --git a/scripts/multisrc/readnovelfull/template.ts b/scripts/multisrc/readnovelfull/template.ts new file mode 100644 index 0000000..43ef931 --- /dev/null +++ b/scripts/multisrc/readnovelfull/template.ts @@ -0,0 +1,177 @@ +import { CheerioAPI, load } from 'cheerio'; +import { fetchApi, fetchFile } from '@libs/fetch'; +import { Plugin } from '@typings/plugin'; +import { NovelStatus } from '@libs/novelStatus'; + +interface ReadNovelFullOptions { + lang?: string; + versionIncrements?: number; +} + +export interface ReadNovelFullMetadata { + id: string; + sourceSite: string; + sourceName: string; + options?: ReadNovelFullOptions; + filters?: any; +} + +class ReadNovelFullPlugin implements Plugin.PluginBase { + id: string; + name: string; + icon: string; + site: string; + version: string; + options?: ReadNovelFullOptions; + + constructor(metadata: ReadNovelFullMetadata) { + this.id = metadata.id; + this.name = metadata.sourceName; + this.icon = `multisrc/readnovelfull/${metadata.id}.png`; + this.site = metadata.sourceSite; + const versionIncrements = metadata.options?.versionIncrements || 0; + this.version = `1.0.${0 + versionIncrements}`; + this.options = metadata.options; + } + + async getCheerio(url: string, search: boolean): Promise { + const r = await fetchApi(url); + if (!r.ok && search != true) + throw new Error( + 'Could not reach site (' + r.status + ') try to open in webview.', + ); + const $ = load(await r.text()); + + return $; + } + + parseNovels($: CheerioAPI): Plugin.NovelItem[] { + const novels: Plugin.NovelItem[] = []; + + $('.list-novel > .row').each((i, elem) => { + const novelName = $(elem).find('h3').text(); + const coverUrl = $(elem).find('img').attr('src'); + const novelUrl = this.site + $(elem).find('a').attr('href'); + + if (novelUrl) { + novels.push({ + name: novelName, + cover: coverUrl, + path: novelUrl.replace(this.site, ''), + }); + } + }); + + return novels; + } + + async popularNovels( + pageNo: number, + { filters, showLatestNovels }: Plugin.PopularNovelsOptions, + ): Promise { + let url = this.site + '/novel-list/most-popular-novel?page=' + pageNo; + + if (!filters) filters = {}; + + if (showLatestNovels) { + url += this.site + '/novel-list/latest-release-novel?page=' + pageNo; + } + + const $ = await this.getCheerio(url, false); + + return this.parseNovels($); + } + + async parseNovel(novelPath: string): Promise { + let $ = await this.getCheerio(this.site + novelPath, false); + + const novel: Plugin.SourceNovel = { + path: novelPath.replace(this.site, ''), + name: 'Untitled', + }; + + novel.name = $('h3').text().trim(); + novel.cover = $('.book > img').attr('src'); + + $('ul.info.info-meta > li').each(function () { + const detailName = $(this) + .find('h3') + .text() + .toLowerCase() + .replace(':', ''); + + const detail = $(this).text().split(':')[1].trim(); + + switch (detailName) { + case 'author': + novel.author = detail; + break; + case 'genre': + novel.genres = detail; + break; + case 'status': + switch (detail) { + case 'completed': + novel.status = NovelStatus.Completed; + break; + case 'ongoing': + novel.status = NovelStatus.Ongoing; + break; + case 'hiatus': + novel.status = NovelStatus.OnHiatus; + break; + default: + novel.status = NovelStatus.Unknown; + break; + } + break; + } + }); + + novel.summary = $('.desc-text').text(); + + const novelId = $('#rating').attr('data-novel-id'); + const chaptersUrl = this.site + '/ajax/chapter-archive?novelId=' + novelId; + + $ = await this.getCheerio(chaptersUrl, false); + + const chapters: Plugin.ChapterItem[] = []; + + $('.panel-body') + .find('li') + .each(function () { + const chapterName = $(this).find('a').attr('title') || ''; + const chapterUrl = $(this).find('a').attr('href') || ''; + const chapterNumber = Number(chapterName.match(/Chapter (\d*)/g)?.[1]); + + chapters.push({ + name: chapterName, + path: chapterUrl, + chapterNumber, + }); + }); + + novel.chapters = chapters; + + return novel; + } + + async parseChapter(chapterPath: string): Promise { + const $ = await this.getCheerio(this.site + chapterPath, false); + + return $('#chr-content').text(); + } + + async searchNovels( + searchTerm: string, + page: number, + ): Promise { + const url = + this.site + '/novel-list/search?keyword=' + searchTerm + '&page=' + page; + const $ = await this.getCheerio(url, true); + + return this.parseNovels($); + } + + fetchImage = fetchFile; +}