class AnimePahe { constructor() { this.baseUrl = "https://animepahe.si"; this.api = "https://animepahe.si"; this.type = "anime-board"; this.version = "1.0"; this.headers = { Referer: "https://kwik.cx" }; } getSettings() { return { episodeServers: ["Kwik", "Pahe"], supportsDub: false, }; } async search(queryObj) { const req = await fetch( `${this.api}/api?m=search&q=${encodeURIComponent(queryObj.query)}`, { headers: { Cookie: "__ddg1_=;__ddg2_=;" } } ); if (!req.ok) return []; const data = await req.json(); if (!data?.data) return []; return data.data.map((item) => ({ id: item.session, title: item.title, url: "", subOrDub: "sub", })); } async findEpisodes(id) { let episodes = []; const req = await fetch( `${this.api}${id.includes("-") ? `/anime/${id}` : `/a/${id}`}`, { headers: { Cookie: "__ddg1_=;__ddg2_=;" } } ); const html = await req.text(); const $ = this.cheerio.load(html); const tempId = $("head > meta[property='og:url']") .attr("content") .split("/") .pop(); const pushData = (data) => { for (const item of data) { episodes.push({ id: item.session + "$" + id, number: item.episode, title: item.title && item.title.length > 0 ? item.title : "Episode " + item.episode, url: req.url, }); } }; const first = await fetch( `${this.api}/api?m=release&id=${tempId}&sort=episode_asc&page=1`, { headers: { Cookie: "__ddg1_=;__ddg2_=;" } } ).then((r) => r.json()); pushData(first.data); const pages = Array.from( { length: first.last_page - 1 }, (_, i) => i + 2 ); const results = await Promise.all( pages.map((p) => fetch( `${this.api}/api?m=release&id=${tempId}&sort=episode_asc&page=${p}`, { headers: { Cookie: "__ddg1_=;__ddg2_=;" } } ).then((r) => r.json()) ) ); results.forEach((r) => r.data && pushData(r.data)); episodes.sort((a, b) => a.number - b.number); if (!episodes.length) throw new Error("No episodes found."); const lowest = episodes[0].number; if (lowest > 1) { episodes.forEach((ep) => (ep.number = ep.number - lowest + 1)); } return episodes.filter((ep) => Number.isInteger(ep.number)); } async findEpisodeServer(episodeOrId, server) { const [episodeId, animeId] = episodeOrId.id.split("$"); const req = await fetch( `${this.api}/play/${animeId}/${episodeId}`, { headers: { Cookie: "__ddg1_=;__ddg2_=;" } } ); const html = await req.text(); const matches = html.match(/https:\/\/kwik\.cx\/e\/\w+/g); if (!matches) throw new Error("Failed to fetch episode server."); const $ = this.cheerio.load(html); const sourcePromises = $("button[data-src]") .toArray() .map(async (el) => { const embedUrl = $(el).data("src"); if (!embedUrl) return null; const fansub = $(el).data("fansub"); const quality = $(el).data("resolution"); let label = `${quality}p - ${fansub}`; if ($(el).data("audio") === "eng") label += " (Eng)"; if (embedUrl === matches[0]) label += " (default)"; try { const srcReq = await fetch(embedUrl, { headers: { Referer: this.headers.Referer, "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36", }, }); const srcHtml = await srcReq.text(); const scripts = srcHtml.match(/eval\(f.+?\}\)\)/g); if (!scripts) return null; for (const s of scripts) { const m = s.match(/eval(.+)/); if (!m?.[1]) continue; try { const decoded = eval(m[1]); const link = decoded.match(/source='(.+?)'/); if (!link?.[1]) continue; const m3u8 = link[1]; if (server === "Pahe") { return { url: m3u8 .replace("owocdn.top", "kwik.cx") .replace("/stream/", "/mp4/") .replace("/uwu.m3u8", ""), type: "mp4", quality: label, subtitles: [], }; } return { url: m3u8, type: "m3u8", quality: label, subtitles: [], }; } catch {} } return null; } catch { return null; } }); const videoSources = (await Promise.all(sourcePromises)).filter(Boolean); if (!videoSources.length) throw new Error(`Failed to extract any sources for ${server}.`); return { server, headers: this.headers, videoSources, }; } } module.exports = AnimePahe;