From 25e3f72c44eb74faa38690d5790fc9c6e5491994 Mon Sep 17 00:00:00 2001 From: RibatTRW Date: Thu, 15 Oct 2026 17:32:01 +0800 Subject: [PATCH 1/3] feat(english/sylthorian): add The Sylthorian source plugin Standalone English plugin for thesylthorian.com, the author's Squarespace storefront. Browses the free web-novel catalogue, parses each novel's chapter list, reads chapter text, and searches the catalogue by title. Co-authored-by: opencode-agent --- plugins/english/sylthorian.ts | 190 +++++++++++++++++++++++ public/static/src/en/sylthorian/icon.png | Bin 0 -> 20122 bytes 2 files changed, 190 insertions(+) create mode 100644 plugins/english/sylthorian.ts create mode 100644 public/static/src/en/sylthorian/icon.png diff --git a/plugins/english/sylthorian.ts b/plugins/english/sylthorian.ts new file mode 100644 index 000000000..ca8fd1a0f --- /dev/null +++ b/plugins/english/sylthorian.ts @@ -0,0 +1,190 @@ +import { load as parseHTML } from 'cheerio'; +import { fetchApi, FetchInit } from '@libs/fetch'; +import { Plugin } from '@/types/plugin'; +import { defaultCover } from '@libs/defaultCover'; + +/** + * The Sylthorian (thesylthorian.com) — English LNReader plugin. + * + * thesylthorian.com is the author's own Squarespace storefront. The free web + * chapters are readable in full; some of the same books are also sold as + * e-books/paperbacks under `/published-books-portofolio/`, which this plugin + * intentionally ignores. + * + * The site is a hand-built Squarespace 7.1 site, so each section is authored + * HTML rather than a CMS collection. The shape the plugin relies on: + * + * - Listing: `/web-novels` — one `

` title immediately followed by a + * "Read it!" button linking to `/web-novels-1/`. All 10 + * web novels fit on this single page; no covers are exposed. + * - Novel page: `/web-novels-1/` — title in the first `

`, cover in + * `og:image`, a "Synopsis:" text block, and the full chapter + * list as `/latest-chapters/-N` links (ascending, no + * pagination — up to ~189 chapters for the largest novel). + * - Chapter page: `/latest-chapters/-N` — body HTML inside + * `.blog-item-content.e-content .sqs-html-content`. + * - Search: the site's own `/search?q=` page is JS-driven and mostly + * returns chapter hits, burying novel pages behind them, so + * the plugin filters the small `/web-novels` catalogue by + * title instead — deterministic and complete for 10 novels. + */ +class Sylthorian implements Plugin.PluginBase { + id = 'sylthorian'; + name = 'The Sylthorian'; + version = '1.0.0'; + icon = 'src/en/sylthorian/icon.png'; + site = 'https://thesylthorian.com'; + + // Throw (carrying the HTTP status) on a refused response so a runner-side + // block is reported INCONCLUSIVE per docs/testing.md instead of being + // parsed into a false empty-result FAIL. + private async fetchSite(url: string, init?: FetchInit) { + const res = await fetchApi(url, init); + if (!res.ok) { + throw Object.assign(new Error('Request failed: ' + res.status), { + status: res.status, + }); + } + return res; + } + + private fetchSiteText(url: string) { + return this.fetchSite(url).then(res => res.text()); + } + + /** Squarespace serves `og:image` over cleartext `http://`; upgrade it. */ + private toHttps(url?: string) { + return url ? url.replace(/^http:\/\//, 'https://') : url; + } + + /** + * The `/web-novels` page pairs each novel's `

` title with the "Read it!" + * button that follows it, so walk the two in document order. + */ + private parseNovelList(html: string): Plugin.NovelItem[] { + const $ = parseHTML(html); + const novels: Plugin.NovelItem[] = []; + const seen = new Set(); + let lastTitle = ''; + + $('h3, a[href^="/web-novels-1/"]').each((_, el) => { + const node = $(el); + if (node.is('h3')) { + lastTitle = node.text().replace(/\s+/g, ' ').trim(); + return; + } + const path = node.attr('href'); + if (!path || seen.has(path)) return; + seen.add(path); + novels.push({ + name: lastTitle || path.split('/').pop() || path, + path, + cover: defaultCover, + }); + lastTitle = ''; + }); + + return novels; + } + + private parseSummary($: ReturnType): string | undefined { + let summary: string | undefined; + $('.sqs-html-content').each((_, el) => { + const text = $(el).text().replace(/\s+/g, ' ').trim(); + if (/^synopsis\b/i.test(text)) { + summary = text.replace(/^synopsis\s*:?\s*/i, '').trim(); + } + }); + return summary; + } + + private parseChapters( + $: ReturnType, + novelPath: string, + ): Plugin.ChapterItem[] { + // Chapter slugs reuse the novel slug (`/web-novels-1/` -> + // `/latest-chapters/-N`), so match on that prefix to avoid picking + // up unrelated chapter links from the page navigation. + const slug = novelPath.split('/').filter(Boolean).pop() ?? ''; + const prefix = `/latest-chapters/${slug}-`; + const chapters: Plugin.ChapterItem[] = []; + const seen = new Set(); + + $(`a[href^="${prefix}"]`).each((_, el) => { + const node = $(el); + const path = node.attr('href'); + const name = node.text().replace(/\s+/g, ' ').trim(); + if (!path || !name || seen.has(path)) return; + seen.add(path); + + const chapter: Plugin.ChapterItem = { name, path }; + const number = path.slice(prefix.length).match(/^(\d+)$/); + if (number) { + chapter.chapterNumber = parseInt(number[1], 10); + } + chapters.push(chapter); + }); + + return chapters; + } + + async popularNovels(pageNo: number): Promise { + // All web novels are listed on one page. + if (pageNo > 1) return []; + const html = await this.fetchSiteText(this.site + '/web-novels'); + return this.parseNovelList(html); + } + + async parseNovel(novelPath: string): Promise { + const html = await this.fetchSiteText(this.site + novelPath); + const $ = parseHTML(html); + + const novel: Plugin.SourceNovel = { + path: novelPath, + name: + $('h2').first().text().replace(/\s+/g, ' ').trim() || + $('meta[itemprop="name"]') + .attr('content') + ?.replace(/\s*—\s*The Sylthorian\s*$/, '') + .trim() || + 'Untitled', + cover: this.toHttps($('meta[property="og:image"]').attr('content')), + summary: this.parseSummary($), + chapters: this.parseChapters($, novelPath), + }; + + return novel; + } + + async parseChapter(chapterPath: string): Promise { + const html = await this.fetchSiteText(this.site + chapterPath); + const $ = parseHTML(html); + + const content = $('.blog-item-content.e-content .sqs-html-content').first(); + const node = content.length ? content : $('.blog-item-content').first(); + if (!node.length) { + // A page without the post body is a real failure, not empty content. + throw new Error('Chapter content not found'); + } + + node.find('script, style').remove(); + return node.html() ?? ''; + } + + async searchNovels( + searchTerm: string, + pageNo: number, + ): Promise { + if (pageNo > 1) return []; + const novels = this.parseNovelList( + await this.fetchSiteText(this.site + '/web-novels'), + ); + const term = searchTerm.trim().toLowerCase(); + if (!term) return novels; + return novels.filter(novel => novel.name.toLowerCase().includes(term)); + } + + resolveUrl = (path: string) => this.site + path; +} + +export default new Sylthorian(); diff --git a/public/static/src/en/sylthorian/icon.png b/public/static/src/en/sylthorian/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2564f0db7a3aae42b2e5d1b3e9085d9faa54e9d9 GIT binary patch literal 20122 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 Date: Thu, 15 Oct 2026 19:35:25 +0800 Subject: [PATCH 2/3] no-mistakes(review): only keep numeric-suffix chapter links in sylthorian --- plugins/english/sylthorian.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/plugins/english/sylthorian.ts b/plugins/english/sylthorian.ts index ca8fd1a0f..f753fc686 100644 --- a/plugins/english/sylthorian.ts +++ b/plugins/english/sylthorian.ts @@ -114,15 +114,11 @@ class Sylthorian implements Plugin.PluginBase { const node = $(el); const path = node.attr('href'); const name = node.text().replace(/\s+/g, ' ').trim(); - if (!path || !name || seen.has(path)) return; + const number = path?.slice(prefix.length).match(/^(\d+)$/); + if (!path || !name || !number || seen.has(path)) return; seen.add(path); - const chapter: Plugin.ChapterItem = { name, path }; - const number = path.slice(prefix.length).match(/^(\d+)$/); - if (number) { - chapter.chapterNumber = parseInt(number[1], 10); - } - chapters.push(chapter); + chapters.push({ name, path, chapterNumber: parseInt(number[1], 10) }); }); return chapters; From b80b6cffce4eb877b1287d91c6495d9273ce5de7 Mon Sep 17 00:00:00 2001 From: Raiyn Aydin Date: Thu, 15 Oct 2026 19:37:11 +0800 Subject: [PATCH 3/3] no-mistakes(review): remove unrequired title and body selector fallbacks --- plugins/english/sylthorian.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/plugins/english/sylthorian.ts b/plugins/english/sylthorian.ts index f753fc686..1bd4163a0 100644 --- a/plugins/english/sylthorian.ts +++ b/plugins/english/sylthorian.ts @@ -137,13 +137,7 @@ class Sylthorian implements Plugin.PluginBase { const novel: Plugin.SourceNovel = { path: novelPath, - name: - $('h2').first().text().replace(/\s+/g, ' ').trim() || - $('meta[itemprop="name"]') - .attr('content') - ?.replace(/\s*—\s*The Sylthorian\s*$/, '') - .trim() || - 'Untitled', + name: $('h2').first().text().replace(/\s+/g, ' ').trim() || 'Untitled', cover: this.toHttps($('meta[property="og:image"]').attr('content')), summary: this.parseSummary($), chapters: this.parseChapters($, novelPath), @@ -156,8 +150,7 @@ class Sylthorian implements Plugin.PluginBase { const html = await this.fetchSiteText(this.site + chapterPath); const $ = parseHTML(html); - const content = $('.blog-item-content.e-content .sqs-html-content').first(); - const node = content.length ? content : $('.blog-item-content').first(); + const node = $('.blog-item-content.e-content .sqs-html-content').first(); if (!node.length) { // A page without the post body is a real failure, not empty content. throw new Error('Chapter content not found');