fix: StorySeedling (#1857)

* Story seedling beginning

* Font decoding

* Bump version number

* Remove spam

* Remove story seedling ad

* Live update nonce

* add error for captcha

* JSON instead of text compare

* Fix proxy crashing on error

* Fix captcha error

* Fix updatedNonce check

* Compression headers should only be forwarded by proxy's that keep the content compressed

* Revert index.ts change
This commit is contained in:
Soopyboo32
2025-12-23 18:56:31 +08:00
committed by GitHub
parent a4168018f0
commit 2443d8f7fc
2 changed files with 79 additions and 30 deletions
+59 -17
View File
@@ -1,15 +1,15 @@
import { CheerioAPI, load } from 'cheerio'; import { CheerioAPI, load } from 'cheerio';
import { Plugin } from '@/types/plugin'; import { Plugin } from '@/types/plugin';
import { fetchApi } from '@libs/fetch'; import { fetchApi, fetchText } from '@libs/fetch';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
import { defaultCover } from '@libs/defaultCover';
class StorySeedlingPlugin implements Plugin.PluginBase { class StorySeedlingPlugin implements Plugin.PluginBase {
id = 'storyseedling'; id = 'storyseedling';
name = 'StorySeedling'; name = 'StorySeedling';
icon = 'src/en/storyseedling/icon.png'; icon = 'src/en/storyseedling/icon.png';
site = 'https://storyseedling.com/'; site = 'https://storyseedling.com/';
version = '1.0.3'; version = '1.0.4';
nonce: string | undefined;
async getCheerio(url: string, search: boolean): Promise<CheerioAPI> { async getCheerio(url: string, search: boolean): Promise<CheerioAPI> {
const r = await fetchApi(url); const r = await fetchApi(url);
@@ -162,24 +162,66 @@ class StorySeedlingPlugin implements Plugin.PluginBase {
return novel as Plugin.SourceNovel; return novel as Plugin.SourceNovel;
} }
async parseChapter(chapterPath: string): Promise<string> { async updateNonce(chapterPath: string) {
const $ = await this.getCheerio(this.site + chapterPath, false); const $ = await this.getCheerio(this.site + chapterPath, false);
this.nonce = $('div.mb-4:has(h1.text-xl) > div')
.attr('x-data')
?.match(/loadChapter\('.+?', '(.+?)'\)/)[1];
}
const xdata = $('div[ax-load][x-data]').attr('x-data'); async parseChapter(chapterPath: string): Promise<string> {
const updatedNonce = !this.nonce;
const t = $('div.justify-center > div.mb-4'); if (!this.nonce) await this.updateNonce(chapterPath);
let chapterText = t.html() || ''; const text = await fetchApi(this.site + chapterPath + '/content', {
method: 'POST',
if (xdata) { headers: {
chapterText = 'referrer': this.site + chapterPath + '/',
chapterText + 'x-nonce': this.nonce,
"\n\n Error parsing chapter: Turnstile detected. Advise just reading in web view until there's a fix."; },
// const listXdata = xdata?.split("'"); body: JSON.stringify({ 'captcha_response': '' }),
// const dataNovelId = listXdata[1]; }).then(r => r.text());
// const dataNovelN = listXdata[3]; let textJson;
try {
textJson = JSON.parse(text);
} catch (_) {
//not json :fire: we have chapter
} }
if (textJson && !textJson.success) {
if (textJson.message === 'Invalid security.') {
if (updatedNonce) {
throw new Error(`Failed to find code!`);
}
this.nonce = '';
return await this.parseChapter(chapterPath);
}
if (textJson.captcha) {
throw new Error(
`Failed to bypass turnstile captcha (read in webview until it stops ig)`,
);
}
}
let html = text
.replace(/cls[a-f0-9]+/g, '')
.split('')
.map(char => {
const code = char.charCodeAt(0);
const offset = code > 12123 ? 12027 : 12033;
const decoded = code - offset;
return decoded >= 32 && decoded <= 126
? String.fromCharCode(decoded)
: char;
})
.join('');
let $ = load(html);
return chapterText; $('span').text((_, txt) =>
txt.toLowerCase().includes('storyseedling') ||
txt.toLowerCase().includes('story seedling')
? ''
: txt,
);
return $.html();
} }
async searchNovels( async searchNovels(
+20 -13
View File
@@ -21,13 +21,7 @@ const settings: ServerSetting = {
'sec-fetch-dest', 'sec-fetch-dest',
'pragma', 'pragma',
], ],
disAllowResponseHeaders: [ disAllowResponseHeaders: ['link', 'set-cookie', 'set-cookie2'],
'link',
'set-cookie',
'set-cookie2',
'content-encoding',
'content-length',
],
useUserAgent: true, useUserAgent: true,
}; };
@@ -173,7 +167,11 @@ const proxyRequest: Connect.SimpleHandleFunction = (req, res) => {
.then(([res2, text]) => { .then(([res2, text]) => {
res.statusCode = res2.status; res.statusCode = res2.status;
res2.headers.forEach((val, key) => { res2.headers.forEach((val, key) => {
if (!settings.disAllowResponseHeaders.includes(key)) { if (
!settings.disAllowResponseHeaders.includes(key) &&
key !== 'content-encoding' &&
key !== 'content-length'
) {
res.setHeader(key, val); res.setHeader(key, val);
} }
}); });
@@ -186,11 +184,20 @@ const proxyRequest: Connect.SimpleHandleFunction = (req, res) => {
res.end(); res.end();
}); });
} else if (settings.fetchMode === FetchMode.PROXY) { } else if (settings.fetchMode === FetchMode.PROXY) {
proxy.web(req, res, { proxy.web(
target: _url.origin, req,
selfHandleResponse: true, res,
followRedirects: true, {
}); target: _url.origin,
selfHandleResponse: true,
followRedirects: true,
},
err => {
console.error(err);
res.statusCode = 500;
res.end();
},
);
} }
}; };