diff --git a/api.php b/api.php index 81202ff..1aeeb68 100644 --- a/api.php +++ b/api.php @@ -64,19 +64,31 @@ function getTmdbApiKey($pdo) { return $row ? decryptData($row['key_value']) : null; } -function httpGet($url, $timeout = 3) { +function httpGet($url, $timeout = 3, $ua = null) { + if (!$ua) $ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'; + if (!function_exists('curl_init')) { - $ctx = stream_context_create(['http' => ['timeout' => $timeout, 'user_agent' => 'MonCinema/5.0']]); + $ctx = stream_context_create(['http' => [ + 'timeout' => $timeout, + 'user_agent' => $ua, + 'header' => "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: fr-FR,fr;q=0.9\r\n" + ]]); return @file_get_contents($url, false, $ctx); } $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => $timeout, - CURLOPT_CONNECTTIMEOUT => 2, + CURLOPT_CONNECTTIMEOUT => 3, CURLOPT_SSL_VERIFYPEER => false, - CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) MonCinema/5.0', - CURLOPT_FOLLOWLOCATION => true + CURLOPT_USERAGENT => $ua, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_HTTPHEADER => [ + 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Language: fr-FR,fr;q=0.9,en;q=0.8', + 'Accept-Encoding: identity', + 'Connection: keep-alive', + ], ]); $res = curl_exec($ch); curl_close($ch); @@ -105,150 +117,148 @@ function extractYear($dateStr) { return ''; } -// ── API DVDFr (NOUVELLE VERSION : Scraping HTML car l'API XML est morte) ── -function fetchDVDFr($ean, $pdo) { +// ── Scraping Fnac.com ── +function fetchFnac($ean) { if (empty($ean) || strlen($ean) < 8) return null; $ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'; - // Étape 1 : Recherche par EAN sur le site DVDfr - $searchUrl = "https://www.dvdfr.com/dvd/recherche.html?search=" . urlencode($ean); + // Étape 1 : Recherche par EAN + $searchUrl = "https://www.fnac.com/SearchResult/ResultList.aspx?Search=" . urlencode($ean); + $html = httpGet($searchUrl, 8, $ua); - $ch = curl_init($searchUrl); - curl_setopt_array($ch, [ - CURLOPT_RETURNTRANSFER => true, - CURLOPT_TIMEOUT => 10, - CURLOPT_CONNECTTIMEOUT => 5, - CURLOPT_SSL_VERIFYPEER => false, - CURLOPT_USERAGENT => $ua, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_HTTPHEADER => [ - 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', - 'Accept-Language: fr-FR,fr;q=0.9', - ], - ]); - $html = curl_exec($ch); - $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); - - if (!$html || $httpCode !== 200) { - error_log("DVDFr: Échec recherche HTML - HTTP $httpCode"); + if (!$html) { + error_log("Fnac: Échec recherche HTML pour EAN $ean"); return null; } - // Étape 2 : Extraire le lien vers la fiche du film - $dvdUrl = null; + // Étape 2 : Extraire le lien vers la fiche produit + $productUrl = null; - // Chercher un lien vers une fiche DVD/Blu-ray qui contient l'EAN ou le premier résultat - if (preg_match('/href=["\']([^"\']*\/dvd\/[^"\']*\.html)["\']/i', $html, $matches)) { - $dvdUrl = $matches[1]; - if (strpos($dvdUrl, 'http') !== 0) { - $dvdUrl = 'https://www.dvdfr.com' . $dvdUrl; + // Chercher un lien vers une fiche produit (format /a12345678/...) + if (preg_match('/href=["\']([^"\']*\/a\d+\/[^"\'\s]+\.html)["\']/i', $html, $matches)) { + $productUrl = $matches[1]; + if (strpos($productUrl, 'http') !== 0) { + $productUrl = 'https://www.fnac.com' . $productUrl; } } - if (!$dvdUrl) { - error_log("DVDFr: Aucune fiche trouvée pour EAN $ean"); + if (!$productUrl) { + error_log("Fnac: Aucune fiche trouvée pour EAN $ean"); return null; } - error_log("DVDFr: Fiche trouvée - $dvdUrl"); + error_log("Fnac: Fiche trouvée - $productUrl"); // Étape 3 : Récupérer la fiche complète - $ch2 = curl_init($dvdUrl); - curl_setopt_array($ch2, [ - CURLOPT_RETURNTRANSFER => true, - CURLOPT_TIMEOUT => 10, - CURLOPT_CONNECTTIMEOUT => 5, - CURLOPT_SSL_VERIFYPEER => false, - CURLOPT_USERAGENT => $ua, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_HTTPHEADER => [ - 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', - 'Accept-Language: fr-FR,fr;q=0.9', - ], - ]); - $ficheHtml = curl_exec($ch2); - curl_close($ch2); - + $ficheHtml = httpGet($productUrl, 8, $ua); if (!$ficheHtml) { - error_log("DVDFr: Impossible de charger la fiche"); + error_log("Fnac: Impossible de charger la fiche"); return null; } // Étape 4 : Extraire les données depuis le HTML $result = [ - 'poster' => '', - 'publisher' => '', - 'format' => '', - 'length' => '', - 'aspect' => '', - 'discs' => '', + 'title' => '', + 'poster' => '', + 'publisher' => '', + 'format' => '', + 'length' => '', + 'aspect' => '', + 'discs' => '', + 'actors' => '', + 'description' => '', ]; + // Extraction du titre + if (preg_match('/]+property=["\']og:title["\'][^>]+content=["\']([^"\']+)["\']/i', $ficheHtml, $m)) { + $result['title'] = trim($m[1]); + } elseif (preg_match('/]*class=["\']ProductTitle[^"\']*["\'][^>]*>([^<]+)<\/h1>/i', $ficheHtml, $m)) { + $result['title'] = trim(strip_tags($m[1])); + } elseif (preg_match('/]*>([^<]+)<\/h1>/i', $ficheHtml, $m)) { + $result['title'] = trim(strip_tags($m[1])); + } + // Extraction de l'affiche (plusieurs méthodes fallback) - // Méthode 1 : Chercher les balises meta Open Graph - if (preg_match('/]+property=["\']og:image["\'][^>]+content=["\']([^"\']+)["\']/i', $ficheHtml, $matches)) { - $result['poster'] = $matches[1]; - } - // Méthode 2 : Chercher dans les balises link - elseif (preg_match('/]+rel=["\']image_src["\'][^>]+href=["\']([^"\']+)["\']/i', $ficheHtml, $matches)) { - $result['poster'] = $matches[1]; - } - // Méthode 3 : Chercher une image avec "cover" ou "pochette" dans le nom - elseif (preg_match('/src=["\']([^"\']+(?:cover|pochette|affiche)[^"\']*\.(?:jpg|jpeg|png|webp))["\']/i', $ficheHtml, $matches)) { - $result['poster'] = $matches[1]; + if (preg_match('/]+property=["\']og:image["\'][^>]+content=["\']([^"\']+)["\']/i', $ficheHtml, $m)) { + $result['poster'] = $m[1]; + } elseif (preg_match('/]+rel=["\']image_src["\'][^>]+href=["\']([^"\']+)["\']/i', $ficheHtml, $m)) { + $result['poster'] = $m[1]; + } elseif (preg_match('/src=["\']([^"\']+(?:cdn\.fnac\.com|fnac\.com)[^"\']*\.(?:jpg|jpeg|png|webp))["\']/i', $ficheHtml, $m)) { + $result['poster'] = $m[1]; } - // Extraction de l'éditeur - if (preg_match('/(?:éditeur|distributeur|studio)\s*[:<\/]>\s*([^<]+)/i', $ficheHtml, $matches)) { - $result['publisher'] = trim(strip_tags($matches[1])); + // Extraction des caractéristiques techniques (bloc "Fiche technique") + // On cherche les paires label/valeur dans le tableau de caractéristiques + if (preg_match('/]*class=["\'][^"\']*fiche-technique[^"\']*["\'][^>]*>(.*?)<\/div>/is', $ficheHtml, $bloc)) { + $techHtml = $bloc[1]; + } else { + $techHtml = $ficheHtml; } - // Extraction du format - if (preg_match('/(4k\s*ultra\s*hd|ultra\s*hd|blu[\s-]?ray|dvd|coffret)/i', $ficheHtml, $matches)) { - $format = strtoupper(trim($matches[1])); - if (strpos($format, '4K') !== false || strpos($format, 'ULTRA') !== false) { - $result['format'] = '4K Ultra HD'; - } elseif (strpos($format, 'BLU') !== false) { - $result['format'] = 'Blu-ray'; - } elseif (strpos($format, 'DVD') !== false) { - $result['format'] = 'DVD'; - } elseif (strpos($format, 'COFFRET') !== false) { - $result['format'] = 'Coffret'; - } + // Éditeur / Distributeur + if (preg_match('/(?:Éditeur|Editeur|Distributeur|Studio|Label)\s*<\/[^>]+>\s*<[^>]+>([^<]+)/i', $techHtml, $m)) { + $result['publisher'] = trim(strip_tags($m[1])); + } elseif (preg_match('/(?:Éditeur|Editeur|Distributeur|Studio|Label)\s*[:<\/][^>]*>\s*([^<\n]+)/i', $ficheHtml, $m)) { + $result['publisher'] = trim(strip_tags($m[1])); } - // Extraction de la durée - if (preg_match('/(?:durée|duree|duration)\s*[:<\/]>\s*(\d+)\s*(?:min|mn|h)/i', $ficheHtml, $matches)) { - $result['length'] = trim($matches[1]) . ' min'; + // Durée + if (preg_match('/(?:Durée|Duree|Durata)\s*<\/[^>]+>\s*<[^>]+>(\d+)\s*(?:min|mn|h)/i', $techHtml, $m)) { + $result['length'] = trim($m[1]) . ' min'; + } elseif (preg_match('/(?:Durée|Duree)\s*[:<\/][^>]*>\s*(\d+)\s*(?:min|mn|h)/i', $ficheHtml, $m)) { + $result['length'] = trim($m[1]) . ' min'; } - // Extraction de l'aspect ratio - if (preg_match('/(?:format\s*image|aspect\s*ratio|ratio)\s*[:<\/]>\s*([0-9.]+\s*[:\.]\s*[0-9.]+)/i', $ficheHtml, $matches)) { - $result['aspect'] = trim($matches[1]); + // Nombre de disques + if (preg_match('/(?:Nombre\s*de\s*disques?|Nb\s*disques?|Disques?)\s*<\/[^>]+>\s*<[^>]+>(\d+)/i', $techHtml, $m)) { + $result['discs'] = trim($m[1]); + } elseif (preg_match('/(?:Nombre\s*de\s*disques?|Nb\s*disques?)\s*[:<\/][^>]*>\s*(\d+)/i', $ficheHtml, $m)) { + $result['discs'] = trim($m[1]); } - // Extraction du nombre de disques - if (preg_match('/(?:nombre\s*de\s*disques?|disques?|nb\s*disques?)\s*[:<\/]>\s*(\d+)/i', $ficheHtml, $matches)) { - $result['discs'] = trim($matches[1]); + // Format image / Aspect ratio + if (preg_match('/(?:Format\s*(?:image|vidéo|video)|Ratio|Aspect\s*ratio)\s*<\/[^>]+>\s*<[^>]+>([0-9.]+\s*[:\.]\s*[0-9.]+)/i', $techHtml, $m)) { + $result['aspect'] = trim($m[1]); } - // Nettoyage des données - $result = array_map(function($val) { - return trim(strip_tags(html_entity_decode($val, ENT_QUOTES | ENT_HTML5, 'UTF-8'))); - }, $result); + // Acteurs / Casting + if (preg_match('/(?:Acteurs?|Casting|Avec)\s*<\/[^>]+>\s*<[^>]+>([^<]+)/i', $techHtml, $m)) { + $result['actors'] = trim(strip_tags($m[1])); + } elseif (preg_match('/(?:Avec|Acteurs?)\s*[:<\/][^>]*>\s*([^<\n]+)/i', $ficheHtml, $m)) { + $result['actors'] = trim(strip_tags($m[1])); + } - return (!empty($result['poster']) || !empty($result['publisher'])) ? $result : null; + // Description / Synopsis + if (preg_match('/]*class=["\'][^"\']*product-description[^"\']*["\'][^>]*>(.*?)<\/div>/is', $ficheHtml, $m)) { + $result['description'] = trim(strip_tags($m[1])); + } elseif (preg_match('/]+property=["\']og:description["\'][^>]+content=["\']([^"\']+)["\']/i', $ficheHtml, $m)) { + $result['description'] = trim($m[1]); + } + + // Format (détection depuis le titre ou la fiche) + $formatText = $result['title'] . ' ' . $ficheHtml; + if (preg_match('/(4K\s*Ultra\s*HD|Ultra\s*HD|4K|Blu[\s-]?Ray|DVD|Coffret)/i', $formatText, $m)) { + $fmt = strtoupper(trim($m[1])); + if (strpos($fmt, '4K') !== false || strpos($fmt, 'ULTRA') !== false) $result['format'] = '4K Ultra HD'; + elseif (strpos($fmt, 'BLU') !== false) $result['format'] = 'Blu-ray'; + elseif (strpos($fmt, 'DVD') !== false) $result['format'] = 'DVD'; + elseif (strpos($fmt, 'COFFRET') !== false) $result['format'] = 'Coffret'; + } + + // Nettoyage final + foreach ($result as $k => $v) { + $result[$k] = trim(html_entity_decode($v, ENT_QUOTES | ENT_HTML5, 'UTF-8')); + } + + return (!empty($result['title']) || !empty($result['poster'])) ? $result : null; } -// ── 2. API TMDB (SANS CACHE - TITRE FRANÇAIS) ── +// ── API TMDB (SANS CACHE - TITRE FRANÇAIS) ── function fetchTMDBFull($title, $year, $apiKey, $pdo) { if (empty($apiKey) || empty($title)) return null; $cleanTitle = cleanTitle($title); - // 🔥 SUPPRESSION DES APPELS À getCache() QUI N'EXISTE PLUS - $searchUrl = "https://api.themoviedb.org/3/search/movie?api_key={$apiKey}&query=" . urlencode($cleanTitle) . "&year={$year}&language=fr-FR"; $searchRes = httpGet($searchUrl, 5); $searchData = $searchRes ? json_decode($searchRes, true) : []; @@ -316,8 +326,6 @@ function fetchTMDBFull($title, $year, $apiKey, $pdo) { 'cast' => $cast ]; - // 🔥 SUPPRESSION DE L'APPEL À setCache() QUI N'EXISTE PLUS - return $result; } @@ -392,24 +400,33 @@ switch ($action) { 'length' => '', 'number_of_discs' => 1, 'aspect_ratio' => '', 'actors' => '' ]; - $dvdfrData = fetchDVDFr($ean, $pdo); - if (!empty($dvdfrData)) { - if (!empty($dvdfrData['poster'])) $result['poster'] = $dvdfrData['poster']; - if (!empty($dvdfrData['publisher'])) $result['publisher'] = $dvdfrData['publisher']; - if (!empty($dvdfrData['format'])) $result['format'] = $dvdfrData['format']; - if (!empty($dvdfrData['length'])) $result['length'] = $dvdfrData['length']; - if (!empty($dvdfrData['aspect'])) $result['aspect_ratio'] = $dvdfrData['aspect']; - if (!empty($dvdfrData['discs'])) $result['number_of_discs'] = (int)$dvdfrData['discs']; + // Étape 1 : Scraping Fnac + $fnacData = fetchFnac($ean); + $titleForTmdb = ''; + if (!empty($fnacData)) { + if (!empty($fnacData['title'])) $result['title'] = $fnacData['title']; + if (!empty($fnacData['poster'])) $result['poster'] = $fnacData['poster']; + if (!empty($fnacData['publisher'])) $result['publisher'] = $fnacData['publisher']; + if (!empty($fnacData['format'])) $result['format'] = $fnacData['format']; + if (!empty($fnacData['length'])) $result['length'] = $fnacData['length']; + if (!empty($fnacData['aspect'])) $result['aspect_ratio'] = $fnacData['aspect']; + if (!empty($fnacData['discs'])) $result['number_of_discs'] = (int)$fnacData['discs']; + if (!empty($fnacData['actors'])) $result['actors'] = $fnacData['actors']; + + $titleForTmdb = cleanTitle($fnacData['title'] ?? ''); } + // Étape 2 : TMDB pour métadonnées film (réalisateur, vraie affiche, année) $tmdbKey = getTmdbApiKey($pdo); - if ($tmdbKey && !empty($result['publisher'])) { - $tmdbData = fetchTMDBFull($result['publisher'], '', $tmdbKey, $pdo); + if ($tmdbKey && !empty($titleForTmdb)) { + $tmdbData = fetchTMDBFull($titleForTmdb, '', $tmdbKey, $pdo); if ($tmdbData) { if (!empty($tmdbData['title'])) $result['title'] = $tmdbData['title']; if (!empty($tmdbData['year'])) $result['year'] = $tmdbData['year']; if (!empty($tmdbData['director'])) $result['director'] = $tmdbData['director']; + if (!empty($tmdbData['length'])) $result['length'] = $tmdbData['length']; if (!empty($tmdbData['cast'])) $result['actors'] = implode(', ', $tmdbData['cast']); + if (!empty($tmdbData['poster'])) $result['poster'] = $tmdbData['poster']; // TMDB prioritaire } } echo json_encode(['success' => true, 'data' => $result]); @@ -508,18 +525,41 @@ switch ($action) { $poster = $rowData['poster'] ?? ''; $director = ''; + // Enrichissement via Fnac si on a un EAN if (!empty($ean)) { - $dvdfrData = fetchDVDFr($ean, $pdo); - if (!empty($dvdfrData)) { - if (!empty($dvdfrData['poster'])) $poster = $dvdfrData['poster']; - if (!empty($dvdfrData['publisher'])) $publisher = $dvdfrData['publisher']; - if (!empty($dvdfrData['format'])) $format = $dvdfrData['format']; - if (!empty($dvdfrData['length']) && empty($length)) $length = $dvdfrData['length']; - if (!empty($dvdfrData['aspect']) && empty($aspect)) $aspect = $dvdfrData['aspect']; - if (!empty($dvdfrData['discs']) && $discs === 1) $discs = (int)$dvdfrData['discs']; + $fnacData = fetchFnac($ean); + if (!empty($fnacData)) { + if ((empty($title) || $title === 'Sans titre') && !empty($fnacData['title'])) { + $title = cleanTitle($fnacData['title']); + } + if (empty($publisher) && !empty($fnacData['publisher'])) { + $publisher = $fnacData['publisher']; + } + if (empty($format) && !empty($fnacData['format'])) { + $format = $fnacData['format']; + } + if (empty($length) && !empty($fnacData['length'])) { + $length = $fnacData['length']; + } + if (empty($aspect) && !empty($fnacData['aspect'])) { + $aspect = $fnacData['aspect']; + } + if ($discs === 1 && !empty($fnacData['discs'])) { + $discs = (int)$fnacData['discs']; + } + if (empty($actors) && !empty($fnacData['actors'])) { + $actors = $fnacData['actors']; + } + if (empty($poster) && !empty($fnacData['poster'])) { + $poster = $fnacData['poster']; + } + if (empty($description) && !empty($fnacData['description'])) { + $description = $fnacData['description']; + } } } + // Enrichissement via TMDB if ($tmdbApiKey && !empty($title)) { $tmdbTitle = $title; $tmdbTitle = preg_replace('/\s*[\[\(].*?[\]\)]\s*/', '', $tmdbTitle); @@ -542,6 +582,7 @@ switch ($action) { if (empty($length) && !empty($tmdbData['length'])) $length = $tmdbData['length']; if (!empty($tmdbData['overview'])) $description = $tmdbData['overview']; if (!empty($tmdbData['cast'])) $actors = implode(', ', $tmdbData['cast']); + if (!empty($tmdbData['poster'])) $poster = $tmdbData['poster']; // TMDB prioritaire } } @@ -607,130 +648,542 @@ switch ($action) { } break; - case 'debug_dvdfr': - // 🔥 DEBUG SANS AUTHENTIFICATION (à supprimer après test) - $ean = $_GET['ean'] ?? ''; - if (!$ean) { echo json_encode(['error' => 'EAN manquant']); exit; } - - $result = ['ean' => $ean, 'steps' => []]; - - // Étape 1 : Recherche - $ua = 'MonCinema/1.0 (collection privée; contact@moncineapp.fr)'; - $searchUrl = "https://www.dvdfr.com/api/search.php?gencode=" . urlencode($ean); - $result['steps']['search_url'] = $searchUrl; - - $ch = curl_init($searchUrl); - curl_setopt_array($ch, [ - CURLOPT_RETURNTRANSFER => true, - CURLOPT_TIMEOUT => 8, - CURLOPT_CONNECTTIMEOUT => 5, - CURLOPT_SSL_VERIFYPEER => false, - CURLOPT_USERAGENT => $ua, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_HTTPHEADER => ['Accept: application/xml, text/xml, */*'], - ]); - $res = curl_exec($ch); - $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - $curlError = curl_error($ch); - curl_close($ch); - - $result['steps']['search'] = [ - 'http_code' => $httpCode, - 'curl_error' => $curlError, - 'response_length' => strlen($res ?: ''), - 'response_preview' => substr($res ?: '', 0, 500) - ]; - - if (!$res || $httpCode !== 200) { - $result['error'] = "Échec recherche HTTP $httpCode"; - echo json_encode($result); - exit; + case 'debug_fnac': + $ean = $_GET['ean'] ?? ''; + if (!$ean) { echo json_encode(['error' => 'EAN manquant']); exit; } + + $result = ['ean' => $ean]; + $data = fetchFnac($ean); + $result['data'] = $data; + $result['status'] = $data ? 'OK' : 'AUCUN_RÉSULTAT'; + + echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + exit; + break; +} +?> + \ No newline at end of file