all extensions to new format :P

This commit is contained in:
2025-12-15 19:40:07 +01:00
parent 9986b64ace
commit 9fe48f93fe
19 changed files with 1725 additions and 1375 deletions

View File

@@ -1,130 +1,181 @@
class nhentai {
constructor(fetchPath, cheerioPath, browser) {
this.baseUrl = "https://nhentai.net";
this.browser = browser;
this.type = "book-board";
}
constructor() {
this.baseUrl = "https://nhentai.net";
this.type = "book-board";
this.mediaType = "manga";
}
async fetchSearchResult(query = "", page = 1) {
const q = query.trim().replace(/\s+/g, "+");
const url = q ? `${this.baseUrl}/search/?q=${q}&page=${page}` : `${this.baseUrl}/?q=&page=${page}`;
async search(queryObj) {
const q = queryObj.query.trim().replace(/\s+/g, "+");
const url = q
? `${this.baseUrl}/search/?q=${q}`
: `${this.baseUrl}/?q=`;
const data = await this.browser.scrape(
url,
() => {
const container = document.querySelector('.container.index-container');
if (!container) return { results: [], hasNextPage: false };
const { result: data } = await this.scrape(
url,
async (page) => {
return page.evaluate(() => {
const container = document.querySelector('.container.index-container');
if (!container) return {results: [], hasNextPage: false};
const galleryEls = container.querySelectorAll('.gallery');
const results = [];
const galleryEls = container.querySelectorAll('.gallery');
const results = [];
galleryEls.forEach(el => {
const a = el.querySelector('a.cover');
if (!a) return;
galleryEls.forEach(el => {
const a = el.querySelector('a.cover');
if (!a) return;
const href = a.getAttribute('href');
const id = href.match(/\d+/)?.[0] || null;
const href = a.getAttribute('href');
const id = href.match(/\d+/)?.[0] || null;
const img = a.querySelector('img.lazyload');
const thumbRaw = img?.dataset?.src || img?.src || "";
const thumb = thumbRaw.startsWith("//") ? "https:" + thumbRaw : thumbRaw;
const coverUrl = thumb.replace("thumb", "cover");
const img = a.querySelector('img.lazyload');
const thumbRaw = img?.dataset?.src || img?.src || "";
const thumb = thumbRaw.startsWith("//") ? "https:" + thumbRaw : thumbRaw;
const coverUrl = thumb.replace("thumb", "cover");
const caption = a.querySelector('.caption');
const title = caption?.textContent.trim() || "";
const caption = a.querySelector('.caption');
const title = caption?.textContent.trim() || "";
const tagsRaw = el.getAttribute('data-tags') || "";
const tags = tagsRaw.split(" ").filter(Boolean);
results.push({
id,
title,
image: coverUrl,
rating: null,
type: "book"
});
});
results.push({
id,
image: thumb,
sampleImageUrl: coverUrl,
title,
tags,
type: "book"
});
});
const hasNextPage = !!document.querySelector('section.pagination a.next');
return {results, hasNextPage};
});
},
{
waitSelector: '.container.index-container',
timeout: 55000
}
);
const hasNextPage = !!document.querySelector('section.pagination a.next');
return data?.results || [];
}
return {
results,
hasNextPage
};
},{ waitSelector: '.container.index-container', timeout: 5000}
);
async getMetadata(id) {
const { result: data } = await this.scrape(
`${this.baseUrl}/g/${id}/`,
async (page) => {
return page.evaluate(() => {
const title = document.querySelector('h1.title .pretty')?.textContent?.trim() || "";
return {
results: data.results,
hasNextPage: data.hasNextPage,
page
};
}
const img = document.querySelector('#cover img');
const image =
img?.dataset?.src ? "https:" + img.dataset.src :
img?.src?.startsWith("//") ? "https:" + img.src :
img?.src || "";
const tagBlock = document.querySelector('.tag-container.field-name');
const genres = tagBlock
? [...tagBlock.querySelectorAll('.tags .name')].map(x => x.textContent.trim())
: [];
async findChapters(mangaId) {
const data = await this.browser.scrape(
`https://nhentai.net/g/${mangaId}/`,
() => {
const title = document.querySelector('#info > h1 .pretty')?.textContent?.trim() || "";
const timeEl = document.querySelector('.tag-container.field-name time');
const published =
timeEl?.getAttribute("datetime") ||
timeEl?.textContent?.trim() ||
"???";
const img = document.querySelector('#cover img');
const cover = img?.dataset?.src ? "https:" + img.dataset.src : img?.src?.startsWith("//") ? "https:" + img.src : img?.src || "";
return {title, image, genres, published};
});
},
{
waitSelector: "#bigcontainer",
timeout: 55000
}
);
const hash = cover.match(/galleries\/(\d+)\//)?.[1] || null;
if (!data) throw new Error(`Fallo al obtener metadatos para ID ${id}`);
const thumbs = document.querySelectorAll('.thumbs img');
const pages = thumbs.length;
const formattedDate = data.published
? new Date(data.published).toLocaleDateString("es-ES")
: "???";
const first = thumbs[0];
const s = first?.dataset?.src || first?.src || "";
const ext = s.match(/t\.(\w+)/)?.[1] || "jpg";
return {
id,
title: data.title || "",
format: "Manga",
score: 0,
genres: Array.isArray(data.genres) ? data.genres : [],
status: "Finished",
published: formattedDate,
summary: "",
chapters: 1,
image: data.image || ""
};
}
const langTag = [...document.querySelectorAll('#tags .tag-container')].find(x => x.textContent.includes("Languages:"));
const language = langTag?.querySelector('.tags .name')?.textContent?.trim() || "";
async findChapters(mangaId) {
const { result: data } = await this.scrape(
`${this.baseUrl}/g/${mangaId}/`,
async (page) => {
return page.evaluate(() => {
const title = document.querySelector('#info > h1 .pretty')?.textContent?.trim() || "";
return { title, cover, hash, pages, ext, language };
}, { waitSelector: '#bigcontainer', timeout: 4000 }
);
const img = document.querySelector('#cover img');
const cover =
img?.dataset?.src ? "https:" + img.dataset.src :
img?.src?.startsWith("//") ? "https:" + img.src :
img?.src || "";
const encodedChapterId = Buffer.from(JSON.stringify({
hash: data.hash,
pages: data.pages,
ext: data.ext
})).toString("base64");
const hash = cover.match(/galleries\/(\d+)\//)?.[1] || null;
return {
chapters: [
{
id: encodedChapterId,
title: data.title,
chapter: 1,
index: 0,
language: data.language
}
],
cover: data.cover
};
const thumbs = document.querySelectorAll('.thumbs img');
const pages = thumbs.length;
}
const first = thumbs[0];
const s = first?.dataset?.src || first?.src || "";
const ext = s.match(/t\.(\w+)/)?.[1] || "jpg";
async findChapterPages(chapterId) {
const decoded = JSON.parse(
Buffer.from(chapterId, "base64").toString("utf8")
);
const langTag = [...document.querySelectorAll('#tags .tag-container')]
.find(x => x.textContent.includes("Languages:"));
const { hash, pages, ext } = decoded;
const baseUrl = "https://i.nhentai.net/galleries";
const language = langTag?.querySelector('.tags .name')?.textContent?.trim() || "";
return Array.from({ length: pages }, (_, i) => ({
url: `${baseUrl}/${hash}/${i + 1}.${ext}`,
index: i,
headers: {
Referer: `https://nhentai.net/g/${hash}/`
}
}));
}
return {title, cover, hash, pages, ext, language};
});
},
{
waitSelector: '#bigcontainer',
timeout: 55000
}
);
if (!data?.hash) throw new Error(`Fallo al obtener hash para ID ${mangaId}`);
const encodedChapterId = Buffer.from(JSON.stringify({
hash: data.hash,
pages: data.pages,
ext: data.ext
})).toString("base64");
return [{
id: encodedChapterId,
title: data.title,
number: 1,
releaseDate: null,
index: 0,
}];
}
async findChapterPages(chapterId) {
const decoded = JSON.parse(Buffer.from(chapterId, "base64").toString("utf8"));
const { hash, pages, ext } = decoded;
const baseUrl = "https://i.nhentai.net/galleries";
return Array.from({ length: pages }, (_, i) => ({
url: `${baseUrl}/${hash}/${i + 1}.${ext}`,
index: i,
headers: { Referer: `https://nhentai.net/g/${hash}/` }
}));
}
}
module.exports = { nhentai };
module.exports = nhentai;