all extensions to new format :P
This commit is contained in:
@@ -1,156 +1,188 @@
|
||||
class ligntnovelworld {
|
||||
constructor(fetchPath, cheerioPath, browser) {
|
||||
this.browser = browser;
|
||||
this.fetch = require(fetchPath);
|
||||
this.cheerio = require(cheerioPath);
|
||||
this.baseUrl = "https://lightnovelworld.org/api";
|
||||
this.type = "book-board";
|
||||
}
|
||||
|
||||
async fetchSearchResult(query = "", page = 1) {
|
||||
if (query.trim() !== "") {
|
||||
const res = await this.fetch(`${this.baseUrl}/search/?q=${encodeURIComponent(query)}&search_type=title`);
|
||||
const data = await res.json();
|
||||
|
||||
const results = data.novels.map(n => ({
|
||||
id: n.slug,
|
||||
title: n.title,
|
||||
image: `https://lightnovelworld.org/${n.cover_path}`,
|
||||
sampleImageUrl: `https://lightnovelworld.org/${n.cover_path}`,
|
||||
tags: [],
|
||||
type: "book"
|
||||
}));
|
||||
|
||||
return {
|
||||
results,
|
||||
hasNextPage: false,
|
||||
page
|
||||
};
|
||||
}
|
||||
|
||||
const res = await this.fetch("https://lightnovelworld.org/");
|
||||
const html = await res.text();
|
||||
const $ = this.cheerio.load(html);
|
||||
|
||||
const cards = $(".recommendations-grid .recommendation-card");
|
||||
const results = [];
|
||||
|
||||
cards.each((_, el) => {
|
||||
const card = $(el);
|
||||
const link = card.find("a.card-cover-link").attr("href") || "";
|
||||
const id = link.replace(/^\/novel\//, "").replace(/\/$/, "");
|
||||
const title = card.find(".card-title").text().trim();
|
||||
|
||||
const img = card.find(".card-cover img").attr("src") || "";
|
||||
const imageUrl = img.startsWith("http") ? img : `https://lightnovelworld.org${img}`;
|
||||
|
||||
const tags = card.find(".card-genres .genre-tag").map((_, t) => $(t).text().trim()).get();
|
||||
|
||||
results.push({
|
||||
id,
|
||||
title,
|
||||
image: imageUrl,
|
||||
sampleImageUrl: imageUrl,
|
||||
tags,
|
||||
type: "book"
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
results,
|
||||
hasNextPage: false,
|
||||
page
|
||||
};
|
||||
}
|
||||
|
||||
async findChapters(bookId) {
|
||||
let offset = 0;
|
||||
const limit = 500;
|
||||
const chapters = [];
|
||||
|
||||
while (true) {
|
||||
const res = await this.fetch(`https://lightnovelworld.org/api/novel/${bookId}/chapters/?offset=${offset}&limit=${limit}`);
|
||||
const data = await res.json();
|
||||
|
||||
chapters.push(
|
||||
...data.chapters.map(c => ({
|
||||
id: `https://lightnovelworld.org/novel/${bookId}/chapter/${c.number}/`,
|
||||
title: c.title,
|
||||
chapter: c.number,
|
||||
language: 'en'
|
||||
}))
|
||||
);
|
||||
if (!data.has_more) break;
|
||||
offset += limit;
|
||||
|
||||
}
|
||||
|
||||
return { chapters: chapters};
|
||||
}
|
||||
|
||||
async findChapterPages(chapterId) {
|
||||
const res = await this.fetch(chapterId, {
|
||||
headers: {
|
||||
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
||||
'referer': chapterId.replace(/\/\d+\/$/, ''),
|
||||
'sec-ch-ua': '"Chromium";v="139", "Not;A=Brand";v="99"',
|
||||
'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'
|
||||
}
|
||||
});
|
||||
|
||||
const html = await res.text();
|
||||
const $ = this.cheerio.load(html);
|
||||
|
||||
const contentDiv = $('#chapterText');
|
||||
|
||||
if (!contentDiv || contentDiv.length === 0) {
|
||||
return [{
|
||||
type: 'text',
|
||||
content: '<p>Error: content not found</p>',
|
||||
index: 0
|
||||
}];
|
||||
}
|
||||
|
||||
contentDiv.find('script').remove();
|
||||
contentDiv.find('style').remove();
|
||||
contentDiv.find('ins').remove();
|
||||
contentDiv.find("[id^='pf-']").remove();
|
||||
contentDiv.find('.chapter-ad-container').remove();
|
||||
contentDiv.find('.ad-unit').remove();
|
||||
contentDiv.find('.ads').remove();
|
||||
contentDiv.find('.adsbygoogle').remove();
|
||||
contentDiv.find('.nf-ads').remove();
|
||||
contentDiv.find('div[align="center"]').remove();
|
||||
contentDiv.find("div[style*='text-align:center']").remove();
|
||||
|
||||
const paragraphs = contentDiv.find('p');
|
||||
let cleanHtml = '';
|
||||
|
||||
paragraphs.each((_, el) => {
|
||||
const p = $(el);
|
||||
let text = p.text() || '';
|
||||
|
||||
text = text.replace(/△▼△▼△▼△/g, '').replace(/[※]+/g, '').replace(/\s{2,}/g, ' ').trim();
|
||||
const htmlP = p.html()?.trim() || '';
|
||||
const isEmpty = htmlP === '' || htmlP === ' ' || text.trim() === '';
|
||||
const isAd = text.includes('Remove Ads') || text.includes('Buy no ads') || text.includes('novelfire');
|
||||
|
||||
if (!isEmpty && !isAd) {
|
||||
if (p.text() !== text) p.text(text);
|
||||
cleanHtml += $.html(p);
|
||||
}
|
||||
});
|
||||
|
||||
if (!cleanHtml.trim()) { cleanHtml = contentDiv.html() || ''; }
|
||||
|
||||
return [
|
||||
{
|
||||
type: 'text',
|
||||
content: cleanHtml.trim(),
|
||||
index: 0
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { novelupdates: ligntnovelworld };
|
||||
class lightnovelworld {
|
||||
constructor() {
|
||||
this.baseUrl = "https://lightnovelworld.org/api";
|
||||
|
||||
this.type = "book-board";
|
||||
this.mediaType = "ln";
|
||||
}
|
||||
|
||||
async search(queryObj) {
|
||||
const query = queryObj.query?.trim() || "";
|
||||
|
||||
if (query !== "") {
|
||||
const res = await fetch(
|
||||
`${this.baseUrl}/search/?q=${encodeURIComponent(query)}&search_type=title`
|
||||
);
|
||||
const data = await res.json();
|
||||
|
||||
if (!data.novels) return [];
|
||||
|
||||
return data.novels.map(n => ({
|
||||
id: n.slug,
|
||||
title: n.title,
|
||||
image: `https://lightnovelworld.org/${n.cover_path}`,
|
||||
rating: `Rank ${n.rank}`,
|
||||
format: "Light Novel"
|
||||
}));
|
||||
}
|
||||
|
||||
const res = await fetch("https://lightnovelworld.org/");
|
||||
const html = await res.text();
|
||||
|
||||
const cards = html.split('class="recommendation-card"').slice(1);
|
||||
const results = [];
|
||||
|
||||
for (const block of cards) {
|
||||
const link = block.match(/href="([^"]+)"/)?.[1] || "";
|
||||
const id = link.replace(/^\/novel\//, "").replace(/\/$/, "");
|
||||
|
||||
const title = block.match(/class="card-title"[^>]*>([^<]+)/)?.[1]?.trim() || null;
|
||||
|
||||
let img = block.match(/<img[^>]+src="([^"]+)"/)?.[1] || "";
|
||||
if (img && !img.startsWith("http"))
|
||||
img = `https://lightnovelworld.org${img}`;
|
||||
|
||||
if (id && title) {
|
||||
results.push({
|
||||
id,
|
||||
title,
|
||||
image: img,
|
||||
rating: null,
|
||||
format: "Light Novel"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async getMetadata(id){
|
||||
const res = await fetch(`https://lightnovelworld.org/novel/${id}`);
|
||||
const html = await res.text();
|
||||
|
||||
const match = html.match(
|
||||
/<script type="application\/ld\+json">([\s\S]*?)<\/script>/
|
||||
);
|
||||
|
||||
let data = {};
|
||||
if(match){
|
||||
try{
|
||||
data = JSON.parse(match[1]);
|
||||
}catch(e){}
|
||||
}
|
||||
|
||||
const rawScore = Number(data.aggregateRating?.ratingValue || 1);
|
||||
const score100 = Math.round((rawScore / 5) * 100);
|
||||
|
||||
return {
|
||||
id: id,
|
||||
title: data.name || "",
|
||||
format: "Light Novel",
|
||||
score: score100,
|
||||
genres: Array.isArray(data.genre) ? data.genre : [],
|
||||
status: data.status || "",
|
||||
published: "???",
|
||||
summary: data.description || "",
|
||||
chapters: data.numberOfPages ? Number(data.numberOfPages) : 1,
|
||||
image: data.image
|
||||
? (data.image.startsWith("http")
|
||||
? data.image
|
||||
: `https://lightnovelworld.org${data.image}`)
|
||||
: ""
|
||||
};
|
||||
}
|
||||
|
||||
async findChapters(bookId) {
|
||||
const chapters = [];
|
||||
let offset = 0;
|
||||
const limit = 500;
|
||||
|
||||
while (true) {
|
||||
const res = await fetch(
|
||||
`https://lightnovelworld.org/api/novel/${bookId}/chapters/?offset=${offset}&limit=${limit}`
|
||||
);
|
||||
const data = await res.json();
|
||||
|
||||
if (!data.chapters) break;
|
||||
|
||||
chapters.push(
|
||||
...data.chapters.map((c, i) => ({
|
||||
id: `https://lightnovelworld.org/novel/${bookId}/chapter/${c.number}/`,
|
||||
title: c.title,
|
||||
number: Number(c.number),
|
||||
releaseDate: null,
|
||||
index: offset + i
|
||||
}))
|
||||
);
|
||||
|
||||
if (!data.has_more) break;
|
||||
offset += limit;
|
||||
}
|
||||
|
||||
return chapters;
|
||||
}
|
||||
|
||||
async findChapterPages(chapterId) {
|
||||
const data = await this.scrape(
|
||||
chapterId,
|
||||
(page) => page.evaluate(() => document.documentElement.outerHTML),
|
||||
{
|
||||
waitUntil: "domcontentloaded",
|
||||
timeout: 15000
|
||||
}
|
||||
);
|
||||
|
||||
const html = data.result;
|
||||
if (!html) return '<p>Error loading chapter</p>';
|
||||
|
||||
const cutPoints = [
|
||||
'<div class="bottom-nav"',
|
||||
'<div class="comments-section"',
|
||||
'<div class="settings-panel"',
|
||||
'©'
|
||||
];
|
||||
|
||||
let cutIndex = html.length;
|
||||
for (const marker of cutPoints) {
|
||||
const pos = html.indexOf(marker);
|
||||
if (pos !== -1 && pos < cutIndex) cutIndex = pos;
|
||||
}
|
||||
|
||||
const chapterHtml = html.substring(0, cutIndex);
|
||||
|
||||
const pMatches = [...chapterHtml.matchAll(/<p[^>]*>([\s\S]*?)<\/p>/gi)];
|
||||
let cleanHtml = '';
|
||||
|
||||
for (const match of pMatches) {
|
||||
let text = match[1]
|
||||
.replace(/△▼△▼△▼△/g, '')
|
||||
.replace(/[※▲▼■◆]/g, '')
|
||||
.replace(/ /gi, ' ')
|
||||
.replace(/\s{2,}/g, ' ')
|
||||
.trim();
|
||||
|
||||
text = text
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/'/g, "'")
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/“/g, '“')
|
||||
.replace(/”/g, '”')
|
||||
.replace(/‘/g, '‘')
|
||||
.replace(/’/g, '’')
|
||||
.replace(/—/g, '—')
|
||||
.replace(/–/g, '–');
|
||||
|
||||
if (!text || text.length < 3) continue;
|
||||
if (/svg|button|modal|comment|loading|default|dyslexic|roboto|lora|line spacing/i.test(text)) continue;
|
||||
|
||||
cleanHtml += `<p>${text}</p>\n`;
|
||||
}
|
||||
|
||||
return cleanHtml.trim() || '<p>Empty chapter</p>';
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = lightnovelworld;
|
||||
|
||||
Reference in New Issue
Block a user