129 lines
4.4 KiB
JavaScript
129 lines
4.4 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: 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 === ' ' || 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 }; |