class MangaPill { constructor() { this.baseUrl = "https://mangapill.com"; this.type = "book-board"; this.version = "1.0"; this.mediaType = "manga"; } async fetch(url) { return fetch(url, { headers: { "User-Agent": "Mozilla/5.0", Referer: this.baseUrl, }, }); } async search(queryObj) { const q = queryObj.query || ""; const res = await this.fetch( `${this.baseUrl}/search?q=${encodeURIComponent(q)}` ); const html = await res.text(); const $ = this.cheerio.load(html); const results = []; $("div.container div.my-3.justify-end > div").each((_, el) => { const link = $(el).find("a").attr("href"); if (!link) return; const id = link.split("/manga/")[1].replace(/\//g, "$"); const title = $(el).find("div > a > div.mt-3").text().trim(); const image = $(el).find("a img").attr("data-src") || null; results.push({ id, title, image, rating: null, type: "book", }); }); return results; } async getMetadata(id) { const uriId = id.replace(/\$/g, "/"); let res = await fetch(`${this.baseUrl}/manga/${uriId}`, { headers: this.headers, redirect: "manual", }); // follow redirect manually if (res.status === 301 || res.status === 302) { const loc = res.headers.get("location"); if (loc) { res = await fetch(`${this.baseUrl}${loc}`, { headers: this.headers, }); } } if (!res.ok) { return { id, title: "", format: "MANGA", score: 0, genres: "", status: "unknown", published: "", summary: "", chapters: "???", image: null, }; } const html = await res.text(); const $ = this.cheerio.load(html); const title = $("h1.font-bold").first().text().trim(); const summary = $("div.mb-3 p.text-sm").first().text().trim() || ""; const status = $("label:contains('Status')") .next("div") .text() .trim() || "unknown"; const published = $("label:contains('Year')") .next("div") .text() .trim() || ""; const genres = []; $("label:contains('Genres')") .parent() .find("a") .each((_, a) => genres.push($(a).text().trim())); const image = $("img[data-src]").first().attr("data-src") || null; return { id, title, format: "MANGA", score: 0, genres: genres.join(", "), status, published, summary, chapters: "???", image }; } async findChapters(mangaId) { const uriId = mangaId.replace(/\$/g, "/"); const res = await this.fetch(`${this.baseUrl}/manga/${uriId}`); const html = await res.text(); const $ = this.cheerio.load(html); const chapters = []; $("div#chapters a").each((_, el) => { const href = $(el).attr("href"); if (!href) return; const id = href.split("/chapters/")[1].replace(/\//g, "$"); const title = $(el).text().trim(); const match = title.match(/Chapter\s+([\d.]+)/); const number = match ? Number(match[1]) : 0; chapters.push({ id, title, number, releaseDate: null, index: 0, }); }); chapters.reverse(); chapters.forEach((c, i) => (c.index = i)); return chapters; } async findChapterPages(chapterId) { const uriId = chapterId.replace(/\$/g, "/"); const res = await this.fetch(`${this.baseUrl}/chapters/${uriId}`); const html = await res.text(); const $ = this.cheerio.load(html); const pages = []; $("chapter-page").each((i, el) => { const img = $(el).find("div picture img").attr("data-src"); if (!img) return; pages.push({ url: img, index: i, headers: { Referer: "https://mangapill.com/", Origin: "https://mangapill.com", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", Accept: "image/avif,image/webp,image/apng,image/*,*/*;q=0.8", }, }); }); return pages; } } module.exports = MangaPill;