From ae8d8c7b3eb4f82851eb01b2e2358e11fa4f6e1f Mon Sep 17 00:00:00 2001 From: MrGus Date: Thu, 25 Dec 2025 03:14:13 +0100 Subject: [PATCH] Add anime/animeav1/source.js --- anime/animeav1/source.js | 171 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 anime/animeav1/source.js diff --git a/anime/animeav1/source.js b/anime/animeav1/source.js new file mode 100644 index 0000000..1ee6f23 --- /dev/null +++ b/anime/animeav1/source.js @@ -0,0 +1,171 @@ +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;