UNPKG

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
"use strict"; 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