From 3f7a068c1685f61f1ac859972d7098f149f1cb6c Mon Sep 17 00:00:00 2001 From: RibatTRW Date: Tue, 22 Sep 2026 18:32:07 +0800 Subject: [PATCH] feat(english): add Firebird's Nest source plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a standalone source plugin for Firebird's Nest (firebirdsnest.org), an English WordPress.com-hosted translation site. Site mapping: - novel list: the primary navigation "Projects" sub-menus (the homepage itself is a paginated feed of chapter announcement posts, not novels) - novel detail: each novel page's entry content (title, author, status, synopsis, hand-maintained Table of Contents) - chapters: child pages (/heavy-knight/v1-ch1/), completed from the per-novel tag archives because the site's hand-written ToCs are stale (Heavy Knight's stops at v2-ch19 while the site publishes to v3-ch39); pre-2017 announcement-style posts carry the chapter text directly - search: native WordPress /?s=term&paged=N, results mapped back to the novel catalogue Validation: npm run check:plugin passes all four steps against the live site; repo lint/format/compile add no new problems. 96x96 icon added at public/static/src/en/firebirdsnest/icon.png (derived from the site icon). 🤖 Generated with AI assistance --- plugins/english/firebirdsnest.ts | 739 ++++++++++++++++++++ public/static/src/en/firebirdsnest/icon.png | Bin 0 -> 17549 bytes 2 files changed, 739 insertions(+) create mode 100644 plugins/english/firebirdsnest.ts create mode 100644 public/static/src/en/firebirdsnest/icon.png diff --git a/plugins/english/firebirdsnest.ts b/plugins/english/firebirdsnest.ts new file mode 100644 index 000000000..756bcbda2 --- /dev/null +++ b/plugins/english/firebirdsnest.ts @@ -0,0 +1,739 @@ +import { fetchApi } from '@libs/fetch'; +import { Plugin } from '@/types/plugin'; +import { Filters } from '@libs/filterInputs'; +import { load as loadCheerio } from 'cheerio'; +import { defaultCover } from '@libs/defaultCover'; +import { NovelStatus } from '@libs/novelStatus'; +import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; + +type TagPost = { + /** Site-relative path of the announcement post, e.g. `/2024/03/25/heavy-knight-v3ch39/` */ + path: string; + slug: string; + title: string; + /** "YYYY-MM-DD", taken from the post's dated permalink */ + date: string; +}; + +type TagArchive = { + tagSlug: string; + posts: TagPost[]; +}; + +type TocEntry = { + path: string; + name: string; +}; + +type ChapterCandidate = { + post: TagPost; + path: string; +}; + +/** Non-novel pages that can sit next to novels in navigation menus. */ +const NON_NOVEL_PATHS = ['/projects/', '/about/']; + +/** Hosts that serve this same site (custom domain + wpcom mapped domain). */ +const SAME_SITE_HOSTS = [ + 'firebirdsnest.org', + 'www.firebirdsnest.org', + 'firebirdsnest.wordpress.com', +]; + +/** + * Firebird's Nest (firebirdsnest.org) is a small WordPress.com-hosted fan + * translation site. Its structure differs from the usual novel CMS themes: + * + * - Each novel is a WordPress *page* (`/heavy-knight/`) whose entry content + * holds the synopsis and a hand-maintained "Table of Contents". + * Chapters are child pages (`/heavy-knight/v1-ch1/`). + * - The site's novel catalogue is the "Projects" sub-menu of the primary + * navigation; the homepage itself is a paginated feed of chapter + * *announcement posts* (`/2024/03/25/heavy-knight-v3ch39/`), not novels. + * - Announcement posts are tagged per novel (`/tag/heavy-knight/`, paginated + * 10 per page) and each announcement links to the real chapter page. The + * tag archives are the only complete index of chapters: the hand-written + * ToCs are stale (Heavy Knight's ToC stops at v2-ch19 while the site + * publishes up to v3-ch39). Pre-2017 announcement-style posts (e.g. the + * No Fatigue "ch-6" era) contain the chapter text directly instead of + * linking to a child page. + * - Search is native WordPress `/?s=term&paged=N` and mixes chapter posts + * with chapter pages; results are mapped back to their novel. + */ +class FirebirdsNestPlugin implements Plugin.PluginBase { + id = 'firebirdsnest'; + name = "Firebird's Nest"; + icon = 'src/en/firebirdsnest/icon.png'; + site = 'https://firebirdsnest.org'; + version = '1.0.0'; + + filters: Filters | undefined = undefined; + + async popularNovels(pageNo: number): Promise { + // The full catalogue lives in the site's "Projects" navigation sub-menu + // and is a single page; there is no paginated novel browse on this site. + if (pageNo > 1) { + return []; + } + return this.fetchNovelCatalog(); + } + + async parseNovel(novelPath: string): Promise { + const path = this.toPath(novelPath); + const $ = loadCheerio(await this.fetchHtml(this.site + path)); + const content = this.entryContent($); + if (content.length === 0) { + throw new Error(`Firebird's Nest: no entry content at ${path}`); + } + + const name = this.normalizeText($('#main h1.entry-title').first().text()); + if (!name) { + throw new Error(`Firebird's Nest: missing novel title at ${path}`); + } + + const novelSlug = this.lastSegment(path); + + const novel: Plugin.SourceNovel = { + path, + name, + cover: defaultCover, + }; + + this.applyMetadata(content, $, novel); + + const tocEntries = this.parseToc(content, $); + + // The site's ToCs are hand-maintained and often stale; the per-novel tag + // archive lists every chapter announcement. Merge both so no chapter the + // site offers is left out, keeping the ToC's own order and titles first. + const archive = await this.fetchTagArchive(novelSlug); + const annDates: Record = {}; + if (archive) { + for (const post of archive.posts) { + annDates[post.slug] = post.date; + } + } + + const chapters: Plugin.ChapterItem[] = []; + const used: Record = {}; + for (const entry of tocEntries) { + if (used[entry.path]) { + continue; + } + used[entry.path] = true; + const chapter: Plugin.ChapterItem = { + name: entry.name, + path: entry.path, + }; + const date = this.announcementDateFor( + entry.path, + novelSlug, + archive, + annDates, + ); + if (date) { + chapter.releaseTime = date; + } + chapters.push(chapter); + } + + if (archive) { + const extras = await this.resolveAnnouncementChapters( + archive, + novelSlug, + path, + ); + extras.sort((a, b) => { + if (a.post.date !== b.post.date) { + return a.post.date < b.post.date ? -1 : 1; + } + return this.lastNumber(a.post.slug) - this.lastNumber(b.post.slug); + }); + for (const candidate of extras) { + if (used[candidate.path]) { + continue; + } + used[candidate.path] = true; + chapters.push({ + name: candidate.post.title, + path: candidate.path, + releaseTime: candidate.post.date, + }); + } + } + + if (chapters.length === 0) { + throw new Error(`Firebird's Nest: no chapters found for ${path}`); + } + chapters.forEach((chapter, index) => { + chapter.chapterNumber = index + 1; + }); + novel.chapters = chapters; + return novel; + } + + async parseChapter(chapterPath: string): Promise { + const path = this.toPath(chapterPath); + const url = isUrlAbsolute(path) ? path : this.site + path; + const $ = loadCheerio(await this.fetchHtml(url)); + const content = this.entryContent($); + if (content.length === 0) { + throw new Error(`Firebird's Nest: no chapter content at ${path}`); + } + + // Drop the "Previous | TOC | Next" navigation line and Jetpack widgets + // (sharing/likes/rating) that live inside the entry content. + content.find('#jp-post-flair, .sharedaddy, .jp-relatedposts').remove(); + content.find('p').each((i, el) => { + const text = this.normalizeText($(el).text()); + if ( + text.length < 60 && + text.indexOf('|') !== -1 && + /(TOC|Contents)/i.test(text) + ) { + $(el).remove(); + } + }); + + const html = content.html(); + if (!html || html.trim().length === 0) { + throw new Error(`Firebird's Nest: empty chapter content at ${path}`); + } + return html.trim(); + } + + async searchNovels( + searchTerm: string, + pageNo: number, + ): Promise { + const catalog = await this.fetchNovelCatalog(); + const query = encodeURIComponent(searchTerm); + const paged = pageNo > 1 ? `&paged=${pageNo}` : ''; + const $ = loadCheerio( + await this.fetchHtml(`${this.site}/?s=${query}${paged}`), + ); + + const slugByName: Record = {}; + const novelSlugs: string[] = []; + for (const novel of catalog) { + const slug = this.lastSegment(novel.path); + novelSlugs.push(slug); + slugByName[slug] = novel.name; + } + + const results: Plugin.NovelItem[] = []; + const seen: Record = {}; + $('h2.entry-title > a').each((i, el) => { + const href = $(el).attr('href'); + if (!href) { + return; + } + const slug = this.novelSlugForResult(this.toPath(href), novelSlugs); + if (!slug || seen[slug]) { + return; + } + seen[slug] = true; + results.push({ + name: slugByName[slug], + path: `/${slug}/`, + cover: defaultCover, + }); + }); + return results; + } + + resolveUrl = (path: string) => + isUrlAbsolute(path) ? path : this.site + this.toPath(path); + + /** + * The site's novel catalogue: the "Projects" sub-menus of the primary + * navigation, which list every live novel page with its real title. + */ + private async fetchNovelCatalog(): Promise { + const $ = loadCheerio(await this.fetchHtml(this.site + '/')); + const novels: Plugin.NovelItem[] = []; + const seen: Record = {}; + $('#site-navigation .sub-menu a').each((i, el) => { + const href = $(el).attr('href'); + if (!href) { + return; + } + const path = this.toPath(href); + if (!path || NON_NOVEL_PATHS.indexOf(path) !== -1 || seen[path]) { + return; + } + const name = this.normalizeText($(el).text()); + if (!name) { + return; + } + seen[path] = true; + novels.push({ name, path, cover: defaultCover }); + }); + if (novels.length === 0) { + throw new Error("Firebird's Nest: no novels found in the site menu"); + } + return novels; + } + + /** Author/status/synopsis from the info block above the ToC. */ + private applyMetadata( + content: ReturnType, + $: ReturnType, + novel: Plugin.SourceNovel, + ): void { + // Flatten the info block into lines so "Author: ..." / "Status: ..." + // (separated by
) can be picked up regardless of markup. + const raw = (content.html() || '') + .replace(//gi, '\n') + .replace(/<\/p>/gi, '\n'); + const flattened = loadCheerio(`
${raw}
`)('div').text(); + const lines = flattened + .split('\n') + .map(line => this.normalizeText(line)) + .filter(line => line); + for (const line of lines) { + const authorMatch = line.match(/^Author:\s*(.+)$/i); + if (authorMatch) { + novel.author = this.normalizeText(authorMatch[1]); + continue; + } + const statusMatch = line.match(/^Status:\s*(.+)$/i); + if (statusMatch) { + novel.status = this.parseStatus(statusMatch[1]); + } + } + + // The synopsis is every link-free paragraph above the ToC heading; the + // info paragraph itself carries links (source novel, collaborator blogs) + // and is skipped by the same rule. + const paragraphs: string[] = []; + let reachedToc = false; + content.find('p, strong, h1, h2, h3, h4, h5, h6').each((i, el) => { + if (reachedToc) { + return; + } + const element = $(el); + const text = this.normalizeText(element.text()); + if (this.isTocMarker(element, text)) { + reachedToc = true; + return; + } + if (!element.is('p')) { + return; + } + if ( + element.find('a').length > 0 || + /^Author:/i.test(text) || + /^Status:/i.test(text) || + !text + ) { + return; + } + paragraphs.push(text); + }); + if (paragraphs.length > 0) { + novel.summary = paragraphs.join('\n\n'); + } + } + + /** Detect the "Table of Contents" / "VOLUME 1 CONTENTS" heading. */ + private isTocMarker( + element: { is: (selector: string) => boolean }, + text: string, + ): boolean { + if (element.is('p')) { + return /^table of contents/i.test(text); + } + return /(table of )?contents$/i.test(text); + } + + private parseStatus(value: string): NovelStatus { + const normalized = value.toLowerCase(); + if (normalized.indexOf('ongoing') === 0) { + return NovelStatus.Ongoing; + } + if (normalized.indexOf('completed') === 0) { + return NovelStatus.Completed; + } + if (normalized.indexOf('hiatus') !== -1) { + return NovelStatus.OnHiatus; + } + return NovelStatus.Unknown; + } + + /** + * Chapter links from the novel page's "Table of Contents" block: every + * anchor after the ToC marker inside the entry content, in document order. + */ + private parseToc( + content: ReturnType, + $: ReturnType, + ): TocEntry[] { + const entries: TocEntry[] = []; + const seen: Record = {}; + let reachedToc = false; + content.find('a, p, strong, h1, h2, h3, h4, h5, h6').each((i, el) => { + const element = $(el); + if (!reachedToc) { + if (element.is('a')) { + return; + } + if (this.isTocMarker(element, this.normalizeText(element.text()))) { + reachedToc = true; + } + return; + } + if (!element.is('a')) { + return; + } + const href = element.attr('href'); + if (!href) { + return; + } + const path = this.toPath(href); + const name = this.normalizeText(element.text()); + if (!path || !name || seen[path] || path.indexOf('/feed') !== -1) { + return; + } + seen[path] = true; + entries.push({ path, name }); + }); + return entries; + } + + /** + * All chapter announcements for a novel from its paginated tag archive. + * The tag slug is usually the novel slug; for novels whose tag was created + * under a shorter name (e.g. `contractor`), fall back to the first word of + * the novel slug. Returns null when the site has no tag archive at all. + */ + private async fetchTagArchive(novelSlug: string): Promise { + const candidates = [novelSlug]; + const firstWord = novelSlug.split('-')[0]; + if (firstWord && firstWord !== novelSlug) { + candidates.push(firstWord); + } + for (const tagSlug of candidates) { + const url = `${this.site}/tag/${tagSlug}/`; + const res = await fetchApi(url); + if (res.status === 404) { + continue; + } + if (!res.ok) { + throw this.httpError(res.status, url); + } + const first = this.parseTagPage(await res.text()); + const pageNumbers: number[] = []; + for (let n = 2; n <= first.maxPage; n++) { + pageNumbers.push(n); + } + const restPages = await this.mapLimit(pageNumbers, 6, n => + this.parseTagPageAsync(n, tagSlug), + ); + const posts = first.posts; + for (const page of restPages) { + for (const post of page.posts) { + posts.push(post); + } + } + return { tagSlug, posts }; + } + return null; + } + + private async parseTagPageAsync( + pageNo: number, + tagSlug: string, + ): Promise<{ posts: TagPost[]; maxPage: number }> { + return this.parseTagPage( + await this.fetchHtml(`${this.site}/tag/${tagSlug}/page/${pageNo}/`), + ); + } + + private parseTagPage(html: string): { posts: TagPost[]; maxPage: number } { + const $ = loadCheerio(html); + const posts: TagPost[] = []; + $('#main h2.entry-title > a').each((i, el) => { + const href = $(el).attr('href'); + if (!href) { + return; + } + const path = this.toPath(href); + const match = path.match(/^\/(\d{4})\/(\d{2})\/(\d{2})\/([^/]+)\/$/); + if (!match) { + return; + } + posts.push({ + path, + slug: match[4], + date: `${match[1]}-${match[2]}-${match[3]}`, + title: this.normalizeText($(el).text()), + }); + }); + let maxPage = 1; + $('#main .nav-links a').each((i, el) => { + const href = $(el).attr('href') || ''; + const match = href.match(/\/page\/(\d+)\//); + if (match && Number(match[1]) > maxPage) { + maxPage = Number(match[1]); + } + }); + return { posts, maxPage }; + } + + /** + * Turn announcement posts into chapter paths the ToC does not already + * cover. Most slugs map directly onto their child chapter page; unusual + * slugs are resolved by reading the announcement's own chapter link, and + * pre-2017 text posts (no child link) are chapters themselves. + */ + private async resolveAnnouncementChapters( + archive: TagArchive, + novelSlug: string, + novelPath: string, + ): Promise { + const prefix = archive.tagSlug + '-'; + const resolved: ChapterCandidate[] = []; + const unresolved: TagPost[] = []; + for (const post of archive.posts) { + if (post.slug.indexOf(prefix) !== 0) { + continue; + } + const rest = post.slug.slice(prefix.length); + const direct = this.deriveChapterPath(rest, novelSlug, post.path); + if (direct) { + resolved.push({ post, path: direct }); + } else { + unresolved.push(post); + } + } + const fetched = await this.mapLimit(unresolved, 4, async post => { + const path = await this.resolveAnnouncementPath(post, novelPath); + return path ? { post, path } : null; + }); + for (const candidate of fetched) { + if (candidate) { + resolved.push(candidate); + } + } + return resolved; + } + + /** + * Known announcement-slug shapes. Returns the chapter path, the post's own + * path when the post *is* the chapter (2015-era `ch-6` style posts), or + * null when the slug is unknown and the announcement must be read. + */ + private deriveChapterPath( + rest: string, + novelSlug: string, + postPath: string, + ): string | null { + if (/^ch-\d+(-\d+)?$/.test(rest)) { + return postPath; + } + const volume = rest.match(/^v(\d+)ch(\d+)$/); + if (volume) { + return `/${novelSlug}/v${volume[1]}-ch${volume[2]}/`; + } + if (/^(v\d+c\d+|ch\d+)(-\d+)?$/.test(rest)) { + return `/${novelSlug}/${rest}/`; + } + return null; + } + + /** Read an announcement and follow its "Chapter here." style link. */ + private async resolveAnnouncementPath( + post: TagPost, + novelPath: string, + ): Promise { + const $ = loadCheerio(await this.fetchHtml(this.site + post.path)); + const content = this.entryContent($); + if (content.length === 0) { + return null; + } + const prefix = + novelPath.charAt(novelPath.length - 1) === '/' + ? novelPath + : novelPath + '/'; + let found: string | null = null; + content.find('a').each((i, el) => { + if (found) { + return; + } + const href = $(el).attr('href'); + if (!href) { + return; + } + const path = this.toPath(href); + if ( + path.indexOf(prefix) === 0 && + path !== novelPath && + path.indexOf('/feed') === -1 + ) { + found = path; + } + }); + if (found) { + return found; + } + // Old posts carry the chapter text itself; anything too short to be a + // chapter (an announcement without a resolvable link) is skipped. + const text = this.normalizeText(content.text()); + return text.length >= 200 ? post.path : null; + } + + /** Release date of a ToC chapter, when the site announced it by date. */ + private announcementDateFor( + chapterPath: string, + novelSlug: string, + archive: TagArchive | null, + annDates: Record, + ): string | undefined { + if (isUrlAbsolute(chapterPath) || !archive) { + return undefined; + } + const base = this.lastSegment(chapterPath); + const compact = base.replace(/^v(\d+)-ch/, 'v$1ch'); + const keys = [ + base, + compact, + `${novelSlug}-${base}`, + `${novelSlug}-${compact}`, + `${archive.tagSlug}-${base}`, + `${archive.tagSlug}-${compact}`, + ]; + for (const key of keys) { + if (annDates[key]) { + return annDates[key]; + } + } + return undefined; + } + + /** + * Map one search-result URL (dated announcement, chapter child page, or + * top-level novel page) back to a novel slug from the site catalogue. + */ + private novelSlugForResult( + path: string, + novelSlugs: string[], + ): string | null { + const segments = path.split('/').filter(segment => segment); + if (segments.length === 0) { + return null; + } + const isDatedPost = segments.length === 4 && /^\d{4}$/.test(segments[0]); + if (isDatedPost) { + const slug = segments[3]; + for (const novelSlug of novelSlugs) { + if (slug.indexOf(novelSlug + '-') === 0) { + return novelSlug; + } + } + return null; + } + if (novelSlugs.indexOf(segments[0]) !== -1) { + return segments[0]; + } + if (segments.length > 1 && novelSlugs.indexOf(segments[1]) !== -1) { + return segments[1]; + } + return null; + } + + private entryContent($: ReturnType) { + let content = $('#main .entry-content').first(); + if (content.length === 0) { + content = $('.entry-content').first(); + } + return content; + } + + /** Normalize a URL or href to a site-relative path (or an external URL). */ + private toPath(href: string): string { + let raw = href.trim(); + if (raw.indexOf('//') === 0) { + raw = 'https:' + raw; + } + if (/^https?:\/\//i.test(raw)) { + const match = raw.match(/^https?:\/\/([^/?#]+)([/?#].*)?$/i); + if (!match) { + return ''; + } + const host = match[1].toLowerCase(); + if (SAME_SITE_HOSTS.indexOf(host) === -1) { + return raw.split('#')[0]; + } + raw = match[2] || '/'; + } + const clean = raw.split('#')[0].split('?')[0]; + if (!clean) { + return '/'; + } + const path = clean.charAt(0) === '/' ? clean : '/' + clean; + return path.charAt(path.length - 1) === '/' ? path : path + '/'; + } + + private async fetchHtml(url: string): Promise { + const res = await fetchApi(url); + if (!res.ok) { + throw this.httpError(res.status, url); + } + return res.text(); + } + + private httpError(status: number, url: string): Error { + // Carry the status so tooling can tell a refused/blocked request + // (403/503) apart from a genuine parsing failure. + return Object.assign(new Error(`HTTP ${status} while fetching ${url}`), { + status, + }); + } + + private async mapLimit( + items: T[], + limit: number, + task: (item: T) => Promise, + ): Promise { + const results: R[] = new Array(items.length); + let next = 0; + const workers: Promise[] = []; + const count = Math.min(limit, items.length); + for (let w = 0; w < count; w++) { + workers.push( + (async () => { + while (next < items.length) { + const index = next; + next += 1; + results[index] = await task(items[index]); + } + })(), + ); + } + await Promise.all(workers); + return results; + } + + private normalizeText(text: string): string { + return text + .replace(/\u00a0/g, ' ') + .replace(/\s+/g, ' ') + .trim(); + } + + private lastSegment(path: string): string { + const segments = path.split('/').filter(segment => segment); + return segments.length > 0 ? segments[segments.length - 1] : ''; + } + + private lastNumber(value: string): number { + const matches = value.match(/\d+/g); + if (!matches) { + return 0; + } + return Number(matches[matches.length - 1]); + } +} + +export default new FirebirdsNestPlugin(); diff --git a/public/static/src/en/firebirdsnest/icon.png b/public/static/src/en/firebirdsnest/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d99557a233fed148dd1796713c04b44c7f030eea GIT binary patch literal 17549 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 XSN&izar@8)00000NkvXXu0mjf_*<3H literal 0 HcmV?d00001