// Anime details module // Search anime and display details async function searchAnimeDetails(query, malId = null) { const resultsContainer = document.getElementById('animeSearchResults'); if (!resultsContainer) return; try { resultsContainer.innerHTML = '
Recherche en cours...
'; // If we have a MAL ID, fetch directly by ID, otherwise search by query let malUrl; if (malId) { malUrl = `${API_BASE}/anime/mal/${malId}`; } else { malUrl = `${API_BASE}/anime/mal/search?q=${encodeURIComponent(query)}&limit=5`; } // Search MAL and get streaming results in parallel const [malResponse, streamingData] = await Promise.allSettled([ fetch(malUrl), searchAnime(query, 'vostfr', false) ]); let animeData = null; let malFound = false; // Check MAL search results if (malResponse.status === 'fulfilled') { try { // malResponse.value is the Response object from fetch const response = malResponse.value; // Check if the HTTP request was successful if (response.ok) { const data = await response.json(); console.log('MAL search response:', data); // Handle both direct ID response and search response if (data.anime) { animeData = data.anime; malFound = true; } else if (data.mal_id) { // Direct MAL ID response animeData = data; malFound = true; } } else { console.warn(`MAL search returned HTTP ${response.status}`); } } catch (e) { console.error('Error parsing MAL response:', e); } } else { console.error('MAL search promise rejected:', malResponse.reason); } // Build streaming results HTML let streamingHtml = ''; if (streamingData.status === 'fulfilled' && streamingData.value && streamingData.value.results) { const providersData = await getProvidersInfo(); // Build results HTML const streamingParts = []; let hasResults = false; // Display results from each provider - render all cards in parallel for (const [providerId, results] of Object.entries(streamingData.value.results)) { if (results && results.length > 0) { hasResults = true; const provider = providersData.anime_providers[providerId]; // Render all cards for this provider const cardPromises = results.map((anime) => renderAnimeCard(anime, providerId, provider, 'vostfr')); const cards = await Promise.all(cardPromises); streamingParts.push(...cards); } } // Only add header and wrapper if we have results if (hasResults) { streamingParts.unshift( `

🎬 Résultats de streaming

` ); streamingParts.push('
'); streamingHtml = streamingParts.join(''); } } // Display results if (malFound && animeData) { // We found MAL data - display anime details card let html = renderAnimeDetails(animeData); // Append streaming results if available html += streamingHtml; resultsContainer.innerHTML = html; // Now load seasons after HTML is in DOM if (streamingData.status === 'fulfilled' && streamingData.value && streamingData.value.results) { loadStreamingResultsSeasons(streamingData.value.results); } } else { // MAL found nothing but we have streaming results if (streamingHtml) { resultsContainer.innerHTML = `

ℹ️ Aucune fiche trouvée sur MyAnimeList pour "${escapeHtml(query)}"

Essayez le nom en anglais ou japonais (ex: "Frieren: Beyond Journey's End")

${streamingHtml} `; // Now load seasons after HTML is in DOM if (streamingData.status === 'fulfilled' && streamingData.value && streamingData.value.results) { loadStreamingResultsSeasons(streamingData.value.results); } } else { resultsContainer.innerHTML = `

❌ Aucun résultat trouvé pour "${escapeHtml(query)}"

Essayez le nom en anglais ou japonais (ex: "Frieren: Beyond Journey's End", "One Piece")

`; } } } catch (error) { console.error('Error searching anime details:', error); resultsContainer.innerHTML = `

❌ Erreur lors de la recherche.

${error.message}

`; } } // Get provider search results as HTML async function getProviderSearchResults(query) { try { // Use the existing searchAnime function const data = await searchAnime(query, 'vostfr', false); if (!data.results) { return ''; } // Build results HTML const htmlParts = []; let hasResults = false; // Display results from each provider for (const [providerId, results] of Object.entries(data.results)) { if (results && results.length > 0) { hasResults = true; const providersData = await getProvidersInfo(); const provider = providersData.anime_providers[providerId]; // Render all cards for this provider in parallel const cardPromises = results.map((anime) => renderAnimeCard(anime, providerId, provider, 'vostfr')); const cards = await Promise.all(cardPromises); htmlParts.push(...cards); } } // Only add header and wrapper if we have results if (hasResults) { htmlParts.unshift( `

