all extensions to new format :P
This commit is contained in:
248
novelfire.js
248
novelfire.js
@@ -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 === " " || 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;
|
||||
Reference in New Issue
Block a user