122 lines
3.8 KiB
JavaScript
122 lines
3.8 KiB
JavaScript
class NovelBin {
|
|
constructor() {
|
|
this.baseUrl = "https://novelbin.me";
|
|
this.type = "book-board";
|
|
this.mediaType = "ln";
|
|
this.version = "1.0"
|
|
}
|
|
|
|
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; |