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 0000000..e28d8db Binary files /dev/null and b/icons/multisrc/readnovelfull/readnovelfull.png differ 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; +}