Files
WaifuBoard-Extensions/novelbin.js
2025-11-23 23:23:26 +01:00

129 lines
4.3 KiB
JavaScript

class NovelBin {
constructor(fetchPath, cheerioPath, browser) {
this.browser = browser;
this.fetch = require(fetchPath);
this.cheerio = require(cheerioPath);
this.baseUrl = "https://novelbin.me";
this.type = "book-board";
}
async fetchSearchResult(query = "", page = 1) {
const url = !query || query.trim() === ""
? `${this.baseUrl}/sort/novelbin-hot?page=${page}`
: `${this.baseUrl}/search?keyword=${encodeURIComponent(query)}`;
const res = await this.fetch(url);
const html = await res.text();
const $ = this.cheerio.load(html);
const results = [];
$(".list-novel .row, .col-novel-main .list-novel .row").each((i, el) => {
const titleEl = $(el).find("h3.novel-title a");
if (!titleEl.length) return;
const title = titleEl.text().trim();
let href = titleEl.attr("href");
if (!href) return;
if (!href.startsWith("http")) { href = `${this.baseUrl}${href}` }
const idMatch = href.match(/novel-book\/([^/?]+)/);
const id = idMatch ? idMatch[1] : null;
if (!id) return;
const coverUrl = `${this.baseUrl}/media/novel/${id}.jpg`;
results.push({
id,
title,
image: coverUrl,
sampleImageUrl: coverUrl,
tags: [],
type: "book"
});
});
const hasNextPage = $(".PagedList-skipToNext a").length > 0;
return {
results,
hasNextPage,
page
};
}
async findChapters(bookId) {
const res = await this.fetch(`${this.baseUrl}/novel-book/${bookId}`);
const html = await res.text();
const $ = this.cheerio.load(html);
const chapters = [];
$("#chapter-archive ul.list-chapter li a").each((i, el) => {
const a = $(el);
const title = a.attr("title") || a.text().trim();
let href = a.attr("href");
if (!href) return;
if (href.startsWith("https://novelbin.me")) { href = href.replace("https://novelbin.me", "") }
const match = title.match(/chapter\s*([\d.]+)/i);
const chapterNumber = match ? match[1] : "0";
chapters.push({
id: href,
title: title.trim(),
chapter: chapterNumber,
language: "en"
});
});
return chapters;
}
async findChapterPages(chapterId) {
const url = chapterId.startsWith('http') ? chapterId : `${this.baseUrl}${chapterId}`;
const content = await this.browser.scrape(
url,
() => {
const contentDiv = document.querySelector('#chr-content, .chr-c');
if (!contentDiv) return "<p>Error: Could not find content.</p>";
contentDiv.querySelectorAll('script, div[id^="pf-"], div[style*="text-align:center"], ins, div[align="center"], .ads, .adsbygoogle').forEach(el => el.remove());
const paragraphs = contentDiv.querySelectorAll('p');
let cleanHtml = '';
paragraphs.forEach(p => {
let text = p.textContent || '';
text = text.replace(/△▼△▼△▼△/g, '');
text = text.replace(/[※\s]{2,}/g, '');
const html = p.innerHTML.trim();
const isAd = text.includes('Remove Ads From') || text.includes('Buy no ads experience');
const isEmpty = html === '' || html === '&nbsp;' || text.trim() === '';
if (!isAd && !isEmpty) {
if (p.textContent !== text) {
p.textContent = text;
}
cleanHtml += p.outerHTML;
}
});
return cleanHtml || contentDiv.innerHTML;
},
{ waitSelector: '#chr-content', timeout: 2000 }
);
return [{
type: 'text',
content: content,
index: 0
}];
}
}
module.exports = { NovelBin };