Skip to content

Commit e206405

Browse files
committed
feat(lnori): add more cache and remove asyncPool
Signed-off-by: K1ngfish3r <26593485+K1ngfish3r@users.noreply.github.com>
1 parent e056b5d commit e206405

1 file changed

Lines changed: 78 additions & 58 deletions

File tree

plugins/english/lnori.ts

Lines changed: 78 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -22,59 +22,63 @@ class LnorisPlugin implements Plugin.PluginBase {
2222
},
2323
};
2424

25-
private async fetchPage(url: string): Promise<string> {
25+
private libraryCache: CachedNovel[] | null = null;
26+
27+
private async fetchPage(
28+
url: string,
29+
ttl = 8 * 60 * 60 * 1000,
30+
): Promise<string> {
2631
if (storage.get('clearCache')) {
2732
storage.clearAll();
33+
this.libraryCache = null;
2834
storage.set('clearCache', false);
2935
}
3036
const cached = storage.get<string>(url);
3137
if (cached) return cached;
3238
const body = await (await fetchApi(url)).text();
33-
// cache for 8 hours
34-
storage.set(url, body, 8 * 60 * 60 * 1000);
39+
storage.set(url, body, ttl);
3540
return body;
3641
}
3742

3843
private extractAppData(html: string): Record<string, unknown> | null {
39-
const match = html.match(
40-
/<script[^>]*id="app-data"[^>]*>([\s\S]*?)<\/script>/,
41-
);
42-
if (!match?.[1]) return null;
44+
const scriptContent: string[] = [];
45+
let inScript = false;
46+
47+
const parser = new Parser({
48+
onopentag: (name, attribs) => {
49+
if (name === 'script' && attribs.id === 'app-data') {
50+
inScript = true;
51+
}
52+
},
53+
ontext: text => {
54+
if (inScript) {
55+
scriptContent.push(text);
56+
}
57+
},
58+
onclosetag: name => {
59+
if (name === 'script') {
60+
inScript = false;
61+
}
62+
},
63+
});
64+
65+
parser.write(html);
66+
parser.end();
67+
68+
if (!scriptContent.length) return null;
4369
try {
44-
return JSON.parse(match[1]);
70+
return JSON.parse(scriptContent.join(''));
4571
} catch {
4672
return null;
4773
}
4874
}
4975

50-
/**
51-
* Run async tasks with concurrency limit. Avoids flooding mobile network
52-
* with 30+ parallel requests that compete for bandwidth.
53-
*/
54-
private async asyncPool<T, R>(
55-
items: T[],
56-
processor: (item: T) => Promise<R>,
57-
limit = 5,
58-
): Promise<R[]> {
59-
const results: R[] = new Array(items.length);
60-
let nextIndex = 0;
61-
62-
const worker = async (): Promise<void> => {
63-
while (nextIndex < items.length) {
64-
const idx = nextIndex++;
65-
results[idx] = await processor(items[idx]);
66-
}
67-
};
68-
69-
await Promise.all(
70-
Array.from({ length: Math.min(limit, items.length) }, () => worker()),
71-
);
72-
return results;
73-
}
74-
7576
private async getLibraryNovels(): Promise<CachedNovel[]> {
77+
if (this.libraryCache) return this.libraryCache;
78+
7679
const url = this.site + 'library';
77-
const body = await this.fetchPage(url);
80+
// library gets 1 hour cache
81+
const body = await this.fetchPage(url, 1 * 60 * 60 * 1000);
7882
let tempNovel: Partial<Plugin.NovelItem> & {
7983
author?: string;
8084
tags?: string;
@@ -141,11 +145,12 @@ class LnorisPlugin implements Plugin.PluginBase {
141145
parser.write(body);
142146
parser.end();
143147

148+
this.libraryCache = novels;
144149
return novels;
145150
}
146151

147152
async popularNovels(
148-
_pageNo: number,
153+
pageNo: number,
149154
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
150155
): Promise<Plugin.NovelItem[]> {
151156
const parsedList = await this.getLibraryNovels();
@@ -188,7 +193,10 @@ class LnorisPlugin implements Plugin.PluginBase {
188193
break;
189194
}
190195

191-
return filtered.map(item => item.novel);
196+
const novels = filtered.map(item => item.novel);
197+
if (filters.reverse.value) novels.reverse();
198+
const start = (pageNo - 1) * 36;
199+
return novels.slice(start, start + 36);
192200
}
193201

194202
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
@@ -237,7 +245,6 @@ class LnorisPlugin implements Plugin.PluginBase {
237245
break;
238246
case 'img':
239247
if (state === ParsingState.HeroCard) {
240-
novel.name = attribs.alt;
241248
novel.cover = attribs.src;
242249
}
243250
break;
@@ -275,6 +282,11 @@ class LnorisPlugin implements Plugin.PluginBase {
275282
pushState(ParsingState.VolCardMeta);
276283
}
277284
break;
285+
case 'button':
286+
if (state === ParsingState.HeroCard) {
287+
novel.name = attribs['data-series-title'];
288+
}
289+
break;
278290
}
279291
},
280292

@@ -369,6 +381,9 @@ class LnorisPlugin implements Plugin.PluginBase {
369381
}
370382
}
371383
}
384+
for (const key of Object.keys(volumeMap))
385+
if (novel.name && volumeMap[key].includes(novel.name))
386+
volumeMap[key] = volumeMap[key].replace(novel.name, '-');
372387
},
373388
});
374389

