From 9ae579ab2258cb567680d86dfbff69a37a495707 Mon Sep 17 00:00:00 2001 From: Raiyn Aydin Date: Thu, 15 Oct 2026 19:40:32 +0800 Subject: [PATCH 1/4] fix(english/genesis): read chapter text from the site's chapter endpoint The Supabase chapters table no longer allows anonymous reads ("permission denied for table chapters"), so parseChapter got no chapter_content back. parseChapter now reads the body from the site's own reader endpoint, /api/chapters//content, which returns it as data.chapter_content. The API-key scraping from the page scripts is removed because nothing uses it. Bump version 2.0.1 -> 2.0.2. --- plugins/english/genesis.ts | 82 +++++++------------------------------- 1 file changed, 15 insertions(+), 67 deletions(-) diff --git a/plugins/english/genesis.ts b/plugins/english/genesis.ts index e6644b3b1..7fd64ab85 100644 --- a/plugins/english/genesis.ts +++ b/plugins/english/genesis.ts @@ -1,4 +1,3 @@ -import { load } from 'cheerio'; import { fetchApi } from '@libs/fetch'; import { Filters, FilterTypes } from '@libs/filterInputs'; import { Plugin } from '@/types/plugin'; @@ -13,7 +12,7 @@ class Genesis implements Plugin.PluginBase { customCSS = 'src/en/genesis/customCSS.css'; site = 'https://genesistudio.com'; api = 'https://api.genesistudio.com'; - version = '2.0.1'; + version = '2.0.2'; hideLocked = storage.get('hideLocked'); pluginSettings = { @@ -138,75 +137,18 @@ class Genesis implements Plugin.PluginBase { } async parseChapter(chapterPath: string): Promise { - const url = `${this.site}${chapterPath}`; const id = chapterPath.replace('/viewer/', ''); - // Fetch the novel's data in JSON format - const raw = await fetchApi(url); - const $ = load(await raw.text()); - let external_api; - let apikey; - - const URLs: string[] = []; - let code; - - $('head script[src]').each((_, el) => { - const src = $(el).attr('src')!; - if (!URLs.includes(src)) { - URLs.push(src); - } - }); + // The chapter body is served by the site's own reader endpoint + const json: ChapterContentJSON = await fetchApi( + `${this.site}/api/chapters/${id}/content`, + ).then(r => r.json()); - for (const src of URLs) { - const script = await fetchApi(`${this.site}${src}`); - const raw = await script.text(); - if (raw.includes('sb_publishable')) { - code = raw; - break; - } - } - if (!code) { - throw new Error('Failed to find API Key'); - } - // Find right segment of code - let arr = code.split(';'); - for (const seg of arr) { - if (seg.includes('sb_publishable')) { - code = seg; - break; - } - } - arr = code.split('"'); - for (const seg of arr) { - if (seg.includes('https')) { - external_api = seg; - continue; - } - if (seg.includes('sb_publishable')) { - apikey = seg; - continue; - } + const content = json.data?.chapter_content; + if (!content) { + throw new Error('Chapter content is unavailable'); } - - const path = `${external_api}/rest/v1/chapters`; - const search = new URLSearchParams({ - select: 'id,chapter_title,chapter_number,chapter_content,status,novel', - id: `eq.${id}`, - status: 'eq.released', - }); - - const chQuery = await fetchApi(`${path}?${search}`, { - method: 'GET', - headers: { - // Cookie: 'csrftoken=' + csrftoken, - Referer: this.site, - 'apikey': apikey, - 'x-client-info': 'supabase-ssr/0.7.0 createBrowserClient', - }, - }); - const json = await chQuery.json(); - const ch = json[0].chapter_content.replaceAll('\n', '
'); - return ch; + return content.replaceAll('\n', '
'); } async searchNovels( @@ -310,3 +252,9 @@ type ChapterJSON = { }[]; }; }; + +type ChapterContentJSON = { + data?: { + chapter_content?: string | null; + }; +}; From 4257301a912636b4edce81ce89e9511eb1d9a008 Mon Sep 17 00:00:00 2001 From: Raiyn Aydin Date: Thu, 15 Oct 2026 19:51:18 +0800 Subject: [PATCH 2/4] no-mistakes(review): fix(english/genesis): refuse locked chapters before fetching content --- plugins/english/genesis.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/english/genesis.ts b/plugins/english/genesis.ts index 7fd64ab85..48f968fbf 100644 --- a/plugins/english/genesis.ts +++ b/plugins/english/genesis.ts @@ -12,7 +12,7 @@ class Genesis implements Plugin.PluginBase { customCSS = 'src/en/genesis/customCSS.css'; site = 'https://genesistudio.com'; api = 'https://api.genesistudio.com'; - version = '2.0.2'; + version = '2.0.3'; hideLocked = storage.get('hideLocked'); pluginSettings = { @@ -117,8 +117,10 @@ class Genesis implements Plugin.PluginBase { const chapters = json.data.chapters .map(index => { const title = index.chapter_title; - const chapterPath = `/viewer/${index.id}`; const isLocked = !index.isUnlocked; + const chapterPath = isLocked + ? `/viewer/locked/${index.id}` + : `/viewer/${index.id}`; if (this.hideLocked && isLocked) return null; const chapterName = isLocked ? '🔒 ' + title : title; const chapterNum = index.chapter_number; @@ -137,6 +139,9 @@ class Genesis implements Plugin.PluginBase { } async parseChapter(chapterPath: string): Promise { + if (chapterPath.startsWith('/viewer/locked/')) { + throw new Error('This chapter is locked on the site'); + } const id = chapterPath.replace('/viewer/', ''); // The chapter body is served by the site's own reader endpoint From ee493bccdd23b2e7bbd300eb2edaaa112505f15d Mon Sep 17 00:00:00 2001 From: Raiyn Aydin Date: Thu, 15 Oct 2026 19:55:35 +0800 Subject: [PATCH 3/4] no-mistakes(review): fix(english/genesis): treat content refusals as locked chapters --- plugins/english/genesis.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/plugins/english/genesis.ts b/plugins/english/genesis.ts index 48f968fbf..5432fa8b3 100644 --- a/plugins/english/genesis.ts +++ b/plugins/english/genesis.ts @@ -144,14 +144,14 @@ class Genesis implements Plugin.PluginBase { } const id = chapterPath.replace('/viewer/', ''); - // The chapter body is served by the site's own reader endpoint - const json: ChapterContentJSON = await fetchApi( - `${this.site}/api/chapters/${id}/content`, - ).then(r => r.json()); + // Anonymous requests for paid chapters get HTTP 401, error "Authentication required", + // and chapter_content "None" (checked 2026-10-15), so a refusal is treated as locked + const res = await fetchApi(`${this.site}/api/chapters/${id}/content`); + const json: ChapterContentJSON = await res.json(); const content = json.data?.chapter_content; - if (!content) { - throw new Error('Chapter content is unavailable'); + if (!res.ok || json.error || !content || content === 'None') { + throw new Error('This chapter is locked on the site'); } return content.replaceAll('\n', '
'); } @@ -259,6 +259,7 @@ type ChapterJSON = { }; type ChapterContentJSON = { + error?: string; data?: { chapter_content?: string | null; }; From 8ef018cfeaa54c53ad32b02113a4bfbfa31f8d63 Mon Sep 17 00:00:00 2001 From: Raiyn Aydin Date: Thu, 15 Oct 2026 19:59:09 +0800 Subject: [PATCH 4/4] no-mistakes(review): fix(english/genesis): list only released chapters --- plugins/english/genesis.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/english/genesis.ts b/plugins/english/genesis.ts index 5432fa8b3..b596802b9 100644 --- a/plugins/english/genesis.ts +++ b/plugins/english/genesis.ts @@ -113,8 +113,10 @@ class Genesis implements Plugin.PluginBase { const raw = await fetchApi(url); const json: ChapterJSON = await raw.json(); - // Format each chapter and add only valid ones + // Only released chapters are listed; the pre-2.0.1 Supabase query filtered on status=eq.released + // (every chapter observed had status released: 5 of 5 and 157 of 157 in the listing, 2026-10-15) const chapters = json.data.chapters + .filter(index => index.status === 'released') .map(index => { const title = index.chapter_title; const isLocked = !index.isUnlocked; @@ -253,6 +255,7 @@ type ChapterJSON = { id: string; chapter_number: number; chapter_title: string; + status: string; isUnlocked: boolean; }[]; };