class AnimeAV1 { constructor() { this.type = "anime-board"; // Required for scanner this.api = "https://animeav1.com"; } getSettings() { return { episodeServers: ["HLS", "HLS-DUB"], supportsDub: true, }; } async search(query) { const res = await fetch(`${this.api}/api/search`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: query.query }), }); if (!res.ok) return []; const data = await res.json(); return data.map(anime => ({ id: anime.title.toLowerCase().replace(/\s+/g, '-'), title: anime.title, url: `${this.api}/anime/${anime.slug}`, subOrDub: "both", })); } async getMetadata(id) { const html = await fetch(`${this.api}/media/${id}`).then(r => r.text()); const parsed = this.parseSvelteData(html); const media = parsed.find(x => x?.data?.media)?.data.media ?? {}; // IMAGE const imageMatch = html.match(/]*class="aspect-poster[^"]*"[^>]*src="([^"]+)"/i); const image = imageMatch ? imageMatch[1] : null; // BLOCK INFO (STATUS, SEASON, YEAR) const infoBlockMatch = html.match( /
([\s\S]*?)<\/div>/ ); let status = media.status ?? "Unknown"; let season = media.seasons ?? null; let year = media.startDate ? Number(media.startDate.slice(0, 4)) : null; if (infoBlockMatch) { const raw = infoBlockMatch[1]; // Extraer spans internos const spans = [...raw.matchAll(/]*>([^<]+)<\/span>/g)].map(m => m[1].trim()); // EJEMPLO: // ["TV Anime", "•", "2025", "•", "Temporada Otoño", "•", "En emisión"] const clean = spans.filter(x => x !== "•"); // YEAR const yearMatch = clean.find(x => /^\d{4}$/.test(x)); if (yearMatch) year = Number(yearMatch); // SEASON (el que contiene "Temporada") const seasonMatch = clean.find(x => x.toLowerCase().includes("temporada")); if (seasonMatch) season = seasonMatch; // STATUS (normalmente "En emisión", "Finalizado", etc) const statusMatch = clean.find(x => /emisión|finalizado|completado|pausa|cancelado/i.test(x) ); if (statusMatch) status = statusMatch; } return { title: media.title ?? "Unknown", summary: media.synopsis ?? "No summary available", episodes: media.episodesCount ?? 0, characters: [], season, status, studio: "Unknown", score: media.score ?? 0, year, genres: media.genres?.map(g => g.name) ?? [], image }; } async findEpisodes(id) { const html = await fetch(`${this.api}/media/${id}`).then(r => r.text()); const parsed = this.parseSvelteData(html); const media = parsed.find(x => x?.data?.media)?.data?.media; if (!media?.episodes) throw new Error("No se encontró media.episodes"); return media.episodes.map((ep, i) => ({ id: `${media.slug}$${ep.number ?? i + 1}`, number: ep.number ?? i + 1, title: ep.title ?? `Episode ${ep.number ?? i + 1}`, url: `${this.api}/media/${media.slug}/${ep.number ?? i + 1}`, })); } 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]}` : undefined ); if (!pageUrl) throw new Error("No se pudo determinar la URL del episodio."); const html = await fetch(pageUrl, { headers: { Cookie: "__ddg1_=;__ddg2_=;" }, }).then(r => r.text()); const parsedData = this.parseSvelteData(html); const entry = parsedData.find(x => x?.data?.embeds) || parsedData[3]; const embeds = entry?.data?.embeds; if (!embeds) throw new Error("No se encontraron 'embeds' en los datos del episodio."); const selectedEmbeds = _server === "HLS" ? embeds.SUB ?? [] : _server === "HLS-DUB" ? embeds.DUB ?? [] : (() => { throw new Error(`Servidor desconocido: ${_server}`); })(); if (!selectedEmbeds.length) throw new Error(`No hay mirrors disponibles para ${_server === "HLS" ? "SUB" : "DUB"}.`); const match = selectedEmbeds.find(m => (m.url || "").includes("zilla-networks.com/play/") ); if (!match) throw new Error(`No se encontró ningún embed de ZillaNetworks en ${_server}.`); return { server: _server, headers: { Referer: 'null' }, videoSources: [ { url: 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("No se encontró bloque SvelteKit en el HTML."); const dataMatch = scriptMatch[1].match(/data:\s*(\[[\s\S]*?\])\s*,\s*form:/); if (!dataMatch) throw new Error("No se encontró el bloque 'data' en el script SvelteKit."); 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;