class asmhentai { constructor() { this.baseUrl = "https://asmhentai.com"; this.type = "book-board"; this.version = "1.2"; this.mediaType = "manga"; } getFilters() { return { sort: { label: "Order", type: "select", options: [ { value: "latest", label: "Latest" }, { value: "popular", label: "Popular" } ], default: "latest" } }; } async search({ query, page = 1, filters }) { let url; if (query && query.trim().length > 0) { const q = query.trim().replace(/\s+/g, "+"); url = `${this.baseUrl}/search/?q=${q}&page=${page}`; } else { const sort = filters?.sort || "latest"; if (sort === "popular") { url = page === 1 ? `${this.baseUrl}/popular/` : `${this.baseUrl}/popular/pag/${page}/`; } else { url = page === 1 ? `${this.baseUrl}/` : `${this.baseUrl}/pag/${page}/`; } } const html = await fetch(url).then(r => r.text()); const $ = this.cheerio.load(html); const results = []; $(".preview_item").each((_, el) => { const href = $(el).find(".image a").attr("href"); const id = href?.match(/\/g\/(\d+)\//)?.[1]; if (!id) return; let img = $(el).find(".image img").attr("data-src") || $(el).find(".image img").attr("src") || ""; if (img.startsWith("//")) img = "https:" + img; const image = img.replace("thumb.jpg", "1.jpg"); const title = $(el).find("h2.caption").text().trim(); results.push({ id, image, title, rating: null, type: "book" }); }); return results; } async getMetadata(id) { const html = await fetch(`${this.baseUrl}/g/${id}/`).then(r => r.text()); const $ = this.cheerio.load(html); let image = $('a[href^="/gallery/"] img').attr("data-src") || $('a[href^="/gallery/"] img').attr("src") || ""; if (image.startsWith("//")) image = "https:" + image; const genres = $(".tags a.tag") .map((_, el) => $(el).text().trim()) .get(); return { id, title: $("h1").first().text().trim(), format: "MANGA", score: 0, genres, status: "unknown", published: "???", summary: "", chapters: 1, image }; } async findChapters(mangaId) { return [{ id: mangaId.toString(), title: "Chapter", number: 1, index: 0 }]; } async findChapterPages(chapterId) { const html = await fetch(`${this.baseUrl}/g/${chapterId}/`).then(r => r.text()); const $ = this.cheerio.load(html); const token = $('meta[name="csrf-token"]').attr("content") || ""; const loadId = $("#load_id").val(); const loadDir = $("#load_dir").val(); const totalPages = $("#t_pages").val() || "0"; const body = new URLSearchParams({ id: loadId, dir: loadDir, visible_pages: "0", t_pages: totalPages, type: "2", }); if (token) body.append("_token", token); const res = await fetch(`${this.baseUrl}/gallery/`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", "Referer": `${this.baseUrl}/g/${chapterId}/`, }, body }).then(r => r.text()); const $$ = this.cheerio.load(res); return $$("img[data-src], img[src]").get() .map((el) => { let url = $$(el).attr("data-src") || $$(el).attr("src"); if (url?.startsWith("//")) url = "https:" + url; return url; }) .filter(url => { return url && url.includes("images.") && !url.includes("/images/"); }) .map((url, i) => { const newUrl = url.replace("thumb", (i + 1).toString()); return { index: i, url: newUrl }; }); } } module.exports = asmhentai;