Files
WaifuBoard-Extensions/anime/AnimePahe.js
2026-01-05 04:46:26 +01:00

190 lines
6.1 KiB
JavaScript

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;