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 || "
Error: chapter text not found
"; } } module.exports = NovelBin;