class Comix { constructor() { this.baseUrl = "https://comix.to"; this.apiUrl = "https://comix.to/api/v2"; this.type = "book-board"; this.version = "1.0"; this.mediaType = "manga"; } get headers() { return { "Referer": `${this.baseUrl}/`, "User-Agent": "Mozilla/5.0" }; } async search(queryObj) { const q = (queryObj.query || "").trim().replace(/\s+/g, "+"); const url = new URL(`${this.apiUrl}/manga`); if (q) { url.searchParams.set("keyword", q); url.searchParams.set("order[relevance]", "desc"); } else { url.searchParams.set("order[views_30d]", "desc"); } url.searchParams.set("limit", "50"); url.searchParams.set("page", "1"); const res = await fetch(url, { headers: this.headers }); if (!res.ok) throw new Error(`Search failed: ${res.status}`); const json = await res.json(); return json.result.items.map(m => ({ id: m.hash_id, title: m.title, image: m.poster?.large || null, rating: m.score ?? null, type: "book" })); } async getMetadata(id) { const url = `${this.apiUrl}/manga/${id}?includes[]=genre&includes[]=author&includes[]=artist`; const res = await fetch(url, { headers: this.headers }); if (!res.ok) throw new Error(`Metadata failed: ${res.status}`); const { result } = await res.json(); return { id: result.hash_id, title: result.title, format: "MANGA", score: result.score ?? 0, genres: result.genres?.map(g => g.name).join(", ") ?? "", status: result.status ?? "unknown", published: result.created_at ?? "", summary: result.description ?? "", chapters: result.chapters_count ?? 0, image: result.poster?.large || null }; } async getSlug(mangaId) { const res = await fetch(`${this.apiUrl}/manga/${mangaId}`, { headers: this.headers }); if (!res.ok) return ""; const { result } = await res.json(); return result?.slug || ""; } async findChapters(mangaId) { const slug = await this.getSlug(mangaId); if (!slug) return []; const baseUrl = `${this.apiUrl}/manga/${mangaId}/chapters?order[number]=desc&limit=100`; const res = await fetch(baseUrl, { headers: this.headers }); if (!res.ok) return []; const first = await res.json(); const totalPages = first.result.pagination?.last_page || 1; let all = [...first.result.items]; for (let p = 2; p <= totalPages; p++) { const r = await fetch(`${baseUrl}&page=${p}`, { headers: this.headers }); if (!r.ok) continue; const d = await r.json(); if (d?.result?.items) all.push(...d.result.items); } const map = new Map(); for (const ch of all) { if (ch.language !== "en") continue; const key = ch.number; if (!map.has(key) || ch.is_official === 1) { map.set(key, ch); } } return Array.from(map.values()) .sort((a, b) => a.number - b.number) .map((ch, i) => ({ id: `${mangaId}|${slug}|${ch.chapter_id}|${ch.number}`, title: ch.name ? `Chapter ${ch.number} — ${ch.name}` : `Chapter ${ch.number}`, number: Number(ch.number), releaseDate: ch.updated_at ?? null, index: i })); } async findChapterPages(chapterId) { const parts = chapterId.split("|"); console.log(parts) if (parts.length < 4) return []; const [hashId, slug, chapterRealId, number] = parts; const readerUrl = `${this.baseUrl}/title/${hashId}-${slug}/${chapterRealId}-chapter-${number}`; const res = await fetch(readerUrl, { headers: this.headers }); if (!res.ok) return []; const html = await res.text(); const regex = /["\\]*images["\\]*\s*:\s*(\[[^\]]*\])/s; const match = html.match(regex); if (!match?.[1]) return []; let images; try { images = JSON.parse(match[1]); } catch { images = JSON.parse(match[1].replace(/\\"/g, '"')); } return images.map((img, i) => ({ url: img.url, index: i, headers: { Referer: readerUrl, "User-Agent": "Mozilla/5.0" } })); } } module.exports = Comix;