all extensions to new format :P

This commit is contained in:
2025-12-15 19:40:07 +01:00
parent 9986b64ace
commit 9fe48f93fe
19 changed files with 1725 additions and 1375 deletions

View File

@@ -1,155 +1,137 @@
class novelfire {
constructor(fetchPath, cheerioPath, browser) {
this.browser = browser;
this.fetch = require(fetchPath);
this.cheerio = require(cheerioPath);
class NovelFire {
constructor() {
this.baseUrl = "https://novelfire.net";
this.type = "book-board";
this.mediaType = "ln";
}
async fetchSearchResult(query = "", page = 1) {
let html;
async search(queryObj) {
const query = queryObj.query;
if (query.trim() === "") {
const res = await this.fetch(`${this.baseUrl}/home`);
html = await res.text();
} else {
const res = await this.fetch(
`${this.baseUrl}/ajax/searchLive?inputContent=${encodeURIComponent(query)}`
);
const data = await res.json();
html = data.html;
const res = await fetch(
`${this.baseUrl}/ajax/searchLive?inputContent=${encodeURIComponent(query)}`,
{ headers: { "accept": "application/json" } }
);
const data = await res.json();
if (!data.data) return [];
return data.data.map(item => ({
id: item.slug,
title: item.title,
image: `https://novelfire.net/${item.image}`,
rating: item.rank ?? null,
type: "book"
}));
}
async getMetadata(id) {
const url = `https://novelfire.net/book/${id}`;
const html = await (await fetch(url)).text();
const $ = this.cheerio.load(html);
const title = $('h1[itemprop="name"]').first().text().trim() || null;
const summary = $('meta[itemprop="description"]').attr('content') || null;
const image =
$('figure.cover img').attr('src') ||
$('img.cover').attr('src') ||
$('img[src*="server-"]').attr('src') ||
null;
const genres = $('.categories a.property-item')
.map((_, el) => $(el).attr('title') || $(el).text().trim())
.get();
let chapters = null;
const latest = $('.chapter-latest-container .latest').text();
if (latest) {
const m = latest.match(/Chapter\s+(\d+)/i);
if (m) chapters = Number(m[1]);
}
const $ = this.cheerio.load(html);
const results = [];
$(".novel-item").each((_, el) => {
const a = $(el).find("a");
const href = a.attr("href") || "";
const title = $(el).find(".novel-title").text().trim();
const img = $(el).find("img");
const image = img.attr("data-src") || img.attr("src") || "";
const id = href.replace("https://novelfire.net/book/", "").replace(/\/$/, "");
results.push({
id,
title,
image,
sampleImageUrl: image,
tags: [],
type: "book"
});
});
let status = 'unknown';
const statusClass = $('strong.ongoing, strong.completed').attr('class');
if (statusClass) {
status = statusClass.toLowerCase();
}
return {
results,
hasNextPage: false,
page
id,
title,
format: 'Light Novel',
score: 0,
genres,
status,
published: '???',
summary,
chapters,
image
};
}
async findChapters(bookId) {
const url = `https://novelfire.net/book/${bookId}/chapter-1`;
const options = await this.browser.scrape(
url,
async () => {
const sleep = ms => new Promise(r => setTimeout(r, ms));
const select = document.querySelector('.chapindex');
if (!select) return [];
select.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
select.dispatchEvent(new MouseEvent('click', { bubbles: true }));
for (let i = 0; i < 20; i++) {
if (document.querySelectorAll('.select2-results__option').length > 0) break;
await sleep(300);
}
return [...select.querySelectorAll('option')].map(opt => ({
id: opt.value,
title: opt.textContent.trim(),
chapter: Number(opt.dataset.n_sort || 0),
}));
},
{
waitSelector: '.chapindex',
timeout: 10000
}
);
return {
chapters: options.map(o => ({
id: `https://novelfire.net/book/${bookId}/chapter-${o.chapter}`,
title: o.title,
chapter: o.chapter,
language: "en"
}))
};
}
async findChapterPages(chapterId) {
const res = await this.fetch(chapterId);
const html = await res.text();
const url = `https://novelfire.net/book/${bookId}/chapters`;
const html = await (await fetch(url)).text();
const $ = this.cheerio.load(html);
const contentDiv = $("#content");
let postId;
if (!contentDiv || contentDiv.length === 0) {
return [{
type: "text",
content: "<p>Error: content not found</p>",
index: 0
}];
}
contentDiv.find("script").remove();
contentDiv.find("ins").remove();
contentDiv.find("[id^='pf-']").remove();
contentDiv.find(".ads").remove();
contentDiv.find(".adsbygoogle").remove();
contentDiv.find("div[style*='text-align:center']").remove();
contentDiv.find("div[align='center']").remove();
contentDiv.find(".nf-ads").remove();
contentDiv.find("nfne597").remove();
const paragraphs = contentDiv.find("p");
let cleanHtml = "";
paragraphs.each((_, el) => {
const p = $(el);
let text = p.text() || "";
text = text.replace(/△▼△▼△▼△/g, "");
text = text.replace(/[※]+/g, "");
text = text.replace(/\s{2,}/g, " ");
const htmlP = p.html()?.trim() || "";
const isEmpty = htmlP === "" || htmlP === "&nbsp;" || text.trim() === "";
const isAd = text.includes("Remove Ads") || text.includes("Buy no ads") || text.includes("novelfire");
if (!isEmpty && !isAd) {
if (p.text() !== text) p.text(text);
cleanHtml += $.html(p);
}
$("script").each((_, el) => {
const txt = $(el).html() || "";
const m = txt.match(/listChapterDataAjax\?post_id=(\d+)/);
if (m) postId = m[1];
});
if (!cleanHtml.trim()) { cleanHtml = contentDiv.html(); }
if (!postId) throw new Error("post_id not found");
return [
{
type: "text",
content: cleanHtml.trim(),
index: 0
}
];
const params = new URLSearchParams({
post_id: postId,
draw: 1,
"columns[0][data]": "title",
"columns[0][orderable]": "false",
"columns[1][data]": "created_at",
"columns[1][orderable]": "true",
"order[0][column]": 1,
"order[0][dir]": "asc",
start: 0,
length: 1000
});
const res = await fetch(
`https://novelfire.net/listChapterDataAjax?${params}`,
{ headers: { "x-requested-with": "XMLHttpRequest" } }
);
const json = await res.json();
if (!json?.data) throw new Error("Invalid response");
return json.data.map((c, i) => ({
id: `https://novelfire.net/book/${bookId}/chapter-${c.n_sort}`,
title: c.title,
number: Number(c.n_sort),
release_date: c.created_at ?? null,
index: i,
language: "en"
}));
}
async findChapterPages(url) {
const html = await (await fetch(url)).text();
const $ = this.cheerio.load(html);
const $content = $("#content").clone();
$content.find("script, ins, .nf-ads, img, nfn2a74").remove();
$content.find("*").each((_, el) => {
$(el).removeAttr("id").removeAttr("class").removeAttr("style");
});
return $content.html()
.replace(/adsbygoogle/gi, "")
.replace(/novelfire/gi, "")
.trim();
}
}
module.exports = { novelupdates: novelfire };
module.exports = NovelFire;