class novelfire { constructor(fetchPath, cheerioPath, browser) { this.browser = browser; this.fetch = require(fetchPath); this.cheerio = require(cheerioPath); this.baseUrl = "https://novelfire.net"; this.type = "book-board"; } async fetchSearchResult(query = "", page = 1) { let html; 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 $ = 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" }); }); return { results, hasNextPage: false, page }; } 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 $ = this.cheerio.load(html); const contentDiv = $("#content"); if (!contentDiv || contentDiv.length === 0) { return [{ type: "text", content: "
Error: content not found
", 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 === " " || 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); } }); if (!cleanHtml.trim()) { cleanHtml = contentDiv.html(); } return [ { type: "text", content: cleanHtml.trim(), index: 0 } ]; } } module.exports = { novelupdates: novelfire };