190 lines
6.1 KiB
JavaScript
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;
|