Fix mvlempyr again following website recode (#1750)

This commit is contained in:
Soopyboo32
2025-07-08 09:16:09 +08:00
committed by GitHub
parent 893d2e1bce
commit 15a9e48503
+34 -266
View File
@@ -16,7 +16,6 @@ const parserData: {
avgReview?: string; avgReview?: string;
reviewCount?: string; reviewCount?: string;
chapterCount?: string; chapterCount?: string;
updated?: string;
created?: string; created?: string;
genres?: string; genres?: string;
tags?: string; tags?: string;
@@ -29,9 +28,9 @@ class MVLEMPYRPlugin implements Plugin.PluginBase {
name = 'MVLEMPYR'; name = 'MVLEMPYR';
icon = 'src/en/mvlempyr/icon.png'; icon = 'src/en/mvlempyr/icon.png';
site = 'https://www.mvlempyr.com/'; site = 'https://www.mvlempyr.com/';
version = '1.0.7'; version = '1.0.8';
_chapSite = 'https://chap.mvlempyr.space/'; _chapSite = 'https://chap.heliosarchive.online/';
_allNovels: (Plugin.NovelItem & ExtraNovelData)[] | undefined; _allNovels: (Plugin.NovelItem & ExtraNovelData)[] | undefined;
_allNovelsPromise: Promise<(Plugin.NovelItem & ExtraNovelData)[]> | undefined; _allNovelsPromise: Promise<(Plugin.NovelItem & ExtraNovelData)[]> | undefined;
@@ -44,194 +43,6 @@ class MVLEMPYRPlugin implements Plugin.PluginBase {
throw new Error('Captcha error, please open in webview'); throw new Error('Captcha error, please open in webview');
} }
parseNovels(
loadedCheerio: CheerioAPI,
nextPageConsumer?: (nextPage: string, pageCount: number) => void,
) {
this.checkCaptcha(loadedCheerio);
if (nextPageConsumer) {
const nextPage = loadedCheerio('a.w-pagination-next.next').attr('href');
if (nextPage)
nextPageConsumer(
nextPage,
parseInt(
loadedCheerio('div.w-page-count.hide').text().split(' / ').pop()!,
),
);
}
return loadedCheerio('.novelcolumn')
.map((i, el) => this.parseNovelHtml(parseHTML(el)))
.toArray();
}
parseNovelsFast(body: string) {
const novelInfosStart = body.split('<div class="novelcolumn">');
novelInfosStart.shift(); //remove first element that doesn't have a novel
const ret = [];
for (const novelInfo of novelInfosStart) {
const realNovelInfo =
novelInfo.split('READ</a></div></div></div>')[0] +
'READ</a></div></div></div>';
const cheerio = parseHTML(realNovelInfo);
ret.push(this.parseNovelHtml(cheerio));
}
return ret;
}
/**
* Equivalent to parseNovelsFast except uses htmlparser2 instead of cheerio for even more fast-fast
*/
parseNovelsFastNew(body: string) {
const novelInfosStart = body.split('<div class="novelcolumn">');
novelInfosStart.shift(); //remove first element that doesn't have a novel
const ret = [];
for (const novelInfo of novelInfosStart) {
const realNovelInfo =
novelInfo.split('READ</a></div></div></div>')[0] +
'READ</a></div></div></div>';
ret.push(this.parseNovelHtmlNew(realNovelInfo));
}
// console.log(ret);
return ret;
}
parseNovelHtmlNew(el: string): Plugin.NovelItem & ExtraNovelData {
parserData.ret = {
name: '',
path: '',
cover: '',
avgReview: '',
reviewCount: '',
chapterCount: '',
updated: '',
created: '',
genres: '',
tags: '',
};
parserData.inTag = '';
parserData.depth = 0;
parserData.depthRatingWrapper = 0;
if (!parserData.parser) {
parserData.parser = new Parser({
onopentag(
name: string,
attribs: Record<string, string>,
isImplied: boolean,
) {
if (parserData.depthRatingWrapper) parserData.depthRatingWrapper++;
if (parserData.inTag) {
parserData.depth++;
return;
}
if (attribs['class']?.includes?.('ratingwrapper'))
parserData.depthRatingWrapper = 1;
if (name === 'h2' && attribs['fs-cmsfilter-field'] === 'name')
parserData.inTag = 'name';
if (name === 'a' && !parserData.ret!.path)
parserData.ret!.path = attribs['href'].replace(/^\//, '');
if (name === 'img' && !parserData.ret!.cover)
parserData.ret!.cover = attribs['src'];
if (
name === 'div' &&
attribs['fs-cmssort-field'] === 'avgr' &&
parserData.depthRatingWrapper
)
parserData.inTag = 'avgReview';
if (
name === 'div' &&
attribs['fs-cmssort-field'] === 'reviews' &&
parserData.depthRatingWrapper
)
parserData.inTag = 'reviewCount';
if (
name === 'div' &&
attribs['fs-cmssort-field'] === 'chapter' &&
attribs['class']?.includes?.('chapter-count')
)
parserData.inTag = 'chapterCount';
if (name === 'div' && attribs['fs-cmssort-field'] === 'update')
parserData.inTag = 'updated';
if (name === 'div' && attribs['fs-cmssort-field'] === 'crdate')
parserData.inTag = 'created';
if (name === 'div' && attribs['fs-cmsnest-collection'] === 'genre')
parserData.inTag = 'genres';
if (name === 'div' && attribs['fs-cmsnest-collection'] === 'tags')
parserData.inTag = 'tags';
if (parserData.inTag) {
parserData.depth++;
}
},
ontext(data: string) {
if (!parserData.inTag) return;
// @ts-ignore
parserData.ret[parserData.inTag] =
(parserData.ret[parserData.inTag] || '') + data;
},
onclosetag(name: string, isImplied: boolean) {
if (parserData.depthRatingWrapper) parserData.depthRatingWrapper--;
if (!parserData.inTag) return;
parserData.depth--;
if (!parserData.depth) parserData.inTag = '';
},
});
}
parserData.parser.reset();
parserData.parser.write(el);
parserData.parser.end();
// @ts-ignore
parserData.ret.avgReview = parseFloat(parserData.ret.avgReview);
// @ts-ignore
parserData.ret.reviewCount = parseFloat(parserData.ret.reviewCount);
// @ts-ignore
parserData.ret.chapterCount = parseFloat(parserData.ret.chapterCount);
// @ts-ignore
parserData.ret.updated = new Date(parserData.ret.updated).getTime();
// @ts-ignore
parserData.ret.created = new Date(parserData.ret.created).getTime();
// @ts-ignore
parserData.ret.genres = parserData.ret.genres.split(', ');
// @ts-ignore
parserData.ret.tags = parserData.ret.tags.split(', ');
// @ts-ignore
return parserData.ret;
}
parseNovelHtml(el: CheerioAPI) {
return {
name: el('h2[fs-cmsfilter-field="name"]').text(),
path: el('a').attr('href')!.replace(/^\//, ''),
cover: el('img').attr('src'),
avgReview: parseFloat(
el('.ratingwrapper > div[fs-cmssort-field="avgr"]').text(),
),
reviewCount: parseFloat(
el('.ratingwrapper > div[fs-cmssort-field="reviews"]').text(),
),
chapterCount: parseFloat(
el('div[fs-cmssort-field="chapter"].chapter-count').text(),
),
updated: new Date(el('div[fs-cmssort-field="update"]').text()).getTime(),
created: new Date(el('div[fs-cmssort-field="crdate"]').text()).getTime(),
genres: el('div[fs-cmsnest-collection="genre"]').text().split(', '),
tags: el('div[fs-cmsnest-collection="tags"]').text().split(', '),
};
}
async popularNovels( async popularNovels(
pageNo: number, pageNo: number,
{ filters }: Plugin.PopularNovelsOptions<Filters>, { filters }: Plugin.PopularNovelsOptions<Filters>,
@@ -275,25 +86,6 @@ class MVLEMPYRPlugin implements Plugin.PluginBase {
return this.paginate(sorted, pageNo); return this.paginate(sorted, pageNo);
} }
async parseNovelListPage(
url: string,
nextPageConsumer?: (nextPage: string, pageCount: number) => void,
): Promise<(Plugin.NovelItem & ExtraNovelData)[]> {
const result = await fetchApi(url, {
headers: {
origin2: this.site.replace(/\/$/, ''),
origin: this.site.replace(/\/$/, ''),
},
});
const body = await result.text();
if (!nextPageConsumer) {
return this.parseNovelsFastNew(body);
}
const loadedCheerio = parseHTML(body);
return this.parseNovels(loadedCheerio, nextPageConsumer);
}
convertNovelId(e: bigint) { convertNovelId(e: bigint) {
let t = 1999999997n; let t = 1999999997n;
let u = 1n, let u = 1n,
@@ -323,15 +115,9 @@ class MVLEMPYRPlugin implements Plugin.PluginBase {
'wp-json/wp/v2/posts?tags=' + 'wp-json/wp/v2/posts?tags=' +
newNovelId + newNovelId +
'&per_page=500&page=1', '&per_page=500&page=1',
{
headers: {
origin2: this.site.replace(/\/$/, ''),
origin: this.site.replace(/\/$/, ''),
},
},
); );
const pages = parseInt(firstPostsReq.headers.get('x-wp-totalpages')); const pages = parseInt(firstPostsReq.headers.get('X-Wp-Totalpages')) || 1;
const posts = [ const posts = [
await firstPostsReq.json(), await firstPostsReq.json(),
@@ -346,12 +132,6 @@ class MVLEMPYRPlugin implements Plugin.PluginBase {
newNovelId + newNovelId +
'&per_page=500&page=' + '&per_page=500&page=' +
page, page,
{
headers: {
origin2: this.site.replace(/\/$/, ''),
origin: this.site.replace(/\/$/, ''),
},
},
).then(res => res.json()), ).then(res => res.json()),
), ),
)), )),
@@ -359,16 +139,9 @@ class MVLEMPYRPlugin implements Plugin.PluginBase {
return { return {
path: novelPath, path: novelPath,
name: name: loadedCheerio('h1.novel-title').text() || 'Untitled',
loadedCheerio( cover: loadedCheerio('img.novel-image').attr('src'),
'.novelpaewrapper div.image-container.w-embed > img', summary: loadedCheerio('div.synopsis.w-richtext').text().trim(),
).attr('alt') || 'Untitled',
cover: loadedCheerio(
'.novelpaewrapper div.image-container.w-embed > img',
).attr('src'),
summary: loadedCheerio('.novelpaewrapper div.synopsis.w-richtext')
.text()
.trim(),
chapters: posts chapters: posts
.map(chap => ({ .map(chap => ({
name: chap.acf.ch_name, name: chap.acf.ch_name,
@@ -378,13 +151,18 @@ class MVLEMPYRPlugin implements Plugin.PluginBase {
chapterNumber: chap.acf.chapter_number, chapterNumber: chap.acf.chapter_number,
})) }))
.reverse(), .reverse(),
status: loadedCheerio( status: loadedCheerio('.novelstatustextlarge').text(),
'.novelpaewrapper div.novelstatustextmedium', author: loadedCheerio(
).text(), 'div.additionalinfo.tm10 > div.textwrapper:nth-child(1)',
author: loadedCheerio('.novelpaewrapper div.mobileauthorname').text(),
genres: loadedCheerio(
'.novelpaewrapper div.novelgenre.mobile > div > div > a > div',
) )
.toArray()
.filter(e => {
return (
e.children.length == 2 &&
e.children[0].children[0].data === 'Author:'
);
})[0].children[1].children[0].data,
genres: loadedCheerio('.genre-tags')
.map((i, el) => loadedCheerio(el).text()) .map((i, el) => loadedCheerio(el).text())
.toArray() .toArray()
.join(','), .join(','),
@@ -436,31 +214,23 @@ class MVLEMPYRPlugin implements Plugin.PluginBase {
} }
async loadAll() { async loadAll() {
let pageQueryId; const data = await fetchApi(
let pageCount = 1; this._chapSite + 'wp-json/wp/v2/mvl-novels?per_page=10000',
const pages = [ ).then(r => r.json());
...(await this.parseNovelListPage( // @ts-ignore
`${this.site}advance-search`, return data.map(novel => {
(a, b) => { return {
pageQueryId = a.split('_page=')[0]; name: novel.name,
pageCount = b; path: 'novel/' + novel.slug,
}, cover: `https://assets.mvlempyr.app/images/600/${novel['novel-code']}.webp`,
)), avgReview: novel['average-review'],
]; reviewCount: novel['total-reviews'],
chapterCount: novel['total-chapters'],
const followingPages = []; created: new Date(novel['created']).getTime(),
for (let i = 2; i <= pageCount; i++) { genres: novel.genre,
followingPages.push( tags: novel.tags,
this.parseNovelListPage( };
`${this.site}advance-search${pageQueryId}_page=${i}`, });
),
);
}
const pagesAwaited = await Promise.all(followingPages);
pagesAwaited.forEach(page => pages.push(...page));
return pages;
} }
filters = { filters = {
@@ -470,7 +240,6 @@ class MVLEMPYRPlugin implements Plugin.PluginBase {
label: 'Order by', label: 'Order by',
options: [ options: [
{ label: 'Latest Added', value: 'created' }, { label: 'Latest Added', value: 'created' },
{ label: 'Latest Updated', value: 'updated' },
{ label: 'Best Rated', value: 'avgReview' }, { label: 'Best Rated', value: 'avgReview' },
{ label: 'Most Reviewed', value: 'reviewCount' }, { label: 'Most Reviewed', value: 'reviewCount' },
{ label: 'Chapter Count', value: 'chapterCount' }, { label: 'Chapter Count', value: 'chapterCount' },
@@ -1468,7 +1237,6 @@ type ExtraNovelData = {
avgReview: number; avgReview: number;
reviewCount: number; reviewCount: number;
chapterCount: number; chapterCount: number;
updated: number;
created: number; created: number;
genres: string[]; genres: string[];
tags: string[]; tags: string[];