🎬 Résultats de streaming

` ); htmlParts.push('
'); } return htmlParts.join(''); } catch (error) { console.error('Error getting provider search results:', error); return ''; } } // After displaying streaming results, load seasons for Anime-Sama async function loadStreamingResultsSeasons(providerResults) { // providerResults should be the data.results object let delayCounter = 0; for (const [providerId, results] of Object.entries(providerResults)) { if (results && results.length > 0) { results.forEach((anime, index) => { // Only load seasons for Anime-Sama if (providerId === 'animesama' || (anime.url && anime.url.includes('anime-sama'))) { // Stagger requests: 500ms delay between each anime setTimeout(() => { loadSeasonsForAnime(providerId, encodeURIComponent(anime.url)); }, 500 * delayCounter); delayCounter++; } }); } } } // Render anime details card function renderAnimeDetails(anime) { const images = anime.images || {}; const imageUrl = images.jpg?.large_image_url || images.jpg?.image_url || images.webp?.large_image_url || ''; const genres = anime.genres || []; const themes = anime.themes || []; const studios = anime.studios || []; const score = anime.score || 0; const rank = anime.rank || 0; const popularity = anime.popularity || 0; const synopsis = anime.synopsis || ''; const related = anime.related || []; // Generate unique ID for synopsis element const synopsisId = `synopsis-${anime.mal_id}`; // Filter only seasons (Sequel, Prequel) const seasons = related.filter(r => { const relationType = r.type?.toLowerCase() || ''; return relationType === 'sequel' || relationType === 'prequel'; }); return `
${imageUrl ? `` : ''}

${escapeHtml(anime.title)}

${anime.title_english && anime.title_english !== anime.title ? `

${escapeHtml(anime.title_english)}

` : ''}
${score > 0 ? `
★ ${score.toFixed(2)}
` : ''} ${rank > 0 ? `
#${rank}
` : ''} ${popularity > 0 ? `
Popularity #${popularity}
` : ''}
${anime.episodes ? `📺 ${anime.episodes} épisodes` : ''} ${anime.status ? `📡 ${translateStatus(anime.status)}` : ''} ${anime.duration ? `⏱️ ${escapeHtml(anime.duration)}` : ''} ${anime.year ? `📅 ${anime.year}` : ''}
${studios.length > 0 ? `
Studio: ${studios.map(s => escapeHtml(s)).join(', ')}
` : ''}
🔗 Voir sur MAL
${(genres.length > 0 || themes.length > 0) ? `
${genres.map(g => `${escapeHtml(g)}`).join('')} ${themes.map(t => `${escapeHtml(t)}`).join('')}
` : ''} ${synopsis ? `

📖 Synopsis

${escapeHtml(synopsis)}

` : ''} ${seasons.length > 0 ? `

📺 Saisons

` : ''}
`; } // Load streaming results from providers async function loadStreamingResults(query) { const container = document.getElementById('streamingResults'); if (!container) return; try { container.innerHTML = '
Recherche des sources de streaming...
'; // Load providers info const providersData = await getProvidersInfo(); const animeProviders = Object.entries(providersData.anime_providers); // Search on all providers const results = await Promise.allSettled( animeProviders.map(([id, provider]) => loadEpisodes(null, query).then(episodes => ({ provider: id, name: provider.name, icon: provider.icon, episodes: episodes.episodes || [] })) ) ); // Filter successful results const successfulResults = results .filter(r => r.status === 'fulfilled' && r.value.episodes.length > 0) .map(r => r.value); if (successfulResults.length === 0) { container.innerHTML = `

⚠️ Aucun résultat de streaming trouvé pour "${escapeHtml(query)}"

`; return; } // Display results container.innerHTML = `

🎬 Disponible sur

