class asmhentai { constructor(fetchPath, cheerioPath, browser) { this.baseUrl = "https://asmhentai.com/"; this.fetch = require(fetchPath); this.cheerio = require(cheerioPath); this.browser = browser; this.type = "book-board"; } 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}`; const res = await this.fetch(url); const html = await res.text(); const $ = this.cheerio.load(html); const items = $(".ov_item .preview_item"); const results = []; items.each((_, el) => { const $el = $(el); const href = $el.find(".image a").attr("href") || ""; const id = href.match(/\d+/)?.[0] || null; const img = $el.find(".image img"); const raw = img.attr("data-src") || img.attr("src") || ""; let image = raw.startsWith("//") ? "https:" + raw : raw; const sampleImageUrl = image.replace("thumb", "cover"); image = image.replace(/[^\/]+$/, "1.jpg"); const title = ($el.find(".cpt h2.caption").text() || "").trim(); const tagsRaw = $el.attr("data-tags") || ""; const tags = tagsRaw.split(" ").filter(Boolean); results.push({ id, image, sampleImageUrl, title, tags, type: "book" }); }); const hasNextPage = $('ul.pagination a.page-link').filter((_, el) => $(el).text().trim().toLowerCase() === "next").length > 0; return { results, hasNextPage, page }; } async findChapters(mangaId) { const res = await this.fetch(`${this.baseUrl}/g/${mangaId}/`); const html = await res.text(); const $ = this.cheerio.load(html); const title = $(".right .info h1").first().text().trim() || ""; let cover = $(".cover img").attr("data-src") || $(".cover img").attr("src") || ""; if (cover.startsWith("//")) cover = "https:" + cover; const firstThumb = $('.gallery a img').first(); let t = firstThumb.attr("data-src") || ""; if (t.startsWith("//")) t = "https:" + t; // ex: https://images.asmhentai.com/017/598614/8t.jpg const baseMatch = t.match(/https:\/\/[^\/]+\/\d+\/\d+\//); const basePath = baseMatch ? baseMatch[0] : null; const pagesText = $(".pages h3").text(); // "Pages: 39" const pagesMatch = pagesText.match(/(\d+)/); const pages = pagesMatch ? parseInt(pagesMatch[1]) : 0; let ext = "jpg"; const extMatch = t.match(/\.(jpg|png|jpeg|gif)/i); if (extMatch) ext = extMatch[1]; let language = "unknown"; const langFlag = $('.info a[href^="/language/"] img').attr("src") || ""; if (langFlag.includes("en")) language = "english"; if (langFlag.includes("jp")) language = "japanese"; const encodedChapterId = Buffer.from( JSON.stringify({ base: basePath, pages, ext }) ).toString("base64"); return { chapters: [ { id: encodedChapterId, title, chapter: 1, index: 0, language } ], cover }; } async findChapterPages(chapterId) { const decoded = JSON.parse( Buffer.from(chapterId, "base64").toString("utf8") ); const { base, pages, ext } = decoded; return Array.from({ length: pages }, (_, i) => ({ url: `${base}${i + 1}.${ext}`, index: i, })); } } module.exports = { asmhentai };