class nhentai { constructor() { this.baseUrl = "https://nhentai.net"; this.type = "book-board"; this.mediaType = "manga"; this.version = "1.0" } async search(queryObj) { const q = queryObj.query.trim().replace(/\s+/g, "+"); const url = q ? `${this.baseUrl}/search/?q=${q}` : `${this.baseUrl}/?q=`; 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 = []; 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 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() || ""; results.push({ id, title, image: coverUrl, rating: null, type: "book" }); }); const hasNextPage = !!document.querySelector('section.pagination a.next'); return {results, hasNextPage}; }); }, { waitSelector: '.container.index-container', timeout: 55000 } ); return data?.results || []; } 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() || ""; 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()) : []; const timeEl = document.querySelector('.tag-container.field-name time'); const published = timeEl?.getAttribute("datetime") || timeEl?.textContent?.trim() || "???"; return {title, image, genres, published}; }); }, { waitSelector: "#bigcontainer", timeout: 55000 } ); if (!data) throw new Error(`Fallo al obtener metadatos para ID ${id}`); const formattedDate = data.published ? new Date(data.published).toLocaleDateString("es-ES") : "???"; 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 || "" }; } 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() || ""; const img = document.querySelector('#cover img'); const cover = img?.dataset?.src ? "https:" + img.dataset.src : img?.src?.startsWith("//") ? "https:" + img.src : img?.src || ""; const hash = cover.match(/galleries\/(\d+)\//)?.[1] || null; 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"; const langTag = [...document.querySelectorAll('#tags .tag-container')] .find(x => x.textContent.includes("Languages:")); const language = langTag?.querySelector('.tags .name')?.textContent?.trim() || ""; 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;