diff --git a/public/static/src/en/novelbin/icon.png b/public/static/multisrc/readnovelfull/novelbin/icon.png similarity index 100% rename from public/static/src/en/novelbin/icon.png rename to public/static/multisrc/readnovelfull/novelbin/icon.png diff --git a/scripts/multisrc/readnovelfull/filters/novelbin.json b/scripts/multisrc/readnovelfull/filters/novelbin.json new file mode 100644 index 0000000..c6ff03f --- /dev/null +++ b/scripts/multisrc/readnovelfull/filters/novelbin.json @@ -0,0 +1,263 @@ +{ + "filters": { + "type": { + "type": "Picker", + "label": "Novel Listing", + "value": "sort/top-view-novel", + "options": [ + { + "label": "Hot Novel", + "value": "sort/top-hot-novel" + }, + { + "label": "Completed Novel", + "value": "sort/completed" + }, + { + "label": "Most Popular", + "value": "sort/top-view-novel" + } + ] + }, + "genres": { + "type": "Picker", + "label": "Genre (Cancels out 'Novel Listing')", + "value": "", + "options": [ + { + "label": "Action", + "value": "genre/action" + }, + { + "label": "Adventure", + "value": "genre/adventure" + }, + { + "label": "Anime & comics", + "value": "genre/anime-&-comics" + }, + { + "label": "Comedy", + "value": "genre/comedy" + }, + { + "label": "Drama", + "value": "genre/drama" + }, + { + "label": "Eastern", + "value": "genre/eastern" + }, + { + "label": "Fan-fiction", + "value": "genre/fan-fiction" + }, + { + "label": "Fanfiction", + "value": "genre/fanfiction" + }, + { + "label": "Fantasy", + "value": "genre/fantasy" + }, + { + "label": "Game", + "value": "genre/game" + }, + { + "label": "Games", + "value": "genre/games" + }, + { + "label": "Gender bender", + "value": "genre/gender-bender" + }, + { + "label": "General", + "value": "genre/general" + }, + { + "label": "Harem", + "value": "genre/harem" + }, + { + "label": "Historical", + "value": "genre/historical" + }, + { + "label": "Horror", + "value": "genre/horror" + }, + { + "label": "Isekai", + "value": "genre/isekai" + }, + { + "label": "Josei", + "value": "genre/josei" + }, + { + "label": "Litrpg", + "value": "genre/litrpg" + }, + { + "label": "Magic", + "value": "genre/magic" + }, + { + "label": "Magical realism", + "value": "genre/magical-realism" + }, + { + "label": "Martial arts", + "value": "genre/martial-arts" + }, + { + "label": "Mature", + "value": "genre/mature" + }, + { + "label": "Mecha", + "value": "genre/mecha" + }, + { + "label": "Military", + "value": "genre/military" + }, + { + "label": "Modern life", + "value": "genre/modern-life" + }, + { + "label": "Myster", + "value": "genre/myster" + }, + { + "label": "Mystery", + "value": "genre/mystery" + }, + { + "label": "Other", + "value": "genre/other" + }, + { + "label": "Other", + "value": "genre/other" + }, + { + "label": "Psychological", + "value": "genre/psychological" + }, + { + "label": "Reincarnation", + "value": "genre/reincarnation" + }, + { + "label": "Romance", + "value": "genre/romance" + }, + { + "label": "Romance.smut", + "value": "genre/romance.smut" + }, + { + "label": "School life", + "value": "genre/school-life" + }, + { + "label": "Sci-fi", + "value": "genre/sci-fi" + }, + { + "label": "Seinen", + "value": "genre/seinen" + }, + { + "label": "Shoujo", + "value": "genre/shoujo" + }, + { + "label": "Shoujo ai", + "value": "genre/shoujo-ai" + }, + { + "label": "Shounen", + "value": "genre/shounen" + }, + { + "label": "Shounen ai", + "value": "genre/shounen-ai" + }, + { + "label": "Slice of life", + "value": "genre/slice-of-life" + }, + { + "label": "Smut", + "value": "genre/smut" + }, + { + "label": "Sports", + "value": "genre/sports" + }, + { + "label": "Supernatural", + "value": "genre/supernatural" + }, + { + "label": "System", + "value": "genre/system" + }, + { + "label": "Thriller", + "value": "genre/thriller" + }, + { + "label": "Tragedy", + "value": "genre/tragedy" + }, + { + "label": "Urban", + "value": "genre/urban" + }, + { + "label": "Urban life", + "value": "genre/urban-life" + }, + { + "label": "Video games", + "value": "genre/video-games" + }, + { + "label": "War", + "value": "genre/war" + }, + { + "label": "Wuxia", + "value": "genre/wuxia" + }, + { + "label": "Xianxia", + "value": "genre/xianxia" + }, + { + "label": "Xuanhuan", + "value": "genre/xuanhuan" + }, + { + "label": "Yaoi", + "value": "genre/yaoi" + }, + { + "label": "Yuri", + "value": "genre/yuri" + } + ] + }, + "complete": { + "type": "Switch", + "label": "Show Completed Novels Only", + "value": false + } + } +} \ No newline at end of file diff --git a/scripts/multisrc/readnovelfull/sources.json b/scripts/multisrc/readnovelfull/sources.json index dbb95f3..47e0509 100644 --- a/scripts/multisrc/readnovelfull/sources.json +++ b/scripts/multisrc/readnovelfull/sources.json @@ -89,5 +89,14 @@ "searchPage": "search", "chapterListing": "ajax-chapter-option" } + }, + { + "id": "novelbin", + "sourceSite": "https://novelbin.com/", + "sourceName": "Novel Bin", + "options": { + "latestPage": "sort/latest", + "searchPage": "search" + } } ] diff --git a/src/plugins/english/novelbin.ts b/src/plugins/english/novelbin.ts deleted file mode 100644 index 78033f2..0000000 --- a/src/plugins/english/novelbin.ts +++ /dev/null @@ -1,240 +0,0 @@ -import { CheerioAPI, load as parseHTML } from 'cheerio'; -import { fetchApi } from '@libs/fetch'; -import { Plugin } from '@typings/plugin'; -import { Filters, FilterTypes } from '@libs/filterInputs'; - -class NovelBin implements Plugin.PluginBase { - id = 'novelbin'; - name = 'Novel Bin'; - icon = 'src/en/novelbin/icon.png'; - site = 'https://novelbin.com/'; - subsite = 'https://novelbin.lanovels.net/'; - version = '1.2.1'; - imageRequestInit?: Plugin.ImageRequestInit | undefined = { - headers: { - 'referrer': this.site, - }, - }; - parseNovels(loadedCheerio: CheerioAPI) { - const novels: Plugin.NovelItem[] = []; - - loadedCheerio('.col-novel-main .list-novel .row').each((i, el) => { - const novelName = loadedCheerio(el).find('h3.novel-title > a').text(); - const novelCover = loadedCheerio(el) - .find('img.cover') - .attr('data-src') - ?.replace('_200_89', ''); - const novelUrl = loadedCheerio(el) - .find('h3.novel-title > a') - .attr('href'); - - if (!novelUrl) return; - - const novel = { - name: novelName, - cover: novelCover, - path: novelUrl.replace(this.site, ''), - }; - - novels.push(novel); - }); - - return novels; - } - - async popularNovels( - page: number, - { showLatestNovels, filters }: Plugin.PopularNovelsOptions, - ): Promise { - let link = this.site; - - if (showLatestNovels) link += 'sort/latest'; - else { - if (filters.genre.value !== '') link += `genre/${filters.genre.value}`; - else link += `sort/${filters.sort.value}`; - } - - if (filters.complete.value) link += `/completed`; - - link += `?page=${page}`; - - const body = await fetchApi(link).then(r => r.text()); - - const loadedCheerio = parseHTML(body); - return this.parseNovels(loadedCheerio); - } - - async parseNovel(novelPath: string): Promise { - const body = await fetchApi(this.site + novelPath).then(r => r.text()); - - let loadedCheerio = parseHTML(body); - - const novel: Plugin.SourceNovel = { - path: novelPath, - name: loadedCheerio('div.book > img').attr('alt') || 'Untitled', - cover: loadedCheerio('div.book > img').attr('data-src'), - summary: loadedCheerio('div.desc-text').text().trim(), - chapters: [], - }; - - loadedCheerio('ul.info > li > h3').each(function () { - const detailName = loadedCheerio(this).text(); - const detail = loadedCheerio(this) - .siblings() - .map((i, el) => loadedCheerio(el).text()) - .toArray() - .join(','); - - switch (detailName) { - case 'Author:': - novel.author = detail; - break; - case 'Status:': - novel.status = detail; - break; - case 'Genre:': - novel.genres = detail; - break; - } - }); - - const novelId = loadedCheerio('#rating').attr('data-novel-id'); - - const getChapters = async (id: string) => { - const data = await fetchApi( - this.site + 'ajax/chapter-archive?novelId=' + id, - ); - loadedCheerio = parseHTML(await data.text()); - - return loadedCheerio('ul.list-chapter > li') - .map((i, el) => { - const chapterName = loadedCheerio(el).find('a').attr('title'); - const chapterUrl = loadedCheerio(el).find('a').attr('href'); - const segments = chapterUrl?.split('/').filter(Boolean); - return chapterName && segments - ? { - name: chapterName, - path: segments.slice(2).join('/'), - } - : null; - }) - .get() - .filter(Boolean); - }; - - if (novelId) { - novel.chapters = await getChapters(novelId); - } - - return novel; - } - - async parseChapter(chapterPath: string): Promise { - const isSubsite = chapterPath.includes('subsite=1'); - const path = (isSubsite ? this.subsite : this.site) + chapterPath; - - const body = await fetchApi(path).then(r => r.text()); - - const loadedCheerio = parseHTML(body); - - loadedCheerio('#chr-content > div,h6,p[style="display: none;"]').remove(); - const chapterText = loadedCheerio('#chr-content').html() || ''; - - return chapterText; - } - - async searchNovels(searchTerm: string): Promise { - const url = `${this.site}search/?keyword=${encodeURIComponent(searchTerm)}`; - const body = await fetchApi(url).then(r => r.text()); - - const loadedCheerio = parseHTML(body); - return this.parseNovels(loadedCheerio); - } - - filters = { - sort: { - value: 'popular-novels', - label: 'Sort by', - options: [ - { label: 'Latest Release', value: 'update' }, - { label: 'Hot Novel', value: 'hot-novels' }, - { label: 'Completed Novel', value: 'c' }, - { label: 'Most Popular', value: 'popular-novels' }, - ], - type: FilterTypes.Picker, - }, - genre: { - value: '', - label: "Genres (Cancels out 'Sort by')", - options: [ - { label: 'None', value: '' }, - { label: 'Action', value: 'action' }, - { label: 'Adult', value: 'adult' }, - { label: 'Adventure', value: 'adventure' }, - { label: 'Anime', value: 'anime' }, - { label: 'Arts', value: 'arts' }, - { label: 'Comedy', value: 'comedy' }, - { label: 'Drama', value: 'drama' }, - { label: 'Eastern', value: 'eastern' }, - { label: 'Ecchi', value: 'ecchi' }, - { label: 'Fan-fiction', value: 'fan-fiction' }, - { label: 'Fanfiction', value: 'fanfiction' }, - { label: 'Fantas', value: 'fantas' }, - { label: 'Fantasy', value: 'fantasy' }, - { label: 'Game', value: 'game' }, - { label: 'Gender bender', value: 'gender-bender' }, - { label: 'Harem', value: 'harem' }, - { label: 'Hentai', value: 'hentai' }, - { label: 'Historical', value: 'historical' }, - { label: 'Horror', value: 'horror' }, - { label: 'Isekai', value: 'isekai' }, - { label: 'Josei', value: 'josei' }, - { label: 'Lgbt+', value: 'lgbt+' }, - { label: 'Litrpg', value: 'litrpg' }, - { label: 'Magic', value: 'magic' }, - { label: 'Magical realism', value: 'magical-realism' }, - { label: 'Manhua', value: 'manhua' }, - { label: 'Martial arts', value: 'martial-arts' }, - { label: 'Mature', value: 'mature' }, - { label: 'Mecha', value: 'mecha' }, - { label: 'Military', value: 'military' }, - { label: 'Modern life', value: 'modern-life' }, - { label: 'Movies', value: 'movies' }, - { label: 'Mystery', value: 'mystery' }, - { label: 'Portal fantasy', value: 'portal-fantasy' }, - { label: 'Psychological', value: 'psychological' }, - { label: 'Realistic fiction', value: 'realistic-fiction' }, - { label: 'Reincarnation', value: 'reincarnation' }, - { label: 'Romance', value: 'romance' }, - { label: 'School life', value: 'school-life' }, - { label: 'Sci-fi', value: 'sci-fi' }, - { label: 'Seinen', value: 'seinen' }, - { label: 'Shoujo', value: 'shoujo' }, - { label: 'Shoujo ai', value: 'shoujo-ai' }, - { label: 'Shounen', value: 'shounen' }, - { label: 'Shounen ai', value: 'shounen-ai' }, - { label: 'Slice of life', value: 'slice-of-life' }, - { label: 'Smut', value: 'smut' }, - { label: 'Sports', value: 'sports' }, - { label: 'Supernatural', value: 'supernatural' }, - { label: 'System', value: 'system' }, - { label: 'Tragedy', value: 'tragedy' }, - { label: 'Urban life', value: 'urban-life' }, - { label: 'Video games', value: 'video-games' }, - { label: 'Wuxia', value: 'wuxia' }, - { label: 'Xianxia', value: 'xianxia' }, - { label: 'Xuanhuan', value: 'xuanhuan' }, - { label: 'Yaoi', value: 'yaoi' }, - { label: 'Yuri', value: 'yuri' }, - ], - type: FilterTypes.Picker, - }, - complete: { - value: false, - label: 'Show Completed Novels Only', - type: FilterTypes.Switch, - }, - } satisfies Filters; -} - -export default new NovelBin();