class AnimeAV1 { constructor() { this.type = "anime-board"; this.version = "1.0.0"; this.api = "https://animeav1.com"; } getSettings() { return { episodeServers: ["HLS", "HLS-DUB"], supportsDub: true, }; } async search(query) { const q = (query && typeof query === "object" && query.query) ? String(query.query) : (typeof query === "string" ? query : ""); if (!q.trim()) return []; const res = await fetch(`${this.api}/api/search`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: q }), }); if (!res.ok) return []; const data = await res.json(); if (!Array.isArray(data)) return []; return data .map(anime => { if (!anime || !anime.slug) return null; return { id: anime.slug, title: anime.title || "Unknown", url: `${this.api}/media/${anime.slug}`, subOrDub: "both", }; }) .filter(Boolean); } async getMetadata(id) { const slug = String(id || "").trim(); if (!slug) throw new Error("Missing id"); const html = await fetch(`${this.api}/media/${slug}`).then(r => r.text()); const parsed = this.parseSvelteData(html); const media = parsed.find(x => x?.data?.media)?.data?.media || {}; let image = null; const imageMatch = html.match(/]*class="aspect-poster[^"]*"[^>]*src="([^"]+)"/i); if (imageMatch) image = imageMatch[1]; let year = null; if (media.startDate) { const y = Number(String(media.startDate).slice(0, 4)); if (!Number.isNaN(y)) year = y; } return { title: media.title || "Unknown", summary: media.synopsis || "No summary available", episodes: media.episodesCount || 0, characters: [], season: media.seasons || null, status: media.status || "Unknown", studio: "Unknown", score: media.score || 0, year, genres: (media.genres || []).map(g => g?.name).filter(Boolean), image, }; } async findEpisodes(id) { const slug = String(id || "").trim(); if (!slug) throw new Error("Missing id"); const html = await fetch(`${this.api}/media/${slug}`).then(r => r.text()); const parsed = this.parseSvelteData(html); const media = parsed.find(x => x?.data?.media)?.data?.media; if (!media || !Array.isArray(media.episodes)) throw new Error("No episodes"); return media.episodes.map((ep, i) => { const n = ep?.number ?? (i + 1); return { id: `${media.slug || slug}$${n}`, number: n, title: ep?.title || `Episode ${n}`, url: `${this.api}/media/${media.slug || slug}/${n}`, }; }); } async findEpisodeServer(episodeOrId, server) { const ep = typeof episodeOrId === "string" ? (() => { try { return JSON.parse(episodeOrId); } catch { return { id: episodeOrId }; } })() : episodeOrId; const pageUrl = ep.url || (typeof ep.id === "string" && ep.id.includes("$") ? `${this.api}/media/${ep.id.split("$")[0]}/${ep.number || ep.id.split("$")[1]}` : null); if (!pageUrl) throw new Error("Missing episode url"); const html = await fetch(pageUrl, { headers: { Referer: `${this.api}/` }, }).then(r => r.text()); const parsed = this.parseSvelteData(html); const entry = parsed.find(x => x?.data?.embeds) || parsed[3]; const embeds = entry?.data?.embeds; if (!embeds) throw new Error("No embeds"); const list = server === "HLS" ? embeds.SUB : server === "HLS-DUB" ? embeds.DUB : null; if (!Array.isArray(list) || !list.length) throw new Error("No mirrors"); const match = list.find(m => String(m?.url || "").includes("zilla-networks.com/play/")); if (!match) throw new Error("No valid source"); return { server, headers: { Referer: pageUrl }, videoSources: [ { url: String(match.url).replace("/play/", "/m3u8/"), type: "m3u8", quality: "auto", subtitles: [], }, ], }; } parseSvelteData(html) { const scriptMatch = html.match(/]*>\s*({[^<]*__sveltekit_[\s\S]*?)<\/script>/i); if (!scriptMatch) throw new Error("Svelte data not found"); const dataMatch = scriptMatch[1].match(/data:\s*(\[[\s\S]*?\])\s*,\s*form:/); if (!dataMatch) throw new Error("Data block missing"); const jsArray = dataMatch[1]; try { return new Function(`"use strict"; return (${jsArray});`)(); } catch { const cleaned = jsArray .replace(/\bvoid 0\b/g, "null") .replace(/undefined/g, "null"); return new Function(`"use strict"; return (${cleaned});`)(); } } } module.exports = AnimeAV1;