147 lines
4.4 KiB
JavaScript
147 lines
4.4 KiB
JavaScript
class NovelFire {
|
|
constructor() {
|
|
this.baseUrl = "https://novelfire.net";
|
|
this.type = "book-board";
|
|
this.mediaType = "ln";
|
|
this.version = "1.1"
|
|
}
|
|
|
|
async search(queryObj) {
|
|
const query = queryObj.query;
|
|
|
|
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]);
|
|
}
|
|
|
|
let status = 'unknown';
|
|
const statusClass = $('strong.ongoing, strong.completed').attr('class');
|
|
if (statusClass) {
|
|
status = statusClass.toLowerCase();
|
|
}
|
|
|
|
return {
|
|
id,
|
|
title,
|
|
format: 'Light Novel',
|
|
score: 0,
|
|
genres,
|
|
status,
|
|
published: '???',
|
|
summary,
|
|
chapters,
|
|
image
|
|
};
|
|
}
|
|
|
|
async findChapters(bookId) {
|
|
const chapterUrl = `https://novelfire.net/book/${bookId}/chapter-1`;
|
|
const html = await (await fetch(chapterUrl)).text();
|
|
const $ = this.cheerio.load(html);
|
|
|
|
// csrf token
|
|
const csrf = $('meta[name="csrf-token"]').attr('content');
|
|
if (!csrf) throw new Error("csrf-token not found");
|
|
|
|
// post_id desde script
|
|
let postId;
|
|
$("script").each((_, el) => {
|
|
const txt = $(el).html() || "";
|
|
const m = txt.match(/post_id\s*=\s*parseInt\("(\d+)"\)/);
|
|
if (m) postId = m[1];
|
|
});
|
|
if (!postId) throw new Error("post_id not found");
|
|
|
|
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",
|
|
"x-csrf-token": csrf,
|
|
"referer": chapterUrl
|
|
}
|
|
}
|
|
);
|
|
|
|
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 = NovelFire; |