class Anizone { constructor() { this.type = "anime-board"; this.version = "1.2"; this.api = "https://anizone.to"; } getSettings() { return { episodeServers: ["HLS"], supportsDub: false, }; } 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) { const url = `https://anizone.to/anime/${id}`; const res = await fetch(url); const html = await res.text(); const $ = this.cheerio.load(html); const title = $('div.flex.flex-col.items-center.lg\\:items-start h1').first().text().trim(); const summary = $('div.text-slate-100.text-center.lg\\:text-start.text-sm.md\\:text-base.xl\\:text-lg div').text().trim(); // Episodios let episodes = 0; $('span.flex.items-center.gap-1').each((i, el) => { const text = $(el).text().trim(); const match = text.match(/(\d+)\s+Episodes?/i); if (match) { episodes = parseInt(match[1], 10); return false; // rompe el each cuando lo encuentra } }); let status = "unknown"; $('span.flex.items-center.gap-1.5').each((i, el) => { const text = $(el).text().trim().toLowerCase(); if (text.includes("completed")) status = "completed"; else if (text.includes("ongoing")) status = "ongoing"; }); const yearText = $('span.flex.items-center.gap-1 span.inline-block').text().trim(); const year = yearText ? parseInt(yearText, 10) : null; const genres = []; $('div.flex.flex-wrap.gap-2.justify-center.lg\\:justify-start a').each((i, el) => { const genre = $(el).attr('title')?.trim(); if (genre) genres.push(genre); }); const image = $('div.mx-auto.lg\\:mx-0 img').attr('src') || null; return { id, title: title || "Unknown", summary: summary || "", episodes, status, season: null, year, genres, score: 0, image, }; } 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;