fix: Quanben Plugin (#2174)

* Quanben missing chapters fix

* Renaming chapter list with a mirror

* Version number
This commit is contained in:
Straekz
2026-05-18 00:48:48 -04:00
committed by GitHub
parent d7ab8385ea
commit d161170582
+47 -41
View File
@@ -18,7 +18,7 @@ const getStandardNovelPath = (url?: string): string | undefined => {
const parsedUrl = parseUrl(url);
if (!parsedUrl) return undefined;
const match = parsedUrl.pathname.match(/^(\/amp)?(\/n\/[^\/]+\/)/);
return match?.[2];
return match?.[2]?.replace(/^\//, '');
};
const getChapterFileName = (url?: string): string | undefined => {
@@ -47,7 +47,7 @@ class QuanbenPlugin implements Plugin.PluginBase {
id = 'quanben';
name = 'Quanben';
site = 'https://www.quanben.io/';
version = '1.0.1';
version = '1.1.0';
icon = 'src/cn/quanben/icon.png';
defaultCover = defaultCover;
@@ -134,80 +134,86 @@ class QuanbenPlugin implements Plugin.PluginBase {
// novel details and metadata
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
const standardPath = novelPath.replace(/^\/amp/, '');
if (!standardPath.startsWith('/n/') || !standardPath.endsWith('/'))
const standardPath = novelPath.replace(/^\/amp/, '').replace(/^\//, '');
if (!standardPath.startsWith('n/') || !standardPath.endsWith('/'))
throw new Error(`[Quanben parseNovel] Invalid path: ${novelPath}`);
const fullUrl = makeAbsolute(standardPath, this.site);
if (!fullUrl)
throw new Error(`[Quanben parseNovel] Could not construct full URL`);
const fullUrl = this.site + standardPath;
const res = await fetchApi(fullUrl);
if (!res.ok)
throw new Error(`[Quanben parseNovel] Failed to fetch: ${fullUrl}`);
const $ = parseHTML(await res.text());
// Helper to read Open Graph / novel meta tags, falling back to empty string
const getMeta = (prop: string) =>
$(`meta[property="${prop}"]`).attr('content')?.trim() || '';
const $info = $('div.list2').first();
const $desc = $('div.description').first();
const statusText = getMeta('og:novel:status');
const novel: Plugin.SourceNovel = {
path: standardPath,
name: $info.find('h3').text().trim() || 'Unknown Novel',
name:
getMeta('og:novel:book_name') ||
$info.find('h3').text().trim() ||
'Unknown Novel',
cover:
getMeta('og:image') ||
makeAbsolute($info.find('img').attr('src'), this.site) ||
this.defaultCover,
summary:
$desc.find('p').text().trim() || $desc.text().trim() || undefined,
author: $info.find("p:contains('作者:') span").text().trim() || undefined,
status: NovelStatus.Unknown,
genres: $info.find("p:contains('类别:') span").text().trim() || undefined,
getMeta('og:description') ||
$desc.find('p').text().trim() ||
$desc.text().trim() ||
undefined,
author:
getMeta('og:novel:author') ||
$info.find("p:contains('作者:') span").text().trim() ||
undefined,
status: statusText
? statusText.includes('完结')
? NovelStatus.Completed
: NovelStatus.Ongoing
: NovelStatus.Unknown,
genres:
getMeta('og:novel:category') ||
$info.find("p:contains('类别:') span").text().trim() ||
undefined,
chapters: await this.parseChapterList(standardPath),
};
novel.status = NovelStatus.Completed;
return novel;
}
async parseChapterList(novelPath: string): Promise<Plugin.ChapterItem[]> {
if (!novelPath.startsWith('/n/') || !novelPath.endsWith('/')) return [];
if (!novelPath.startsWith('n/') || !novelPath.endsWith('/')) return [];
const url = makeAbsolute(novelPath + 'list.html', this.site);
if (!url) return [];
const novelSlug = novelPath.match(/^n\/([^\/]+)\//)?.[1];
if (!novelSlug) return [];
const res = await fetchApi(url);
const mirrorUrl = `https://quanben5.com/n/${novelSlug}/xiaoshuo.html`;
const res = await fetchApi(mirrorUrl);
if (!res.ok) return [];
const $ = parseHTML(await res.text());
const chapters: Plugin.ChapterItem[] = [];
const novelName = novelPath.match(/\/n\/([^\/]+)\//)?.[1];
if (!novelName) return [];
$('ul.list3 li a').each((_i, el) => {
const $el = $(el);
const name = $el.text().trim();
const href = $el.attr('href');
if (!name || !href) return;
const fileName = getChapterFileName(makeAbsolute(href, this.site));
if (!fileName) return;
$('ul li a').each((_, el) => {
const name = $(el).text().trim();
if (!name) return;
const i = chapters.length + 1;
chapters.push({
name,
path: `${novelName}/${fileName}`,
path: `${novelSlug}/${i}.html`,
chapterNumber: i,
});
});
const uniqueChapters = Array.from(
new Map(chapters.map(c => [c.path, c])).values(),
);
uniqueChapters.sort((a, b) => {
const numA = parseInt(a.path.match(/(\d+)\.html$/)?.[1] || '0', 10);
const numB = parseInt(b.path.match(/(\d+)\.html$/)?.[1] || '0', 10);
return numA - numB;
});
return uniqueChapters.map((c, idx) => ({ ...c, chapterNumber: idx + 1 }));
return chapters;
}
async parseChapter(chapterPath: string): Promise<string> {
@@ -263,7 +269,7 @@ class QuanbenPlugin implements Plugin.PluginBase {
if (path)
novels.push({
name,
path: '/amp' + path,
path,
cover: cover || this.defaultCover,
});
}