diff --git a/package-lock.json b/package-lock.json index 3cb3ca8fe..760ce4bf9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2280,9 +2280,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2296,9 +2293,6 @@ "cpu": [ "arm" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2312,9 +2306,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2328,9 +2319,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2344,9 +2332,6 @@ "cpu": [ "loong64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2360,9 +2345,6 @@ "cpu": [ "loong64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2376,9 +2358,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2392,9 +2371,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2408,9 +2384,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2424,9 +2397,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2440,9 +2410,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2456,9 +2423,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2472,9 +2436,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ diff --git a/plugins/arabic/Markazriwayat.ts b/plugins/arabic/Markazriwayat.ts new file mode 100644 index 000000000..71d0cb550 --- /dev/null +++ b/plugins/arabic/Markazriwayat.ts @@ -0,0 +1,328 @@ +import { fetchApi } from '@libs/fetch'; +import { Plugin } from '@/types/plugin'; +import { CheerioAPI, load as parseHTML } from 'cheerio'; +import { defaultCover } from '@libs/defaultCover'; +import { NovelStatus } from '@libs/novelStatus'; +import { Filters, FilterTypes } from '@libs/filterInputs'; + +const SITE = 'https://markazriwayat.com/'; + +const getCheerio = async (url: string, search = false): Promise => { + const r = await fetchApi(url); + if (!r.ok && !search) + throw new Error( + 'Could not reach site (' + r.status + ') try to open in webview.', + ); + return parseHTML(await r.text()); +}; + +class Markazriwayat implements Plugin.PluginBase { + id = 'markazriwayat'; + name = 'Markazriwayat'; + version = '3.0.1'; + icon = 'src/ar/markazriwayat/icon.png'; + site = SITE; + + filters = { + status: { + label: 'الحالة', + value: '', + options: [ + { label: 'الكل', value: '' }, + { label: 'مستمرة', value: 'is-ongoing' }, + { label: 'مكتملة', value: 'is-complete' }, + { label: 'متوقفة', value: 'is-stopped' }, + ], + type: FilterTypes.Picker, + }, + sort: { + label: 'الترتيب', + value: '', + options: [ + { label: 'الأكثر شعبية', value: '' }, + { label: 'الأحدث', value: 'new' }, + ], + type: FilterTypes.Picker, + }, + } satisfies Filters; + + async popularNovels( + pageNo: number, + { + showLatestNovels, + filters, + }: Plugin.PopularNovelsOptions, + ): Promise { + let url = SITE + 'popular/'; + if (showLatestNovels || filters.sort.value === 'new') { + url = SITE + 'new/'; + } + if (pageNo > 1) url += 'page/' + pageNo + '/'; + + const $ = await getCheerio(url, pageNo !== 1); + const novels: Plugin.NovelItem[] = []; + + $('a.lib-card, a.novel-card').each((_, el) => { + const card = $(el); + const name = card.find('.lib-card__title, .novel-card__title').text().trim(); + const href = card.attr('href') || ''; + const img = card.find('img'); + const cover = img.attr('data-src') || img.attr('src') || defaultCover; + const statusEl = card.find('.status-pill'); + const statusClass = statusEl.attr('class') || ''; + + if (filters.status.value && !statusClass.includes(filters.status.value)) + return; + + if (!name || !href) return; + + novels.push({ + name, + cover, + path: href.replace(SITE, ''), + }); + }); + + return novels; + } + + async parseNovel(novelPath: string): Promise { + const $ = await getCheerio(SITE + novelPath, false); + + const name = + $('h1.manga-title').text().trim() || + $('h1').first().text().trim() || + ''; + + const coverImg = $( + '.manga-cover-wrap img, .summary_image > a > img', + ).first(); + const cover = + coverImg.attr('data-src') || + coverImg.attr('src') || + defaultCover; + + const author = + $('.manga-author a.manga-author__link').text().trim() || + $('.manga-author').text().replace(/مترجم الرواية\s*:\s*/, '').trim() || + ''; + + let status: string | undefined; + const statusEl = $('.manga-status-pill, .status-pill'); + const statusClass = statusEl.attr('class') || ''; + if (statusClass.includes('is-ongoing')) status = NovelStatus.Ongoing; + else if (statusClass.includes('is-complete')) status = NovelStatus.Completed; + else if (statusClass.includes('is-stopped')) status = NovelStatus.OnHiatus; + + const summary = $('#manga-summary, .manga-summary').text().trim() || ''; + + const genres = $('a[href*="/genre/"], a[href*="/tag/"]') + .map((_, el) => $(el).text().trim()) + .get() + .join(', '); + + const chapters: Plugin.ChapterItem[] = []; + + const parseChaptersFromHtml = (cheerio: CheerioAPI) => { + cheerio('.ch-row').each((index, element) => { + const row = cheerio(element); + const a = row.find('a').first(); + const chapterUrl = a.attr('href') || ''; + if (!chapterUrl) return; + + const chNum = parseInt(row.attr('data-ch-num') || '0', 10); + const chTitle = row.find('.ch-title').text().trim(); + const chDate = row.find('.ch-date').text().trim(); + + let releaseTime: string | null = chDate || null; + if (releaseTime && /^\d{4}\/\d{2}\/\d{2}$/.test(releaseTime)) { + releaseTime = releaseTime.replace(/\//g, '-'); + } + + chapters.push({ + name: chTitle || `الفصل ${chNum}`, + path: chapterUrl.replace(SITE, ''), + chapterNumber: chNum || index + 1, + releaseTime, + }); + }); + }; + + parseChaptersFromHtml($); + + if (chapters.length === 0) { + try { + const ajaxUrl = SITE + novelPath + 'ajax/chapters/'; + const res = await fetchApi(ajaxUrl, { + method: 'POST', + referrer: SITE + novelPath, + }); + if (res.ok) { + const html = await res.text(); + if (html && html !== '0') { + const $ajax = parseHTML(html); + parseChaptersFromHtml($ajax); + + $ajax('.wp-manga-chapter').each((index, element) => { + const el = $ajax(element); + const a = el.find('a').first(); + const chapterUrl = a.attr('href') || ''; + if (!chapterUrl) return; + + const chapterName = a.text().trim(); + const releaseDate = el + .find('span.chapter-release-date') + .text() + .trim(); + + chapters.push({ + name: chapterName, + path: chapterUrl.replace(SITE, ''), + chapterNumber: chapters.length + 1, + releaseTime: releaseDate || null, + }); + }); + } + } + } catch { + // AJAX chapter loading failed + } + } + + const novel: Plugin.SourceNovel = { + path: novelPath, + name, + cover, + author, + genres, + summary, + status, + chapters, + }; + + return novel; + } + + async parseChapter(chapterPath: string): Promise { + const $ = await getCheerio(SITE + chapterPath, false); + + const chapterText = + $('.reading-content .text-right').html() || + $('.reading-content').html() || + $('.text-left').html() || + $('.text-right').html() || + $('.entry-content').html() || + ''; + + if (chapterText) { + const $content = parseHTML(chapterText); + $content( + 'script, noscript, .theam-chobf, span[data-theam-chobf]', + ).remove(); + $content('p:empty').remove(); + return $content.html() || ''; + } + + return ''; + } + + async searchNovels( + searchTerm: string, + pageNo?: number, + ): Promise { + pageNo = pageNo || 1; + + try { + const url = + SITE + + 'wp-json/wp/v2/wp-manga?search=' + + encodeURIComponent(searchTerm) + + '&page=' + + pageNo + + '&per_page=20'; + const res = await fetchApi(url); + if (res.ok) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const data = (await res.json()) as any[]; + if (Array.isArray(data) && data.length > 0) { + return data.map( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (item: any) => ({ + name: item.title?.rendered || item.title || '', + path: (item.link || '').replace(SITE, ''), + cover: defaultCover, + })); + } + } + } catch { + // WP REST API search failed + } + + try { + const $ = await getCheerio( + SITE + '?s=' + encodeURIComponent(searchTerm) + '&post_type=wp-manga', + true, + ); + + const novels: Plugin.NovelItem[] = []; + + $('a.lib-card, a.novel-card, .page-item-detail').each((_, el) => { + const card = $(el); + const name = + card.find('.lib-card__title, .novel-card__title, .post-title').text().trim(); + const href = card.attr('href') || ''; + const img = card.find('img'); + const cover = + img.attr('data-src') || img.attr('src') || defaultCover; + + if (!name || !href) return; + + novels.push({ + name, + cover, + path: href.replace(SITE, ''), + }); + }); + + if (novels.length > 0) return novels; + } catch { + // Search page scraping failed + } + + try { + const $ = await getCheerio(SITE + 'library/', true); + + const allItems = $('a.lib-card, a.novel-card'); + const term = searchTerm.toLowerCase(); + const novels: Plugin.NovelItem[] = []; + + allItems.each((_, el) => { + const card = $(el); + const name = card.find('.lib-card__title, .novel-card__title').text().trim(); + const href = card.attr('href') || ''; + const img = card.find('img'); + const cover = img.attr('data-src') || img.attr('src') || defaultCover; + + if ( + name && + href && + (name.toLowerCase().includes(term) || name.includes(searchTerm)) + ) { + novels.push({ + name, + cover, + path: href.replace(SITE, ''), + }); + } + }); + + return novels; + } catch { + // Library page search failed + } + + return []; + } +} + +export default new Markazriwayat(); diff --git a/plugins/arabic/galaxynovels.ts b/plugins/arabic/galaxynovels.ts new file mode 100644 index 000000000..7f3b98970 --- /dev/null +++ b/plugins/arabic/galaxynovels.ts @@ -0,0 +1,276 @@ +import { load as loadCheerio } from 'cheerio'; +import { fetchApi } from '@libs/fetch'; +import { Plugin } from '@/types/plugin'; +import { NovelStatus } from '@libs/novelStatus'; +import { Filters, FilterTypes } from '@libs/filterInputs'; + +type ChapterJSON = { + id: number; + position: number; + number: string; + label: string; + title: string; + url: string; + content_api: string; + date_iso: string; +}; + +type ChaptersIndex = { + novel_id: number; + total: number; + chapters: ChapterJSON[]; +}; + +type ChapterContentResponse = { + schema: number; + data: { + content_html: string; + display_title: string; + navigation: { next_url: string; previous_url: string }; + }; +}; + +class GalaxyNovels implements Plugin.PluginBase { + id = 'galaxynovels'; + name = 'Galaxy Novels'; + version = '1.0.0'; + icon = 'src/ar/galaxynovels/icon.png'; + site = 'https://galaxynovels.com/'; + + filters = { + sort: { + label: 'Sort By', + value: 'popular', + options: [ + { label: 'Most Popular', value: 'popular' }, + { label: 'Newest', value: 'new' }, + { label: 'Recently Updated', value: 'recent' }, + ], + type: FilterTypes.Picker, + }, + period: { + label: 'Period', + value: 'month', + options: [ + { label: 'Month', value: 'month' }, + { label: 'Week', value: 'week' }, + { label: 'All Time', value: 'all' }, + ], + type: FilterTypes.Picker, + }, + } satisfies Filters; + + private baseUrl = 'https://galaxynovels.com'; + + private async fetchHtml(url: string): Promise { + const res = await fetchApi(url); + if (!res.ok) { + throw new Error(`Could not reach site (${res.status})`); + } + return res.text(); + } + + private async fetchJson(url: string): Promise { + const res = await fetchApi(url); + if (!res.ok) { + throw new Error(`Could not reach site (${res.status})`); + } + return res.json() as Promise; + } + + async popularNovels( + pageNo: number, + { + showLatestNovels, + filters, + }: Plugin.PopularNovelsOptions, + ): Promise { + const sort = showLatestNovels ? 'new' : filters.sort.value; + const period = filters.period.value; + + let url: string; + if (sort === 'new') { + url = `${this.baseUrl}/novels/?sort=new&page=${pageNo}`; + } else if (sort === 'recent') { + url = `${this.baseUrl}/recent/?page=${pageNo}`; + } else { + url = `${this.baseUrl}/novels/?sort=popular&period=${period}&page=${pageNo}`; + } + + const html = await this.fetchHtml(url); + const $ = loadCheerio(html); + + const novels: Plugin.NovelItem[] = []; + $('article.wor-novel-card').each((_, el) => { + const $el = $(el); + const coverLink = $el.find('a.wor-novel-card__cover'); + const href = coverLink.attr('href'); + const img = $el.find('img.wor-cover-img'); + const cover = + img.attr('src') || img.attr('data-src') || undefined; + const title = $el.find('h3 a').text().trim(); + + if (!href || !title) return; + + const path = new URL(href, this.site).pathname; + + novels.push({ + name: title, + path, + cover, + }); + }); + + return novels; + } + + async parseNovel(novelPath: string): Promise { + const url = `${this.baseUrl}${novelPath}`; + const html = await this.fetchHtml(url); + const $ = loadCheerio(html); + + const title = $('h1').first().text().trim(); + const cover = $('img.wor-cover-img').first().attr('src'); + const author = $('p.wor-single-hero__meta-text span').text().trim(); + const summary = $('.wor-single-summary__text').text().trim(); + + const genres: string[] = []; + $('a.wor-tag-pill').each((_, el) => { + genres.push($(el).text().trim()); + }); + + const statusText = $('span.wor-cover-status').text().trim(); + const statusMap: Record = { + 'مستمرة': NovelStatus.Ongoing, + 'مكتملة': NovelStatus.Completed, + 'متوقفة': NovelStatus.OnHiatus, + }; + const status = statusMap[statusText] || NovelStatus.Unknown; + + const chaptersContainer = $('[data-wor-chapters-container]'); + const chaptersIndexUrl = chaptersContainer.attr('data-index-url'); + + let chapters: Plugin.ChapterItem[] = []; + + if (chaptersIndexUrl) { + try { + const indexUrl = chaptersIndexUrl.startsWith('http') + ? chaptersIndexUrl + : `${this.baseUrl}${chaptersIndexUrl}`; + const index = await this.fetchJson(indexUrl); + + chapters = index.chapters.map(ch => ({ + name: ch.label + (ch.title ? `: ${ch.title}` : ''), + path: `${novelPath}chapter-${ch.id}/`, + chapterNumber: ch.position, + releaseTime: ch.date_iso?.split('T')[0] || '', + page: String(ch.id), + })); + } catch { + // fallback to HTML parsing + } + } + + if (chapters.length === 0) { + $('article.wor-novel-chapter-item').each((_, el) => { + const $el = $(el); + const chapterLink = $el.find('h3 a').attr('href') || $el.find('a.wor-novel-chapter-item__num').attr('href'); + const chapterName = $el.find('h3 a').text().trim() || $el.find('a.wor-novel-chapter-item__num').text().trim(); + const chapterId = $el.attr('data-chapter-id'); + const timeEl = $el.find('time'); + const releaseTime = timeEl.attr('datetime')?.split('T')[0] || ''; + + if (!chapterLink) return; + + const path = chapterId + ? `${novelPath}chapter-${chapterId}/` + : new URL(chapterLink, this.site).pathname; + const numMatch = path.match(/chapter-(\d+)/); + const chapterNumber = numMatch ? parseInt(numMatch[1]) : 0; + + chapters.push({ + name: chapterName, + path, + chapterNumber, + releaseTime, + page: chapterId || '', + }); + }); + } + + return { + path: novelPath, + name: title, + cover, + author: author || 'Unknown', + genres: genres.join(', '), + summary, + status, + chapters, + }; + } + + async parseChapter(chapterPath: string): Promise { + const idMatch = chapterPath.match(/chapter-(\d+)/); + const chapterId = idMatch?.[1]; + + if (chapterId) { + const apiUrl = `${this.baseUrl}/wp-json/wor-reader-app/v1/chapters/${chapterId}`; + try { + const response = await this.fetchJson(apiUrl); + if (response.data?.content_html) { + return response.data.content_html; + } + } catch { + // fallback to HTML + } + } + + const url = `${this.baseUrl}${chapterPath}`; + const html = await this.fetchHtml(url); + const $ = loadCheerio(html); + const content = + $('article.wor-chapter-content, .wor-chapter-text, .entry-content').html(); + return content || '

Content not available.

'; + } + + async searchNovels( + searchTerm: string, + pageNo: number, + ): Promise { + const manifestUrl = + 'https://galaxynovels.com/wp-content/uploads/wor-reader-cache/search/manifest.json'; + const manifest = await this.fetchJson<{ + index: string; + }>(manifestUrl); + + const searchIndex = await this.fetchJson<{ + items: { + t: string; + u: string; + c: string; + s: string; + }[]; + }>(manifest.index); + + const term = searchTerm.toLowerCase(); + const filtered = searchIndex.items.filter( + n => + n.t.toLowerCase().includes(term) || + n.s.toLowerCase().includes(term), + ); + + const limit = 20; + const offset = (pageNo - 1) * limit; + + return filtered.slice(offset, offset + limit).map(novel => ({ + name: novel.t, + path: novel.u, + cover: novel.c.startsWith('http') + ? novel.c + : `${this.baseUrl}${novel.c}`, + })); + } +} + +export default new GalaxyNovels(); diff --git a/plugins/arabic/rewayahfans.ts b/plugins/arabic/rewayahfans.ts new file mode 100644 index 000000000..afaed63c7 --- /dev/null +++ b/plugins/arabic/rewayahfans.ts @@ -0,0 +1,198 @@ +import { load as parseHTML } from 'cheerio'; +import { fetchApi } from '@libs/fetch'; +import { Plugin } from '@/types/plugin'; + +type WPPage = { + id: number; + title: { rendered: string }; + slug: string; + link: string; + content: { rendered: string }; + date: string; +}; + +class RewayahFans implements Plugin.PluginBase { + id = 'rewayahfans'; + name = 'روايه فانز'; + version = '4.0.0'; + icon = 'src/ar/rewayahfans/icon.png'; + site = 'https://rewayahfans.net/'; + + private async fetchJson(url: string): Promise { + const res = await fetchApi(url); + if (!res.ok) throw new Error(`Request failed: ${res.status}`); + return res.json() as Promise; + } + + private async fetchHtml(url: string): Promise { + const res = await fetchApi(url); + if (!res.ok) throw new Error(`Request failed: ${res.status}`); + return res.text(); + } + + async popularNovels( + page: number, + { showLatestNovels }: Plugin.PopularNovelsOptions, + ): Promise { + if (showLatestNovels) { + const pages = await this.fetchJson( + `${this.site}wp-json/wp/v2/pages?per_page=20&page=${page}&orderby=date&order=desc&_fields=slug,title`, + ); + return pages.map(p => ({ + name: this.extractNovelName(p.title.rendered), + path: p.slug, + cover: '', + })); + } + + const allNovels = await this.getAllNovels(); + const pageSize = 20; + const start = (page - 1) * pageSize; + return allNovels.slice(start, start + pageSize); + } + + private async getAllNovels(): Promise { + const seen = new Set(); + const novels: Plugin.NovelItem[] = []; + + let pg = 1; + let hasMore = true; + + while (hasMore) { + const pages = await this.fetchJson( + `${this.site}wp-json/wp/v2/pages?per_page=100&page=${pg}&_fields=slug,title`, + ); + + if (pages.length === 0) { + hasMore = false; + break; + } + + for (const page of pages) { + const novelName = this.extractNovelName(page.title.rendered); + if (novelName && !seen.has(novelName)) { + seen.add(novelName); + novels.push({ + name: novelName, + path: page.slug, + cover: '', + }); + } + } + + if (pages.length < 100) hasMore = false; + pg++; + } + + return novels; + } + + async parseNovel(novelPath: string): Promise { + const novel: Plugin.SourceNovel = { + path: novelPath, + name: '', + chapters: [], + }; + + const slugBase = novelPath.replace(/\/$/, '').split('/').pop() || novelPath; + const searchTerm = slugBase + .replace(/-\d+$/, '') + .replace(/-/g, ' '); + + let pg = 1; + let hasMore = true; + + while (hasMore) { + const pages = await this.fetchJson( + `${this.site}wp-json/wp/v2/pages?search=${encodeURIComponent(searchTerm)}&per_page=100&page=${pg}&_fields=slug,title,date`, + ); + + if (pages.length === 0) { + hasMore = false; + break; + } + + for (const page of pages) { + const postSlug = page.slug; + if (postSlug.startsWith(slugBase.replace(/-\d+$/, ''))) { + if (!novel.name) { + novel.name = this.extractNovelName(page.title.rendered); + } + const numMatch = postSlug.match(/(\d+)$/); + const chapterNum = numMatch ? parseInt(numMatch[1], 10) : 0; + + novel.chapters!.push({ + name: page.title.rendered, + path: postSlug, + chapterNumber: chapterNum, + releaseTime: page.date, + }); + } + } + + if (pages.length < 100) hasMore = false; + pg++; + } + + if (novel.chapters!.length > 0) { + novel.chapters!.sort((a, b) => (a.chapterNumber || 0) - (b.chapterNumber || 0)); + if (!novel.name && novel.chapters!.length > 0) { + novel.name = this.extractNovelName(novel.chapters![0].name); + } + } + + return novel; + } + + async parseChapter(chapterPath: string): Promise { + const pages = await this.fetchJson( + `${this.site}wp-json/wp/v2/pages?slug=${chapterPath}&_fields=content`, + ); + + const arr = Array.isArray(pages) ? pages : [pages]; + if (arr.length > 0 && arr[0].content?.rendered) { + const $ = parseHTML(arr[0].content.rendered); + $('script, style, .sharedaddy, .jp-relatedposts, .wp-block-spacer, .simplefavorite-button').remove(); + return $.html(); + } + + const html = await this.fetchHtml(`${this.site}${chapterPath}/`); + const $ = parseHTML(html); + const content = + $('article .entry-content, .post-content, .entry-content').html() || ''; + return content || '

المحتوى غير متاح.

'; + } + + async searchNovels( + searchTerm: string, + page: number, + ): Promise { + const pages = await this.fetchJson( + `${this.site}wp-json/wp/v2/pages?search=${encodeURIComponent(searchTerm)}&per_page=20&page=${page}&_fields=slug,title`, + ); + + const seen = new Set(); + const novels: Plugin.NovelItem[] = []; + + for (const page of pages) { + const novelName = this.extractNovelName(page.title.rendered); + if (novelName && !seen.has(novelName)) { + seen.add(novelName); + novels.push({ + name: novelName, + path: page.slug, + cover: '', + }); + } + } + + return novels; + } + + private extractNovelName(title: string): string { + const match = title.match(/^(.+?)\s+\d+$/); + return match ? match[1].trim() : title.trim(); + } +} + +export default new RewayahFans(); diff --git a/plugins/arabic/rewayatfans.ts b/plugins/arabic/rewayatfans.ts new file mode 100644 index 000000000..a5ee909d9 --- /dev/null +++ b/plugins/arabic/rewayatfans.ts @@ -0,0 +1,198 @@ +import { load as parseHTML } from 'cheerio'; +import { fetchApi } from '@libs/fetch'; +import { Plugin } from '@/types/plugin'; + +type WPPage = { + id: number; + title: { rendered: string }; + slug: string; + link: string; + content: { rendered: string }; + date: string; +}; + +class RewayatFans implements Plugin.PluginBase { + id = 'rewayatfans'; + name = 'روايات فانز'; + version = '3.0.0'; + icon = 'src/ar/rewayatfans/icon.png'; + site = 'https://rewayatfans.com/'; + + private async fetchJson(url: string): Promise { + const res = await fetchApi(url); + if (!res.ok) throw new Error(`Request failed: ${res.status}`); + return res.json() as Promise; + } + + private async fetchHtml(url: string): Promise { + const res = await fetchApi(url); + if (!res.ok) throw new Error(`Request failed: ${res.status}`); + return res.text(); + } + + async popularNovels( + page: number, + { showLatestNovels }: Plugin.PopularNovelsOptions, + ): Promise { + if (showLatestNovels) { + const pages = await this.fetchJson( + `${this.site}wp-json/wp/v2/pages?per_page=20&page=${page}&orderby=date&order=desc&_fields=slug,title`, + ); + return pages.map(p => ({ + name: this.extractNovelName(p.title.rendered), + path: p.slug, + cover: '', + })); + } + + const allNovels = await this.getAllNovels(); + const pageSize = 20; + const start = (page - 1) * pageSize; + return allNovels.slice(start, start + pageSize); + } + + private async getAllNovels(): Promise { + const seen = new Set(); + const novels: Plugin.NovelItem[] = []; + + let pg = 1; + let hasMore = true; + + while (hasMore) { + const pages = await this.fetchJson( + `${this.site}wp-json/wp/v2/pages?per_page=100&page=${pg}&_fields=slug,title`, + ); + + if (pages.length === 0) { + hasMore = false; + break; + } + + for (const page of pages) { + const novelName = this.extractNovelName(page.title.rendered); + if (novelName && !seen.has(novelName)) { + seen.add(novelName); + novels.push({ + name: novelName, + path: page.slug, + cover: '', + }); + } + } + + if (pages.length < 100) hasMore = false; + pg++; + } + + return novels; + } + + async parseNovel(novelPath: string): Promise { + const novel: Plugin.SourceNovel = { + path: novelPath, + name: '', + chapters: [], + }; + + const slugBase = novelPath.replace(/\/$/, '').split('/').pop() || novelPath; + const searchTerm = slugBase + .replace(/-\d+$/, '') + .replace(/-/g, ' '); + + let pg = 1; + let hasMore = true; + + while (hasMore) { + const pages = await this.fetchJson( + `${this.site}wp-json/wp/v2/pages?search=${encodeURIComponent(searchTerm)}&per_page=100&page=${pg}&_fields=slug,title,date`, + ); + + if (pages.length === 0) { + hasMore = false; + break; + } + + for (const page of pages) { + const postSlug = page.slug; + if (postSlug.startsWith(slugBase.replace(/-\d+$/, ''))) { + if (!novel.name) { + novel.name = this.extractNovelName(page.title.rendered); + } + const numMatch = postSlug.match(/(\d+)$/); + const chapterNum = numMatch ? parseInt(numMatch[1], 10) : 0; + + novel.chapters!.push({ + name: page.title.rendered, + path: postSlug, + chapterNumber: chapterNum, + releaseTime: page.date, + }); + } + } + + if (pages.length < 100) hasMore = false; + pg++; + } + + if (novel.chapters!.length > 0) { + novel.chapters!.sort((a, b) => (a.chapterNumber || 0) - (b.chapterNumber || 0)); + if (!novel.name && novel.chapters!.length > 0) { + novel.name = this.extractNovelName(novel.chapters![0].name); + } + } + + return novel; + } + + async parseChapter(chapterPath: string): Promise { + const pages = await this.fetchJson( + `${this.site}wp-json/wp/v2/pages?slug=${chapterPath}&_fields=content`, + ); + + const arr = Array.isArray(pages) ? pages : [pages]; + if (arr.length > 0 && arr[0].content?.rendered) { + const $ = parseHTML(arr[0].content.rendered); + $('script, style, .sharedaddy, .jp-relatedposts, .wp-block-spacer').remove(); + return $.html(); + } + + const html = await this.fetchHtml(`${this.site}${chapterPath}/`); + const $ = parseHTML(html); + const content = + $('article .entry-content, .post-content, .entry-content').html() || ''; + return content || '

المحتوى غير متاح.

'; + } + + async searchNovels( + searchTerm: string, + page: number, + ): Promise { + const pages = await this.fetchJson( + `${this.site}wp-json/wp/v2/pages?search=${encodeURIComponent(searchTerm)}&per_page=20&page=${page}&_fields=slug,title`, + ); + + const seen = new Set(); + const novels: Plugin.NovelItem[] = []; + + for (const page of pages) { + const novelName = this.extractNovelName(page.title.rendered); + if (novelName && !seen.has(novelName)) { + seen.add(novelName); + novels.push({ + name: novelName, + path: page.slug, + cover: '', + }); + } + } + + return novels; + } + + private extractNovelName(title: string): string { + const match = title.match(/^(.+?)\s+\d+$/); + return match ? match[1].trim() : title.trim(); + } +} + +export default new RewayatFans(); diff --git a/plugins/arabic/seanovel.ts b/plugins/arabic/seanovel.ts new file mode 100644 index 000000000..75fe4c381 --- /dev/null +++ b/plugins/arabic/seanovel.ts @@ -0,0 +1,218 @@ +import { fetchApi } from '@libs/fetch'; +import { Plugin } from '@/types/plugin'; +import { NovelStatus } from '@libs/novelStatus'; +import { Filters, FilterTypes } from '@libs/filterInputs'; + +type SeanovelNovel = { + slug: string; + source_id: number; + title_ar: string; + title_original: string; + origin: string; + author: string; + status: string; + genres: string[]; + chapters_count: number; + last_updated: string; + description: string; + rating: number; + has_volumes: boolean; + chapters: { id: number; title: string; date: string }[]; + cover_version?: number; +}; + +type SeanovelNovelListItem = { + slug: string; + title_ar: string; + title_original: string; + author: string; + status: string; + genres: string[]; + chapters_count: number; + description: string; +}; + +class Seanovel implements Plugin.PluginBase { + id = 'seanovel'; + name = 'Seanovel'; + version = '1.0.0'; + icon = 'src/ar/seanovel/icon.png'; + site = 'https://seanovel.org/'; + + filters = { + sort: { + label: 'Sort By', + value: 'views', + options: [ + { label: 'Most Popular', value: 'views' }, + { label: 'Latest', value: 'latest' }, + { label: 'Rating', value: 'rating' }, + ], + type: FilterTypes.Picker, + }, + origin: { + label: 'Origin', + value: '', + options: [ + { label: 'All', value: '' }, + { label: 'English', value: 'english' }, + { label: 'Chinese', value: 'chinese' }, + { label: 'Korean', value: 'korean' }, + { label: 'Japanese', value: 'japanese' }, + ], + type: FilterTypes.Picker, + }, + } satisfies Filters; + + private baseUrl = 'https://seanovel.org'; + + private async fetchJson(url: string): Promise { + const res = await fetchApi(url); + if (!res.ok) { + throw new Error(`Could not reach site (${res.status})`); + } + return res.json() as Promise; + } + + async popularNovels( + pageNo: number, + { + showLatestNovels, + filters, + }: Plugin.PopularNovelsOptions, + ): Promise { + const sort = showLatestNovels ? 'latest' : filters.sort.value; + const limit = 50; + const offset = (pageNo - 1) * limit; + + let url = `${this.baseUrl}/api/novels?sort=${sort}&page=${pageNo}&limit=${limit}&offset=${offset}`; + if (filters.origin.value) { + url += `&origin=${filters.origin.value}`; + } + + const novels = await this.fetchJson(url); + + return novels.map(novel => ({ + name: novel.title_original || novel.title_ar, + path: `/novels/${novel.slug}`, + cover: `${this.baseUrl}/api/novel/${novel.slug}/cover?type=webp`, + })); + } + + async parseNovel(novelPath: string): Promise { + const slug = novelPath.replace('/novels/', '').replace(/\/$/, ''); + const novel = await this.fetchJson( + `${this.baseUrl}/api/novel/${slug}`, + ); + + const statusMap: Record = { + ongoing: NovelStatus.Ongoing, + completed: NovelStatus.Completed, + hiatus: NovelStatus.OnHiatus, + dropped: NovelStatus.Cancelled, + cancelled: NovelStatus.Cancelled, + }; + + return { + path: novelPath, + name: novel.title_original || novel.title_ar, + cover: `${this.baseUrl}/api/novel/${slug}/cover?type=webp`, + author: novel.author || 'Unknown', + genres: novel.genres?.join(', ') || '', + summary: novel.description || '', + status: statusMap[novel.status?.toLowerCase()] || NovelStatus.Unknown, + chapters: (novel.chapters || []) + .sort((a, b) => a.id - b.id) + .map((ch, index) => ({ + name: ch.title || `Chapter ${ch.id}`, + path: `/novels/${slug}/chapters/${ch.id}`, + chapterNumber: index + 1, + releaseTime: ch.date?.split('T')[0] || '', + })), + }; + } + + async parseChapter(chapterPath: string): Promise { + const url = `${this.baseUrl}${chapterPath}`; + const res = await fetchApi(url); + const html = await res.text(); + + const allChunks: string[] = []; + const regex = + /self\.__next_f\.push\(\s*\[\s*\d+\s*,\s*("(?:[^"\\]|\\.)*")\s*\]/g; + let match; + while ((match = regex.exec(html)) !== null) { + try { + allChunks.push(JSON.parse(match[1])); + } catch { + // skip unparseable chunks + } + } + + if (allChunks.length > 0) { + const fullPayload = allChunks.join(''); + const initIdx = fullPayload.indexOf('initialParagraphs'); + if (initIdx > 0) { + const arrStart = fullPayload.indexOf('[', initIdx); + if (arrStart > 0) { + let depth = 0; + let arrEnd = -1; + for (let i = arrStart; i < fullPayload.length; i++) { + if (fullPayload[i] === '[') depth++; + if (fullPayload[i] === ']') { + depth--; + if (depth === 0) { + arrEnd = i + 1; + break; + } + } + } + if (arrEnd > 0) { + const raw = fullPayload.substring(arrStart, arrEnd); + const unescaped = raw.replace(/\\"/g, '"').replace(/\\\\/g, '\\'); + try { + const paragraphs: string[] = JSON.parse(unescaped); + if (paragraphs.length > 0) { + return paragraphs + .filter(p => typeof p === 'string' && p.trim()) + .map(p => `

${p.trim()}

`) + .join('\n'); + } + } catch { + // fallback below + } + } + } + } + } + + return '

Content not available. Open in webview to read.

'; + } + + async searchNovels( + searchTerm: string, + pageNo: number, + ): Promise { + const limit = 50; + const offset = (pageNo - 1) * limit; + const allNovels = await this.fetchJson( + `${this.baseUrl}/api/novels?sort=views&page=1&limit=500&offset=0`, + ); + + const term = searchTerm.toLowerCase(); + const filtered = allNovels.filter( + n => + (n.title_original && n.title_original.toLowerCase().includes(term)) || + (n.title_ar && n.title_ar.includes(searchTerm)) || + (n.author && n.author.toLowerCase().includes(term)), + ); + + return filtered.slice(offset, offset + limit).map(novel => ({ + name: novel.title_original || novel.title_ar, + path: `/novels/${novel.slug}`, + cover: `${this.baseUrl}/api/novel/${novel.slug}/cover?type=webp`, + })); + } +} + +export default new Seanovel(); diff --git a/plugins/index.ts b/plugins/index.ts index 448606c58..30d52e58d 100644 --- a/plugins/index.ts +++ b/plugins/index.ts @@ -3,254 +3,258 @@ import p_0 from '@plugins/arabic/ArNovel[madara]'; import p_1 from '@plugins/arabic/Azora[madara]'; import p_2 from '@plugins/arabic/dilartube'; import p_3 from '@plugins/arabic/FreeKolNovel[lightnovelwp]'; -import p_4 from '@plugins/arabic/HizoManga[madara]'; -import p_5 from '@plugins/arabic/KolNovel[lightnovelwp]'; -import p_6 from '@plugins/arabic/Markazriwayat[madara]'; -import p_7 from '@plugins/arabic/Novel4Up[madara]'; -import p_8 from '@plugins/arabic/NovelsParadise[lightnovelwp]'; -import p_9 from '@plugins/arabic/Olaoecyou[madara]'; -import p_10 from '@plugins/arabic/rewayatclub'; -import p_11 from '@plugins/arabic/Riwyat[madara]'; -import p_12 from '@plugins/arabic/sunovels'; -import p_13 from '@plugins/chinese/69shu'; -import p_14 from '@plugins/chinese/ixdzs8'; -import p_15 from '@plugins/chinese/linovel'; -import p_16 from '@plugins/chinese/linovelib'; -import p_17 from '@plugins/chinese/linovelib_tw'; -import p_18 from '@plugins/chinese/novel543'; -import p_19 from '@plugins/chinese/Quanben'; -import p_20 from '@plugins/english/AllNovelFull[readnovelfull]'; -import p_21 from '@plugins/english/AllNovel[readnovelfull]'; -import p_22 from '@plugins/english/ao3'; -import p_23 from '@plugins/english/ArcaneTranslations[lightnovelwp]'; -import p_24 from '@plugins/english/BelleReservoir[madara]'; -import p_25 from '@plugins/english/BoxNovel[madara]'; -import p_26 from '@plugins/english/CherryMistCafe[fictioneer]'; -import p_27 from '@plugins/english/chrysanthemumgarden'; -import p_28 from '@plugins/english/CitrusAurora[madara]'; -import p_29 from '@plugins/english/CoralBoutique[madara]'; -import p_30 from '@plugins/english/CPUnovel[lightnovelwp]'; -import p_31 from '@plugins/english/crimsonscrolls'; -import p_32 from '@plugins/english/DaoistQuest[fictioneer]'; -import p_33 from '@plugins/english/DaoNovel[madara]'; -import p_34 from '@plugins/english/DaoTranslate[lightnovelwp]'; -import p_35 from '@plugins/english/DearestRosalie[fictioneer]'; -import p_36 from '@plugins/english/divinedaolibrary'; -import p_37 from '@plugins/english/Dragonholic[madara]'; -import p_38 from '@plugins/english/DragonTea[madara]'; -import p_39 from '@plugins/english/dreambigtl'; -import p_40 from '@plugins/english/DuskBlossoms[madara]'; -import p_41 from '@plugins/english/ElloTL[lightnovelwp]'; -import p_42 from '@plugins/english/Eternalune[madara]'; -import p_43 from '@plugins/english/EtudeTranslations[madara]'; -import p_44 from '@plugins/english/FanNovel[readwn]'; -import p_45 from '@plugins/english/FansMTL[readwn]'; -import p_46 from '@plugins/english/FansTranslations[madara]'; -import p_47 from '@plugins/english/faqwikius'; -import p_48 from '@plugins/english/fenrirrealm'; -import p_49 from '@plugins/english/fictionzone'; -import p_50 from '@plugins/english/FirstKissNovel[madara]'; -import p_51 from '@plugins/english/Foxaholic[madara]'; -import p_52 from '@plugins/english/foxteller'; -import p_53 from '@plugins/english/FreeWebNovel[readnovelfull]'; -import p_54 from '@plugins/english/GalaxyTranslations[madara]'; -import p_55 from '@plugins/english/genesis'; -import p_56 from '@plugins/english/Guavaread[madara]'; -import p_57 from '@plugins/english/HiraethTranslation[madara]'; -import p_58 from '@plugins/english/HotNovelPub[hotnovelpub]'; -import p_59 from '@plugins/english/HyacinthinBloom[lightnovelwp]'; -import p_60 from '@plugins/english/indraTranslations'; -import p_61 from '@plugins/english/inkitt'; -import p_62 from '@plugins/english/inoveltranslation'; -import p_63 from '@plugins/english/Ippotranslations[lightnovelwp]'; -import p_64 from '@plugins/english/KDTNovels[lightnovelwp]'; -import p_65 from '@plugins/english/KeopiTranslations[lightnovelwp]'; -import p_66 from '@plugins/english/KnoxT[lightnovelwp]'; -import p_67 from '@plugins/english/LazyGirlTranslations[lightnovelwp]'; -import p_68 from '@plugins/english/leafstudio'; -import p_69 from '@plugins/english/LibRead[readnovelfull]'; -import p_70 from '@plugins/english/LightNovelCave[lightnovelworld]'; -import p_71 from '@plugins/english/LightNovelHeaven[madara]'; -import p_72 from '@plugins/english/LightNovelPlus[readnovelfull]'; -import p_73 from '@plugins/english/LightNovelPubVip[lightnovelworld]'; -import p_74 from '@plugins/english/lightnoveltranslation'; -import p_75 from '@plugins/english/LightNovelUpdates[madara]'; -import p_76 from '@plugins/english/LilyontheValley[fictioneer]'; -import p_77 from '@plugins/english/lnmtl'; -import p_78 from '@plugins/english/lnori'; -import p_79 from '@plugins/english/Ltnovel[readwn]'; -import p_80 from '@plugins/english/LulloBox[madara]'; -import p_81 from '@plugins/english/LunarLetters[madara]'; -import p_82 from '@plugins/english/Meownovel[madara]'; -import p_83 from '@plugins/english/MoonlightNovels[lightnovelwp]'; -import p_84 from '@plugins/english/MostNovel[madara]'; -import p_85 from '@plugins/english/MTLNovel[madara]'; -import p_86 from '@plugins/english/MTLNovel[mtlnovel]'; -import p_87 from '@plugins/english/mvlempyr'; -import p_88 from '@plugins/english/MysticalSeries[madara]'; -import p_89 from '@plugins/english/NeoSekaiTranslations[madara]'; -import p_90 from '@plugins/english/NitroManga[madara]'; -import p_91 from '@plugins/english/NobleMTL[lightnovelwp]'; -import p_92 from '@plugins/english/NoiceTranslations[madara]'; -import p_93 from '@plugins/english/novelarrow'; -import p_94 from '@plugins/english/NovelBin[readnovelfull]'; -import p_95 from '@plugins/english/novelbuddy'; -import p_96 from '@plugins/english/NovelCool[novelcool]'; -import p_97 from '@plugins/english/novelfire'; -import p_98 from '@plugins/english/NovelFull[readnovelfull]'; -import p_99 from '@plugins/english/novelhall'; -import p_100 from '@plugins/english/novelhi'; -import p_101 from '@plugins/english/novelight'; -import p_102 from '@plugins/english/NovelLib[fictioneer]'; -import p_103 from '@plugins/english/NovelMultiverse[madara]'; -import p_104 from '@plugins/english/NovelNinja[madara]'; -import p_105 from '@plugins/english/NovelOnline'; -import p_106 from '@plugins/english/novelrest'; -import p_107 from '@plugins/english/NovelsKnight[lightnovelwp]'; -import p_108 from '@plugins/english/NovelTranslate[madara]'; -import p_109 from '@plugins/english/novelupdates'; -import p_110 from '@plugins/english/PandaMachineTranslations[lightnovelwp]'; -import p_111 from '@plugins/english/PastelTales[madara]'; -import p_112 from '@plugins/english/pawread'; -import p_113 from '@plugins/english/PenguinSquad[fictioneer]'; -import p_114 from '@plugins/english/Prizma[fictioneer]'; -import p_115 from '@plugins/english/rainofsnow'; -import p_116 from '@plugins/english/Ranobes[ranobes]'; -import p_117 from '@plugins/english/Ranovel[madara]'; -import p_118 from '@plugins/english/ReadFanfic[madara]'; -import p_119 from '@plugins/english/readfrom'; -import p_120 from '@plugins/english/ReadNovelFull[readnovelfull]'; -import p_121 from '@plugins/english/relibrary'; -import p_122 from '@plugins/english/RequiemTranslations[lightnovelwp]'; -import p_123 from '@plugins/english/royalroad'; -import p_124 from '@plugins/english/SalmonLatte[madara]'; -import p_125 from '@plugins/english/scribblehub'; -import p_126 from '@plugins/english/SleepyTranslations[madara]'; -import p_127 from '@plugins/english/SonicMTL[madara]'; -import p_128 from '@plugins/english/SrankManga[madara]'; -import p_129 from '@plugins/english/StorySeedling'; -import p_130 from '@plugins/english/SweetEscape[madara]'; -import p_131 from '@plugins/english/SystemTranslation[lightnovelwp]'; -import p_132 from '@plugins/english/TranslatinOtaku[madara]'; -import p_133 from '@plugins/english/TranslationWeaver[lightnovelwp]'; -import p_134 from '@plugins/english/UniversalNovel[lightnovelwp]'; -import p_135 from '@plugins/english/VandyTranslate[lightnovelwp]'; -import p_136 from '@plugins/english/VioletLily[madara]'; -import p_137 from '@plugins/english/vynovel'; -import p_138 from '@plugins/english/wct'; -import p_139 from '@plugins/english/webnovel'; -import p_140 from '@plugins/english/WebNovelLover[madara]'; -import p_141 from '@plugins/english/WebNovelPub[lightnovelworld]'; -import p_142 from '@plugins/english/WebNovelTranslation[madara]'; -import p_143 from '@plugins/english/WhiteMoonlightNovels[lightnovelwp]'; -import p_144 from '@plugins/english/WooksTeahouse[madara]'; -import p_145 from '@plugins/english/WordExcerpt[madara]'; -import p_146 from '@plugins/english/wtrlab'; -import p_147 from '@plugins/english/Wuxiabox[readwn]'; -import p_148 from '@plugins/english/Wuxiafox[readwn]'; -import p_149 from '@plugins/english/WuxiaSpace[readwn]'; -import p_150 from '@plugins/english/WuxiaV[readwn]'; -import p_151 from '@plugins/english/wuxiaworld'; -import p_152 from '@plugins/english/WuxiaWorldSite[madara]'; -import p_153 from '@plugins/english/ZetroTranslation[madara]'; -import p_154 from '@plugins/french/chireads'; -import p_155 from '@plugins/french/harkeneliwood'; -import p_156 from '@plugins/french/kisswood'; -import p_157 from '@plugins/french/LighNovelFR[lightnovelwp]'; -import p_158 from '@plugins/french/MassNovel[madara]'; -import p_159 from '@plugins/french/MTLNovel(FR)[mtlnovel]'; -import p_160 from '@plugins/french/noveldeglace'; -import p_161 from '@plugins/french/novelfrance'; -import p_162 from '@plugins/french/novhell'; -import p_163 from '@plugins/french/rezerowebnovelfr'; -import p_164 from '@plugins/french/warriorlegendtrad'; -import p_165 from '@plugins/french/WorldNovel[madara]'; -import p_166 from '@plugins/french/wuxialnscantrad'; -import p_167 from '@plugins/french/xiaowaz'; -import p_168 from '@plugins/indonesian/BacaLightNovel[lightnovelwp]'; -import p_169 from '@plugins/indonesian/indowebnovel'; -import p_170 from '@plugins/indonesian/MeioNovel[madara]'; -import p_171 from '@plugins/indonesian/MTLNovel(ID)[mtlnovel]'; -import p_172 from '@plugins/indonesian/NovelBookID[madara]'; -import p_173 from '@plugins/indonesian/sakuranovel'; -import p_174 from '@plugins/indonesian/SekteNovel[lightnovelwp]'; -import p_175 from '@plugins/indonesian/Vanovel[madara]'; -import p_176 from '@plugins/indonesian/WBNovel[madara]'; -import p_177 from '@plugins/japanese/kakuyomu'; -import p_178 from '@plugins/japanese/Syosetu'; -import p_179 from '@plugins/korean/Agitoon'; -import p_180 from '@plugins/korean/FortuneEternal[madara]'; -import p_181 from '@plugins/multi/komga'; -import p_182 from '@plugins/polish/novelki'; -import p_183 from '@plugins/portuguese/BetterNovels[lightnovelwp]'; -import p_184 from '@plugins/portuguese/blogdoamonnovels'; -import p_185 from '@plugins/portuguese/CentralNovel[lightnovelwp]'; -import p_186 from '@plugins/portuguese/illusia'; -import p_187 from '@plugins/portuguese/Kiniga[madara]'; -import p_188 from '@plugins/portuguese/LaNovels[hotnovelpub]'; -import p_189 from '@plugins/portuguese/LightNovelBrasil[lightnovelwp]'; -import p_190 from '@plugins/portuguese/MTLNovel(PT)[mtlnovel]'; -import p_191 from '@plugins/portuguese/novelmania'; -import p_192 from '@plugins/portuguese/tsundoku'; -import p_193 from '@plugins/russian/authortoday'; -import p_194 from '@plugins/russian/Bllate[rulate]'; -import p_195 from '@plugins/russian/Bookhamster[ifreedom]'; -import p_196 from '@plugins/russian/bookriver'; -import p_197 from '@plugins/russian/Erolate[rulate]'; -import p_198 from '@plugins/russian/EzNovels[hotnovelpub]'; -import p_199 from '@plugins/russian/ficbook'; -import p_200 from '@plugins/russian/jaomix'; -import p_201 from '@plugins/russian/MTLNovel(RU)[mtlnovel]'; -import p_202 from '@plugins/russian/neobook'; -import p_203 from '@plugins/russian/NovelCool(RU)[novelcool]'; -import p_204 from '@plugins/russian/novelTL'; -import p_205 from '@plugins/russian/ranobehub'; -import p_206 from '@plugins/russian/ranobelib'; -import p_207 from '@plugins/russian/ranoberf'; -import p_208 from '@plugins/russian/Ranobes(RU)[ranobes]'; -import p_209 from '@plugins/russian/renovels'; -import p_210 from '@plugins/russian/Rulate[rulate]'; -import p_211 from '@plugins/russian/topliba'; -import p_212 from '@plugins/russian/zelluloza'; -import p_213 from '@plugins/russian/СвободныйМирРанобэ[ifreedom]'; -import p_214 from '@plugins/spanish/AllNovelRead[lightnovelwp]'; -import p_215 from '@plugins/spanish/AnimesHoy12[madara]'; -import p_216 from '@plugins/spanish/hasutl'; -import p_217 from '@plugins/spanish/LightNovelDaily[hotnovelpub]'; -import p_218 from '@plugins/spanish/MTLNovel(ES)[mtlnovel]'; -import p_219 from '@plugins/spanish/NOVA'; -import p_220 from '@plugins/spanish/novelasligera'; -import p_221 from '@plugins/spanish/novelawuxia'; -import p_222 from '@plugins/spanish/novelyra'; -import p_223 from '@plugins/spanish/oasistranslations'; -import p_224 from '@plugins/spanish/PanchoTranslations[madara]'; -import p_225 from '@plugins/spanish/skynovels'; -import p_226 from '@plugins/spanish/TC&Sega[lightnovelwp]'; -import p_227 from '@plugins/spanish/TraduccionesAmistosas[madara]'; -import p_228 from '@plugins/spanish/tunovelaligera'; -import p_229 from '@plugins/spanish/yukitls'; -import p_230 from '@plugins/thai/NovelLucky[madara]'; -import p_231 from '@plugins/thai/NovelPDF[madara]'; -import p_232 from '@plugins/turkish/ArazNovel[madara]'; -import p_233 from '@plugins/turkish/EKTAPLAR[madara]'; -import p_234 from '@plugins/turkish/epiknovel'; -import p_235 from '@plugins/turkish/kakikata[madara]'; -import p_236 from '@plugins/turkish/KodeksLibrary[lightnovelwp]'; -import p_237 from '@plugins/turkish/MangaTR'; -import p_238 from '@plugins/turkish/NABSCANS[madara]'; -import p_239 from '@plugins/turkish/Namevt[lightnovelwp]'; -import p_240 from '@plugins/turkish/Noveloku[madara]'; -import p_241 from '@plugins/turkish/NovelTR[lightnovelwp]'; -import p_242 from '@plugins/turkish/RagnarScans[madara]'; -import p_243 from '@plugins/turkish/ThNovels[hotnovelpub]'; -import p_244 from '@plugins/turkish/TurkceLightNovels[madara]'; -import p_245 from '@plugins/turkish/WebNovelOku[madara]'; -import p_246 from '@plugins/ukrainian/bakainua'; -import p_247 from '@plugins/ukrainian/smakolykytl'; -import p_248 from '@plugins/vietnamese/lightnovelvn'; -import p_249 from '@plugins/vietnamese/LNHako'; -import p_250 from '@plugins/vietnamese/nettruyen'; -import p_251 from '@plugins/vietnamese/truyenss'; +import p_4 from '@plugins/arabic/galaxynovels'; +import p_5 from '@plugins/arabic/HizoManga[madara]'; +import p_6 from '@plugins/arabic/KolNovel[lightnovelwp]'; +import p_7 from '@plugins/arabic/Markazriwayat'; +import p_8 from '@plugins/arabic/Novel4Up[madara]'; +import p_9 from '@plugins/arabic/NovelsParadise[lightnovelwp]'; +import p_10 from '@plugins/arabic/Olaoecyou[madara]'; +import p_11 from '@plugins/arabic/rewayahfans'; +import p_12 from '@plugins/arabic/rewayatclub'; +import p_13 from '@plugins/arabic/rewayatfans'; +import p_14 from '@plugins/arabic/Riwyat[madara]'; +import p_15 from '@plugins/arabic/seanovel'; +import p_16 from '@plugins/arabic/sunovels'; +import p_17 from '@plugins/chinese/69shu'; +import p_18 from '@plugins/chinese/ixdzs8'; +import p_19 from '@plugins/chinese/linovel'; +import p_20 from '@plugins/chinese/linovelib'; +import p_21 from '@plugins/chinese/linovelib_tw'; +import p_22 from '@plugins/chinese/novel543'; +import p_23 from '@plugins/chinese/Quanben'; +import p_24 from '@plugins/english/AllNovelFull[readnovelfull]'; +import p_25 from '@plugins/english/AllNovel[readnovelfull]'; +import p_26 from '@plugins/english/ao3'; +import p_27 from '@plugins/english/ArcaneTranslations[lightnovelwp]'; +import p_28 from '@plugins/english/BelleReservoir[madara]'; +import p_29 from '@plugins/english/BoxNovel[madara]'; +import p_30 from '@plugins/english/CherryMistCafe[fictioneer]'; +import p_31 from '@plugins/english/chrysanthemumgarden'; +import p_32 from '@plugins/english/CitrusAurora[madara]'; +import p_33 from '@plugins/english/CoralBoutique[madara]'; +import p_34 from '@plugins/english/CPUnovel[lightnovelwp]'; +import p_35 from '@plugins/english/crimsonscrolls'; +import p_36 from '@plugins/english/DaoistQuest[fictioneer]'; +import p_37 from '@plugins/english/DaoNovel[madara]'; +import p_38 from '@plugins/english/DaoTranslate[lightnovelwp]'; +import p_39 from '@plugins/english/DearestRosalie[fictioneer]'; +import p_40 from '@plugins/english/divinedaolibrary'; +import p_41 from '@plugins/english/Dragonholic[madara]'; +import p_42 from '@plugins/english/DragonTea[madara]'; +import p_43 from '@plugins/english/dreambigtl'; +import p_44 from '@plugins/english/DuskBlossoms[madara]'; +import p_45 from '@plugins/english/ElloTL[lightnovelwp]'; +import p_46 from '@plugins/english/Eternalune[madara]'; +import p_47 from '@plugins/english/EtudeTranslations[madara]'; +import p_48 from '@plugins/english/FanNovel[readwn]'; +import p_49 from '@plugins/english/FansMTL[readwn]'; +import p_50 from '@plugins/english/FansTranslations[madara]'; +import p_51 from '@plugins/english/faqwikius'; +import p_52 from '@plugins/english/fenrirrealm'; +import p_53 from '@plugins/english/fictionzone'; +import p_54 from '@plugins/english/FirstKissNovel[madara]'; +import p_55 from '@plugins/english/Foxaholic[madara]'; +import p_56 from '@plugins/english/foxteller'; +import p_57 from '@plugins/english/FreeWebNovel[readnovelfull]'; +import p_58 from '@plugins/english/GalaxyTranslations[madara]'; +import p_59 from '@plugins/english/genesis'; +import p_60 from '@plugins/english/Guavaread[madara]'; +import p_61 from '@plugins/english/HiraethTranslation[madara]'; +import p_62 from '@plugins/english/HotNovelPub[hotnovelpub]'; +import p_63 from '@plugins/english/HyacinthinBloom[lightnovelwp]'; +import p_64 from '@plugins/english/indraTranslations'; +import p_65 from '@plugins/english/inkitt'; +import p_66 from '@plugins/english/inoveltranslation'; +import p_67 from '@plugins/english/Ippotranslations[lightnovelwp]'; +import p_68 from '@plugins/english/KDTNovels[lightnovelwp]'; +import p_69 from '@plugins/english/KeopiTranslations[lightnovelwp]'; +import p_70 from '@plugins/english/KnoxT[lightnovelwp]'; +import p_71 from '@plugins/english/LazyGirlTranslations[lightnovelwp]'; +import p_72 from '@plugins/english/leafstudio'; +import p_73 from '@plugins/english/LibRead[readnovelfull]'; +import p_74 from '@plugins/english/LightNovelCave[lightnovelworld]'; +import p_75 from '@plugins/english/LightNovelHeaven[madara]'; +import p_76 from '@plugins/english/LightNovelPlus[readnovelfull]'; +import p_77 from '@plugins/english/LightNovelPubVip[lightnovelworld]'; +import p_78 from '@plugins/english/lightnoveltranslation'; +import p_79 from '@plugins/english/LightNovelUpdates[madara]'; +import p_80 from '@plugins/english/LilyontheValley[fictioneer]'; +import p_81 from '@plugins/english/lnmtl'; +import p_82 from '@plugins/english/lnori'; +import p_83 from '@plugins/english/Ltnovel[readwn]'; +import p_84 from '@plugins/english/LulloBox[madara]'; +import p_85 from '@plugins/english/LunarLetters[madara]'; +import p_86 from '@plugins/english/Meownovel[madara]'; +import p_87 from '@plugins/english/MoonlightNovels[lightnovelwp]'; +import p_88 from '@plugins/english/MostNovel[madara]'; +import p_89 from '@plugins/english/MTLNovel[madara]'; +import p_90 from '@plugins/english/MTLNovel[mtlnovel]'; +import p_91 from '@plugins/english/mvlempyr'; +import p_92 from '@plugins/english/MysticalSeries[madara]'; +import p_93 from '@plugins/english/NeoSekaiTranslations[madara]'; +import p_94 from '@plugins/english/NitroManga[madara]'; +import p_95 from '@plugins/english/NobleMTL[lightnovelwp]'; +import p_96 from '@plugins/english/NoiceTranslations[madara]'; +import p_97 from '@plugins/english/novelarrow'; +import p_98 from '@plugins/english/NovelBin[readnovelfull]'; +import p_99 from '@plugins/english/novelbuddy'; +import p_100 from '@plugins/english/NovelCool[novelcool]'; +import p_101 from '@plugins/english/novelfire'; +import p_102 from '@plugins/english/NovelFull[readnovelfull]'; +import p_103 from '@plugins/english/novelhall'; +import p_104 from '@plugins/english/novelhi'; +import p_105 from '@plugins/english/novelight'; +import p_106 from '@plugins/english/NovelLib[fictioneer]'; +import p_107 from '@plugins/english/NovelMultiverse[madara]'; +import p_108 from '@plugins/english/NovelNinja[madara]'; +import p_109 from '@plugins/english/NovelOnline'; +import p_110 from '@plugins/english/novelrest'; +import p_111 from '@plugins/english/NovelsKnight[lightnovelwp]'; +import p_112 from '@plugins/english/NovelTranslate[madara]'; +import p_113 from '@plugins/english/novelupdates'; +import p_114 from '@plugins/english/PandaMachineTranslations[lightnovelwp]'; +import p_115 from '@plugins/english/PastelTales[madara]'; +import p_116 from '@plugins/english/pawread'; +import p_117 from '@plugins/english/PenguinSquad[fictioneer]'; +import p_118 from '@plugins/english/Prizma[fictioneer]'; +import p_119 from '@plugins/english/rainofsnow'; +import p_120 from '@plugins/english/Ranobes[ranobes]'; +import p_121 from '@plugins/english/Ranovel[madara]'; +import p_122 from '@plugins/english/ReadFanfic[madara]'; +import p_123 from '@plugins/english/readfrom'; +import p_124 from '@plugins/english/ReadNovelFull[readnovelfull]'; +import p_125 from '@plugins/english/relibrary'; +import p_126 from '@plugins/english/RequiemTranslations[lightnovelwp]'; +import p_127 from '@plugins/english/royalroad'; +import p_128 from '@plugins/english/SalmonLatte[madara]'; +import p_129 from '@plugins/english/scribblehub'; +import p_130 from '@plugins/english/SleepyTranslations[madara]'; +import p_131 from '@plugins/english/SonicMTL[madara]'; +import p_132 from '@plugins/english/SrankManga[madara]'; +import p_133 from '@plugins/english/StorySeedling'; +import p_134 from '@plugins/english/SweetEscape[madara]'; +import p_135 from '@plugins/english/SystemTranslation[lightnovelwp]'; +import p_136 from '@plugins/english/TranslatinOtaku[madara]'; +import p_137 from '@plugins/english/TranslationWeaver[lightnovelwp]'; +import p_138 from '@plugins/english/UniversalNovel[lightnovelwp]'; +import p_139 from '@plugins/english/VandyTranslate[lightnovelwp]'; +import p_140 from '@plugins/english/VioletLily[madara]'; +import p_141 from '@plugins/english/vynovel'; +import p_142 from '@plugins/english/wct'; +import p_143 from '@plugins/english/webnovel'; +import p_144 from '@plugins/english/WebNovelLover[madara]'; +import p_145 from '@plugins/english/WebNovelPub[lightnovelworld]'; +import p_146 from '@plugins/english/WebNovelTranslation[madara]'; +import p_147 from '@plugins/english/WhiteMoonlightNovels[lightnovelwp]'; +import p_148 from '@plugins/english/WooksTeahouse[madara]'; +import p_149 from '@plugins/english/WordExcerpt[madara]'; +import p_150 from '@plugins/english/wtrlab'; +import p_151 from '@plugins/english/Wuxiabox[readwn]'; +import p_152 from '@plugins/english/Wuxiafox[readwn]'; +import p_153 from '@plugins/english/WuxiaSpace[readwn]'; +import p_154 from '@plugins/english/WuxiaV[readwn]'; +import p_155 from '@plugins/english/wuxiaworld'; +import p_156 from '@plugins/english/WuxiaWorldSite[madara]'; +import p_157 from '@plugins/english/ZetroTranslation[madara]'; +import p_158 from '@plugins/french/chireads'; +import p_159 from '@plugins/french/harkeneliwood'; +import p_160 from '@plugins/french/kisswood'; +import p_161 from '@plugins/french/LighNovelFR[lightnovelwp]'; +import p_162 from '@plugins/french/MassNovel[madara]'; +import p_163 from '@plugins/french/MTLNovel(FR)[mtlnovel]'; +import p_164 from '@plugins/french/noveldeglace'; +import p_165 from '@plugins/french/novelfrance'; +import p_166 from '@plugins/french/novhell'; +import p_167 from '@plugins/french/rezerowebnovelfr'; +import p_168 from '@plugins/french/warriorlegendtrad'; +import p_169 from '@plugins/french/WorldNovel[madara]'; +import p_170 from '@plugins/french/wuxialnscantrad'; +import p_171 from '@plugins/french/xiaowaz'; +import p_172 from '@plugins/indonesian/BacaLightNovel[lightnovelwp]'; +import p_173 from '@plugins/indonesian/indowebnovel'; +import p_174 from '@plugins/indonesian/MeioNovel[madara]'; +import p_175 from '@plugins/indonesian/MTLNovel(ID)[mtlnovel]'; +import p_176 from '@plugins/indonesian/NovelBookID[madara]'; +import p_177 from '@plugins/indonesian/sakuranovel'; +import p_178 from '@plugins/indonesian/SekteNovel[lightnovelwp]'; +import p_179 from '@plugins/indonesian/Vanovel[madara]'; +import p_180 from '@plugins/indonesian/WBNovel[madara]'; +import p_181 from '@plugins/japanese/kakuyomu'; +import p_182 from '@plugins/japanese/Syosetu'; +import p_183 from '@plugins/korean/Agitoon'; +import p_184 from '@plugins/korean/FortuneEternal[madara]'; +import p_185 from '@plugins/multi/komga'; +import p_186 from '@plugins/polish/novelki'; +import p_187 from '@plugins/portuguese/BetterNovels[lightnovelwp]'; +import p_188 from '@plugins/portuguese/blogdoamonnovels'; +import p_189 from '@plugins/portuguese/CentralNovel[lightnovelwp]'; +import p_190 from '@plugins/portuguese/illusia'; +import p_191 from '@plugins/portuguese/Kiniga[madara]'; +import p_192 from '@plugins/portuguese/LaNovels[hotnovelpub]'; +import p_193 from '@plugins/portuguese/LightNovelBrasil[lightnovelwp]'; +import p_194 from '@plugins/portuguese/MTLNovel(PT)[mtlnovel]'; +import p_195 from '@plugins/portuguese/novelmania'; +import p_196 from '@plugins/portuguese/tsundoku'; +import p_197 from '@plugins/russian/authortoday'; +import p_198 from '@plugins/russian/Bllate[rulate]'; +import p_199 from '@plugins/russian/Bookhamster[ifreedom]'; +import p_200 from '@plugins/russian/bookriver'; +import p_201 from '@plugins/russian/Erolate[rulate]'; +import p_202 from '@plugins/russian/EzNovels[hotnovelpub]'; +import p_203 from '@plugins/russian/ficbook'; +import p_204 from '@plugins/russian/jaomix'; +import p_205 from '@plugins/russian/MTLNovel(RU)[mtlnovel]'; +import p_206 from '@plugins/russian/neobook'; +import p_207 from '@plugins/russian/NovelCool(RU)[novelcool]'; +import p_208 from '@plugins/russian/novelTL'; +import p_209 from '@plugins/russian/ranobehub'; +import p_210 from '@plugins/russian/ranobelib'; +import p_211 from '@plugins/russian/ranoberf'; +import p_212 from '@plugins/russian/Ranobes(RU)[ranobes]'; +import p_213 from '@plugins/russian/renovels'; +import p_214 from '@plugins/russian/Rulate[rulate]'; +import p_215 from '@plugins/russian/topliba'; +import p_216 from '@plugins/russian/zelluloza'; +import p_217 from '@plugins/russian/СвободныйМирРанобэ[ifreedom]'; +import p_218 from '@plugins/spanish/AllNovelRead[lightnovelwp]'; +import p_219 from '@plugins/spanish/AnimesHoy12[madara]'; +import p_220 from '@plugins/spanish/hasutl'; +import p_221 from '@plugins/spanish/LightNovelDaily[hotnovelpub]'; +import p_222 from '@plugins/spanish/MTLNovel(ES)[mtlnovel]'; +import p_223 from '@plugins/spanish/NOVA'; +import p_224 from '@plugins/spanish/novelasligera'; +import p_225 from '@plugins/spanish/novelawuxia'; +import p_226 from '@plugins/spanish/novelyra'; +import p_227 from '@plugins/spanish/oasistranslations'; +import p_228 from '@plugins/spanish/PanchoTranslations[madara]'; +import p_229 from '@plugins/spanish/skynovels'; +import p_230 from '@plugins/spanish/TC&Sega[lightnovelwp]'; +import p_231 from '@plugins/spanish/TraduccionesAmistosas[madara]'; +import p_232 from '@plugins/spanish/tunovelaligera'; +import p_233 from '@plugins/spanish/yukitls'; +import p_234 from '@plugins/thai/NovelLucky[madara]'; +import p_235 from '@plugins/thai/NovelPDF[madara]'; +import p_236 from '@plugins/turkish/ArazNovel[madara]'; +import p_237 from '@plugins/turkish/EKTAPLAR[madara]'; +import p_238 from '@plugins/turkish/epiknovel'; +import p_239 from '@plugins/turkish/kakikata[madara]'; +import p_240 from '@plugins/turkish/KodeksLibrary[lightnovelwp]'; +import p_241 from '@plugins/turkish/MangaTR'; +import p_242 from '@plugins/turkish/NABSCANS[madara]'; +import p_243 from '@plugins/turkish/Namevt[lightnovelwp]'; +import p_244 from '@plugins/turkish/Noveloku[madara]'; +import p_245 from '@plugins/turkish/NovelTR[lightnovelwp]'; +import p_246 from '@plugins/turkish/RagnarScans[madara]'; +import p_247 from '@plugins/turkish/ThNovels[hotnovelpub]'; +import p_248 from '@plugins/turkish/TurkceLightNovels[madara]'; +import p_249 from '@plugins/turkish/WebNovelOku[madara]'; +import p_250 from '@plugins/ukrainian/bakainua'; +import p_251 from '@plugins/ukrainian/smakolykytl'; +import p_252 from '@plugins/vietnamese/lightnovelvn'; +import p_253 from '@plugins/vietnamese/LNHako'; +import p_254 from '@plugins/vietnamese/nettruyen'; +import p_255 from '@plugins/vietnamese/truyenss'; const PLUGINS: Plugin.PluginBase[] = [ p_0, @@ -505,5 +509,9 @@ const PLUGINS: Plugin.PluginBase[] = [ p_249, p_250, p_251, + p_252, + p_253, + p_254, + p_255, ]; export default PLUGINS; diff --git a/plugins/multisrc/madara/sources.json b/plugins/multisrc/madara/sources.json index 7f2f12720..1dc461d89 100644 --- a/plugins/multisrc/madara/sources.json +++ b/plugins/multisrc/madara/sources.json @@ -633,15 +633,7 @@ "useNewChapterEndpoint": true } }, - { - "id": "markazriwayat", - "sourceSite": "https://markazriwayat.com/", - "sourceName": "Markazriwayat", - "options": { - "lang": "Arabic", - "useNewChapterEndpoint": true - } - }, + { "id": "lullobox", "sourceSite": "https://lullobox.com/", diff --git a/public/static/src/ar/galaxynovels/icon.png b/public/static/src/ar/galaxynovels/icon.png new file mode 100644 index 000000000..484c3da66 Binary files /dev/null and b/public/static/src/ar/galaxynovels/icon.png differ diff --git a/public/static/src/ar/markazriwayat/icon.png b/public/static/src/ar/markazriwayat/icon.png new file mode 100644 index 000000000..a2f829263 Binary files /dev/null and b/public/static/src/ar/markazriwayat/icon.png differ diff --git a/public/static/src/ar/rewayahfans/icon.png b/public/static/src/ar/rewayahfans/icon.png new file mode 100644 index 000000000..81b122268 Binary files /dev/null and b/public/static/src/ar/rewayahfans/icon.png differ diff --git a/public/static/src/ar/rewayatfans/icon.png b/public/static/src/ar/rewayatfans/icon.png new file mode 100644 index 000000000..8015a6242 Binary files /dev/null and b/public/static/src/ar/rewayatfans/icon.png differ diff --git a/public/static/src/ar/seanovel/icon.png b/public/static/src/ar/seanovel/icon.png new file mode 100644 index 000000000..b71e51a32 Binary files /dev/null and b/public/static/src/ar/seanovel/icon.png differ