class Anizone { constructor() { this.type = "anime-board"; this.version = "1.0"; this.api = "https://anizone.to"; } getSettings() { return { episodeServers: ["HLS"], supportsDub: true, }; } async search(queryObj) { const query = queryObj.query ?? ""; const res = await fetch( `${this.api}/anime?search=${encodeURIComponent(query)}`, { headers: { accept: "*/*", referer: "https://anizone.to/", "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36", }, } ); const html = await res.text(); const itemRegex = /]*class="relative overflow-hidden h-26 rounded-lg[\s\S]*?]*src="([^"]+)"[^>]*alt="([^"]+)"[\s\S]*?]*href="([^"]+)"[^>]*title="([^"]+)"/g; const results = []; let match; while ((match = itemRegex.exec(html)) !== null) { const [, image, altTitle, href, title] = match; const animeId = href.split("/").pop(); // detectar sub / dub desde el episodio 1 let subOrDub = "sub"; try { const epHtml = await fetch(`${this.api}/anime/${animeId}/1`, { headers: { referer: "https://anizone.to/", "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36", }, }).then(r => r.text()); const audioMatch = epHtml.match( /
([\s\S]*?)<\/div>/ ); if (audioMatch) { const block = audioMatch[1]; const hasJP = /Japanese/i.test(block); const hasOther = /(English|Spanish|French|German|Italian)/i.test(block); if (hasJP && hasOther) subOrDub = "both"; else if (hasOther) subOrDub = "dub"; } } catch {} results.push({ id: animeId, title: title || altTitle, image, url: href, subOrDub, }); } return results; } async getMetadata(id) { // HARDCODED de momento return { id, title: "Unknown", summary: "", episodes: 0, status: "unknown", season: null, year: null, genres: [], score: 0, image: null, }; } async findEpisodes(id) { const html = await fetch(`${this.api}/anime/${id}/1`).then(r => r.text()); const regex = /]*href="([^"]*\/anime\/[^"]+?)"[^>]*>\s*]*>\s*]*class='[^']*min-w-10[^']*'[^>]*>(\d+)<\/div>\s*]*class="[^"]*line-clamp-1[^"]*"[^>]*>([^<]+)<\/div>/g; const episodes = []; let match; while ((match = regex.exec(html)) !== null) { const [, href, num, title] = match; episodes.push({ id: href.split("/").pop(), number: Number(num), title: title.trim(), url: href, }); } return episodes; } async findEpisodeServer(episode, server) { const html = await fetch(episode.url).then(r => r.text()); const srcMatch = html.match( /]+src="([^"]+\.m3u8)"/i ); if (!srcMatch) throw new Error("No m3u8 found"); const masterUrl = srcMatch[1]; const subtitles = []; const trackRegex = /]+src=([^ >]+)[^>]*label="([^"]+)"[^>]*srclang="([^"]+)"[^>]*(default)?/gi; let match; while ((match = trackRegex.exec(html)) !== null) { const [, src, label, lang, def] = match; subtitles.push({ id: lang, url: src, language: label.trim(), isDefault: Boolean(def), }); } return { server, headers: { Origin: "https://anizone.to", Referer: "https://anizone.to/", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36", }, videoSources: [ { url: masterUrl, type: "m3u8", quality: "auto", subtitles, }, ], }; } } module.exports = Anizone;