fix: Quanben Plugin (#2174)
* Quanben missing chapters fix * Renaming chapter list with a mirror * Version number
This commit is contained in:
+47
-41
@@ -18,7 +18,7 @@ const getStandardNovelPath = (url?: string): string | undefined => {
|
|||||||
const parsedUrl = parseUrl(url);
|
const parsedUrl = parseUrl(url);
|
||||||
if (!parsedUrl) return undefined;
|
if (!parsedUrl) return undefined;
|
||||||
const match = parsedUrl.pathname.match(/^(\/amp)?(\/n\/[^\/]+\/)/);
|
const match = parsedUrl.pathname.match(/^(\/amp)?(\/n\/[^\/]+\/)/);
|
||||||
return match?.[2];
|
return match?.[2]?.replace(/^\//, '');
|
||||||
};
|
};
|
||||||
|
|
||||||
const getChapterFileName = (url?: string): string | undefined => {
|
const getChapterFileName = (url?: string): string | undefined => {
|
||||||
@@ -47,7 +47,7 @@ class QuanbenPlugin implements Plugin.PluginBase {
|
|||||||
id = 'quanben';
|
id = 'quanben';
|
||||||
name = 'Quanben';
|
name = 'Quanben';
|
||||||
site = 'https://www.quanben.io/';
|
site = 'https://www.quanben.io/';
|
||||||
version = '1.0.1';
|
version = '1.1.0';
|
||||||
icon = 'src/cn/quanben/icon.png';
|
icon = 'src/cn/quanben/icon.png';
|
||||||
defaultCover = defaultCover;
|
defaultCover = defaultCover;
|
||||||
|
|
||||||
@@ -134,80 +134,86 @@ class QuanbenPlugin implements Plugin.PluginBase {
|
|||||||
|
|
||||||
// novel details and metadata
|
// novel details and metadata
|
||||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||||
const standardPath = novelPath.replace(/^\/amp/, '');
|
const standardPath = novelPath.replace(/^\/amp/, '').replace(/^\//, '');
|
||||||
if (!standardPath.startsWith('/n/') || !standardPath.endsWith('/'))
|
if (!standardPath.startsWith('n/') || !standardPath.endsWith('/'))
|
||||||
throw new Error(`[Quanben parseNovel] Invalid path: ${novelPath}`);
|
throw new Error(`[Quanben parseNovel] Invalid path: ${novelPath}`);
|
||||||
|
|
||||||
const fullUrl = makeAbsolute(standardPath, this.site);
|
const fullUrl = this.site + standardPath;
|
||||||
if (!fullUrl)
|
|
||||||
throw new Error(`[Quanben parseNovel] Could not construct full URL`);
|
|
||||||
|
|
||||||
const res = await fetchApi(fullUrl);
|
const res = await fetchApi(fullUrl);
|
||||||
if (!res.ok)
|
if (!res.ok)
|
||||||
throw new Error(`[Quanben parseNovel] Failed to fetch: ${fullUrl}`);
|
throw new Error(`[Quanben parseNovel] Failed to fetch: ${fullUrl}`);
|
||||||
|
|
||||||
const $ = parseHTML(await res.text());
|
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 $info = $('div.list2').first();
|
||||||
const $desc = $('div.description').first();
|
const $desc = $('div.description').first();
|
||||||
|
|
||||||
|
const statusText = getMeta('og:novel:status');
|
||||||
|
|
||||||
const novel: Plugin.SourceNovel = {
|
const novel: Plugin.SourceNovel = {
|
||||||
path: standardPath,
|
path: standardPath,
|
||||||
name: $info.find('h3').text().trim() || 'Unknown Novel',
|
name:
|
||||||
|
getMeta('og:novel:book_name') ||
|
||||||
|
$info.find('h3').text().trim() ||
|
||||||
|
'Unknown Novel',
|
||||||
cover:
|
cover:
|
||||||
|
getMeta('og:image') ||
|
||||||
makeAbsolute($info.find('img').attr('src'), this.site) ||
|
makeAbsolute($info.find('img').attr('src'), this.site) ||
|
||||||
this.defaultCover,
|
this.defaultCover,
|
||||||
summary:
|
summary:
|
||||||
$desc.find('p').text().trim() || $desc.text().trim() || undefined,
|
getMeta('og:description') ||
|
||||||
author: $info.find("p:contains('作者:') span").text().trim() || undefined,
|
$desc.find('p').text().trim() ||
|
||||||
status: NovelStatus.Unknown,
|
$desc.text().trim() ||
|
||||||
genres: $info.find("p:contains('类别:') span").text().trim() || undefined,
|
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),
|
chapters: await this.parseChapterList(standardPath),
|
||||||
};
|
};
|
||||||
|
|
||||||
novel.status = NovelStatus.Completed;
|
|
||||||
|
|
||||||
return novel;
|
return novel;
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseChapterList(novelPath: string): Promise<Plugin.ChapterItem[]> {
|
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);
|
const novelSlug = novelPath.match(/^n\/([^\/]+)\//)?.[1];
|
||||||
if (!url) return [];
|
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 [];
|
if (!res.ok) return [];
|
||||||
|
|
||||||
const $ = parseHTML(await res.text());
|
const $ = parseHTML(await res.text());
|
||||||
const chapters: Plugin.ChapterItem[] = [];
|
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({
|
chapters.push({
|
||||||
name,
|
name,
|
||||||
path: `${novelName}/${fileName}`,
|
path: `${novelSlug}/${i}.html`,
|
||||||
|
chapterNumber: i,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const uniqueChapters = Array.from(
|
return chapters;
|
||||||
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 }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseChapter(chapterPath: string): Promise<string> {
|
async parseChapter(chapterPath: string): Promise<string> {
|
||||||
@@ -263,7 +269,7 @@ class QuanbenPlugin implements Plugin.PluginBase {
|
|||||||
if (path)
|
if (path)
|
||||||
novels.push({
|
novels.push({
|
||||||
name,
|
name,
|
||||||
path: '/amp' + path,
|
path,
|
||||||
cover: cover || this.defaultCover,
|
cover: cover || this.defaultCover,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user