${successfulResults.map(result => renderStreamingResult(result, query)).join('')}
`; } catch (error) { console.error('Error loading streaming results:', error); container.innerHTML = `

❌ Erreur lors de la recherche des sources de streaming.

`; } } // Render a single streaming result function renderStreamingResult(result, query) { const { provider, name, icon, episodes } = result; return `
${icon} ${escapeHtml(name)} ${episodes.length} épisodes
Voir tous les épisodes sur ${escapeHtml(name)} →
`; } // Download selected episode from streaming results async function downloadSelectedEpisode(button) { const select = button.parentElement.querySelector('.streaming-episode-select'); const episodeUrl = select.value; if (!episodeUrl) { alert('Veuillez sélectionner un épisode'); return; } try { await downloadEpisode(episodeUrl); loadDownloads(); } catch (error) { console.error('Download error:', error); alert('Erreur lors du téléchargement'); } } // Translate status function translateStatus(status) { const translations = { 'Airing': 'En cours', 'Finished Airing': 'Terminé', 'To Be Aired': 'À venir', 'Currently Airing': 'En cours' }; return translations[status] || status; } // Translate relation type to French function translateRelationType(type) { const translations = { 'Sequel': 'Suite', 'Prequel': 'Préquelle', 'Spin-off': 'Spin-off', 'Side Story': 'Histoire secondaire', 'Summary': 'Résumé', 'Other': 'Autre', 'Alternative Setting': 'Version alternative', 'Full Story': 'Histoire complète' }; return translations[type] || type; } // Translate synopsis to French using backend API async function translateSynopsis(synopsisId, button) { const synopsisElement = document.getElementById(synopsisId); if (!synopsisElement) return; // Get original text (use textContent to get pure text without HTML) const originalText = synopsisElement.dataset.original || synopsisElement.textContent; // Check if already translated if (synopsisElement.dataset.translated === 'true') { // Revert to original synopsisElement.textContent = originalText; synopsisElement.dataset.translated = 'false'; button.innerHTML = '🌐 Traduire en français'; return; } // Store original text synopsisElement.dataset.original = originalText; // Show loading state button.disabled = true; button.innerHTML = '⏳ Traduction...'; synopsisElement.style.opacity = '0.5'; try { console.log('Translating text (first 100 chars):', originalText.substring(0, 100) + '...'); // Use backend translation API const response = await fetch(`${API_BASE}/translate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: originalText.substring(0, 5000) }) }); console.log('Translation API response status:', response.status); if (response.ok) { const data = await response.json(); console.log('Translation successful!'); synopsisElement.textContent = data.translatedText; synopsisElement.dataset.translated = 'true'; button.innerHTML = '🔄 Voir l\'original'; } else { const errorData = await response.json().catch(() => ({ detail: 'Unknown error' })); console.error('Translation API error:', errorData); throw new Error(errorData.detail || 'Translation failed'); } } catch (error) { console.error('Translation error:', error); synopsisElement.style.opacity = '1'; // Show user-friendly error const errorMessage = document.createElement('div'); errorMessage.style.cssText = 'margin-top: 10px; padding: 10px; background: rgba(255, 107, 107, 0.2); border-radius: 8px; font-size: 12px; color: #ff6b6b;'; errorMessage.innerHTML = ` ⚠️ Service de traduction temporairement indisponible.
Essayez à nouveau dans quelques instants. `; // Remove existing error message if any const existingError = synopsisElement.parentElement.querySelector('.translation-error'); if (existingError) { existingError.remove(); } errorMessage.className = 'translation-error'; synopsisElement.parentElement.appendChild(errorMessage); // Auto-remove error after 5 seconds setTimeout(() => { if (errorMessage.parentElement) { errorMessage.remove(); } }, 5000); } finally { button.disabled = false; synopsisElement.style.opacity = '1'; } } // Fallback translation - kept for compatibility but no longer used async function fallbackTranslation(text, synopsisElement, button) { // This function is deprecated since we now use backend translation console.log('Fallback translation called (should not happen)'); }