diff --git a/plugins/english/bestlightnovel.ts b/plugins/english/bestlightnovel.broken.ts similarity index 100% rename from plugins/english/bestlightnovel.ts rename to plugins/english/bestlightnovel.broken.ts diff --git a/plugins/english/earlynovel.ts b/plugins/english/earlynovel.broken.ts similarity index 100% rename from plugins/english/earlynovel.ts rename to plugins/english/earlynovel.broken.ts diff --git a/plugins/english/lightnovelpub.ts b/plugins/english/lightnovelpub.ts deleted file mode 100644 index e2eacc9df..000000000 --- a/plugins/english/lightnovelpub.ts +++ /dev/null @@ -1,499 +0,0 @@ -import { Parser } from 'htmlparser2'; -import { fetchApi } from '@libs/fetch'; -import { Plugin } from '@/types/plugin'; -import { Filters, FilterTypes } from '@libs/filterInputs'; -import dayjs from 'dayjs'; - -class LightNovelPub implements Plugin.PagePlugin { - id = 'lightnovelpub'; - name = 'LightNovelPub'; - version = '2.2.0'; - icon = 'src/en/lightnovelpub/icon.png'; - site = 'https://www.lightnovelpub.com/'; - headers = { - Accept: 'application/json', - 'Content-Type': 'application/json', - }; - - parseNovels(html: string) { - const novels: Plugin.NovelItem[] = []; - let tempNovel: Partial = {}; - let state: ParsingState = ParsingState.Idle; - const parser = new Parser({ - onopentag(name, attribs) { - if (attribs['class'] === 'novel-item') { - state = ParsingState.Novel; - } - if (state !== ParsingState.Novel) return; - - switch (name) { - case 'a': - tempNovel.path = attribs['href'].slice(1); - tempNovel.name = attribs['title']; - break; - case 'img': - tempNovel.cover = attribs['data-src'] || attribs['src']; - break; - } - }, - onclosetag(name) { - if (name === 'li') { - if (tempNovel.path && tempNovel.cover) { - novels.push(tempNovel as Plugin.NovelItem); - tempNovel = {}; - } - state = ParsingState.Idle; - } - }, - }); - - parser.write(html); - parser.end(); - - return novels; - } - - async popularNovels( - page: number, - { filters }: Plugin.PopularNovelsOptions, - ): Promise { - const linkParts = [ - this.site + 'browse', - filters.genres.value, - filters.order.value, - filters.status.value, - page.toString(), - ]; - - const body = await fetchApi(linkParts.join('/')).then(r => r.text()); - - return this.parseNovels(body); - } - - async parseNovel( - novelPath: string, - ): Promise { - const body = await fetchApi(this.site + novelPath).then(r => r.text()); - const novel: Partial & Partial<{ totalPages: number }> = - { - path: novelPath, - chapters: [], - }; - let state: ParsingState = ParsingState.Idle; - const summaryParts: string[] = []; - const genreArray: string[] = []; - const parser = new Parser({ - onopentag(name, attribs) { - switch (name) { - case 'h1': - if (attribs['class']?.includes('novel-title')) { - state = ParsingState.NovelName; - } - break; - case 'figure': - if (attribs['class'] === 'cover') { - state = ParsingState.Cover; - } - break; - case 'img': - if (state === ParsingState.Cover) { - novel.cover = attribs['data-src'] || attribs['src']; - } - break; - case 'strong': - if (state === ParsingState.HeaderStats) { - if (attribs['class']) { - state = ParsingState.Status; - } else { - state = ParsingState.TotalChapters; - } - } - break; - case 'br': - if (state === ParsingState.Summary) { - summaryParts.push(''); - } - break; - case 'a': - if (state === ParsingState.Genres) { - state = ParsingState.Tags; - } - break; - case 'div': - if (attribs['class']) { - if (attribs['class'].includes('content')) { - state = ParsingState.Summary; - } else { - const map: Record = { - 'categories': ParsingState.Genres, - 'header-stats': ParsingState.HeaderStats, - 'expand': ParsingState.Idle, - }; - state = map[attribs['class']] ?? state; - } - } - break; - default: - if (attribs['itemprop'] === 'author') { - state = ParsingState.AuthorName; - } - break; - } - }, - ontext(data) { - switch (state) { - case ParsingState.TotalChapters: - if (!novel.totalPages) { - novel.totalPages = Math.ceil(parseInt(data, 10) / 100); - } - break; - case ParsingState.Status: - novel.status = data.trim(); - break; - case ParsingState.NovelName: - novel.name = (novel.name || '') + data.trim(); - break; - case ParsingState.AuthorName: - novel.author = data; - break; - case ParsingState.Summary: - summaryParts.push(data); - break; - case ParsingState.Tags: - genreArray.push(data); - break; - } - }, - onclosetag(name) { - switch (name) { - case 'strong': - if (state === ParsingState.TotalChapters) { - state = ParsingState.HeaderStats; - } else if (state === ParsingState.Status) { - state = ParsingState.Idle; - } - break; - case 'i': - if (state === ParsingState.Status) { - state = ParsingState.Idle; - } - break; - case 'h1': - if (state === ParsingState.NovelName) { - state = ParsingState.Idle; - } - break; - case 'span': - if (state === ParsingState.AuthorName) { - state = ParsingState.Idle; - } - break; - case 'div': - if ( - state === ParsingState.Summary || - state === ParsingState.Genres - ) { - state = ParsingState.Idle; - } - break; - case 'a': - if (state === ParsingState.Tags) { - state = ParsingState.Genres; - } - break; - case 'figure': - if (state === ParsingState.Cover) { - state = ParsingState.Idle; - } - break; - case 'p': - if (state === ParsingState.Summary) { - summaryParts.push(''); - } - break; - } - }, - onend() { - const text = summaryParts - .join('') - .replace(//g, '\n\n') - .replace(//g, '\n') - .replace(/\r\n/g, '\n') - .replace(/ /g, ' '); - - const paragraphs = text - .split('\n\n') - .map(p => p.trim().replace(/[ \t]+/g, ' ')) - .filter(p => p.length > 0); - - novel.summary = paragraphs.join('\n\n'); - summaryParts.length = 0; - - novel.genres = genreArray.join(', '); - }, - }); - - parser.write(body); - parser.end(); - - return novel as Plugin.SourceNovel & { totalPages: number }; - } - - async parsePage(novelPath: string, page: string): Promise { - const url = this.site + novelPath + '/chapters/page-' + page; - const body = await fetchApi(url).then(res => res.text()); - const chapters: Plugin.ChapterItem[] = []; - let tempChapter: Partial = {}; - let state: ParsingState = ParsingState.Idle; - - const parser = new Parser({ - onopentag(name, attribs) { - if (attribs['class'] === 'chapter-list') { - state = ParsingState.ChapterList; - return; - } - - switch (state) { - case ParsingState.ChapterList: - if (name === 'li') { - state = ParsingState.ChapterItem; - tempChapter = { - chapterNumber: Number(attribs['data-orderno'] || 0), - }; - } - break; - case ParsingState.ChapterItem: - switch (name) { - case 'a': - tempChapter.name = attribs['title']; - tempChapter.path = attribs['href']?.slice(1); - break; - case 'time': - tempChapter.releaseTime = dayjs( - attribs['datetime'], - ).toISOString(); - break; - } - break; - } - }, - onclosetag(name) { - switch (state) { - case ParsingState.ChapterItem: - if (name === 'li') { - if ( - tempChapter.chapterNumber !== undefined && - tempChapter.path && - tempChapter.releaseTime - ) { - chapters.push(tempChapter as Plugin.ChapterItem); - } - state = ParsingState.ChapterList; - } - break; - case ParsingState.ChapterList: - if (name === 'ul') { - state = ParsingState.Idle; - } - break; - } - }, - }); - - parser.write(body); - parser.end(); - - return { chapters }; - } - - async parseChapter(chapterPath: string): Promise { - const html = await fetchApi(this.site + chapterPath).then(r => r.text()); - - let depth = 0; - let state: ParsingState = ParsingState.Idle; - const chapterHtml: string[] = []; - - type EscapeChar = '&' | '<' | '>' | '"' | "'"; - const escapeRegex = /[&<>"']/g; - const escapeMap: Record = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', - }; - const escapeHtml = (text: string): string => - text.replace(escapeRegex, char => escapeMap[char as EscapeChar]); - - const parser = new Parser({ - onopentag(name, attribs) { - switch (state) { - case ParsingState.Idle: - if (name === 'div' && attribs['id'] === 'chapter-container') { - state = ParsingState.Chapter; - depth++; - } - break; - case ParsingState.Chapter: - if (name === 'div') depth++; - break; - default: - return; - } - - if (state === ParsingState.Chapter) { - const attr = Object.keys(attribs).map(key => { - const value = attribs[key].replace(/"/g, '"'); - return ` ${key}="${value}"`; - }); - chapterHtml.push(`<${name}${attr.join('')}>`); - } - }, - - ontext(text) { - if (state === ParsingState.Chapter) { - chapterHtml.push(escapeHtml(text)); - } - }, - - onclosetag(name) { - if (state === ParsingState.Chapter) { - if (!parser['isVoidElement'](name)) { - chapterHtml.push(``); - } - if (name === 'div') depth--; - if (depth === 0) { - state = ParsingState.Stopped; - } - } - }, - }); - - parser.write(html); - parser.end(); - - return chapterHtml.join(''); - } - - async searchNovels(searchTerm: string): Promise { - const url = `${this.site}lnsearchlive`; - const link = `${this.site}search`; - const response = await fetchApi(link).then(r => r.text()); - let verifytoken = ''; - const parser = new Parser({ - onopentag(name, attribs) { - if ( - name === 'input' && - attribs['name']?.includes('LNRequestVerifyToken') - ) { - verifytoken = attribs['value']; - } - }, - }); - parser.write(response); - parser.end(); - - const formData = new FormData(); - formData.append('inputContent', searchTerm); - - const body = await fetchApi(url, { - method: 'POST', - headers: { LNRequestVerifyToken: verifytoken! }, - body: formData, - }).then(r => r.json()); - - return this.parseNovels(body.resultview); - } - - filters = { - order: { - value: 'popular', - label: 'Order by', - options: [ - { label: 'New', value: 'new' }, - { label: 'Popular', value: 'popular' }, - { label: 'Updates', value: 'updated' }, - ], - type: FilterTypes.Picker, - }, - status: { - value: 'all', - label: 'Status', - options: [ - { label: 'All', value: 'all' }, - { label: 'Completed', value: 'completed' }, - { label: 'Ongoing', value: 'ongoing' }, - ], - type: FilterTypes.Picker, - }, - genres: { - value: 'all', - label: 'Genre', - options: [ - { label: 'All', value: 'all' }, - { label: 'Action', value: 'action' }, - { label: 'Adventure', value: 'adventure' }, - { label: 'Drama', value: 'drama' }, - { label: 'Fantasy', value: 'fantasy' }, - { label: 'Harem', value: 'harem' }, - { label: 'Martial Arts', value: 'martial-arts' }, - { label: 'Mature', value: 'mature' }, - { label: 'Romance', value: 'romance' }, - { label: 'Tragedy', value: 'tragedy' }, - { label: 'Xuanhuan', value: 'xuanhuan' }, - { label: 'Ecchi', value: 'ecchi' }, - { label: 'Comedy', value: 'comedy' }, - { label: 'Slice of Life', value: 'slice-of-life' }, - { label: 'Mystery', value: 'mystery' }, - { label: 'Supernatural', value: 'supernatural' }, - { label: 'Psychological', value: 'psychological' }, - { label: 'Sci-fi', value: 'sci-fi' }, - { label: 'Xianxia', value: 'xianxia' }, - { label: 'School Life', value: 'school-life' }, - { label: 'Josei', value: 'josei' }, - { label: 'Wuxia', value: 'wuxia' }, - { label: 'Shounen', value: 'shounen' }, - { label: 'Horror', value: 'horror' }, - { label: 'Mecha', value: 'mecha' }, - { label: 'Historical', value: 'historical' }, - { label: 'Shoujo', value: 'shoujo' }, - { label: 'Adult', value: 'adult' }, - { label: 'Seinen', value: 'seinen' }, - { label: 'Sports', value: 'sports' }, - { label: 'Lolicon', value: 'lolicon' }, - { label: 'Gender Bender', value: 'gender-bender' }, - { label: 'Shounen Ai', value: 'shounen-ai' }, - { label: 'Yaoi', value: 'yaoi' }, - { label: 'Video Games', value: 'video-games' }, - { label: 'Smut', value: 'smut' }, - { label: 'Magical Realism', value: 'magical-realism' }, - { label: 'Eastern Fantasy', value: 'eastern-fantasy' }, - { label: 'Contemporary Romance', value: 'contemporary-romance' }, - { label: 'Fantasy Romance', value: 'fantasy-romance' }, - { label: 'Shoujo Ai', value: 'shoujo-ai' }, - { label: 'Yuri', value: 'yuri' }, - ], - type: FilterTypes.Picker, - }, - } satisfies Filters; -} - -export default new LightNovelPub(); - -enum ParsingState { - Idle, - Novel, - HeaderStats, - Status, - Stopped, - Chapter, - ChapterItem, - ChapterList, - TotalChapters, - NovelName, - AuthorName, - Summary, - Genres, - Tags, - Cover, -} diff --git a/plugins/english/novelfire.paged.ts b/plugins/english/novelfire.paged.ts deleted file mode 100644 index 6e2b449ab..000000000 --- a/plugins/english/novelfire.paged.ts +++ /dev/null @@ -1,382 +0,0 @@ -import { CheerioAPI, load } from 'cheerio'; -import { fetchApi } from '@libs/fetch'; -import { Plugin } from '@/types/plugin'; -import { NovelStatus } from '@libs/novelStatus'; -import { Filters, FilterTypes } from '@libs/filterInputs'; -import { defaultCover } from '@/types/constants'; - -class NovelFirePaged implements Plugin.PagePlugin { - id = 'novelfire.paged'; - name = 'Novel Fire Paged'; - version = '1.1.2'; - icon = 'src/en/novelfire/icon.png'; - site = 'https://novelfire.net/'; - - 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()); - - if ($('title').text().includes('Cloudflare')) { - throw new Error('Cloudflare is blocking requests. Try again later.'); - } - - return $; - } - - async popularNovels( - pageNo: number, - { - showLatestNovels, - filters, - }: Plugin.PopularNovelsOptions, - ): Promise { - let url = this.site + 'search-adv'; - if (showLatestNovels) { - url += `?ctgcon=and&totalchapter=0&ratcon=min&rating=0&status=-1&sort=date&tagcon=and&page=${pageNo}`; - } else if (filters) { - const params = new URLSearchParams(); - for (const language of filters.language.value) { - params.append('country_id[]', language); - } - params.append('ctgcon', filters.genre_operator.value); - for (const genre of filters.genres.value) { - params.append('categories[]', genre); - } - params.append('totalchapter', filters.chapters.value); - params.append('ratcon', filters.rating_operator.value); - params.append('rating', filters.rating.value); - params.append('status', filters.status.value); - params.append('sort', filters.sort.value); - params.append('page', pageNo.toString()); - url += `?${params.toString()}`; - } else { - url += `?ctgcon=and&totalchapter=0&ratcon=min&rating=0&status=-1&sort=rank-top&page=${pageNo}`; - } - - const loadedCheerio = await this.getCheerio(url, false); - - return loadedCheerio('.novel-item') - .map((index, ele) => { - const novelName = - loadedCheerio(ele).find('.novel-title > a').text() || - 'No Title Found'; - const novelCover = loadedCheerio(ele) - .find('.novel-cover > img') - .attr('data-src'); - const novelPath = loadedCheerio(ele) - .find('.novel-title > a') - .attr('href'); - - if (!novelPath) return; - - return { - name: novelName, - cover: novelCover, - path: novelPath.replace(this.site, ''), - }; - }) - .get() - .filter(novel => novel !== null); - } - - async parseNovel( - novelPath: string, - ): Promise { - const $ = await this.getCheerio(this.site + novelPath, false); - const baseUrl = this.site; - - const novel: Partial = { - path: novelPath, - }; - - novel.name = - $('.novel-title').text().trim() ?? - $('.cover > img').attr('alt') ?? - 'No Titled Found'; - const coverUrl = - $('.cover > img').attr('data-src') ?? $('.cover > img').attr('src'); - - if (coverUrl) { - novel.cover = new URL(coverUrl, baseUrl).href; - } else { - novel.cover = defaultCover; - } - - novel.genres = $('.categories .property-item') - .map((i, el) => $(el).text()) - .toArray() - .join(','); - - let summary = $('.summary .content').text().trim(); - - if (summary) { - summary = summary.replace('Show More', ''); - novel.summary = summary; - } else { - novel.summary = 'No Summary Found'; - } - - novel.author = $('.author .property-item > span').text(); - - const rawStatus = - $('.header-stats .ongoing').text() || - $('.header-stats .completed').text() || - 'Unknown'; - const map: Record = { - ongoing: NovelStatus.Ongoing, - hiatus: NovelStatus.OnHiatus, - dropped: NovelStatus.Cancelled, - cancelled: NovelStatus.Cancelled, - completed: NovelStatus.Completed, - unknown: NovelStatus.Unknown, - }; - novel.status = map[rawStatus.toLowerCase()] ?? NovelStatus.Unknown; - - novel.rating = parseFloat($('.nub').text().trim()); - - const totalChapters = $('.header-stats .icon-book-open') - .parent() - .text() - .trim(); - - novel.totalPages = Math.ceil(parseInt(totalChapters) / 100); - - return novel as Plugin.SourceNovel & { totalPages: number }; - } - - async parsePage(novelPath: string, page: string): Promise { - const url = `${this.site}${novelPath}/chapters?page=${page}`; - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = load(body); - - const chapters = loadedCheerio('.chapter-list li') - .map((index, ele) => { - const chapterName = - loadedCheerio(ele).find('a').attr('title') || 'No Title Found'; - const chapterPath = loadedCheerio(ele).find('a').attr('href'); - - if (!chapterPath) return null; - - return { - name: chapterName, - path: chapterPath.replace(this.site, ''), - }; - }) - .get() - .filter(chapter => chapter !== null) as Plugin.ChapterItem[]; - - return { - chapters, - }; - } - - async parseChapter(chapterPath: string): Promise { - const url = this.site + chapterPath; - const loadedCheerio = await this.getCheerio(url, false); - - const chapterText = loadedCheerio('#content'); - - loadedCheerio(chapterText).find('div').remove(); - - return chapterText.html()?.replace(/ /g, ' ') || ''; - } - - async searchNovels( - searchTerm: string, - page: number, - ): Promise { - const url = `${this.site}search?keyword=${encodeURIComponent(searchTerm)}&page=${page}`; - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = load(body); - - return loadedCheerio('.novel-list.chapters .novel-item') - .map((index, ele) => { - const novelName = - loadedCheerio(ele).find('a').attr('title') || 'No Title Found'; - const novelCover = loadedCheerio(ele) - .find('.novel-cover > img') - .attr('src'); - const novelPath = loadedCheerio(ele).find('a').attr('href'); - - if (!novelPath) return null; - - return { - name: novelName, - cover: novelCover, - path: novelPath.replace(this.site, ''), - }; - }) - .get() - .filter(novel => novel !== null); - } - - filters = { - sort: { - label: 'Sort Results By', - value: 'rank-top', - options: [ - { label: 'Rank (Top)', value: 'rank-top' }, - { label: 'Rating Score (Top)', value: 'rating-score-top' }, - { label: 'Review Count (Most)', value: 'review' }, - { label: 'Comment Count (Most)', value: 'comment' }, - { label: 'Bookmark Count (Most)', value: 'bookmark' }, - { label: 'Today Views (Most)', value: 'today-view' }, - { label: 'Monthly Views (Most)', value: 'monthly-view' }, - { label: 'Total Views (Most)', value: 'total-view' }, - { label: 'Title (A>Z)', value: 'abc' }, - { label: 'Title (Z>A)', value: 'cba' }, - { label: 'Last Updated (Newest)', value: 'date' }, - { label: 'Chapter Count (Most)', value: 'chapter-count-most' }, - ], - type: FilterTypes.Picker, - }, - status: { - label: 'Translation Status', - value: '-1', - options: [ - { label: 'All', value: '-1' }, - { label: 'Completed', value: '1' }, - { label: 'Ongoing', value: '0' }, - ], - type: FilterTypes.Picker, - }, - genre_operator: { - label: 'Genres (And/Or/Exclude)', - value: 'and', - options: [ - { label: 'AND', value: 'and' }, - { label: 'OR', value: 'or' }, - { label: 'EXCLUDE', value: 'exclude' }, - ], - type: FilterTypes.Picker, - }, - genres: { - label: 'Genres', - value: [], - options: [ - { label: 'Action', value: '3' }, - { label: 'Adult', value: '28' }, - { label: 'Adventure', value: '4' }, - { label: 'Anime', value: '46' }, - { label: 'Arts', value: '47' }, - { label: 'Comedy', value: '5' }, - { label: 'Drama', value: '24' }, - { label: 'Eastern', value: '44' }, - { label: 'Ecchi', value: '26' }, - { label: 'Fan-fiction', value: '48' }, - { label: 'Fantasy', value: '6' }, - { label: 'Game', value: '19' }, - { label: 'Gender Bender', value: '25' }, - { label: 'Harem', value: '7' }, - { label: 'Historical', value: '12' }, - { label: 'Horror', value: '37' }, - { label: 'Isekai', value: '49' }, - { label: 'Josei', value: '2' }, - { label: 'Lgbt+', value: '45' }, - { label: 'Magic', value: '50' }, - { label: 'Magical Realism', value: '51' }, - { label: 'Manhua', value: '52' }, - { label: 'Martial Arts', value: '15' }, - { label: 'Mature', value: '8' }, - { label: 'Mecha', value: '34' }, - { label: 'Military', value: '53' }, - { label: 'Modern Life', value: '54' }, - { label: 'Movies', value: '55' }, - { label: 'Mystery', value: '16' }, - { label: 'Other', value: '64' }, - { label: 'Psychological', value: '9' }, - { label: 'Realistic Fiction', value: '56' }, - { label: 'Reincarnation', value: '43' }, - { label: 'Romance', value: '1' }, - { label: 'School Life', value: '21' }, - { label: 'Sci-fi', value: '20' }, - { label: 'Seinen', value: '10' }, - { label: 'Shoujo', value: '38' }, - { label: 'Shoujo Ai', value: '57' }, - { label: 'Shounen', value: '17' }, - { label: 'Shounen Ai', value: '39' }, - { label: 'Slice of Life', value: '13' }, - { label: 'Smut', value: '29' }, - { label: 'Sports', value: '42' }, - { label: 'Supernatural', value: '18' }, - { label: 'System', value: '58' }, - { label: 'Tragedy', value: '32' }, - { label: 'Urban', value: '63' }, - { label: 'Urban Life', value: '59' }, - { label: 'Video Games', value: '60' }, - { label: 'War', value: '61' }, - { label: 'Wuxia', value: '31' }, - { label: 'Xianxia', value: '23' }, - { label: 'Xuanhuan', value: '22' }, - { label: 'Yaoi', value: '14' }, - { label: 'Yuri', value: '62' }, - ], - type: FilterTypes.CheckboxGroup, - }, - language: { - label: 'Language', - value: [], - options: [ - { label: 'Chinese', value: '1' }, - { label: 'Korean', value: '2' }, - { label: 'Japanese', value: '3' }, - { label: 'English', value: '4' }, - ], - type: FilterTypes.CheckboxGroup, - }, - rating_operator: { - label: 'Rating (Min/Max)', - value: 'min', - options: [ - { label: 'Min', value: 'min' }, - { label: 'Max', value: 'max' }, - ], - type: FilterTypes.Picker, - }, - rating: { - label: 'Rating', - value: '0', - options: [ - { label: 'All', value: '0' }, - { label: '1', value: '1' }, - { label: '2', value: '2' }, - { label: '3', value: '3' }, - { label: '4', value: '4' }, - { label: '5', value: '5' }, - ], - type: FilterTypes.Picker, - }, - chapters: { - label: 'Chapters', - value: '0', - options: [ - { label: 'All', value: '0' }, - { label: '<50', value: '1,49' }, - { label: '50-100', value: '50,100' }, - { label: '100-200', value: '100,200' }, - { label: '200-500', value: '200,500' }, - { label: '500-1000', value: '500,1000' }, - { label: '>1000', value: '1001,1000000' }, - ], - type: FilterTypes.Picker, - }, - } satisfies Filters; -} - -export default new NovelFirePaged(); - -// Custom error for when Novel Fire is rate limiting requests -class NovelFireThrottlingError extends Error { - constructor(message = 'Novel Fire is rate limiting requests') { - super(message); - this.name = 'NovelFireError'; - } -} diff --git a/plugins/english/readlitenovel.ts b/plugins/english/readlitenovel.broken.ts similarity index 100% rename from plugins/english/readlitenovel.ts rename to plugins/english/readlitenovel.broken.ts diff --git a/plugins/english/reaperscans.ts b/plugins/english/reaperscans.broken.ts similarity index 100% rename from plugins/english/reaperscans.ts rename to plugins/english/reaperscans.broken.ts diff --git a/plugins/french/phenixscans.ts b/plugins/french/phenixscans.broken.ts similarity index 100% rename from plugins/french/phenixscans.ts rename to plugins/french/phenixscans.broken.ts diff --git a/plugins/indonesian/novelringan.ts b/plugins/indonesian/novelringan.broken.ts similarity index 100% rename from plugins/indonesian/novelringan.ts rename to plugins/indonesian/novelringan.broken.ts diff --git a/plugins/multisrc/fictioneer/sources.json b/plugins/multisrc/fictioneer/sources.json index 103a10071..7e0cb86b5 100644 --- a/plugins/multisrc/fictioneer/sources.json +++ b/plugins/multisrc/fictioneer/sources.json @@ -49,7 +49,9 @@ "sourceSite": "https://dearestrosalie.com", "sourceName": "Dearest Rosalie", "options": { - "browsePage": "stories" + "browsePage": "stories", + "down": true, + "downSince": 1768289212907 } } ] diff --git a/plugins/multisrc/hotnovelpub/sources.json b/plugins/multisrc/hotnovelpub/sources.json index 5d56c9aa8..1e9bb6863 100644 --- a/plugins/multisrc/hotnovelpub/sources.json +++ b/plugins/multisrc/hotnovelpub/sources.json @@ -233,7 +233,9 @@ } }, "options": { - "lang": "es" + "lang": "es", + "down": true, + "downSince": 1768289212956 } }, { @@ -313,7 +315,9 @@ } }, "options": { - "lang": "pt" + "lang": "pt", + "down": true, + "downSince": 1768289212942 } }, { @@ -381,7 +385,9 @@ } }, "options": { - "lang": "th" + "lang": "th", + "down": true, + "downSince": 1768289212962 } } -] \ No newline at end of file +] diff --git a/plugins/multisrc/lightnovelworld/sources.json b/plugins/multisrc/lightnovelworld/sources.json index 67731c249..922937c4e 100644 --- a/plugins/multisrc/lightnovelworld/sources.json +++ b/plugins/multisrc/lightnovelworld/sources.json @@ -4,11 +4,6 @@ "sourceName": "Web Novel Pub", "sourceSite": "https://www.webnovelworld.org/" }, - { - "id": "lightnovelworld", - "sourceName": "LightNovelWord", - "sourceSite": "https://www.lightnovelworld.com/" - }, { "id": "lightnovelpubvip", "sourceName": "LightNovelPub Vip", diff --git a/plugins/multisrc/lightnovelwp/sources.json b/plugins/multisrc/lightnovelwp/sources.json index 078e5d468..02e6f9661 100644 --- a/plugins/multisrc/lightnovelwp/sources.json +++ b/plugins/multisrc/lightnovelwp/sources.json @@ -53,7 +53,9 @@ "sourceName": "AllNovelRead", "options": { "lang": "Spanish", - "reverseChapters": true + "reverseChapters": true, + "down": true, + "downSince": 1768289212954 } }, { @@ -106,7 +108,9 @@ "sourceName": "Panda Machine Translations", "options": { "lang": "English", - "reverseChapters": true + "reverseChapters": true, + "down": true, + "downSince": 1768289212928 } }, { @@ -163,7 +167,9 @@ "options": { "versionIncrements": 1, "lang": "English", - "reverseChapters": true + "reverseChapters": true, + "down": true, + "downSince": 1768289212919 } }, { @@ -181,7 +187,9 @@ "sourceName": "ElloTL", "options": { "lang": "English", - "reverseChapters": true + "reverseChapters": true, + "down": true, + "downSince": 1768289212947 } }, { @@ -199,7 +207,9 @@ "sourceName": "CPUnovel", "options": { "lang": "English", - "reverseChapters": true + "reverseChapters": true, + "down": true, + "downSince": 1768289212904 } }, { @@ -218,7 +228,9 @@ "sourceSite": "https://betternovels.net/", "sourceName": "Better Novels", "options": { - "lang": "Portuguese" + "lang": "Portuguese", + "down": true, + "downSince": 1768289212945 } }, { @@ -274,7 +286,9 @@ "sourceName": "Keopi Translations", "options": { "lang": "English", - "reverseChapters": true + "reverseChapters": true, + "down": true, + "downSince": 1768289212914 } }, { diff --git a/plugins/multisrc/madara/sources.json b/plugins/multisrc/madara/sources.json index de9884969..e3b5c7f75 100644 --- a/plugins/multisrc/madara/sources.json +++ b/plugins/multisrc/madara/sources.json @@ -3,7 +3,10 @@ "id": "novelTL", "sourceSite": "https://noveltranslate.com/", "sourceName": "NovelTranslate", - "options": {} + "options": { + "down": true, + "downSince": 1768289212925 + } }, { "id": "lunarletters", @@ -81,7 +84,11 @@ { "id": "webnovelover", "sourceSite": "https://www.webnovelover.com/", - "sourceName": "WebNovelLover" + "sourceName": "WebNovelLover", + "options": { + "down": true, + "downSince": 1768289212967 + } }, { "id": "wbnovel", @@ -167,7 +174,9 @@ "sourceSite": "https://guavaread.com/", "sourceName": "Guavaread", "options": { - "useNewChapterEndpoint": true + "useNewChapterEndpoint": true, + "down": true, + "downSince": 1768289212912 } }, { @@ -181,7 +190,9 @@ "sourceName": "MTL-Novel", "options": { "useNewChapterEndpoint": false, - "versionIncrements": 1 + "versionIncrements": 1, + "down": true, + "downSince": 1768289212922 } }, { @@ -203,7 +214,11 @@ { "id": "LightNovelUpdates", "sourceSite": "https://www.lightnovelupdates.com/", - "sourceName": "Light Novel Updates" + "sourceName": "Light Novel Updates", + "options": { + "down": true, + "downSince": 1768289212916 + } }, { "id": "foxaholic", @@ -226,7 +241,9 @@ "sourceSite": "https://kiniga.com/", "sourceName": "Kiniga", "options": { - "lang": "Portuguese" + "lang": "Portuguese", + "down": true, + "downSince": 1768289212949 } }, { @@ -342,7 +359,9 @@ "sourceName": "Meownovel", "options": { "useNewChapterEndpoint": true, - "lang": "English" + "lang": "English", + "down": true, + "downSince": 1768289212931 } }, { @@ -352,7 +371,9 @@ "options": { "useNewChapterEndpoint": true, "versionIncrements": 3, - "lang": "English" + "lang": "English", + "down": true, + "downSince": 1768289212909 } }, { @@ -361,7 +382,9 @@ "sourceName": "Olaoe.cyou", "options": { "useNewChapterEndpoint": true, - "lang": "Arabic" + "lang": "Arabic", + "down": true, + "downSince": 1768289212938 } }, { @@ -443,7 +466,9 @@ "sourceName": "Novel oku", "options": { "useNewChapterEndpoint": true, - "lang": "Turkish" + "lang": "Turkish", + "down": true, + "downSince": 1768289212959 } }, { @@ -452,7 +477,9 @@ "sourceName": "NABİ SCANS", "options": { "useNewChapterEndpoint": true, - "lang": "Turkish" + "lang": "Turkish", + "down": true, + "downSince": 1768289212965 } }, { @@ -515,7 +542,9 @@ "sourceName": "Violet Lily", "options": { "lang": "English", - "useNewChapterEndpoint": true + "useNewChapterEndpoint": true, + "down": true, + "downSince": 1768289212936 } }, { @@ -524,7 +553,9 @@ "sourceName": "Sweet Escape", "options": { "lang": "English", - "useNewChapterEndpoint": true + "useNewChapterEndpoint": true, + "down": true, + "downSince": 1768289212933 } }, { @@ -541,7 +572,9 @@ "sourceSite": "https://animeshoy12.com/", "sourceName": "AnimesHoy12", "options": { - "lang": "Spanish" + "lang": "Spanish", + "down": true, + "downSince": 1768289212951 } }, { diff --git a/plugins/multisrc/readwn/sources.json b/plugins/multisrc/readwn/sources.json index 48eae65be..70627905f 100644 --- a/plugins/multisrc/readwn/sources.json +++ b/plugins/multisrc/readwn/sources.json @@ -7,7 +7,11 @@ { "id": "wuxiacity", "sourceSite": "https://www.wuxiafox.com", - "sourceName": "Wuxiafox" + "sourceName": "Wuxiafox", + "options": { + "down": true, + "downSince": 1768289212969 + } }, { "id": "ltnovel", @@ -44,6 +48,10 @@ { "id": "wuxiav", "sourceSite": "https://www.wuxiav.com", - "sourceName": "WuxiaV" + "sourceName": "WuxiaV", + "options": { + "down": true, + "downSince": 1768289212940 + } } -] \ No newline at end of file +] diff --git a/plugins/russian/LitSpace.ts b/plugins/russian/LitSpace.broken.ts similarity index 100% rename from plugins/russian/LitSpace.ts rename to plugins/russian/LitSpace.broken.ts diff --git a/plugins/russian/novelOvh.ts b/plugins/russian/novelOvh.broken.ts similarity index 100% rename from plugins/russian/novelOvh.ts rename to plugins/russian/novelOvh.broken.ts diff --git a/plugins/russian/ruvers.ts b/plugins/russian/ruvers.broken.ts similarity index 100% rename from plugins/russian/ruvers.ts rename to plugins/russian/ruvers.broken.ts diff --git a/plugins/ukrainian/uaranobeclub.ts b/plugins/ukrainian/uaranobeclub.broken.ts similarity index 100% rename from plugins/ukrainian/uaranobeclub.ts rename to plugins/ukrainian/uaranobeclub.broken.ts diff --git a/plugins/vietnamese/Truyenconect.ts b/plugins/vietnamese/Truyenconect.broken.ts similarity index 100% rename from plugins/vietnamese/Truyenconect.ts rename to plugins/vietnamese/Truyenconect.broken.ts diff --git a/plugins/vietnamese/truyenchu.ts b/plugins/vietnamese/truyenchu.broken.ts similarity index 100% rename from plugins/vietnamese/truyenchu.ts rename to plugins/vietnamese/truyenchu.broken.ts diff --git a/plugins/vietnamese/truyenfull.ts b/plugins/vietnamese/truyenfull.broken.ts similarity index 100% rename from plugins/vietnamese/truyenfull.ts rename to plugins/vietnamese/truyenfull.broken.ts diff --git a/public/static/multisrc/fictioneer/prizmatranslation/icon.png b/public/static/multisrc/fictioneer/prizmatranslation/icon.png index 42deacf5d..958b50632 100644 Binary files a/public/static/multisrc/fictioneer/prizmatranslation/icon.png and b/public/static/multisrc/fictioneer/prizmatranslation/icon.png differ diff --git a/public/static/multisrc/lightnovelworld/lightnovelworld/icon.png b/public/static/multisrc/lightnovelworld/lightnovelworld/icon.png deleted file mode 100644 index 728a41b65..000000000 Binary files a/public/static/multisrc/lightnovelworld/lightnovelworld/icon.png and /dev/null differ diff --git a/public/static/multisrc/lightnovelwp/arcane/icon.png b/public/static/multisrc/lightnovelwp/arcane/icon.png index 1fd0de2c0..bc80ecc8d 100644 Binary files a/public/static/multisrc/lightnovelwp/arcane/icon.png and b/public/static/multisrc/lightnovelwp/arcane/icon.png differ diff --git a/public/static/multisrc/lightnovelwp/freekolnovel/icon.png b/public/static/multisrc/lightnovelwp/freekolnovel/icon.png new file mode 100644 index 000000000..c232aac64 Binary files /dev/null and b/public/static/multisrc/lightnovelwp/freekolnovel/icon.png differ diff --git a/public/static/multisrc/lightnovelwp/kolnovel/icon.png b/public/static/multisrc/lightnovelwp/kolnovel/icon.png index ccca7f0eb..c232aac64 100644 Binary files a/public/static/multisrc/lightnovelwp/kolnovel/icon.png and b/public/static/multisrc/lightnovelwp/kolnovel/icon.png differ diff --git a/public/static/multisrc/lightnovelwp/noveltr/icon.png b/public/static/multisrc/lightnovelwp/noveltr/icon.png index 76a7ccd5a..80eac7542 100644 Binary files a/public/static/multisrc/lightnovelwp/noveltr/icon.png and b/public/static/multisrc/lightnovelwp/noveltr/icon.png differ diff --git a/public/static/multisrc/madara/morenovel/icon.png b/public/static/multisrc/madara/morenovel/icon.png index 662144472..609bd0cd7 100644 Binary files a/public/static/multisrc/madara/morenovel/icon.png and b/public/static/multisrc/madara/morenovel/icon.png differ diff --git a/public/static/multisrc/madara/mostnovel/icon.png b/public/static/multisrc/madara/mostnovel/icon.png index 6dbe54e8f..0005d58e6 100644 Binary files a/public/static/multisrc/madara/mostnovel/icon.png and b/public/static/multisrc/madara/mostnovel/icon.png differ diff --git a/public/static/multisrc/madara/webnoveltraslation/icon.png b/public/static/multisrc/madara/webnoveltraslation/icon.png new file mode 100644 index 000000000..3bd1625af Binary files /dev/null and b/public/static/multisrc/madara/webnoveltraslation/icon.png differ diff --git a/public/static/src/en/leafstudio/icon.png b/public/static/src/en/leafstudio/icon.png new file mode 100644 index 000000000..c33125de2 Binary files /dev/null and b/public/static/src/en/leafstudio/icon.png differ diff --git a/public/static/src/en/lightnovelpub/icon.png b/public/static/src/en/lightnovelpub/icon.png deleted file mode 100644 index 728a41b65..000000000 Binary files a/public/static/src/en/lightnovelpub/icon.png and /dev/null differ