From 927f8e9b3f8feada845dca0b0aefb5961ee420df Mon Sep 17 00:00:00 2001 From: Raiyn Aydin Date: Wed, 7 Oct 2026 11:33:59 +0800 Subject: [PATCH 1/2] feat(english/asurascans): add Asura Scans novels source Closes #2636 --- plugins/english/asurascans.ts | 247 +++++++++++++++++++++++ public/static/src/en/asurascans/icon.png | Bin 0 -> 7819 bytes 2 files changed, 247 insertions(+) create mode 100644 plugins/english/asurascans.ts create mode 100644 public/static/src/en/asurascans/icon.png diff --git a/plugins/english/asurascans.ts b/plugins/english/asurascans.ts new file mode 100644 index 000000000..3713115c0 --- /dev/null +++ b/plugins/english/asurascans.ts @@ -0,0 +1,247 @@ +import { load as parseHTML } from 'cheerio'; +import { fetchApi } from '@libs/fetch'; +import { Plugin } from '@/types/plugin'; +import { Filters, FilterTypes } from '@libs/filterInputs'; +import { storage } from '@libs/storage'; +import { defaultCover } from '@libs/defaultCover'; +import { NovelStatus } from '@libs/novelStatus'; + +type SeriesJSON = { + slug: string; + title: string; + description?: string | null; + cover_url?: string | null; + status?: string | null; + author?: string | null; + genres?: string[] | null; +}; + +type SeriesListJSON = { + data?: SeriesJSON[]; +}; + +type ChapterJSON = { + number: number; + title?: string | null; + published_at?: string | null; + is_locked?: boolean; +}; + +type SeriesDetailJSON = { + series: SeriesJSON; + chapters?: ChapterJSON[]; +}; + +type ChapterContentJSON = { + content_html?: string | null; + is_locked?: boolean; +}; + +const PAGE_SIZE = 20; + +class AsuraScansPlugin implements Plugin.PluginBase { + id = 'asurascans'; + name = 'Asura Scans'; + icon = 'src/en/asurascans/icon.png'; + site = 'https://asurascans.com'; + apiUrl = 'https://api.asurascans.com/api'; + version = '1.0.0'; + + hideLocked = storage.get('hideLocked'); + pluginSettings = { + hideLocked: { + value: '', + label: 'Hide locked chapters', + type: 'Switch', + }, + }; + + async fetchJSON(url: string): Promise { + const res = await fetchApi(url); + if (!res.ok) { + throw new Error(`Asura Scans request failed: HTTP ${res.status}`); + } + return res.json(); + } + + async browse( + page: number, + params: URLSearchParams, + ): Promise { + params.set('limit', PAGE_SIZE.toString()); + params.set('offset', ((page - 1) * PAGE_SIZE).toString()); + const data = await this.fetchJSON( + `${this.apiUrl}/novel-series?${params.toString()}`, + ); + return (data.data || []).map(series => ({ + name: series.title, + cover: series.cover_url || defaultCover, + path: `novels/${series.slug}`, + })); + } + + async popularNovels( + page: number, + { + showLatestNovels, + filters, + }: Plugin.PopularNovelsOptions, + ): Promise { + const params = new URLSearchParams({ + sort: showLatestNovels ? 'update' : filters.sort.value, + }); + if (filters.status.value) params.set('status', filters.status.value); + if (filters.genres.value.length) { + params.set('genres', filters.genres.value.join(',')); + } + return this.browse(page, params); + } + + async searchNovels( + searchTerm: string, + page: number, + ): Promise { + return this.browse(page, new URLSearchParams({ search: searchTerm })); + } + + async parseNovel(novelPath: string): Promise { + const slug = novelPath.split('/').pop() || ''; + const { series, chapters = [] } = await this.fetchJSON( + `${this.apiUrl}/novel-series/${encodeURIComponent(slug)}`, + ); + + const statusMap: Record = { + ongoing: NovelStatus.Ongoing, + complete: NovelStatus.Completed, + completed: NovelStatus.Completed, + hiatus: NovelStatus.OnHiatus, + dropped: NovelStatus.Cancelled, + }; + + const novel: Plugin.SourceNovel = { + path: novelPath, + name: series.title || 'Untitled', + cover: series.cover_url || defaultCover, + author: series.author || undefined, + genres: (series.genres || []).join(','), + status: + statusMap[(series.status || '').toLowerCase()] ?? NovelStatus.Unknown, + chapters: [], + }; + + if (series.description) { + const $ = parseHTML(series.description); + novel.summary = $('p') + .map((_, el) => $(el).text().trim()) + .toArray() + .filter(text => text) + .join('\n\n'); + if (!novel.summary) novel.summary = $.root().text().trim(); + } + + novel.chapters = chapters + .filter(chapter => !(chapter.is_locked && this.hideLocked)) + .map(chapter => { + const title = chapter.title?.trim(); + const name = `Chapter ${chapter.number}${title ? `: ${title}` : ''}`; + return { + name: chapter.is_locked ? `🔒 ${name}` : name, + path: `novels/${slug}/chapter/${chapter.number}`, + releaseTime: chapter.published_at || null, + chapterNumber: chapter.number, + }; + }); + + return novel; + } + + async parseChapter(chapterPath: string): Promise { + const [, slug, , number] = chapterPath.split('/'); + const chapter = await this.fetchJSON( + `${this.apiUrl}/novel-series/${encodeURIComponent(slug)}/chapter/${number}`, + ); + + if (!chapter.content_html) { + throw new Error( + chapter.is_locked + ? 'This chapter is locked. Unlock it on Asura Scans to read it.' + : 'This chapter has no content.', + ); + } + + const $ = parseHTML(chapter.content_html); + // Some chapters end with a paragraph of leaked site navigation text. + $('p') + .filter((_, el) => $(el).text().includes('Back to homepage')) + .remove(); + $('script, style, iframe').remove(); + + return $('body').html() || ''; + } + + resolveUrl = (path: string) => `${this.site}/${path}`; + + filters = { + sort: { + type: FilterTypes.Picker, + label: 'Sort by', + value: 'popular', + options: [ + { label: 'Popular', value: 'popular' }, + { label: 'Latest Update', value: 'update' }, + { label: 'Rating', value: 'rating' }, + { label: 'Newest', value: 'newest' }, + { label: 'Name', value: 'name' }, + ], + }, + status: { + type: FilterTypes.Picker, + label: 'Status', + value: '', + options: [ + { label: 'All', value: '' }, + { label: 'Ongoing', value: 'ongoing' }, + { label: 'Completed', value: 'complete' }, + { label: 'Hiatus', value: 'hiatus' }, + ], + }, + genres: { + type: FilterTypes.CheckboxGroup, + label: 'Genres (all selected must match)', + value: [], + options: [ + { label: 'Action', value: 'action' }, + { label: 'Adventure', value: 'adventure' }, + { label: 'Comedy', value: 'comedy' }, + { label: 'Dark Fantasy', value: 'dark-fantasy' }, + { label: 'Demon', value: 'demon' }, + { label: 'Drama', value: 'drama' }, + { label: 'Dungeons', value: 'dungeons' }, + { label: 'Fantasy', value: 'fantasy' }, + { label: 'Game', value: 'game' }, + { label: 'Genius MC', value: 'genius-mc' }, + { label: 'Isekai', value: 'isekai' }, + { label: 'Magic', value: 'magic' }, + { label: 'Martial Arts', value: 'martial-arts' }, + { label: 'Murim', value: 'murim' }, + { label: 'Mystery', value: 'mystery' }, + { label: 'Overpowered', value: 'overpowered' }, + { label: 'Psychological', value: 'psychological' }, + { label: 'Regression', value: 'regression' }, + { label: 'Reincarnation', value: 'reincarnation' }, + { label: 'Revenge', value: 'revenge' }, + { label: 'Romance', value: 'romance' }, + { label: 'School Life', value: 'school-life' }, + { label: 'Sci-fi', value: 'sci-fi' }, + { label: 'Shounen', value: 'shounen' }, + { label: 'Supernatural', value: 'supernatural' }, + { label: 'System', value: 'system' }, + { label: 'Tragedy', value: 'tragedy' }, + { label: 'Transmigration', value: 'transmigration' }, + { label: 'Villain', value: 'villain' }, + ], + }, + } satisfies Filters; +} + +export default new AsuraScansPlugin(); diff --git a/public/static/src/en/asurascans/icon.png b/public/static/src/en/asurascans/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..135326727c15763cd489db67b6dd356bda4e2644 GIT binary patch literal 7819 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 Date: Wed, 7 Oct 2026 11:42:25 +0800 Subject: [PATCH 2/2] fix(english/asurascans): address review feedback Read the hideLocked setting on each parseNovel call, return absolute URLs unchanged from resolveUrl, and only strip the trailing leaked navigation paragraph from chapter text. --- plugins/english/asurascans.ts | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/plugins/english/asurascans.ts b/plugins/english/asurascans.ts index 3713115c0..64f82c182 100644 --- a/plugins/english/asurascans.ts +++ b/plugins/english/asurascans.ts @@ -47,7 +47,6 @@ class AsuraScansPlugin implements Plugin.PluginBase { apiUrl = 'https://api.asurascans.com/api'; version = '1.0.0'; - hideLocked = storage.get('hideLocked'); pluginSettings = { hideLocked: { value: '', @@ -139,8 +138,9 @@ class AsuraScansPlugin implements Plugin.PluginBase { if (!novel.summary) novel.summary = $.root().text().trim(); } + const hideLocked = storage.get('hideLocked'); novel.chapters = chapters - .filter(chapter => !(chapter.is_locked && this.hideLocked)) + .filter(chapter => !(chapter.is_locked && hideLocked)) .map(chapter => { const title = chapter.title?.trim(); const name = `Chapter ${chapter.number}${title ? `: ${title}` : ''}`; @@ -169,17 +169,31 @@ class AsuraScansPlugin implements Plugin.PluginBase { ); } - const $ = parseHTML(chapter.content_html); - // Some chapters end with a paragraph of leaked site navigation text. - $('p') - .filter((_, el) => $(el).text().includes('Back to homepage')) - .remove(); + return this.cleanChapter(chapter.content_html); + } + + cleanChapter(html: string): string { + const $ = parseHTML(html); + // Some chapters end with a paragraph of leaked site navigation and report + // dialog text. Only trailing paragraphs carrying both markers are dropped. + let last = $('p').last(); + while ( + last.length && + last.text().includes('Back to homepage') && + last.text().includes('Reporting chapter:') + ) { + last.remove(); + last = $('p').last(); + } $('script, style, iframe').remove(); return $('body').html() || ''; } - resolveUrl = (path: string) => `${this.site}/${path}`; + resolveUrl = (path: string) => + /^https?:\/\//i.test(path) + ? path + : `${this.site}/${path.replace(/^\/+/, '')}`; filters = { sort: {