diff --git a/api.php b/api.php index 9c3b33c..b0a6205 100644 --- a/api.php +++ b/api.php @@ -69,7 +69,7 @@ function httpGet($url, $timeout = 3, $ua = null) { $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" + 'header' => "Accept: application/json\r\nAccept-Language: fr-FR,fr;q=0.9\r\n" ]]); return @file_get_contents($url, false, $ctx); } @@ -82,10 +82,8 @@ function httpGet($url, $timeout = 3, $ua = null) { CURLOPT_USERAGENT => $ua, CURLOPT_FOLLOWLOCATION => true, CURLOPT_HTTPHEADER => [ - 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept: application/json', 'Accept-Language: fr-FR,fr;q=0.9,en;q=0.8', - 'Accept-Encoding: identity', - 'Connection: keep-alive', ], ]); $res = curl_exec($ch); @@ -115,190 +113,152 @@ function extractYear($dateStr) { return ''; } -// ── DVDBlurayCovers.com Scraper ── -function fetchDVDBlurayCovers($title, $year = '', $format = 'bluray') { +// ── DVDCover.com via API REST WordPress ── +function fetchDVDCover($title, $year = '', $format = 'bluray') { if (empty($title)) 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'; $cleanTitle = cleanTitle($title); + if (empty($cleanTitle)) return null; - // URL de recherche DVDBlurayCovers - $searchUrl = "https://www.dvdbluraycovers.com/?s=" . urlencode($cleanTitle); + // API REST WordPress de DVDcover + // Endpoint : /wp-json/wp/v2/posts?search=TITRE&_embed + $searchUrl = "https://www.dvdcover.com/wp-json/wp/v2/posts?search=" . urlencode($cleanTitle) . "&_embed&per_page=10"; - $html = httpGet($searchUrl, 10, $ua); - if (!$html) { - error_log("DVDBlurayCovers: Échec recherche pour '$title'"); + $json = httpGet($searchUrl, 10); + if (!$json) { + error_log("DVDCover API: Échec requête pour '$title'"); return null; } - $result = [ - 'poster' => '', - 'title' => '', - 'format' => $format, - ]; - - // Parser le HTML avec DOMDocument - $dom = new DOMDocument(); - @$dom->loadHTML($html); - $xpath = new DOMXPath($dom); - - // Chercher les liens vers les pages de covers individuelles - // DVDBlurayCovers utilise des articles avec des liens vers les covers - $links = $xpath->query('//article[contains(@class,"post") or contains(@class,"entry")]//a/@href'); - - if ($links->length === 0) { - // Fallback : chercher tous les liens qui ressemblent à des pages de covers - $links = $xpath->query('//a[contains(@href,"/cover/") or contains(@href,"/covers/") or contains(@href,"/bluray/") or contains(@href,"/dvd/")]'); + $posts = json_decode($json, true); + if (!is_array($posts) || empty($posts)) { + error_log("DVDCover API: Aucun résultat pour '$title'"); + return null; } - $coverPages = []; - foreach ($links as $link) { - $href = $link->nodeValue; - if (strpos($href, 'http') !== 0) { - $href = 'https://www.dvdbluraycovers.com' . $href; - } - // Éviter les doublons et les pages de catégories/tags - if (!in_array($href, $coverPages) && - strpos($href, '/category/') === false && - strpos($href, '/tag/') === false && - strpos($href, '/page/') === false) { - $coverPages[] = $href; - } - if (count($coverPages) >= 5) break; // Limiter à 5 résultats max - } + $cleanTitleLower = strtolower($cleanTitle); + $bestPost = null; + $bestScore = 0; - // Parcourir les pages de covers trouvées - foreach ($coverPages as $coverPage) { - $coverHtml = httpGet($coverPage, 10, $ua); - if (!$coverHtml) continue; - - $domCover = new DOMDocument(); - @$domCover->loadHTML($coverHtml); - $xpathCover = new DOMXPath($domCover); - - // Extraire le titre de la page - $coverTitle = ''; - $titleNodes = $xpathCover->query('//h1[contains(@class,"entry-title") or contains(@class,"post-title")]'); - if ($titleNodes->length > 0) { - $coverTitle = trim($titleNodes->item(0)->textContent); - } else { - $titleNodes = $xpathCover->query('//title'); - if ($titleNodes->length > 0) { - $coverTitle = trim($titleNodes->item(0)->textContent); - // Nettoyer le titre (enlever " - DVDBlurayCovers" etc.) - $coverTitle = preg_replace('/\s*[-|]\s*DVDBlurayCovers.*$/i', '', $coverTitle); - } - } - - // Vérifier que le titre correspond au film recherché - $coverTitleLower = strtolower($coverTitle); - $searchTitleLower = strtolower($cleanTitle); + // Parcourir les posts et scorer leur pertinence + foreach ($posts as $post) { + $postTitle = $post['title']['rendered'] ?? ''; + $postTitleLower = strtolower($postTitle); $score = 0; - if (strpos($coverTitleLower, $searchTitleLower) !== false) { + + // Le titre du post contient-il le film recherché ? + if (strpos($postTitleLower, $cleanTitleLower) !== false) { $score += 50; } - if (!empty($year) && strpos($coverTitle, $year) !== false) { + + // Correspondance de l'année + if (!empty($year) && strpos($postTitle, $year) !== false) { $score += 30; } - if (stripos($coverTitle, $format) !== false) { + + // Correspondance du format + $formatLower = strtolower($format); + if (stripos($postTitle, $format) !== false || + stripos($postTitle, str_replace('-', ' ', $format)) !== false) { $score += 20; } - // Si le score est trop bas, on ignore ce résultat - if ($score < 30) { - continue; + // Bonus si le titre contient "cover" ou "jaquette" + if (stripos($postTitle, 'cover') !== false) { + $score += 10; } - // Chercher l'image principale de la jaquette - $poster = ''; + if ($score > $bestScore) { + $bestScore = $score; + $bestPost = $post; + } - // Méthode 1 : Chercher dans le contenu principal (entry-content) - $contentNodes = $xpathCover->query('//div[contains(@class,"entry-content") or contains(@class,"post-content")]//img'); - if ($contentNodes->length > 0) { - foreach ($contentNodes as $img) { - $src = $img->getAttribute('src'); - if (empty($src)) { - $src = $img->getAttribute('data-src'); - } - if (empty($src)) { - $src = $img->getAttribute('data-lazy-src'); - } - - // Vérifier que c'est une vraie image (pas un logo, icône, etc.) - if (!empty($src) && - strpos($src, 'logo') === false && - strpos($src, 'icon') === false && - strpos($src, 'banner') === false && - strpos($src, 'bg') === false && - strpos($src, 'button') === false && - strpos($src, 'social') === false && - (strpos($src, '.jpg') !== false || strpos($src, '.jpeg') !== false || strpos($src, '.png') !== false || strpos($src, '.webp') !== false)) { - $poster = $src; + // Score parfait, on arrête + if ($score >= 100) break; + } + + // Si aucun post ne correspond assez bien, prendre le premier + if (!$bestPost && !empty($posts)) { + $bestPost = $posts[0]; + } + + if (!$bestPost) return null; + + // Extraire l'image du post + $poster = ''; + + // Méthode 1 : Image mise en avant (_embedded.wp:featuredmedia) + if (isset($bestPost['_embedded']['wp:featuredmedia'][0])) { + $featured = $bestPost['_embedded']['wp:featuredmedia'][0]; + // Chercher la plus grande taille disponible + if (!empty($featured['media_details']['sizes'])) { + $sizes = $featured['media_details']['sizes']; + // Ordre de préférence : full > large > medium > thumbnail + $preferredSizes = ['full', 'large', 'medium', 'thumbnail']; + foreach ($preferredSizes as $size) { + if (isset($sizes[$size]['source_url'])) { + $poster = $sizes[$size]['source_url']; break; } } } - - // Méthode 2 : Chercher dans les figures - if (empty($poster)) { - $figureNodes = $xpathCover->query('//figure//img'); - if ($figureNodes->length > 0) { - foreach ($figureNodes as $img) { - $src = $img->getAttribute('src'); - if (empty($src)) { - $src = $img->getAttribute('data-src'); - } - if (!empty($src) && - strpos($src, 'logo') === false && - strpos($src, 'icon') === false && - (strpos($src, '.jpg') !== false || strpos($src, '.jpeg') !== false || strpos($src, '.png') !== false)) { - $poster = $src; - break; - } - } - } - } - - // Méthode 3 : Meta Open Graph - if (empty($poster)) { - $ogNodes = $xpathCover->query('//meta[@property="og:image"]/@content'); - if ($ogNodes->length > 0) { - $poster = $ogNodes->item(0)->nodeValue; - } - } - - // Méthode 4 : Toutes les images dans wp-content/uploads - if (empty($poster)) { - $allImgs = $xpathCover->query('//img[contains(@src,"wp-content/uploads")]'); - if ($allImgs->length > 0) { - foreach ($allImgs as $img) { - $src = $img->getAttribute('src'); - if (strpos($src, 'logo') === false && - strpos($src, 'icon') === false && - strpos($src, '300x') === false && - strpos($src, '150x') === false) { - $poster = $src; - break; - } - } - } - } - - // Si on a trouvé une image, on la garde - if (!empty($poster)) { - // S'assurer que l'URL est absolue - if (strpos($poster, 'http') !== 0) { - $poster = 'https://www.dvdbluraycovers.com' . (strpos($poster, '/') === 0 ? '' : '/') . ltrim($poster, '/'); - } - - $result['poster'] = $poster; - $result['title'] = $coverTitle; - break; + // Fallback : source_url direct + if (empty($poster) && !empty($featured['source_url'])) { + $poster = $featured['source_url']; } } - return (!empty($result['poster'])) ? $result : null; + // Méthode 2 : Images dans le contenu du post + if (empty($poster) && !empty($bestPost['content']['rendered'])) { + $content = $bestPost['content']['rendered']; + // Chercher les balises img + if (preg_match_all('/]+src=["\']([^"\']+)["\'][^>]*>/i', $content, $matches)) { + foreach ($matches[1] as $img) { + // Filtrer les images non pertinentes + if (strpos($img, 'logo') === false && + strpos($img, 'icon') === false && + strpos($img, 'banner') === false && + strpos($img, 'bg') === false && + strpos($img, 'button') === false && + strpos($img, 'social') === false && + (strpos($img, '.jpg') !== false || strpos($img, '.jpeg') !== false || strpos($img, '.png') !== false || strpos($img, '.webp') !== false)) { + $poster = $img; + break; + } + } + } + } + + // Méthode 3 : Images embarquées dans _embedded.wp:embedded + if (empty($poster) && isset($bestPost['_embedded']['wp:embedded'])) { + foreach ($bestPost['_embedded']['wp:embedded'] as $embedded) { + if (!empty($embedded['source_url'])) { + $poster = $embedded['source_url']; + break; + } + } + } + + if (empty($poster)) { + error_log("DVDCover API: Pas d'image trouvée pour le post ID " . ($bestPost['id'] ?? '?')); + return null; + } + + // S'assurer que l'URL est absolue + if (strpos($poster, 'http') !== 0) { + $poster = 'https://www.dvdcover.com' . (strpos($poster, '/') === 0 ? '' : '/') . ltrim($poster, '/'); + } + + // Supprimer les suffixes de taille WordPress pour avoir la version originale + // Ex: image-300x200.jpg → image.jpg + $poster = preg_replace('/-\d+x\d+(\.\w+)$/', '$1', $poster); + + return [ + 'poster' => $poster, + 'title' => $bestPost['title']['rendered'] ?? $cleanTitle, + 'format' => $format, + ]; } // ── API TMDB (uniquement pour les critiques) ── @@ -480,9 +440,9 @@ switch ($action) { } if ($type === 'videotheque') { - // DVDBlurayCovers pour vidéothèque + // DVDCover API pour vidéothèque $format = $result['format'] ?: 'Blu-ray'; - $dcData = fetchDVDBlurayCovers($titleForSearch, $result['year'], $format); + $dcData = fetchDVDCover($titleForSearch, $result['year'], $format); if (!empty($dcData)) { if (!empty($dcData['poster'])) $result['poster'] = $dcData['poster']; if (!empty($dcData['title'])) $result['title'] = $dcData['title']; @@ -526,10 +486,10 @@ switch ($action) { $stmt = $pdo->prepare($sql); $stmt->execute([$id, $data['title'] ?? '', $data['year'] ?? '', $data['director'] ?? '', $data['poster'] ?? '', $data['rating'] ?? 3.0, $data['review'] ?? '', $streaming]); } else { - // Vidéothèque : DVDBlurayCovers pour la jaquette + // Vidéothèque : DVDCover API pour la jaquette if (empty($data['poster']) && !empty($data['title'])) { $format = $data['format'] ?: 'Blu-ray'; - $dcData = fetchDVDBlurayCovers($data['title'], $data['year'] ?? '', $format); + $dcData = fetchDVDCover($data['title'], $data['year'] ?? '', $format); if (!empty($dcData['poster'])) { $data['poster'] = $dcData['poster']; } @@ -634,10 +594,10 @@ switch ($action) { $poster = $rowData['poster'] ?? ''; $director = ''; - // Récupération jaquette via DVDBlurayCovers + // Récupération jaquette via DVDCover API $cleanTitleForDC = cleanTitle($title); if (!empty($cleanTitleForDC)) { - $dcData = fetchDVDBlurayCovers($cleanTitleForDC, $year, $format); + $dcData = fetchDVDCover($cleanTitleForDC, $year, $format); if (!empty($dcData)) { if (!empty($dcData['poster'])) { $poster = $dcData['poster']; @@ -685,7 +645,7 @@ switch ($action) { } break; - case 'debug_dvdbluraycovers': + case 'debug_dvdcover': $title = $_GET['title'] ?? ''; $year = $_GET['year'] ?? ''; $format = $_GET['format'] ?? 'Blu-ray'; @@ -693,8 +653,53 @@ switch ($action) { if (!$title) { echo json_encode(['error' => 'Titre manquant']); exit; } $result = ['title' => $title, 'year' => $year, 'format' => $format]; - $data = fetchDVDBlurayCovers($title, $year, $format); - $result['data'] = $data; + + // Test direct de l'API + $cleanTitle = cleanTitle($title); + $apiUrl = "https://www.dvdcover.com/wp-json/wp/v2/posts?search=" . urlencode($cleanTitle) . "&_embed&per_page=5"; + $result['api_url'] = $apiUrl; + + $json = httpGet($apiUrl, 10); + if ($json) { + $posts = json_decode($json, true); + $result['posts_count'] = is_array($posts) ? count($posts) : 0; + $result['posts'] = []; + + if (is_array($posts)) { + foreach ($posts as $post) { + $postData = [ + 'id' => $post['id'] ?? null, + 'title' => $post['title']['rendered'] ?? '', + 'link' => $post['link'] ?? '', + ]; + + // Extraire les images + $imgs = []; + if (isset($post['_embedded']['wp:featuredmedia'][0])) { + $featured = $post['_embedded']['wp:featuredmedia'][0]; + if (!empty($featured['source_url'])) { + $imgs[] = $featured['source_url']; + } + if (!empty($featured['media_details']['sizes'])) { + foreach ($featured['media_details']['sizes'] as $size => $data) { + if (!empty($data['source_url'])) { + $imgs[] = $data['source_url'] . " ($size)"; + } + } + } + } + $postData['images'] = $imgs; + + $result['posts'][] = $postData; + } + } + } else { + $result['api_error'] = 'Impossible de joindre l\'API'; + } + + // Test de la fonction complète + $data = fetchDVDCover($title, $year, $format); + $result['fetch_result'] = $data; $result['status'] = $data ? 'OK' : 'AUCUN_RÉSULTAT'; echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);