mirror of
https://github.com/shafat-96/anime-mapper
synced 2026-04-17 15:51:45 +00:00
282 lines
8.7 KiB
JavaScript
282 lines
8.7 KiB
JavaScript
import axios from 'axios';
|
|
import * as cheerio from 'cheerio';
|
|
import { extractKwik } from '../extractors/kwik.js';
|
|
|
|
export class AnimePahe {
|
|
constructor() {
|
|
this.baseUrl = "https://animepahe.si";
|
|
this.sourceName = 'AnimePahe';
|
|
this.isMulti = false;
|
|
}
|
|
|
|
async scrapeSearchResults(query) {
|
|
try {
|
|
const response = await axios.get(`${this.baseUrl}/api?m=search&l=8&q=${query}`, {
|
|
headers: {
|
|
'Cookie': "__ddg1_=;__ddg2_=;",
|
|
}
|
|
});
|
|
|
|
const jsonResult = response.data;
|
|
const searchResults = [];
|
|
|
|
if (!jsonResult.data || !jsonResult.data.length) {
|
|
return searchResults;
|
|
}
|
|
|
|
for (const item of jsonResult.data) {
|
|
searchResults.push({
|
|
id: `${item.id}-${item.title}`,
|
|
title: item.title,
|
|
name: item.title,
|
|
type: item.type || 'TV',
|
|
episodes: item.episodes || 0,
|
|
status: item.status || 'Unknown',
|
|
season: item.season || 'Unknown',
|
|
year: item.year || 0,
|
|
score: item.score || 0,
|
|
poster: item.poster,
|
|
session: item.session,
|
|
episodes: {
|
|
sub: item.episodes || null,
|
|
dub: '??'
|
|
}
|
|
});
|
|
}
|
|
|
|
return searchResults;
|
|
} catch (error) {
|
|
console.error('Error searching AnimePahe:', error.message);
|
|
throw new Error('Failed to search AnimePahe');
|
|
}
|
|
}
|
|
|
|
async scrapeEpisodes(url) {
|
|
try {
|
|
const title = url.split('-')[1];
|
|
const id = url.split('-')[0];
|
|
|
|
const session = await this._getSession(title, id);
|
|
const epUrl = `${this.baseUrl}/api?m=release&id=${session}&sort=episode_desc&page=1`;
|
|
|
|
const response = await axios.get(epUrl, {
|
|
headers: {
|
|
'Cookie': "__ddg1_=;__ddg2_=;",
|
|
}
|
|
});
|
|
|
|
return await this._recursiveFetchEpisodes(epUrl, JSON.stringify(response.data), session);
|
|
} catch (error) {
|
|
console.error('Error fetching episodes:', error.message);
|
|
throw new Error('Failed to fetch episodes');
|
|
}
|
|
}
|
|
|
|
async _recursiveFetchEpisodes(url, responseData, session) {
|
|
try {
|
|
const jsonResult = JSON.parse(responseData);
|
|
const page = jsonResult.current_page;
|
|
const hasNextPage = page < jsonResult.last_page;
|
|
let animeTitle = 'Could not fetch title';
|
|
let episodes = [];
|
|
let animeDetails = {
|
|
type: 'TV',
|
|
status: 'Unknown',
|
|
season: 'Unknown',
|
|
year: 0,
|
|
score: 0
|
|
};
|
|
|
|
for (const item of jsonResult.data) {
|
|
episodes.push({
|
|
title: `Episode ${item.episode}`,
|
|
episodeId: `${session}/${item.session}`,
|
|
number: item.episode,
|
|
image: item.snapshot,
|
|
});
|
|
}
|
|
|
|
if (hasNextPage) {
|
|
const newUrl = `${url.split("&page=")[0]}&page=${page + 1}`;
|
|
const newResponse = await axios.get(newUrl, {
|
|
headers: {
|
|
'Cookie': "__ddg1_=;__ddg2_=;",
|
|
}
|
|
});
|
|
|
|
const moreEpisodes = await this._recursiveFetchEpisodes(newUrl, JSON.stringify(newResponse.data), session);
|
|
episodes = [...episodes, ...moreEpisodes.episodes];
|
|
animeTitle = moreEpisodes.title;
|
|
animeDetails = moreEpisodes.details || animeDetails;
|
|
} else {
|
|
const detailUrl = `https://animepahe.si/a/${jsonResult.data[0].anime_id}`;
|
|
const newResponse = await axios.get(detailUrl, {
|
|
headers: {
|
|
'Cookie': "__ddg1_=;__ddg2_=;",
|
|
}
|
|
});
|
|
|
|
if (newResponse.status === 200) {
|
|
const $ = cheerio.load(newResponse.data);
|
|
animeTitle = $('.title-wrapper span').text().trim() || 'Could not fetch title';
|
|
|
|
try {
|
|
const typeText = $('.col-sm-4.anime-info p:contains("Type")').text();
|
|
if (typeText) {
|
|
animeDetails.type = typeText.replace('Type:', '').trim();
|
|
}
|
|
|
|
const statusText = $('.col-sm-4.anime-info p:contains("Status")').text();
|
|
if (statusText) {
|
|
animeDetails.status = statusText.replace('Status:', '').trim();
|
|
}
|
|
|
|
const seasonText = $('.col-sm-4.anime-info p:contains("Season")').text();
|
|
if (seasonText) {
|
|
const seasonMatch = seasonText.match(/Season:\s+(\w+)\s+(\d{4})/);
|
|
if (seasonMatch) {
|
|
animeDetails.season = seasonMatch[1];
|
|
animeDetails.year = parseInt(seasonMatch[2]);
|
|
}
|
|
}
|
|
|
|
const scoreText = $('.col-sm-4.anime-info p:contains("Score")').text();
|
|
if (scoreText) {
|
|
const scoreMatch = scoreText.match(/Score:\s+([\d.]+)/);
|
|
if (scoreMatch) {
|
|
animeDetails.score = parseFloat(scoreMatch[1]);
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error('Error parsing anime details:', err.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
const sortedEpisodes = [...episodes].sort((a, b) => a.number - b.number);
|
|
|
|
return {
|
|
title: animeTitle,
|
|
session: session,
|
|
totalEpisodes: jsonResult.total,
|
|
details: animeDetails,
|
|
episodes: sortedEpisodes,
|
|
};
|
|
} catch (error) {
|
|
console.error('Error recursively fetching episodes:', error.message);
|
|
throw new Error('Failed to fetch episodes recursively');
|
|
}
|
|
}
|
|
|
|
async fetchEpisodeSources(episodeId, options = {}) {
|
|
return this.scrapeEpisodesSrcs(episodeId, options);
|
|
}
|
|
|
|
async scrapeEpisodesSrcs(episodeId, { category, lang } = {}) {
|
|
try {
|
|
const response = await axios.get(`${this.baseUrl}/play/${episodeId}`, {
|
|
headers: {
|
|
'Cookie': "__ddg1_=;__ddg2_=;",
|
|
}
|
|
});
|
|
|
|
const $ = cheerio.load(response.data);
|
|
const buttons = $('#resolutionMenu > button');
|
|
const videoLinks = [];
|
|
|
|
for (let i = 0; i < buttons.length; i++) {
|
|
const btn = buttons[i];
|
|
const kwikLink = $(btn).attr('data-src');
|
|
const quality = $(btn).text();
|
|
|
|
try {
|
|
const extraction = await extractKwik(kwikLink, response.config.url);
|
|
if (extraction && extraction.url) {
|
|
videoLinks.push({
|
|
quality: quality,
|
|
url: extraction.url,
|
|
isM3U8: extraction.isM3U8,
|
|
});
|
|
}
|
|
} catch (e) {
|
|
console.error(`Error extracting Kwik for ${quality}:`, e.message);
|
|
}
|
|
}
|
|
|
|
return {
|
|
headers: {
|
|
Referer: "https://kwik.cx/"
|
|
},
|
|
sources: videoLinks
|
|
};
|
|
} catch (error) {
|
|
console.error('Error fetching episode sources:', error.message);
|
|
throw new Error('Failed to fetch episode sources');
|
|
}
|
|
}
|
|
|
|
async _getSession(title, animeId) {
|
|
try {
|
|
const response = await axios.get(`${this.baseUrl}/api?m=search&q=${title}`, {
|
|
headers: {
|
|
'Cookie': "__ddg1_=;__ddg2_=;",
|
|
}
|
|
});
|
|
|
|
const resBody = response.data;
|
|
if (!resBody.data || resBody.data.length === 0) {
|
|
throw new Error(`No results found for title: ${title}`);
|
|
}
|
|
|
|
if (animeId) {
|
|
const animeIdMatch = resBody.data.find(anime => String(anime.id) === String(animeId));
|
|
if (animeIdMatch) {
|
|
return animeIdMatch.session;
|
|
}
|
|
}
|
|
|
|
const normalizeTitle = t => t.toLowerCase().replace(/[^\w\s]/g, '').replace(/\s+/g, ' ').trim();
|
|
const normalizedSearchTitle = normalizeTitle(title);
|
|
|
|
let bestMatch = null;
|
|
let highestSimilarity = 0;
|
|
|
|
for (const anime of resBody.data) {
|
|
const normalizedAnimeTitle = normalizeTitle(anime.title);
|
|
let similarity = 0;
|
|
|
|
if (normalizedAnimeTitle === normalizedSearchTitle) {
|
|
similarity = 1;
|
|
}
|
|
else if (normalizedAnimeTitle.includes(normalizedSearchTitle) ||
|
|
normalizedSearchTitle.includes(normalizedAnimeTitle)) {
|
|
const lengthRatio = Math.min(normalizedAnimeTitle.length, normalizedSearchTitle.length) /
|
|
Math.max(normalizedAnimeTitle.length, normalizedSearchTitle.length);
|
|
similarity = 0.8 * lengthRatio;
|
|
}
|
|
else {
|
|
const searchWords = normalizedSearchTitle.split(' ');
|
|
const animeWords = normalizedAnimeTitle.split(' ');
|
|
const commonWords = searchWords.filter(word => animeWords.includes(word));
|
|
similarity = commonWords.length / Math.max(searchWords.length, animeWords.length);
|
|
}
|
|
|
|
if (similarity > highestSimilarity) {
|
|
highestSimilarity = similarity;
|
|
bestMatch = anime;
|
|
}
|
|
}
|
|
|
|
if (bestMatch && highestSimilarity > 0.5) {
|
|
return bestMatch.session;
|
|
}
|
|
|
|
return resBody.data[0].session;
|
|
} catch (error) {
|
|
console.error('Error getting session:', error.message);
|
|
throw new Error('Failed to get session');
|
|
}
|
|
}
|
|
}
|
|
|
|
export default AnimePahe; |