refactor: Novelfire popularNovels and Fix Summary (#2100)

* fix: change novel.totalPages calculation to divide by 50 instead of 100

* update version number

* fix: refactor novelfire popular,search and summary

---------

Co-authored-by: G0yp <goupsoup2@gmail.com>
This commit is contained in:
K1ngfish3r
2026-04-07 07:44:46 +05:00
committed by GitHub
parent b63a6355a7
commit a3cf99996a
+96 -100
View File
@@ -9,11 +9,11 @@ import { storage } from '@libs/storage';
class NovelFire implements Plugin.PluginBase { class NovelFire implements Plugin.PluginBase {
id = 'novelfire'; id = 'novelfire';
name = 'Novel Fire'; name = 'Novel Fire';
version = '1.1.8'; version = '1.2.0';
icon = 'src/en/novelfire/icon.png'; icon = 'src/en/novelfire/icon.png';
site = 'https://novelfire.net/'; site = 'https://novelfire.net/';
novelList = []; novelList: string[] = [];
singlePage = storage.get('singlePage'); singlePage = storage.get('singlePage');
pluginSettings = { pluginSettings = {
@@ -40,63 +40,29 @@ class NovelFire implements Plugin.PluginBase {
return $; return $;
} }
async popularNovels( parseNovels(
pageNo: number, loadedCheerio: CheerioAPI,
{ selector = '.novel-item',
showLatestNovels, ): Plugin.NovelItem[] {
filters, return loadedCheerio(selector)
}: Plugin.PopularNovelsOptions<typeof this.filters>, .map((_, el) => {
): Promise<Plugin.NovelItem[]> { const titleElement = loadedCheerio(el).find('.novel-title > a');
if (pageNo == 1) { const fallbackElement = loadedCheerio(el).find('a');
this.novelList = [];
}
let url = this.site + 'search-adv';
if (showLatestNovels) {
url += `?ctgcon=and&totalchapter=0&ratcon=min&rating=0&status=-1&sort=date&tagcon=and&page=${pageNo}`;
} else if (filters) {
const params = new URLSearchParams();
for (const language of filters.language.value) {
params.append('country_id[]', language);
}
params.append('ctgcon', filters.genre_operator.value);
for (const genre of filters.genres.value) {
params.append('categories[]', genre);
}
params.append('totalchapter', filters.chapters.value);
params.append('ratcon', filters.rating_operator.value);
params.append('rating', filters.rating.value);
params.append('status', filters.status.value);
params.append('sort', filters.sort.value);
params.append('page', pageNo.toString());
url += `?${params.toString()}`;
} else {
url += `?ctgcon=and&totalchapter=0&ratcon=min&rating=0&status=-1&sort=rank-top&page=${pageNo}`;
}
const loadedCheerio = await this.getCheerio(url, false);
return loadedCheerio('.novel-item')
.map((index, ele) => {
const novelName = const novelName =
loadedCheerio(ele).find('.novel-title > a').text() || titleElement.text() ||
fallbackElement.attr('title') ||
'No Title Found'; 'No Title Found';
const imgElement = loadedCheerio(el).find('.novel-cover > img');
const novelCover = const novelCover =
this.site + this.site +
deSlash( deSlash(imgElement.attr('data-src') || imgElement.attr('src') || '');
loadedCheerio(ele).find('.novel-cover > img').attr('data-src') ||
'',
);
const novelPath = loadedCheerio(ele)
.find('.novel-title > a')
.attr('href');
if (!novelPath) return; const novelPath =
titleElement.attr('href') || fallbackElement.attr('href');
if (this.novelList.includes(novelPath)) { if (!novelPath) return null;
return;
} else {
this.novelList.push(novelPath);
}
return { return {
name: novelName, name: novelName,
@@ -105,15 +71,57 @@ class NovelFire implements Plugin.PluginBase {
}; };
}) })
.get() .get()
.filter(novel => novel !== null); .filter(novel => novel !== null)
.filter(novel => {
if (this.novelList.includes(novel.path)) {
return false;
} else {
this.novelList.push(novel.path);
return true;
}
});
}
async popularNovels(
pageNo: number,
{
showLatestNovels,
filters,
}: Plugin.PopularNovelsOptions<typeof this.filters>,
): Promise<Plugin.NovelItem[]> {
if (pageNo === 1) {
this.novelList = [];
}
const url = this.site + 'search-adv';
const params = new URLSearchParams();
for (const language of filters.language.value) {
params.append('country_id[]', language);
}
params.append('ctgcon', filters.genre_operator.value);
for (const genre of filters.genres.value) {
params.append('categories[]', genre);
}
params.append('totalchapter', filters.chapters.value);
params.append('ratcon', filters.rating_operator.value);
params.append('rating', filters.rating.value);
params.append('status', filters.status.value);
params.append('sort', showLatestNovels ? 'date' : filters.sort.value);
params.append('tagcon', 'and');
params.append('page', pageNo.toString());
const loadedCheerio = await this.getCheerio(
`${url}?${params.toString()}`,
false,
);
return this.parseNovels(loadedCheerio);
} }
async getAllChapters( async getAllChapters(
novelPath: string, novelPath: string,
post_id: string, post_id: string,
): Promise<Plugin.ChapterItem[]> { ): Promise<Plugin.ChapterItem[]> {
const allChapters: Plugin.ChapterItem[] = [];
const url = `${this.site}listChapterDataAjax?post_id=${post_id}`; const url = `${this.site}listChapterDataAjax?post_id=${post_id}`;
const result = await fetchApi(url); const result = await fetchApi(url);
const body = await result.text(); const body = await result.text();
@@ -128,7 +136,7 @@ class NovelFire implements Plugin.PluginBase {
const json = JSON.parse(body); const json = JSON.parse(body);
const chapters = json.data const chapters = json.data
.map(index => { .map((index: { title?: string; slug: string; n_sort: number }) => {
const chapterName = load(index.title || index.slug).text(); const chapterName = load(index.title || index.slug).text();
const chapterPath = `${novelPath}/chapter-${index.n_sort}`; const chapterPath = `${novelPath}/chapter-${index.n_sort}`;
const sortNumber = index.n_sort; const sortNumber = index.n_sort;
@@ -141,9 +149,11 @@ class NovelFire implements Plugin.PluginBase {
chapterNumber: Number(sortNumber), chapterNumber: Number(sortNumber),
}; };
}) })
.filter(chapter => chapter !== null) as Plugin.ChapterItem[]; .filter(
(chapter: Plugin.ChapterItem | null) => chapter !== null,
) as Plugin.ChapterItem[];
const sortedChapters = chapters.sort(function (a, b) { const sortedChapters = chapters.sort(function (a, b) {
return a.chapterNumber - b.chapterNumber; return (a.chapterNumber || 0) - (b.chapterNumber || 0);
}); });
return sortedChapters; return sortedChapters;
@@ -161,7 +171,7 @@ class NovelFire implements Plugin.PluginBase {
const retryCount = 10; const retryCount = 10;
const sleepTime = 3.5; // Rate limit seems to be around ~10s, so usually 3 retries should be enough for another ~30 pages. const sleepTime = 3.5; // Rate limit seems to be around ~10s, so usually 3 retries should be enough for another ~30 pages.
const chaptersArray: Plugin.ChapterItem[][] = []; const chaptersArray: Plugin.SourcePage[] = [];
for (let i = 0; i < pagesArray.length; i += chunkSize) { for (let i = 0; i < pagesArray.length; i += chunkSize) {
const pagesArrayChunk = pagesArray.slice(i, i + chunkSize); const pagesArrayChunk = pagesArray.slice(i, i + chunkSize);
@@ -202,11 +212,11 @@ class NovelFire implements Plugin.PluginBase {
} }
// Merge all chapters into a single array // Merge all chapters into a single array
for (let chapters of chaptersArray) { for (const page of chaptersArray) {
// For some reason it's formatted this way, this fixes it. if (page.chapters) {
chapters = chapters.chapters; for (const chapter of page.chapters) {
for (let i = 0; i < Object.keys(chapters).length; i++) { allChapters.push(chapter);
allChapters.push(chapters[i]); }
} }
} }
return allChapters; return allChapters;
@@ -244,19 +254,19 @@ class NovelFire implements Plugin.PluginBase {
.toArray() .toArray()
.join(','); .join(',');
let summary = $('.summary .content') const summary = $('.summary .content');
.find('br') summary.find('.expand').remove();
.replaceWith('\n') summary.find('br').replaceWith('\n');
.end() summary.find('p').before('\n').after('\n\n');
.text()
.trim();
if (summary) { novel.summary =
summary = summary.replace('Show More', ''); summary
novel.summary = summary; .text()
} else { .split('\n')
novel.summary = 'No Summary Found'; .map(line => line.trim())
} .join('\n')
?.replace(/\n{3,}/g, '\n\n')
.trim() || 'Summary Not Found';
novel.author = $('.author .property-item > span').text(); novel.author = $('.author .property-item > span').text();
@@ -285,13 +295,13 @@ class NovelFire implements Plugin.PluginBase {
.parent() .parent()
.text() .text()
.trim(); .trim();
novel.totalPages = Math.ceil(parseInt(totalChapters) / 100); novel.totalPages = Math.ceil(parseInt(totalChapters) / 50);
if (this.singlePage) { if (this.singlePage) {
novel.chapters = await this.getAllChaptersForce( novel.chapters = await this.getAllChaptersForce(
novelPath, novelPath,
novel.totalPages, novel.totalPages,
); );
if (novel.totalPages > 1 && novel.chapters.length > 100) { if (novel.totalPages > 1 && novel.chapters.length > 50) {
novel.totalPages = 1; novel.totalPages = 1;
} }
} }
@@ -350,33 +360,19 @@ class NovelFire implements Plugin.PluginBase {
searchTerm: string, searchTerm: string,
page: number, page: number,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
const url = `${this.site}search?keyword=${encodeURIComponent(searchTerm)}&page=${page}`; if (page === 1) {
this.novelList = [];
}
const params = new URLSearchParams();
params.append('keyword', searchTerm);
params.append('page', page.toString());
const url = `${this.site}search?${params.toString()}`;
const result = await fetchApi(url); const result = await fetchApi(url);
const body = await result.text(); const body = await result.text();
const loadedCheerio = load(body); const loadedCheerio = load(body);
return loadedCheerio('.novel-list.chapters .novel-item') return this.parseNovels(loadedCheerio, '.novel-list.chapters .novel-item');
.map((index, ele) => {
const novelName =
loadedCheerio(ele).find('a').attr('title') || 'No Title Found';
const novelCover =
this.site +
deSlash(
loadedCheerio(ele).find('.novel-cover > img').attr('src') || '',
);
const novelPath = loadedCheerio(ele).find('a').attr('href');
if (!novelPath) return null;
return {
name: novelName,
cover: novelCover,
path: deSlash(novelPath.replace(this.site, '')),
};
})
.get()
.filter(novel => novel !== null);
} }
filters = { filters = {