Test

Prêt.
`; } async function openCinemaView() { if (currentResults.length === 0) { alert("Aucun film à afficher. Lancez d'abord une recherche."); return; } const win = window.open("", "_blank"); if (!win) { alert("Merci d'autoriser les fenêtres pop-up pour ce site afin d'ouvrir la vue cinéma."); return; } win.document.open(); win.document.write(renderCinemaHtml(currentResults, [], true)); win.document.close(); let upcomingFilms = []; try { const listData = await fetchWikidataOnce(buildUpcomingListQuery(UPCOMING_LIMIT)); const rows = listData.results.bindings; if (rows.length) { const uris = rows.map(row => row.item.value); const detailsData = await fetchWikidataOnce(buildDetailsQuery(uris)); const detailsByUri = {}; detailsData.results.bindings.forEach(row => { detailsByUri[row.item.value] = row; }); upcomingFilms = rows.map(row => convertResult(row, detailsByUri[row.item.value])); } } catch (error) { console.warn("Films à venir indisponibles :", error); } if (!win.closed) { win.document.open(); win.document.write(renderCinemaHtml(currentResults, upcomingFilms, false)); win.document.close(); } } /* ========================================================= EVENEMENTS ========================================================= */ cancelButton.addEventListener("click", function () { if (abortController) abortController.abort(); }); document.getElementById("wdfc-searchButton").addEventListener("click", function () { currentPage = 1; searchWikidata(); }); searchInput.addEventListener("keydown", function (event) { if (event.key === "Enter") { currentPage = 1; searchWikidata(); } }); nextButton.addEventListener("click", function () { if (currentPage < totalPages) { currentPage++; searchWikidata(); } }); previousButton.addEventListener("click", function () { if (currentPage > 1) { currentPage--; searchWikidata(); } }); document.getElementById("wdfc-pageButton").addEventListener("click", function () { let page = Number(pageInput.value); if (page < 1) page = 1; if (totalKnown && page > totalPages) page = totalPages; currentPage = page; searchWikidata(); }); document.getElementById("wdfc-reloadButton").addEventListener("click", searchWikidata); allLanguagesCheckbox.addEventListener("change", function () { languageOptionsContainer.classList.toggle("is-disabled", allLanguagesCheckbox.checked); }); destinationSiteSelect.addEventListener("change", function () { customSiteUrlInput.disabled = destinationSiteSelect.value !== "custom"; if (currentResults.length) renderResults(); }); const TITLE_COLOR_STORAGE_KEY = "wdfcFilmCatalogTitleColor"; titleColorPicker.addEventListener("input", function () { document.querySelector(".wdfc-app").style.setProperty("--wdfc-title-color", titleColorPicker.value); try { localStorage.setItem(TITLE_COLOR_STORAGE_KEY, titleColorPicker.value); } catch (error) { console.warn("Impossible de sauvegarder la couleur choisie :", error); } }); customSiteUrlInput.addEventListener("input", function () { if (destinationSiteSelect.value === "custom" && currentResults.length) { renderResults(); } }); youtubeButton.addEventListener("click", function () { if (currentResults.length === 0) { alert("Aucun film à afficher pour le moment. Lancez d'abord une recherche."); return; } const win = window.open("", "_blank"); if (!win) { alert("Merci d'autoriser les fenêtres pop-up pour ce site afin d'ouvrir la sélection YouTube."); return; } const linksHtml = currentResults.map(film => { const title = film.title || "Titre non renseigné"; const query = encodeURIComponent(title + " film complet"); const url = "https://www.youtube.com/results?search_query=" + query; return "
  • " + escapeHTML(title) + "
  • "; }).join(""); win.document.write( "" + "Sélection sur YouTube" + "" + "

    🎬 Recherche YouTube — sélection actuelle (" + currentResults.length + " film(s))

    " + "
      " + linksHtml + "
    " ); win.document.close(); }); cinemaButton.addEventListener("click", openCinemaView); document.getElementById("wdfc-resetButton").addEventListener("click", function () { if (isLoading && abortController) abortController.abort(); searchInput.value = ""; searchCategory.value = "title"; destinationSiteSelect.value = "justwatch"; customSiteUrlInput.value = ""; customSiteUrlInput.disabled = true; dayInput.value = ""; monthInput.value = ""; yearInput.value = ""; yearFromInput.value = ""; yearToInput.value = ""; dateFromInput.value = ""; dateToInput.value = ""; titleFilter.value = "all"; dateFilter.value = "all"; typeFilter.value = "film_animation"; document.querySelectorAll(".wdfc-languageCheckbox").forEach(cb => { cb.checked = (cb.value === "fr"); }); allLanguagesCheckbox.checked = false; languageOptionsContainer.classList.remove("is-disabled"); religiousFilter.checked = true; document.querySelectorAll(".wdfc-lexicalKeyword").forEach(cb => { cb.checked = false; }); lexicalCustomInput.value = ""; excludeWordsInput.value = ""; sortSelect.value = "newest"; pageSizeSelect.value = "20"; maxResultsInput.value = ""; currentPage = 1; totalPages = 1; totalKnown = true; hasNextPageUnknown = false; currentResults = []; wikidataTotalCount = null; tmdbTotalCount = null; wikidataCountToken++; updateTotalCounter(); resultsElement.innerHTML = ""; progressBar.style.width = "0%"; pageInput.value = 1; pageInfo.textContent = "Page 1 / 1"; statusElement.textContent = "Filtres réinitialisés."; }); document.getElementById("wdfc-exportButton").addEventListener("click", function () { if (currentResults.length === 0) { alert("Aucun résultat à exporter."); return; } const header = ["Titre", "Réalisateur(s)", "Casting", "Date", "Lien", "Wikidata", "Source"]; const rows = currentResults.map(film => [ film.title, film.directors, film.cast, film.date, film.primaryLink, film.uri, film.source ]); const csv = [header, ...rows] .map(row => row.map(value => '"' + String(value || "").replace(/"/g, '""') + '"' ).join(";")) .join("\n"); const blob = new Blob(["\ufeff" + csv], { type: "text/csv;charset=utf-8" }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = "films-wikidata.csv"; link.click(); URL.revokeObjectURL(url); }); /* ========================================================= INITIALISATION ========================================================= */ statusElement.textContent = "Prêt. Entrez un terme ou lancez une recherche."; loadAvailableLanguages(); try { const savedTitleColor = localStorage.getItem(TITLE_COLOR_STORAGE_KEY); if (savedTitleColor) { titleColorPicker.value = savedTitleColor; document.querySelector(".wdfc-app").style.setProperty("--wdfc-title-color", savedTitleColor); } } catch (error) { console.warn("Impossible de relire la couleur sauvegardée :", error); } /* ========================================================================================= MODULE TMDB - PLUS DE FILMS CHRETIENS (imbriqué ici pour partager le scope avec le reste du script — currentResults, renderResults, etc. restent accessibles sans passer par window global) ========================================================================================= */ (function () { "use strict"; const TMDB_API_KEY = "af5576c58fe6fd7fab447fbb4dd2423a"; const TMDB_API = "https://api.themoviedb.org/3"; const TMDB_LANGUAGE = "fr-FR"; const TMDB_REGION = "FR"; const TMDB_MAX_PAGES = 500; // limite réelle de l'API /discover const TMDB_PAGE_BATCH_SIZE = 6; // pages récupérées en parallèle par lot const CHRISTIAN_KEYWORDS = [ "christianity", "christian", "christian faith", "christian film", "religious film", "religion", "faith", "bible", "biblical", "jesus christ", "jesus", "gospel", "resurrection", "crucifixion", "church", "evangelical christianity" ]; const typeFilterEl = document.getElementById("wdfc-typeFilter"); const pageSizeSelectEl = document.getElementById("wdfc-pageSize"); const maxResultsInputEl = document.getElementById("wdfc-maxResults"); const sortSelectEl = document.getElementById("wdfc-sort"); const religiousFilterEl = document.getElementById("wdfc-religiousFilter"); const allLanguagesCheckboxEl = document.getElementById("wdfc-allLanguages"); const languageOptionsContainerEl = document.getElementById("wdfc-languageOptions"); const statusElementEl = document.getElementById("wdfc-status"); const progressBarEl = document.getElementById("wdfc-progressBar"); const resultsElementEl = document.getElementById("wdfc-results"); if (!typeFilterEl || !pageSizeSelectEl || !maxResultsInputEl || !sortSelectEl) { console.warn("TMDB Plus : un ou plusieurs filtres nécessaires sont absents."); return; } function createTMDBButton() { if (document.getElementById("wdfc-tmdbPlusButton")) return; const button = document.createElement("button"); button.id = "wdfc-tmdbPlusButton"; button.type = "button"; button.textContent = "🎬 Plus de films"; const searchButtonEl = document.querySelector( '#wdfc-searchButton, [data-action="search"], button[type="submit"]' ); if (searchButtonEl && searchButtonEl.parentNode) { searchButtonEl.parentNode.appendChild(button); } else if (resultsElementEl && resultsElementEl.parentNode) { resultsElementEl.parentNode.insertBefore(button, resultsElementEl); } else { document.body.appendChild(button); } const status = document.createElement("div"); status.id = "wdfc-tmdbPlusStatus"; if (button.parentNode) { button.parentNode.insertBefore(status, button.nextSibling); } button.addEventListener("click", function () { searchTMDB(); }); } function setTMDBStatus(message) { const element = document.getElementById("wdfc-tmdbPlusStatus"); if (element) { element.textContent = message; element.style.display = "block"; } if (statusElementEl) { statusElementEl.textContent = message; } } function normalizeTitle(value) { return String(value || "") .toLowerCase() .normalize("NFD") .replace(/[\u0300-\u036f]/g, "") .replace(/[^\p{L}\p{N}]+/gu, " ") .trim(); } function getYear(value) { if (!value) return ""; const match = String(value).match(/\b(18|19|20)\d{2}\b/); return match ? match[0] : ""; } function getTMDBType() { const value = typeFilterEl.value; if (value === "film") return ["movie"]; if (value === "animation") return ["animation"]; if (value === "film_animation") return ["movie", "animation"]; if (value === "all") return ["movie", "animation"]; return ["movie"]; } function getSelectedLanguagesTMDB() { if (allLanguagesCheckboxEl && allLanguagesCheckboxEl.checked) return []; if (!languageOptionsContainerEl) return []; const checked = languageOptionsContainerEl.querySelectorAll('input[type="checkbox"]:checked'); const languages = []; checked.forEach(function (checkbox) { const language = checkbox.value || checkbox.dataset.lang || checkbox.dataset.language || ""; if (language) languages.push(language.toLowerCase()); }); return [...new Set(languages)]; } async function findKeywordId(keyword) { const params = new URLSearchParams({ api_key: TMDB_API_KEY, query: keyword, page: "1" }); const response = await fetch(TMDB_API + "/search/keyword?" + params.toString()); if (!response.ok) throw new Error("Recherche de mot-clé TMDB : HTTP " + response.status); const data = await response.json(); if (!data.results || !data.results.length) return null; const exact = data.results.find(function (item) { return normalizeTitle(item.name) === normalizeTitle(keyword); }); return (exact || data.results[0]).id; } async function getKeywordIds() { const results = await Promise.all( CHRISTIAN_KEYWORDS.map(function (keyword) { return findKeywordId(keyword).catch(function (error) { console.warn("Mot-clé ignoré :", keyword, error); return null; }); }) ); return [...new Set(results.filter(Boolean))]; } async function fetchTMDBPage(keywordsParam, page, animationOnly, originalLanguage) { const params = new URLSearchParams({ api_key: TMDB_API_KEY, language: TMDB_LANGUAGE, region: TMDB_REGION, with_keywords: keywordsParam, sort_by: "primary_release_date.desc", include_adult: "false", page: String(page) }); if (animationOnly) params.set("with_genres", "16"); if (originalLanguage) params.set("with_original_language", originalLanguage); const response = await fetch(TMDB_API + "/discover/movie?" + params.toString()); if (!response.ok) throw new Error("TMDB Discover : HTTP " + response.status); return await response.json(); } function isExistingResult(movie) { if (!Array.isArray(currentResults)) return false; const tmdbId = String(movie.id); const title = normalizeTitle(movie.title || movie.original_title); const year = getYear(movie.release_date); return currentResults.some(function (existing) { if (existing.tmdbId && String(existing.tmdbId) === tmdbId) return true; const existingTitle = normalizeTitle(existing.title); const existingYear = getYear(existing.date || existing.year || existing.release_date); if (title && existingTitle && title === existingTitle && year && existingYear && year === existingYear) return true; if (title && existingTitle && title === existingTitle && (!year || !existingYear)) return true; return false; }); } function convertTMDBResult(movie) { const title = movie.title || movie.original_title || "Titre non renseigné"; return { uri: "tmdb:" + movie.id, title: title, poster: "", image: "", directors: "", cast: "", actors: "", date: movie.release_date || "", year: getYear(movie.release_date), description: movie.overview || "", imdb: "", website: "", primaryLink: "https://www.themoviedb.org/movie/" + movie.id, tmdbId: String(movie.id), source: "TMDB", isTMDB: true }; } function addTMDBResults(results, map) { (results || []).forEach(function (movie) { const title = normalizeTitle(movie.title || movie.original_title); const year = getYear(movie.release_date); const key = title + "|" + year; if (!map.has(key)) map.set(key, movie); }); } // Ajoute au filtre de langue existant (celui utilisé pour Wikidata) toute // langue d'origine rencontrée parmi les films TMDB récupérés et qui n'y // figurait pas déjà. N'entoche pas les cases déjà cochées par // l'utilisateur — les nouvelles langues sont ajoutées décochées. function addDiscoveredLanguages(movies) { if (!languageOptionsContainerEl) return; const existingCodes = new Set( Array.from(languageOptionsContainerEl.querySelectorAll(".wdfc-languageCheckbox")) .map(function (cb) { return cb.value; }) ); const newCodes = new Set(); movies.forEach(function (movie) { const code = String(movie.original_language || "").toLowerCase(); if (code && !existingCodes.has(code)) newCodes.add(code); }); if (!newCodes.size) return; Array.from(newCodes).sort().forEach(function (code) { const label = document.createElement("label"); const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.className = "wdfc-languageCheckbox"; checkbox.value = code; const span = document.createElement("span"); span.textContent = (typeof languageDisplayName === "function" ? languageDisplayName(code) : code) + " (" + code + ")"; label.appendChild(checkbox); label.appendChild(span); languageOptionsContainerEl.appendChild(label); existingCodes.add(code); }); if (languageCountLabel) { languageCountLabel.textContent = "(" + existingCodes.size + " langues trouvées)"; } } async function fetchTMDBPagesInBatches(keywordsParam, pageNumbers, animationOnly, language, unique, target, comboLabel) { for (let i = 0; i < pageNumbers.length; i += TMDB_PAGE_BATCH_SIZE) { if (unique.size >= target) return; const batch = pageNumbers.slice(i, i + TMDB_PAGE_BATCH_SIZE); setTMDBStatus( "⏳ TMDB : " + comboLabel + " — pages " + batch[0] + " à " + batch[batch.length - 1] + " / " + pageNumbers[pageNumbers.length - 1] + " (" + unique.size + " film(s) trouvé(s) pour l'instant)" ); const pages = await Promise.all( batch.map(function (p) { return fetchTMDBPage(keywordsParam, p, animationOnly, language) .catch(function () { return null; }); }) ); pages.forEach(function (data) { if (data) addTMDBResults(data.results, unique); }); } } async function collectTMDBResults() { const selectedTypes = getTMDBType(); const languages = getSelectedLanguagesTMDB(); // [] = toutes les langues, sinon liste de codes ISO const maxRaw = maxResultsInputEl.value.trim(); const maxResults = maxRaw ? Math.max(1, Number(maxRaw)) : null; // Sans "Nombre maxi de résultats", on récupère tout ce que TMDB peut // renvoyer pour ces mots-clés (borné uniquement par la vraie limite // de l'API, TMDB_MAX_PAGES) plutôt qu'un plafond arbitraire. const target = maxResults || Infinity; const keywordIds = await getKeywordIds(); if (!keywordIds.length) { throw new Error("Aucun mot-clé chrétien reconnu par TMDB."); } // Tous les mots-clés sont combinés en UNE seule requête (logique OU, // séparateur "|" côté API TMDB) au lieu d'une requête par mot-clé : // ça divise le nombre d'appels réseau par ~16 par rapport à l'ancienne // version. const keywordsParam = keywordIds.join("|"); const unique = new Map(); // Un "seau" par langue sélectionnée : si l'utilisateur a coché des // langues précises, on demande directement à TMDB de filtrer par // langue d'origine (with_original_language) au lieu de tout // télécharger puis trier côté navigateur. const languageBuckets = languages.length ? languages : [null]; outer: for (const type of selectedTypes) { const animationOnly = type === "animation"; for (const language of languageBuckets) { if (unique.size >= target) break outer; const comboLabel = type + (language ? " / " + language : ""); setTMDBStatus("⏳ TMDB : recherche " + comboLabel + "..."); try { const firstPage = await fetchTMDBPage(keywordsParam, 1, animationOnly, language); addTMDBResults(firstPage.results, unique); const totalPages = Math.min(Number(firstPage.total_pages || 1), TMDB_MAX_PAGES); if (totalPages > 1 && unique.size < target) { const pageNumbers = []; for (let p = 2; p <= totalPages; p++) pageNumbers.push(p); // Les pages sont récupérées par lots de plusieurs // requêtes en parallèle (au lieu d'une boucle // séquentielle page par page) : c'est ce qui accélère // le plus la recherche, tout en restant raisonnable // pour ne pas se faire limiter par TMDB. await fetchTMDBPagesInBatches( keywordsParam, pageNumbers, animationOnly, language, unique, target, comboLabel ); } } catch (error) { console.warn("TMDB : combo ignoré (" + comboLabel + ")", error); } } } let movies = Array.from(unique.values()); addDiscoveredLanguages(movies); if (sortSelectEl.value === "oldest") { movies.sort(function (a, b) { return String(a.release_date || "").localeCompare(String(b.release_date || "")); }); } else if (sortSelectEl.value === "title") { movies.sort(function (a, b) { return normalizeTitle(a.title).localeCompare(normalizeTitle(b.title)); }); } else { movies.sort(function (a, b) { return String(b.release_date || "").localeCompare(String(a.release_date || "")); }); } if (maxResults) movies = movies.slice(0, maxResults); return movies; } async function searchTMDB() { const button = document.getElementById("wdfc-tmdbPlusButton"); if (button.disabled) return; if (religiousFilterEl && !religiousFilterEl.checked) { const proceed = confirm( "Le filtre « Films chrétiens / religieux » est désactivé.\n\n" + "La recherche TMDB de ce bouton est spécialement conçue " + "pour les films chrétiens et religieux.\n\n" + "Voulez-vous continuer ?" ); if (!proceed) return; } button.disabled = true; if (progressBarEl) progressBarEl.style.width = "10%"; try { setTMDBStatus("⏳ Préparation de la recherche TMDB..."); const movies = await collectTMDBResults(); if (progressBarEl) progressBarEl.style.width = "70%"; let added = 0; let duplicates = 0; movies.forEach(function (movie) { if (isExistingResult(movie)) { duplicates++; return; } const converted = convertTMDBResult(movie); currentResults.push(converted); added++; }); if (sortSelectEl.value === "title") { currentResults.sort(function (a, b) { return normalizeTitle(a.title).localeCompare(normalizeTitle(b.title)); }); } else if (sortSelectEl.value === "oldest") { currentResults.sort(function (a, b) { return String(a.date || "").localeCompare(String(b.date || "")); }); } else { currentResults.sort(function (a, b) { return String(b.date || "").localeCompare(String(a.date || "")); }); } if (typeof renderResults === "function") renderResults(); tmdbTotalCount = currentResults.filter(function (f) { return f.isTMDB; }).length; if (typeof updateTotalCounter === "function") updateTotalCounter(); if (progressBarEl) progressBarEl.style.width = "100%"; setTMDBStatus( "✅ TMDB terminé : " + added + " nouveau(x) film(s) ajouté(s). " + duplicates + " doublon(s) ignoré(s)." ); } catch (error) { console.error("TMDB Plus :", error); setTMDBStatus("❌ Erreur TMDB : " + error.message); if (progressBarEl) progressBarEl.style.width = "100%"; } finally { button.disabled = false; } } createTMDBButton(); console.log("✅ Module TMDB « Plus de films » chargé."); })(); } // fin de init() if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } })();
    Retour en haut