This commit is contained in:
tejaspanchall
2025-05-28 23:11:20 +05:30
commit 00797f0c96
52 changed files with 15166 additions and 0 deletions

510
src/lib/api.js Normal file
View File

@@ -0,0 +1,510 @@
const API_BASE_URL = process.env.ANIWATCH_API || "https://justaniwatchapi.vercel.app/api/v2/hianime";
// Common headers for all API requests
const API_HEADERS = {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Origin': 'https://hianime.to',
'Referer': 'https://hianime.to/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
};
export const fetchRecentEpisodes = async (page = 1) => {
try {
const response = await fetch(`${API_BASE_URL}/category/recently-updated?page=${page}`, {
headers: API_HEADERS,
credentials: 'omit'
});
if (!response.ok) throw new Error('Failed to fetch recent episodes');
const data = await response.json();
return {
results: data.data.animes.map(anime => ({
id: anime.id,
name: anime.name,
poster: anime.poster,
type: anime.type,
episodes: {
sub: anime.episodes?.sub || 0,
dub: anime.episodes?.dub || 0
}
})) || [],
currentPage: data.data.currentPage,
hasNextPage: data.data.hasNextPage
};
} catch (error) {
console.error('Error fetching recent episodes:', error);
return { results: [] };
}
};
export const fetchTopAiring = async (page = 1) => {
try {
const response = await fetch(`${API_BASE_URL}/category/top-airing?page=${page}`);
if (!response.ok) throw new Error('Failed to fetch top airing');
const data = await response.json();
return {
results: data.data.animes.map(anime => ({
id: anime.id,
name: anime.name,
poster: anime.poster,
type: anime.type,
episodes: {
sub: anime.episodes?.sub || 0,
dub: anime.episodes?.dub || 0
}
})) || [],
currentPage: data.data.currentPage,
hasNextPage: data.data.hasNextPage
};
} catch (error) {
console.error('Error fetching top airing:', error);
return { results: [] };
}
};
export const fetchMostPopular = async (page = 1) => {
try {
const response = await fetch(`${API_BASE_URL}/category/most-popular?page=${page}`);
if (!response.ok) throw new Error('Failed to fetch most popular');
const data = await response.json();
return {
results: data.data.animes.map(anime => ({
id: anime.id,
name: anime.name,
poster: anime.poster,
type: anime.type,
episodes: {
sub: anime.episodes?.sub || 0,
dub: anime.episodes?.dub || 0
}
})) || [],
currentPage: data.data.currentPage,
hasNextPage: data.data.hasNextPage
};
} catch (error) {
console.error('Error fetching most popular:', error);
return { results: [] };
}
};
export const fetchMostFavorite = async (page = 1) => {
try {
const response = await fetch(`${API_BASE_URL}/category/most-favorite?page=${page}`);
if (!response.ok) throw new Error('Failed to fetch most favorite');
const data = await response.json();
return {
results: data.data.animes || [],
currentPage: data.data.currentPage,
hasNextPage: data.data.hasNextPage
};
} catch (error) {
console.error('Error fetching most favorite:', error);
return { results: [] };
}
};
export const fetchLatestCompleted = async (page = 1) => {
try {
const response = await fetch(`${API_BASE_URL}/category/completed?page=${page}`);
if (!response.ok) throw new Error('Failed to fetch latest completed');
const data = await response.json();
return {
results: data.data.animes.map(anime => ({
id: anime.id,
name: anime.name,
poster: anime.poster,
type: anime.type,
episodes: {
sub: anime.episodes?.sub || 0,
dub: anime.episodes?.dub || 0
}
})) || [],
currentPage: data.data.currentPage,
hasNextPage: data.data.hasNextPage
};
} catch (error) {
console.error('Error fetching latest completed:', error);
return { results: [] };
}
};
export const fetchTopUpcoming = async (page = 1) => {
try {
const response = await fetch(`${API_BASE_URL}/category/top-upcoming?page=${page}`);
if (!response.ok) throw new Error('Failed to fetch top upcoming');
const data = await response.json();
return {
results: data.data.animes || [],
currentPage: data.data.currentPage,
hasNextPage: data.data.hasNextPage
};
} catch (error) {
console.error('Error fetching top upcoming:', error);
return { results: [] };
}
};
export const fetchTrending = async () => {
try {
const response = await fetch(`${API_BASE_URL}/home`);
if (!response.ok) throw new Error('Failed to fetch trending anime');
const data = await response.json();
// Map the trending animes to match the TrendingList component's expected format
const trendingAnimes = (data.data.trendingAnimes || []).map(anime => ({
id: anime.id,
title: anime.name,
image: anime.poster,
rank: anime.rank
}));
return {
results: trendingAnimes
};
} catch (error) {
console.error('Error fetching trending anime:', error);
return { results: [] };
}
};
export const fetchAnimeInfo = async (id) => {
try {
if (!id) {
console.error('Invalid anime ID provided');
return null;
}
const encodedId = encodeURIComponent(id);
const url = `${API_BASE_URL}/anime/${encodedId}`;
console.log('[API Call] Fetching anime info from:', url);
// Server-side fetch doesn't need credentials or mode settings
const requestOptions = {
method: 'GET',
headers: API_HEADERS,
};
const response = await fetch(url, requestOptions);
// Handle failed requests gracefully
if (!response.ok) {
console.error(`[API Error] Status: ${response.status}`);
return createFallbackAnimeData(id);
}
// Parse the JSON response
const data = await response.json();
console.log('[API Response]', data);
// Check if the response is successful
if (!data.success && data.status !== 200) {
console.error('[API Error] Invalid response format:', data);
return createFallbackAnimeData(id);
}
// The data structure might be nested in different ways depending on the API
const responseData = data.data || data;
// Log the data structure for debugging
console.log('[API Data Structure]', JSON.stringify(responseData, null, 2));
// Extract the anime data from the response
const animeData = responseData.anime;
if (!animeData) {
console.error('[API Error] Missing anime data in response:', responseData);
return createFallbackAnimeData(id);
}
// Return the complete data structure as expected by the components
return {
info: {
id: id,
name: animeData.info?.name || '',
jname: animeData.info?.jname || '',
poster: animeData.info?.poster || '',
description: animeData.info?.description || '',
stats: {
rating: animeData.info?.stats?.rating || '0',
quality: animeData.info?.stats?.quality || 'HD',
episodes: animeData.info?.stats?.episodes || { sub: 0, dub: 0 },
type: animeData.info?.stats?.type || 'TV',
duration: animeData.info?.stats?.duration || 'Unknown'
},
promotionalVideos: Array.isArray(animeData.info?.promotionalVideos)
? animeData.info.promotionalVideos
: [],
characterVoiceActor: Array.isArray(animeData.info?.characterVoiceActor)
? animeData.info.characterVoiceActor
: []
},
moreInfo: animeData.moreInfo || {
aired: '',
genres: [],
status: 'Unknown',
studios: '',
duration: ''
},
relatedAnime: Array.isArray(responseData.relatedAnimes)
? responseData.relatedAnimes
: [],
recommendations: Array.isArray(responseData.recommendedAnimes)
? responseData.recommendedAnimes
: [],
mostPopular: Array.isArray(responseData.mostPopularAnimes)
? responseData.mostPopularAnimes
: [],
seasons: Array.isArray(responseData.seasons)
? responseData.seasons
: []
};
} catch (error) {
console.error('[API Error] Error fetching anime info:', error);
return createFallbackAnimeData(id);
}
};
// Helper function to create fallback anime data when the API fails
function createFallbackAnimeData(id) {
return {
info: {
id: id,
name: 'Anime Information Temporarily Unavailable',
jname: '',
poster: 'https://via.placeholder.com/225x318?text=Anime',
description: 'The anime data could not be loaded at this time. Please try again later.',
stats: {
rating: '0',
quality: 'HD',
episodes: {
sub: 0,
dub: 0
},
type: 'Unknown',
duration: 'Unknown'
},
promotionalVideos: [],
characterVoiceActor: []
},
moreInfo: {
aired: '',
genres: ['Action', 'Adventure'],
status: 'Unknown',
studios: '',
duration: ''
},
relatedAnime: [],
recommendations: [],
mostPopular: [],
seasons: []
};
}
export const fetchEpisodeSources = async (episodeId, dub = false) => {
try {
if (!episodeId || episodeId === 'undefined') {
console.error('Invalid episode ID provided');
return { sources: [] };
}
const apiUrl = `${API_BASE_URL}/episode/sources?animeEpisodeId=${episodeId}&category=${dub ? 'dub' : 'sub'}`;
console.log(`[API Call] Fetching sources from: ${apiUrl}`);
const response = await fetch(apiUrl, {
headers: API_HEADERS,
credentials: 'omit'
});
if (!response.ok) {
throw new Error(`Failed to fetch episode sources: ${response.status} ${response.statusText}`);
}
const data = await response.json();
console.log('[API Response] Raw data:', data);
if (!data || !data.data) {
console.error('[API Error] Empty response received');
return { sources: [] };
}
return {
sources: data.data.sources,
headers: data.data.headers || { "Referer": "https://hianime.to/" },
subtitles: data.data.subtitles || []
};
} catch (error) {
console.error('Error fetching episode sources:', error);
return { sources: [] };
}
};
export const searchAnime = async (query, page = 1) => {
try {
const response = await fetch(`${API_BASE_URL}/search?q=${encodeURIComponent(query)}&page=${page}`);
if (!response.ok) throw new Error('Failed to search anime');
const data = await response.json();
return {
results: data.data.animes || [],
currentPage: data.data.currentPage,
hasNextPage: data.data.hasNextPage
};
} catch (error) {
console.error('Error searching anime:', error);
return { results: [] };
}
};
export const fetchGenres = async () => {
try {
const response = await fetch(`${API_BASE_URL}/home`);
if (!response.ok) throw new Error('Failed to fetch genres');
const data = await response.json();
return data.data.genres || [];
} catch (error) {
console.error('Error fetching genres:', error);
return [];
}
};
export const fetchGenreAnime = async (genre, page = 1) => {
try {
const response = await fetch(`${API_BASE_URL}/genre/${encodeURIComponent(genre)}?page=${page}`);
if (!response.ok) throw new Error('Failed to fetch genre anime');
const data = await response.json();
return {
results: data.data.animes || [],
currentPage: data.data.currentPage,
hasNextPage: data.data.hasNextPage
};
} catch (error) {
console.error('Error fetching genre anime:', error);
return { results: [] };
}
};
export const fetchSearchSuggestions = async (query) => {
try {
const response = await fetch(`${API_BASE_URL}/search/suggestion?q=${encodeURIComponent(query)}`);
if (!response.ok) throw new Error('Failed to fetch search suggestions');
const data = await response.json();
return data.data.suggestions || [];
} catch (error) {
console.error('Error fetching search suggestions:', error);
return [];
}
};
export const fetchSchedule = async () => {
try {
const today = new Date().toISOString().split('T')[0];
const response = await fetch(`${API_BASE_URL}/schedule?date=${today}`);
if (!response.ok) throw new Error('Failed to fetch schedule');
const data = await response.json();
// Map the scheduled animes to include all required fields
const scheduledAnimes = data.data.scheduledAnimes || [];
return {
scheduledAnimes: scheduledAnimes.map(anime => ({
id: anime.id,
time: anime.time,
name: anime.name,
jname: anime.jname,
airingTimestamp: anime.airingTimestamp,
secondsUntilAiring: anime.secondsUntilAiring
}))
};
} catch (error) {
console.error('Error fetching schedule:', error);
return { scheduledAnimes: [] };
}
};
export const fetchSpotlightAnime = async (limit = 8) => {
try {
const response = await fetch(`${API_BASE_URL}/home`);
if (!response.ok) throw new Error('Failed to fetch spotlight anime');
const data = await response.json();
// Map the spotlight animes to match the exact schema from the API
const spotlightAnimes = (data.data.spotlightAnimes || []).map(anime => ({
id: anime.id,
name: anime.name,
jname: anime.jname,
poster: anime.poster,
banner: anime.banner,
description: anime.description,
rank: anime.rank,
otherInfo: anime.otherInfo || [],
episodes: {
sub: anime.episodes?.sub || 0,
dub: anime.episodes?.dub || 0
}
}));
return spotlightAnimes.slice(0, limit);
} catch (error) {
console.error('Error fetching spotlight anime:', error);
return [];
}
};
// Top 10 sections with proper data mapping
export const fetchTopToday = async () => {
try {
const response = await fetch(`${API_BASE_URL}/home`);
if (!response.ok) throw new Error('Failed to fetch top today');
const data = await response.json();
// Map the top 10 animes to include all required fields
return (data.data.top10Animes?.today || []).map(anime => ({
id: anime.id,
name: anime.name,
poster: anime.poster,
rank: anime.rank,
episodes: anime.episodes || { sub: 0, dub: 0 }
}));
} catch (error) {
console.error('Error fetching top today:', error);
return [];
}
};
export const fetchTopWeek = async () => {
try {
const response = await fetch(`${API_BASE_URL}/home`);
if (!response.ok) throw new Error('Failed to fetch top week');
const data = await response.json();
// Map the top 10 animes to include all required fields
return (data.data.top10Animes?.week || []).map(anime => ({
id: anime.id,
name: anime.name,
poster: anime.poster,
rank: anime.rank,
episodes: anime.episodes || { sub: 0, dub: 0 }
}));
} catch (error) {
console.error('Error fetching top week:', error);
return [];
}
};
export const fetchTopMonth = async () => {
try {
const response = await fetch(`${API_BASE_URL}/home`);
if (!response.ok) throw new Error('Failed to fetch top month');
const data = await response.json();
// Map the top 10 animes to include all required fields
return (data.data.top10Animes?.month || []).map(anime => ({
id: anime.id,
name: anime.name,
poster: anime.poster,
rank: anime.rank,
episodes: anime.episodes || { sub: 0, dub: 0 }
}));
} catch (error) {
console.error('Error fetching top month:', error);
return [];
}
};