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 []; } };