new format and marketplace
This commit is contained in:
121
book/novelbin.js
Normal file
121
book/novelbin.js
Normal file
@@ -0,0 +1,121 @@
|
||||
class NovelBin {
|
||||
constructor() {
|
||||
this.baseUrl = "https://novelbin.me";
|
||||
this.type = "book-board";
|
||||
this.mediaType = "ln";
|
||||
}
|
||||
|
||||
async search(queryObj) {
|
||||
const query = queryObj.query || "";
|
||||
const url = `${this.baseUrl}/search?keyword=${encodeURIComponent(query)}`;
|
||||
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
|
||||
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||||
"referer": this.baseUrl + "/"
|
||||
}
|
||||
});
|
||||
|
||||
const html = await res.text();
|
||||
const $ = this.cheerio.load(html);
|
||||
|
||||
const results = [];
|
||||
|
||||
$('h3.novel-title a').each((i, el) => {
|
||||
const href = $(el).attr('href');
|
||||
const title = $(el).text().trim();
|
||||
|
||||
const idMatch = href.match(/novel-book\/([^/?]+)/);
|
||||
const id = idMatch ? idMatch[1] : null;
|
||||
|
||||
const img = `${this.baseUrl}/media/novel/${id}.jpg`;
|
||||
|
||||
results.push({
|
||||
id,
|
||||
title,
|
||||
image: img,
|
||||
rating: null,
|
||||
type: "book"
|
||||
});
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async getMetadata(id) {
|
||||
const res = await fetch(`${this.baseUrl}/novel-book/${id}`);
|
||||
const html = await res.text();
|
||||
const $ = this.cheerio.load(html);
|
||||
|
||||
const getMeta = (property) => $(`meta[property='${property}']`).attr('content') || "";
|
||||
|
||||
const title = getMeta("og:novel:novel_name") || $('title').text() || "";
|
||||
const summary = $('meta[name="description"]').attr('content') || "";
|
||||
const genresRaw = getMeta("og:novel:genre");
|
||||
const genres = genresRaw ? genresRaw.split(',').map(g => g.trim()) : [];
|
||||
const status = getMeta("og:novel:status") || "";
|
||||
const image = getMeta("og:image");
|
||||
|
||||
const lastChapterName = getMeta("og:novel:lastest_chapter_name");
|
||||
const chaptersMatch = lastChapterName.match(/Chapter\s+(\d+)/i);
|
||||
const chapters = chaptersMatch ? Number(chaptersMatch[1]) : 0;
|
||||
|
||||
return {
|
||||
id,
|
||||
title,
|
||||
format: "Light Novel",
|
||||
score: 0,
|
||||
genres,
|
||||
status,
|
||||
published: "???",
|
||||
summary,
|
||||
chapters,
|
||||
image
|
||||
};
|
||||
}
|
||||
|
||||
async findChapters(bookId) {
|
||||
const res = await fetch(`${this.baseUrl}/ajax/chapter-archive?novelId=${bookId}`, {
|
||||
headers: {
|
||||
"user-agent": "Mozilla/5.0"
|
||||
}
|
||||
});
|
||||
|
||||
const html = await res.text();
|
||||
const $ = this.cheerio.load(html);
|
||||
|
||||
const chapters = [];
|
||||
|
||||
$('a[title]').each((i, el) => {
|
||||
const fullUrl = $(el).attr('href');
|
||||
const title = $(el).attr('title').trim();
|
||||
const numMatch = title.match(/chapter\s+(\d+(?:\.\d+)?)/i);
|
||||
|
||||
chapters.push({
|
||||
id: fullUrl,
|
||||
title,
|
||||
number: numMatch ? numMatch[1] : "0",
|
||||
releaseDate: null,
|
||||
index: i
|
||||
});
|
||||
});
|
||||
|
||||
return chapters;
|
||||
}
|
||||
|
||||
async findChapterPages(chapterUrl) {
|
||||
const {result} = await this.scrape(chapterUrl, async (page) => {
|
||||
return page.evaluate(() => {
|
||||
document.querySelectorAll('div[id^="pf-"]').forEach(e => e.remove());
|
||||
const ps = Array.from(document.querySelectorAll("p")).map(p => p.outerHTML.trim()).filter(p => p.length > 7);
|
||||
return ps.join("\n");
|
||||
});
|
||||
}, {
|
||||
waitUntil: "domcontentloaded",
|
||||
renderWaitTime: 300
|
||||
});
|
||||
return result || "<p>Error: chapter text not found</p>";
|
||||
}
|
||||
}
|
||||
module.exports = NovelBin;
|
||||
Reference in New Issue
Block a user