@@ -382,12 +397,9 @@ class LnorisPlugin implements Plugin.PluginBase {
382397

383398
const volumeUrls = Object.keys(volumeMap);
384399

385-
// Process volumes with concurrency limit (5)
386-
const chapters2D = await this.asyncPool(
387-
volumeUrls,
388-
async volUrl => {
389-
const fullVolUrl = this.site + volUrl;
390-
const volHtml = await this.fetchPage(fullVolUrl);
400+
const chapters2D = await Promise.all(
401+
volumeUrls.map(async volUrl => {
402+
const volHtml = await this.fetchPage(this.site + volUrl);
391403
const volTitle = getVolumeName(volUrl, volumeMap[volUrl]);
392404
const volChapters: Plugin.ChapterItem[] = [];
393405

@@ -409,7 +421,7 @@ class LnorisPlugin implements Plugin.PluginBase {
409421
return volChapters;
410422
}
411423

412-
// Fallback: htmlparser2 for #toc-list
424+
// Fallback #toc-list
413425
let inTocList = false;
414426
const tocParser = new Parser({
415427
onopentag: (name, attribs) => {
@@ -432,8 +444,7 @@ class LnorisPlugin implements Plugin.PluginBase {
432444
tocParser.write(volHtml);
433445
tocParser.end();
434446
return volChapters;
435-
},
436-
5,
447+
}),
437448
);
438449
const chapters = chapters2D.flat();
439450

@@ -469,19 +480,23 @@ class LnorisPlugin implements Plugin.PluginBase {
469480
.join('<hr>');
470481
}
471482

472-
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
473-
const parsedList = await this.getLibraryNovels();
474-
483+
async searchNovels(
484+
searchTerm: string,
485+
pageNo: number,
486+
): Promise<Plugin.NovelItem[]> {
475487
const term = searchTerm.toLowerCase();
476-
const filteredList = parsedList.filter(item => {
477-
return (
478-
item.novel.name.toLowerCase().includes(term) ||
479-
item.author.toLowerCase().includes(term) ||
480-
item.tags.some(t => t.includes(term))
481-
);
482-
});
483-
484-
return filteredList.map(item => item.novel);
488+
const parsedList = await this.getLibraryNovels();
489+
const filtered = parsedList
490+
.filter(
491+
item =>
492+
item.novel.name.toLowerCase().includes(term) ||
493+
item.author.toLowerCase().includes(term) ||
494+
item.tags.some(t => t.includes(term)),
495+
)
496+
.map(item => item.novel);
497+
498+
const start = (pageNo - 1) * 36;
499+
return filtered.slice(start, start + 36);
485500
}
486501

487502
// resolveUrl = (path: string, _isNovel?: boolean) => {
@@ -500,6 +515,11 @@ class LnorisPlugin implements Plugin.PluginBase {
500515
],
501516
type: FilterTypes.Picker,
502517
},
518+
reverse: {
519+
label: 'Reverse Results',
520+
value: false,
521+
type: FilterTypes.Switch,
522+
},
503523
genre: {
504524
label: 'Genre',
505525
value: {

0 commit comments

Comments
 (0)