contentapi.ts
Version:
Nodejs library that provides high-level APIs for obtaining information on various entertainment media such as books, movies, comic books, anime, manga, and so on.
309 lines • 13.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const cheerio_1 = require("cheerio");
const models_1 = require("../../models");
const extractors_1 = require("../../extractors");
const utils_1 = require("../../utils");
class AnimePahe extends models_1.AnimeParser {
constructor() {
super(...arguments);
this.name = 'AnimePahe';
this.baseUrl = 'https://animepahe.ru';
this.logo = 'https://animepahe.com/pikacon.ico';
this.classPath = 'ANIME.AnimePahe';
// private readonly sgProxy = 'https://cors.consumet.stream';
/**
* @param query Search query
*/
this.search = async (query) => {
try {
const { data } = await this.client.get(`${this.baseUrl}/api?m=search&q=${encodeURIComponent(query)}`, {
headers: this.Headers(false),
});
const res = {
results: data.data.map((item) => ({
id: item.session,
title: item.title,
image: item.poster,
rating: item.score,
releaseDate: item.year,
type: item.type,
})),
};
return res;
}
catch (err) {
throw new Error(err.message);
}
};
/**
* @param id id format id/session
* @param episodePage Episode page number (optional) default: -1 to get all episodes. number of episode pages can be found in the anime info object
*/
this.fetchAnimeInfo = async (id, episodePage = -1) => {
const animeInfo = {
id: id,
title: '',
};
try {
const res = await this.client.get(`${this.baseUrl}/anime/${id}`, { headers: this.Headers(id) });
const $ = (0, cheerio_1.load)(res.data);
animeInfo.title = $('div.title-wrapper > h1 > span').first().text();
animeInfo.image = $('div.anime-poster a').attr('href');
animeInfo.cover = `https:${$('div.anime-cover').attr('data-src')}`;
animeInfo.description = $('div.anime-summary').text().trim();
animeInfo.genres = $('div.anime-genre ul li')
.map((i, el) => $(el).find('a').attr('title'))
.get();
animeInfo.hasSub = true;
switch ($('div.anime-info p:icontains("Status:") a').text().trim()) {
case 'Currently Airing':
animeInfo.status = models_1.MediaStatus.ONGOING;
break;
case 'Finished Airing':
animeInfo.status = models_1.MediaStatus.COMPLETED;
break;
default:
animeInfo.status = models_1.MediaStatus.UNKNOWN;
}
animeInfo.type = $('div.anime-info > p:contains("Type:") > a')
.text()
.trim()
.toUpperCase();
animeInfo.releaseDate = $('div.anime-info > p:contains("Aired:")')
.text()
.split('to')[0]
.replace('Aired:', '')
.trim();
animeInfo.studios = $('div.anime-info > p:contains("Studio:")')
.text()
.replace('Studio:', '')
.trim()
.split('\n');
animeInfo.totalEpisodes = parseInt($('div.anime-info > p:contains("Episodes:")')
.text().replace('Episodes:', '').trim());
animeInfo.recommendations = [];
$('div.anime-recommendation .col-sm-6').each((i, el) => {
var _a, _b, _c;
(_a = animeInfo.recommendations) === null || _a === void 0 ? void 0 : _a.push({
id: (_b = $(el).find('.col-2 > a').attr('href')) === null || _b === void 0 ? void 0 : _b.split('/')[2],
title: $(el).find('.col-2 > a').attr('title'),
image: $(el).find('.col-2 > a > img').attr('src') || $(el).find('.col-2 > a > img').attr('data-src'),
url: `${this.baseUrl}/anime/${(_c = $(el).find('.col-2 > a').attr('href')) === null || _c === void 0 ? void 0 : _c.split('/')[2]}`,
releaseDate: $(el).find('div.col-9 > a').text().trim(),
status: $(el).find('div.col-9 > strong').text().trim(),
});
});
animeInfo.relations = [];
$('div.anime-relation .col-sm-6').each((i, el) => {
var _a, _b, _c;
(_a = animeInfo.relations) === null || _a === void 0 ? void 0 : _a.push({
id: (_b = $(el).find('.col-2 > a').attr('href')) === null || _b === void 0 ? void 0 : _b.split('/')[2],
title: $(el).find('.col-2 > a').attr('title'),
image: $(el).find('.col-2 > a > img').attr('src') || $(el).find('.col-2 > a > img').attr('data-src'),
url: `${this.baseUrl}/anime/${(_c = $(el).find('.col-2 > a').attr('href')) === null || _c === void 0 ? void 0 : _c.split('/')[2]}`,
releaseDate: $(el).find('div.col-9 > a').text().trim(),
status: $(el).find('div.col-9 > strong').text().trim(),
relationType: $(el).find('h4 > span').text().trim(),
});
});
animeInfo.episodes = [];
if (episodePage < 0) {
const { data: { last_page, data }, } = await this.client.get(`${this.baseUrl}/api?m=release&id=${id}&sort=episode_asc&page=1`, {
headers: this.Headers(id),
});
animeInfo.episodePages = last_page;
animeInfo.episodes.push(...data.map((item) => ({
id: `${id}/${item.session}`,
number: item.episode,
title: item.title,
image: item.snapshot,
duration: item.duration,
url: `${this.baseUrl}/play/${id}/${item.session}`,
})));
for (let i = 1; i < last_page; i++) {
animeInfo.episodes.push(...(await this.fetchEpisodes(id, i + 1)));
}
}
else {
animeInfo.episodes.push(...(await this.fetchEpisodes(id, episodePage)));
}
return animeInfo;
}
catch (err) {
throw new Error(err.message);
}
};
/**
*
* @param episodeId episode id
*/
this.fetchEpisodeSources = async (id, episodeId) => {
console.log("EpisodeId", episodeId);
try {
const { data } = await this.client.get(`${this.baseUrl}/play/${id}/${episodeId}`, {
headers: this.Headers(episodeId.split('/')[0]),
});
console.log("Data", data);
const $ = (0, cheerio_1.load)(data);
const links = $('div#resolutionMenu > button').map((i, el) => ({
url: $(el).attr('data-src'),
quality: $(el).text(),
audio: $(el).attr('data-audio'),
}));
console.log("Links", links);
const iSource = {
headers: {
Referer: 'https://kwik.cx/',
},
sources: [],
};
for (const link of links) {
console.log("Link 0", link);
if (!link.quality.includes("1080p")) {
continue;
}
const res = await new extractors_1.Kwik(this.proxyConfig).extract(new URL(link.url));
res[0].quality = link.quality;
res[0].isDub = link.audio === 'eng';
iSource.sources.push(res[0]);
}
console.log("ISource", iSource);
return iSource;
}
catch (err) {
throw new Error(err.message);
}
};
this.fetchEpisodes = async (session, page) => {
const res = await this.client.get(`${this.baseUrl}/api?m=release&id=${session}&sort=episode_asc&page=${page}`, { headers: this.Headers(session) });
const epData = res.data.data;
return [
...epData.map((item) => ({
id: `${session}/${item.session}`,
number: item.episode,
title: item.title,
image: item.snapshot,
duration: item.duration,
url: `${this.baseUrl}/play/${session}/${item.session}`,
})),
];
};
/**
* @deprecated
* @attention AnimePahe doesn't support this method
*/
this.fetchEpisodeServers = (episodeLink) => {
throw new Error('Method not implemented.');
};
}
async findEpisodeServer(animeId, episodeId) {
const api = "https://animepahe.ru";
const headers = { Referer: "https://kwik.si" };
console.log(`https://animepahe.ru/play/${animeId}/${episodeId}`);
const req = await fetch(`https://animepahe.ru/play/${animeId}/${episodeId}`, {
headers: {
Cookie: "__ddg1_=;__ddg2_=;",
},
});
const html = await req.text();
const regex = /https:\/\/kwik\.si\/e\/\w+/g;
const matches = html.match(regex);
if (matches === null) {
throw new Error("Failed to fetch episode server.");
}
const $ = (0, cheerio_1.load)(html);
const result = {
videoSources: [],
headers: headers !== null && headers !== void 0 ? headers : {},
server: "kwik",
};
$("button[data-src]").each((_, el) => {
let videoSource = {
url: "",
type: "m3u8",
quality: "",
subtitles: [],
};
videoSource.url = $(el).attr("data-src");
if (!videoSource.url) {
return;
}
const fansub = el.attribs["data-fansub"];
const quality = el.attribs["data-resolution"];
videoSource.quality = `${quality}p - ${fansub}`;
if ($(el).data("audio") === "eng") {
videoSource.quality += " (Eng)";
}
if (videoSource.url === matches[0]) {
videoSource.quality += " (default)";
}
result.videoSources.push(videoSource);
});
const queries = result.videoSources.map(async (videoSource) => {
try {
const src_req = await fetch(videoSource.url, {
headers: {
Referer: 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 Edg/107.0.1418.56",
},
});
const src_html = await src_req.text();
const scripts = src_html.match(/eval\(f.+?\}\)\)/g);
if (!scripts) {
return;
}
for (const _script of scripts) {
const scriptMatch = _script.match(/eval(.+)/);
if (!scriptMatch || !scriptMatch[1]) {
continue;
}
try {
const decoded = eval(scriptMatch[1]);
const link = decoded.match(/source='(.+?)'/);
if (!link || !link[1]) {
continue;
}
videoSource.url = link[1];
}
catch (e) {
console.error("Failed to extract kwik link", e);
}
}
}
catch (e) {
console.error("Failed to fetch kwik link", e);
}
});
await Promise.all(queries);
return result;
}
Headers(sessionId) {
return {
authority: 'animepahe.ru',
accept: 'application/json, text/javascript, */*; q=0.01',
'accept-language': 'en-US,en;q=0.9',
cookie: '__ddg2_=;',
dnt: '1',
'sec-ch-ua': '"Not A(Brand";v="99", "Microsoft Edge";v="121", "Chromium";v="121"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
'x-requested-with': 'XMLHttpRequest',
referer: sessionId ? `${this.baseUrl}/anime/${sessionId}` : `${this.baseUrl}`,
'user-agent': utils_1.USER_AGENT,
};
}
}
exports.default = AnimePahe;
// (async () => {
// const animepahe = new AnimePahe();
// const anime = await animepahe.search('Classroom of the elite');
// const info = await animepahe.fetchAnimeInfo(anime.results[0].id);
// // console.log(info);
// const sources = await animepahe.fetchEpisodeSources(info.episodes![0].id);
// console.log(sources);
// })();
//# sourceMappingURL=animepahe.js.map