const ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; const SITE_NAME = "Movix"; const TMDB_API_BASE = "https://api.themoviedb.org/3"; const TMDB_IMAGE_BASE = "https://image.tmdb.org/t/p"; const PROVIDER_NAMES = { 8: "Netflix", 119: "Prime Video", 337: "Disney+", 350: "Apple TV+", 355: "Warner Bros", 356: "DC Comics", 384: "HBO Max", 531: "Paramount+", }; const GENRE_NAMES = { fr: { 12: "Aventure", 14: "Fantastique", 16: "Animation", 18: "Drame", 27: "Horreur", 28: "Action", 35: "Comédie", 36: "Histoire", 37: "Western", 53: "Thriller", 80: "Crime", 99: "Documentaire", 878: "Science-fiction", 9648: "Mystère", 10402: "Musique", 10749: "Romance", 10751: "Famille", 10752: "Guerre", 10759: "Action & aventure", 10762: "Enfants", 10763: "Actualites", 10764: "Télé-réalité", 10765: "Science-fiction & fantastique", 10766: "Feuilleton", 10767: "Talk-show", 10768: "Guerre & politique", 10770: "Téléfilm", }, en: { 12: "Adventure", 14: "Fantasy", 16: "Animation", 18: "Drama", 27: "Horror", 28: "Action", 35: "Comedy", 36: "History", 37: "Western", 53: "Thriller", 80: "Crime", 99: "Documentary", 878: "Science Fiction", 9648: "Mystery", 10402: "Music", 10749: "Romance", 10751: "Family", 10752: "War", 10759: "Action & Adventure", 10762: "Kids", 10763: "News", 10764: "Reality", 10765: "Sci-Fi & Fantasy", 10766: "Soap", 10767: "Talk", 10768: "War & Politics", 10770: "TV Movie", }, }; const COPY = { fr: { tmdbLanguage: "fr-FR", ogLocale: "fr_FR", alternateOgLocale: "en_US", defaultTitle: "Movix - Films et séries en streaming", defaultDescription: "Regardez des films, séries, collections et pages communautaires sur Movix.", movieWord: "films", tvWord: "séries", providerFallback: "Plateforme", genresLabel: "Genres", releaseLabel: "Sortie", firstAirLabel: "Première diffusion", ratingLabel: "Note", seasonsLabel: "Saisons", episodesLabel: "Épisodes", yearLabel: "Année", moviesLabel: "Films", knownForLabel: "Connu pour", bornLabel: "Naissance", movieFallback: "Découvrez {{title}} sur Movix, avec synopsis, note et lecture en ligne.", tvFallback: "Découvrez {{title}} sur Movix, avec synopsis, note, saisons et lecture en ligne.", collectionFallback: "Retrouvez tous les films de la collection {{title}} sur Movix.", personFallback: "Consultez la biographie et la filmographie de {{title}} sur Movix.", watchMovieLabel: "Regarder le film", watchSeriesLabel: "Regarder la série", watchAnimeLabel: "Regarder l’anime", watchPartyRoomLabel: "Watch Party", watchPartyJoinLabel: "Rejoindre la Watch Party", downloadMovieLabel: "Télécharger le film", downloadSeriesLabel: "Télécharger la série", participantsLabel: "Participants", visibilityLabel: "Visibilité", codeLabel: "Code", syncLabel: "Sync", mediaTypeLabel: "Type", publicLabel: "Publique", privateLabel: "Privée", classicLabel: "Classic", syncProLabel: "Sync Pro", movieLabel: "Film", tvLabel: "Serie", sharedListTitle: "Liste partagée - Movix", sharedListDescription: "Consultez une liste partagée par la communauté Movix.", watchPartyTitle: "Watch Party - Movix", watchPartyDescription: "Créez, rejoignez et partagez une watch party synchronisée sur Movix.", homeTitle: "Movix - Streaming films et séries gratuit", homeDescription: "Explorez les films, séries, collections, alertes et recommandations Movix.", searchTitle: "Recherche - Movix", searchDescription: "Cherchez un film, une série, une collection ou une personne sur Movix.", moviesTitle: "Films - Movix", moviesDescription: "Parcourez les films populaires, récents et recommandés sur Movix.", tvTitle: "Séries TV - Movix", tvDescription: "Parcourez les séries TV populaires, récentes et recommandées sur Movix.", collectionsTitle: "Collections - Movix", collectionsDescription: "Retrouvez les sagas et franchises de films disponibles sur Movix.", alertsTitle: "Mes alertes - Movix", alertsDescription: "Gérez vos alertes de sorties et de nouveaux épisodes.", rouletteTitle: "Roulette - Movix", rouletteDescription: "Laissez Movix choisir un film ou une série pour votre prochaine session.", suggestionTitle: "Suggestion - Movix", suggestionDescription: "Lancez une recommandation surprise selon vos critères.", providerBrowseTitle: "Où regarder - Movix", providerBrowseDescription: "Explorez les catalogues par plateforme de streaming sur Movix.", liveTvTitle: "Live TV - Movix", liveTvDescription: "Accédez aux chaînes en direct et au direct TV sur Movix.", extensionTitle: "Extension - Movix", extensionDescription: "Installez l’extension navigateur Movix et ses intégrations.", top10Title: "Top 10 - Movix", top10Description: "Consultez le Top 10 films et séries de la communauté Movix.", settingsTitle: "Paramètres - Movix", settingsDescription: "Réglez la langue, les préférences et les options de votre expérience Movix.", aboutTitle: "À propos de Movix", aboutDescription: "Découvrez ce qu’est Movix et les fonctionnalités de la plateforme.", privacyTitle: "Confidentialité - Movix", privacyDescription: "Consultez la politique de confidentialité et les pratiques de Movix.", cinegraphTitle: "CineGraph - Movix", cinegraphDescription: "Explorez les liens entre films, séries, genres et talents avec CineGraph.", wishboardTitle: "Wishboard - Movix", wishboardDescription: "Demandez un ajout de contenu et suivez les requêtes communautaires.", wrappedTitle: "Wrapped - Movix", wrappedDescription: "Retrouvez vos statistiques et votre récap Movix.", profileTitle: "Profil - Movix", profileDescription: "Retrouvez vos listes, favoris, alertes et historique sur Movix.", authTitle: "Connexion - Movix", authDescription: "Connectez-vous à votre compte Movix.", vipTitle: "VIP - Movix", vipDescription: "Découvrez les avantages VIP et les options premium Movix.", genericPageTitle: "Page Movix", genericPageDescription: "Découvrez cette page sur Movix.", }, en: { tmdbLanguage: "en-US", ogLocale: "en_US", alternateOgLocale: "fr_FR", defaultTitle: "Movix - Movies and TV streaming", defaultDescription: "Watch movies, TV shows, collections and community pages on Movix.", movieWord: "movies", tvWord: "TV shows", providerFallback: "Platform", genresLabel: "Genres", releaseLabel: "Release", firstAirLabel: "First air", ratingLabel: "Rating", seasonsLabel: "Seasons", episodesLabel: "Episodes", yearLabel: "Year", moviesLabel: "Movies", knownForLabel: "Known for", bornLabel: "Born", movieFallback: "Discover {{title}} on Movix with synopsis, rating and online playback.", tvFallback: "Discover {{title}} on Movix with synopsis, rating, seasons and online playback.", collectionFallback: "Browse every movie in the {{title}} collection on Movix.", personFallback: "See the biography and filmography of {{title}} on Movix.", watchMovieLabel: "Watch movie", watchSeriesLabel: "Watch series", watchAnimeLabel: "Watch anime", watchPartyRoomLabel: "Watch Party", watchPartyJoinLabel: "Join Watch Party", downloadMovieLabel: "Download movie", downloadSeriesLabel: "Download series", participantsLabel: "Participants", visibilityLabel: "Visibility", codeLabel: "Code", syncLabel: "Sync", mediaTypeLabel: "Type", publicLabel: "Public", privateLabel: "Private", classicLabel: "Classic", syncProLabel: "Sync Pro", movieLabel: "Movie", tvLabel: "TV Show", sharedListTitle: "Shared List - Movix", sharedListDescription: "Browse a list shared by the Movix community.", watchPartyTitle: "Watch Party - Movix", watchPartyDescription: "Create, join and share synchronized watch parties on Movix.", homeTitle: "Movix - Free movie and TV streaming", homeDescription: "Explore movies, TV shows, collections, alerts and recommendations on Movix.", searchTitle: "Search - Movix", searchDescription: "Search for a movie, TV show, collection or person on Movix.", moviesTitle: "Movies - Movix", moviesDescription: "Browse popular, recent and recommended movies on Movix.", tvTitle: "TV Shows - Movix", tvDescription: "Browse popular, recent and recommended TV shows on Movix.", collectionsTitle: "Collections - Movix", collectionsDescription: "Browse movie sagas and franchises available on Movix.", alertsTitle: "My Alerts - Movix", alertsDescription: "Manage your release and new episode alerts.", rouletteTitle: "Roulette - Movix", rouletteDescription: "Let Movix pick a movie or TV show for your next session.", suggestionTitle: "Suggestion - Movix", suggestionDescription: "Get a surprise recommendation based on your filters.", providerBrowseTitle: "Where to Watch - Movix", providerBrowseDescription: "Explore streaming catalogs by platform on Movix.", liveTvTitle: "Live TV - Movix", liveTvDescription: "Access live channels and TV streams on Movix.", extensionTitle: "Extension - Movix", extensionDescription: "Install the Movix browser extension and integrations.", top10Title: "Top 10 - Movix", top10Description: "See the Movix community Top 10 movies and TV shows.", settingsTitle: "Settings - Movix", settingsDescription: "Adjust language, preferences and experience options on Movix.", aboutTitle: "About Movix", aboutDescription: "Learn what Movix is and what the platform offers.", privacyTitle: "Privacy - Movix", privacyDescription: "Read the Movix privacy policy and data practices.", cinegraphTitle: "CineGraph - Movix", cinegraphDescription: "Explore links between movies, shows, genres and talent with CineGraph.", wishboardTitle: "Wishboard - Movix", wishboardDescription: "Request content additions and track community requests.", wrappedTitle: "Wrapped - Movix", wrappedDescription: "See your Movix stats and recap.", profileTitle: "Profile - Movix", profileDescription: "Find your lists, favorites, alerts and history on Movix.", authTitle: "Login - Movix", authDescription: "Sign in to your Movix account.", vipTitle: "VIP - Movix", vipDescription: "Discover Movix VIP benefits and premium options.", genericPageTitle: "Movix Page", genericPageDescription: "Discover this page on Movix.", }, }; function decodeBase62ToBytes(value) { const base = BigInt(ALPHABET.length); let current = BigInt(0); for (const character of value) { const index = ALPHABET.indexOf(character); if (index === -1) { throw new Error(`Invalid character in base62 string: ${character}`); } current = current * base + BigInt(index); } const bytes = []; while (current > 0) { bytes.unshift(Number(current & BigInt(0xff))); current >>= BigInt(8); } return new Uint8Array(bytes); } function isLegacyBase64Id(value) { return value.includes("="); } function decodeId(encodedId) { try { if (isLegacyBase64Id(encodedId)) { const base64Part = encodedId.slice(0, -7); const originalId = atob(base64Part); return /^\d+$/.test(originalId) ? originalId : null; } const maxSuffix = 30; const start = Math.max(1, encodedId.length - maxSuffix); for (let index = start; index >= 1; index -= 1) { try { const base62Part = encodedId.slice(0, index); const bytes = decodeBase62ToBytes(base62Part); const originalId = new TextDecoder().decode(bytes); if (/^\d+$/.test(originalId)) { return originalId; } } catch (_error) { continue; } } return null; } catch (_error) { return null; } } function getTmdbId(rawId) { if (!rawId) return null; if (/^\d+$/.test(rawId)) return rawId; return decodeId(rawId); } function getTmdbApiKey(env) { return env.TMDB_API_KEY || env.VITE_TMDB_API_KEY || ""; } function resolvePreviewLanguage(requestUrl) { const langParam = (requestUrl.searchParams.get("lang") || "").toLowerCase(); return langParam === "en" ? "en" : "fr"; } function collapseWhitespace(value) { return String(value || "") .replace(/\s+/g, " ") .trim(); } function normalizeMultilineText(value) { return String(value || "") .replace(/\r/g, "") .split("\n") .map((line) => collapseWhitespace(line)) .join("\n") .replace(/\n{3,}/g, "\n\n") .replace(/^\n+|\n+$/g, ""); } function truncateText(value, maxLength) { const normalized = normalizeMultilineText(value); return normalized.length <= maxLength ? normalized : `${normalized.slice(0, maxLength - 1).trimEnd()}...`; } function interpolate(template, values) { return template.replace(/\{\{(\w+)\}\}/g, (_match, key) => values[key] ?? ""); } function normalizePathname(pathname) { if (!pathname || pathname === "/") return "/"; return pathname.replace(/\/+$/, "") || "/"; } function extractYear(dateValue) { if (!dateValue) return null; const parsedDate = new Date(dateValue); return Number.isNaN(parsedDate.getTime()) ? null : String(parsedDate.getFullYear()); } function formatRating(value) { const numberValue = Number(value || 0); if (!numberValue) return null; return Number.isInteger(numberValue) ? String(numberValue) : numberValue.toFixed(1); } function buildImageUrl(origin, payload, fallbackPath = "/movix.png") { const imagePath = payload.backdrop_path || payload.poster_path || payload.profile_path; if (!imagePath) return new URL(fallbackPath, origin).toString(); const size = payload.backdrop_path ? "w1280" : "w780"; return `${TMDB_IMAGE_BASE}/${size}${imagePath}`; } function resolveExternalImageUrl(origin, value, fallbackPath = "/movix.png") { const normalized = String(value || "").trim(); if (!normalized) { return new URL(fallbackPath, origin).toString(); } if (/^https?:\/\//i.test(normalized)) { return normalized; } if (normalized.startsWith("//")) { return `https:${normalized}`; } return new URL(normalized, origin).toString(); } function escapeHtml(value) { return String(value) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'") .replace(/\n/g, " "); } function buildLabeledFact(label, value) { return value ? `• ${label} : ${value}` : ""; } function buildStructuredDescription(facts, body) { const factLines = facts.filter(Boolean); const normalizedBody = collapseWhitespace(body); if (!normalizedBody) { return factLines.join("\n"); } return [...factLines, "", normalizedBody].join("\n"); } function stripExistingHeadTags(html) { const patterns = [ /]*>[\s\S]*?<\/title>/i, /]*>\s*/gi, /]*>\s*/gi, /]*>\s*/gi, /]*>\s*/gi, /]*>\s*/gi, /]*>\s*/gi, ]; return patterns.reduce( (currentHtml, pattern) => currentHtml.replace(pattern, ""), html, ); } function renderHeadTags(metadata) { return [ `${escapeHtml(metadata.title)}`, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ].join("\n "); } function injectMetadata(html, metadata) { const cleanedHtml = stripExistingHeadTags(html); const headTags = renderHeadTags(metadata); if (cleanedHtml.includes("")) { return cleanedHtml.replace("", ` ${headTags}\n`); } return `${headTags}\n${cleanedHtml}`; } function buildBaseMetadata(requestUrl, previewLanguage, payload) { const copy = COPY[previewLanguage]; return { title: payload.title || copy.defaultTitle, description: truncateText( payload.description || copy.defaultDescription, 260, ), canonicalUrl: requestUrl.toString(), imageUrl: payload.imageUrl || new URL("/movix.png", requestUrl.origin).toString(), imageAlt: payload.imageAlt || SITE_NAME, ogType: payload.ogType || "website", ogLocale: copy.ogLocale, alternateOgLocale: copy.alternateOgLocale, language: previewLanguage, }; } async function getSpaResponse(context) { if (typeof context.next === "function") { return context.next(); } if (context.env?.ASSETS?.fetch) { return context.env.ASSETS.fetch(context.request); } return new Response("ASSETS binding unavailable.", { status: 500 }); } async function fetchTmdbPayload(pathname, apiKey, language) { const url = new URL(`${TMDB_API_BASE}${pathname}`); url.searchParams.set("api_key", apiKey); url.searchParams.set("language", language); const response = await fetch(url.toString(), { headers: { accept: "application/json" }, }); if (!response.ok) { throw new Error(`TMDB returned ${response.status} for ${pathname}`); } return response.json(); } function getWatchPartyApiBase(env) { return String(env.WATCHPARTY_API || env.VITE_WATCHPARTY_API || "") .trim() .replace(/\/+$/, ""); } async function fetchWatchPartyPayload(apiBase, pathname) { if (!apiBase) { throw new Error("WATCHPARTY_API binding unavailable"); } const response = await fetch(`${apiBase}${pathname}`, { headers: { accept: "application/json" }, }); if (!response.ok) { throw new Error( `WatchParty API returned ${response.status} for ${pathname}`, ); } return response.json(); } function shouldBypassPreview(pathname) { return ( /^\/(?:assets|wasm)(?:\/|$)/.test(pathname) || /^\/api(?:\/|$)/.test(pathname) || /\.(?:css|js|mjs|json|map|ico|png|jpe?g|webp|gif|svg|txt|xml|webmanifest|woff2?|ttf|otf)$/i.test( pathname, ) ); } function buildMovieOrTvMetadata( requestUrl, payload, mediaType, previewLanguage, ) { const copy = COPY[previewLanguage]; const titleText = collapseWhitespace(mediaType === "movie" ? payload.title : payload.name) || SITE_NAME; const year = extractYear( mediaType === "movie" ? payload.release_date : payload.first_air_date, ); const genres = (payload.genres || []) .slice(0, 3) .map((genre) => genre.name) .filter(Boolean) .join(", "); const facts = [ buildLabeledFact( mediaType === "movie" ? copy.releaseLabel : copy.firstAirLabel, year, ), buildLabeledFact( copy.ratingLabel, formatRating(payload.vote_average) ? `${formatRating(payload.vote_average)}/10` : "", ), buildLabeledFact(copy.genresLabel, genres), buildLabeledFact( copy.seasonsLabel, mediaType === "tv" && payload.number_of_seasons ? payload.number_of_seasons : "", ), buildLabeledFact( copy.episodesLabel, mediaType === "tv" && payload.number_of_episodes ? payload.number_of_episodes : "", ), ]; const fallback = interpolate( mediaType === "movie" ? copy.movieFallback : copy.tvFallback, { title: titleText }, ); const descriptionBody = collapseWhitespace(payload.overview || fallback); const description = buildStructuredDescription(facts, descriptionBody); const title = year ? `${titleText} (${year}) - ${SITE_NAME}` : `${titleText} - ${SITE_NAME}`; return buildBaseMetadata(requestUrl, previewLanguage, { title, description, imageUrl: buildImageUrl(requestUrl.origin, payload), imageAlt: `${titleText} - ${SITE_NAME}`, ogType: mediaType === "movie" ? "video.movie" : "video.tv_show", }); } function buildCollectionMetadata(requestUrl, payload, previewLanguage) { const copy = COPY[previewLanguage]; const titleText = collapseWhitespace(payload.name) || SITE_NAME; const moviesCount = payload.parts?.length || 0; const firstYear = extractYear(payload.parts?.[0]?.release_date); const lastYear = extractYear(payload.parts?.[moviesCount - 1]?.release_date); const yearRange = firstYear && lastYear && firstYear !== lastYear ? `${firstYear}-${lastYear}` : firstYear; const facts = [ buildLabeledFact(copy.moviesLabel, moviesCount || ""), buildLabeledFact(copy.yearLabel, yearRange), ]; const fallback = interpolate(copy.collectionFallback, { title: titleText }); const descriptionBody = collapseWhitespace(payload.overview || fallback); return buildBaseMetadata(requestUrl, previewLanguage, { title: `${titleText} - ${SITE_NAME}`, description: buildStructuredDescription(facts, descriptionBody), imageUrl: buildImageUrl(requestUrl.origin, payload), imageAlt: `${titleText} - ${SITE_NAME}`, }); } function buildPersonMetadata(requestUrl, payload, previewLanguage) { const copy = COPY[previewLanguage]; const titleText = collapseWhitespace(payload.name) || SITE_NAME; const facts = [ buildLabeledFact( copy.knownForLabel, collapseWhitespace(payload.known_for_department || ""), ), buildLabeledFact(copy.bornLabel, extractYear(payload.birthday)), ]; const fallback = interpolate(copy.personFallback, { title: titleText }); const descriptionBody = collapseWhitespace(payload.biography || fallback); return buildBaseMetadata(requestUrl, previewLanguage, { title: `${titleText} - ${SITE_NAME}`, description: buildStructuredDescription(facts, descriptionBody), imageUrl: buildImageUrl(requestUrl.origin, payload), imageAlt: `${titleText} - ${SITE_NAME}`, }); } function buildWatchMovieMetadata(requestUrl, payload, previewLanguage) { const copy = COPY[previewLanguage]; const metadata = buildMovieOrTvMetadata( requestUrl, payload, "movie", previewLanguage, ); metadata.title = `${copy.watchMovieLabel}: ${metadata.title}`; return metadata; } function buildWatchAnimeMetadata(requestUrl, payload, previewLanguage) { const copy = COPY[previewLanguage]; const metadata = buildMovieOrTvMetadata( requestUrl, payload, "tv", previewLanguage, ); metadata.title = `${copy.watchAnimeLabel}: ${metadata.title}`; return metadata; } function buildWatchEpisodeMetadata( requestUrl, payload, watchType, previewLanguage, seasonNumber, episodeNumber, episodePayload = null, ) { const copy = COPY[previewLanguage]; const titleText = collapseWhitespace(payload.name) || SITE_NAME; const year = extractYear(payload.first_air_date); const genres = (payload.genres || []) .slice(0, 3) .map((genre) => genre.name) .filter(Boolean) .join(", "); const episodeName = collapseWhitespace(episodePayload?.name || ""); const episodeCode = `S${seasonNumber}E${episodeNumber}`; const facts = [ `• ${episodeCode}`, buildLabeledFact(copy.firstAirLabel, year), buildLabeledFact( copy.ratingLabel, formatRating(payload.vote_average) ? `${formatRating(payload.vote_average)}/10` : "", ), buildLabeledFact(copy.genresLabel, genres), ]; const fallback = interpolate(copy.tvFallback, { title: titleText }); const descriptionBody = collapseWhitespace( episodePayload?.overview || payload.overview || fallback, ); const label = watchType === "anime" ? copy.watchAnimeLabel : copy.watchSeriesLabel; return buildBaseMetadata(requestUrl, previewLanguage, { title: `${label}: ${titleText} - ${episodeCode}${episodeName ? ` - ${episodeName}` : ""} - ${SITE_NAME}`, description: buildStructuredDescription(facts, descriptionBody), imageUrl: buildImageUrl(requestUrl.origin, { backdrop_path: episodePayload?.still_path || payload.backdrop_path, poster_path: payload.poster_path, }), imageAlt: `${titleText} - ${episodeCode}`, ogType: "video.episode", }); } function getWatchPartyMediaTypeLabel(mediaType, copy) { return mediaType === "movie" ? copy.movieLabel : copy.tvLabel; } function getWatchPartySyncLabel(syncMode, copy) { return syncMode === "pro" ? copy.syncProLabel : copy.classicLabel; } function buildWatchPartyMetadata(requestUrl, roomData, previewLanguage, mode) { const copy = COPY[previewLanguage]; const media = roomData.media || {}; const titleText = collapseWhitespace(media.title || roomData.title || ""); const seasonNumber = media.seasonNumber; const episodeNumber = media.episodeNumber; const episodePart = seasonNumber && episodeNumber ? ` - S${seasonNumber}E${episodeNumber}` : ""; const participantCount = Array.isArray(roomData.participants) ? roomData.participants.length : Number(roomData.participantCount || 0); const maxParticipants = Number(roomData.maxParticipants || 0); const prefix = mode === "join" ? copy.watchPartyJoinLabel : copy.watchPartyRoomLabel; const title = titleText ? `${prefix}: ${titleText}${episodePart} - ${SITE_NAME}` : copy.watchPartyTitle; const facts = [ buildLabeledFact( copy.mediaTypeLabel, getWatchPartyMediaTypeLabel( media.mediaType || roomData.mediaType || "movie", copy, ), ), buildLabeledFact( copy.participantsLabel, maxParticipants ? `${participantCount}/${maxParticipants}` : participantCount || "", ), buildLabeledFact( copy.syncLabel, getWatchPartySyncLabel(roomData.syncMode, copy), ), buildLabeledFact( copy.visibilityLabel, roomData.isPublic ? copy.publicLabel : copy.privateLabel, ), buildLabeledFact(copy.codeLabel, roomData.code || ""), ]; const descriptionBody = titleText ? previewLanguage === "fr" ? `Rejoignez cette Watch Party Movix pour regarder ${titleText}${episodePart} en synchronisation.` : `Join this Movix Watch Party to watch ${titleText}${episodePart} in sync.` : copy.watchPartyDescription; return buildBaseMetadata(requestUrl, previewLanguage, { title, description: buildStructuredDescription(facts, descriptionBody), imageUrl: resolveExternalImageUrl( requestUrl.origin, media.poster || roomData.poster, ), imageAlt: titleText ? `${titleText} - Watch Party` : "Watch Party - Movix", ogType: "website", }); } function buildGenreMetadata(requestUrl, mediaType, genreId, previewLanguage) { const copy = COPY[previewLanguage]; const genreName = GENRE_NAMES[previewLanguage]?.[Number(genreId)] || `${copy.genresLabel} ${genreId}`; const contentType = mediaType === "movie" ? copy.movieWord : copy.tvWord; const description = previewLanguage === "fr" ? `Retrouvez les ${contentType} du genre ${genreName} sur Movix.` : `Browse ${contentType} in the ${genreName} genre on Movix.`; const title = previewLanguage === "fr" ? `${genreName} - ${contentType} - ${SITE_NAME}` : `${genreName} - ${contentType} - ${SITE_NAME}`; return buildBaseMetadata(requestUrl, previewLanguage, { title, description, }); } function buildProviderMetadata( requestUrl, providerId, type, genreId, previewLanguage, ) { const copy = COPY[previewLanguage]; const providerName = PROVIDER_NAMES[Number(providerId)] || `${copy.providerFallback} ${providerId}`; const genreName = genreId ? GENRE_NAMES[previewLanguage]?.[Number(genreId)] || `${copy.genresLabel} ${genreId}` : ""; const contentType = type === "movies" ? copy.movieWord : type === "tv" ? copy.tvWord : previewLanguage === "fr" ? "films et séries" : "movies and TV shows"; const description = type ? previewLanguage === "fr" ? `Retrouvez les ${contentType} de ${providerName}${genreName ? ` dans ${genreName}` : ""} sur Movix.` : `Browse ${contentType} on ${providerName}${genreName ? ` in ${genreName}` : ""} on Movix.` : previewLanguage === "fr" ? `Explorez le catalogue ${providerName} sur Movix.` : `Explore the ${providerName} catalog on Movix.`; return buildBaseMetadata(requestUrl, previewLanguage, { title: type ? `${providerName} - ${contentType} - ${SITE_NAME}` : `${providerName} - ${SITE_NAME}`, description, }); } function buildWrappedMetadata(requestUrl, year, previewLanguage) { const copy = COPY[previewLanguage]; const description = year ? previewLanguage === "fr" ? `Retrouvez votre récap Movix ${year} et partagez vos stats.` : `See your Movix ${year} recap and share your stats.` : copy.wrappedDescription; return buildBaseMetadata(requestUrl, previewLanguage, { title: year ? `Movix Wrapped ${year}` : copy.wrappedTitle, description, }); } function buildStaticMetadata(requestUrl, previewLanguage) { const copy = COPY[previewLanguage]; const pathname = normalizePathname(requestUrl.pathname); switch (pathname) { case "/": return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.homeTitle, description: copy.homeDescription, }); case "/search": return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.searchTitle, description: copy.searchDescription, }); case "/movies": return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.moviesTitle, description: copy.moviesDescription, }); case "/tv-shows": return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.tvTitle, description: copy.tvDescription, }); case "/collections": return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.collectionsTitle, description: copy.collectionsDescription, }); case "/alerts": return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.alertsTitle, description: copy.alertsDescription, }); case "/roulette": return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.rouletteTitle, description: copy.rouletteDescription, }); case "/suggestion": return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.suggestionTitle, description: copy.suggestionDescription, }); case "/live-tv": return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.liveTvTitle, description: copy.liveTvDescription, }); case "/extension": return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.extensionTitle, description: copy.extensionDescription, }); case "/top10": return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.top10Title, description: copy.top10Description, }); case "/settings": return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.settingsTitle, description: copy.settingsDescription, }); case "/about": return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.aboutTitle, description: copy.aboutDescription, }); case "/privacy": return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.privacyTitle, description: copy.privacyDescription, }); case "/cinegraph": return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.cinegraphTitle, description: copy.cinegraphDescription, }); case "/wishboard": case "/wishboard/new": case "/wishboard/my-requests": case "/wishboard/submit-link": return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.wishboardTitle, description: copy.wishboardDescription, }); case "/wrapped": return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.wrappedTitle, description: copy.wrappedDescription, }); case "/profile": case "/profile-selection": case "/profile-management": return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.profileTitle, description: copy.profileDescription, }); case "/auth": case "/auth/google": case "/create-account": case "/login-bip39": case "/link-bip39": case "/link-bip39/create": return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.authTitle, description: copy.authDescription, }); case "/vip": return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.vipTitle, description: copy.vipDescription, }); case "/watchparty/create": case "/watchparty/join": case "/watchparty/list": return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.watchPartyTitle, description: copy.watchPartyDescription, }); case "/list-catalog": return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.sharedListTitle, description: copy.sharedListDescription, }); case "/debrid": return buildBaseMetadata(requestUrl, previewLanguage, { title: "Debrid - Movix", description: previewLanguage === "fr" ? "Débridez et gérez vos liens premium sur Movix." : "Debrid and manage your premium links on Movix.", }); case "/dmca": return buildBaseMetadata(requestUrl, previewLanguage, { title: "DMCA - Movix", description: previewLanguage === "fr" ? "Informations DMCA et demandes legales pour Movix." : "DMCA information and legal requests for Movix.", }); case "/admin": return buildBaseMetadata(requestUrl, previewLanguage, { title: "Admin - Movix", description: "Movix administration.", }); case "/ftv": return buildBaseMetadata(requestUrl, previewLanguage, { title: "France TV - Movix", description: previewLanguage === "fr" ? "Accédez au catalogue et au live France TV via Movix." : "Access the France TV catalog and live streams via Movix.", }); default: if (/^\/watchparty\/(?:join|room)\//.test(pathname)) { return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.watchPartyTitle, description: copy.watchPartyDescription, }); } if (/^\/list\//.test(pathname)) { return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.sharedListTitle, description: copy.sharedListDescription, }); } if (/^\/wrapped\/\d{4}$/.test(pathname)) { return buildWrappedMetadata( requestUrl, pathname.split("/")[2], previewLanguage, ); } if (/^\/ftv\/(?:info|watch)\//.test(pathname)) { return buildBaseMetadata(requestUrl, previewLanguage, { title: "France TV - Movix", description: previewLanguage === "fr" ? "Accédez au catalogue et au live France TV via Movix." : "Access the France TV catalog and live streams via Movix.", }); } return buildBaseMetadata(requestUrl, previewLanguage, { title: copy.genericPageTitle, description: copy.genericPageDescription, }); } } async function resolveRouteMetadata( requestUrl, env, previewLanguage, forcedMediaType, ) { const copy = COPY[previewLanguage]; const apiKey = getTmdbApiKey(env || {}); const pathname = normalizePathname(requestUrl.pathname); const segments = pathname.split("/").filter(Boolean); if (forcedMediaType && segments[1]) { const tmdbId = getTmdbId(segments[1]); if (!tmdbId || !apiKey) return null; const payload = await fetchTmdbPayload( `/${forcedMediaType}/${tmdbId}`, apiKey, copy.tmdbLanguage, ); return buildMovieOrTvMetadata( requestUrl, payload, forcedMediaType, previewLanguage, ); } if (!segments.length) { return buildStaticMetadata(requestUrl, previewLanguage); } if ( (segments[0] === "movie" || segments[0] === "tv") && segments[1] && apiKey ) { const tmdbId = getTmdbId(segments[1]); if (!tmdbId) return buildStaticMetadata(requestUrl, previewLanguage); const payload = await fetchTmdbPayload( `/${segments[0]}/${tmdbId}`, apiKey, copy.tmdbLanguage, ); return buildMovieOrTvMetadata( requestUrl, payload, segments[0], previewLanguage, ); } if (segments[0] === "download" && segments[1] && segments[2] && apiKey) { const mediaType = segments[1] === "tv" ? "tv" : "movie"; const tmdbId = getTmdbId(segments[2]); if (!tmdbId) return buildStaticMetadata(requestUrl, previewLanguage); const payload = await fetchTmdbPayload( `/${mediaType}/${tmdbId}`, apiKey, copy.tmdbLanguage, ); const metadata = buildMovieOrTvMetadata( requestUrl, payload, mediaType, previewLanguage, ); metadata.title = `${mediaType === "movie" ? copy.downloadMovieLabel : copy.downloadSeriesLabel}: ${metadata.title}`; return metadata; } if (segments[0] === "watch" && segments[1] && segments[2] && apiKey) { if (segments[1] === "movie") { const tmdbId = getTmdbId(segments[2]); if (!tmdbId) return buildStaticMetadata(requestUrl, previewLanguage); const payload = await fetchTmdbPayload( "/movie/" + tmdbId, apiKey, copy.tmdbLanguage, ); return buildWatchMovieMetadata(requestUrl, payload, previewLanguage); } if ( segments[1] === "anime" && segments[3] === "season" && segments[5] === "episode" && segments[6] ) { const tmdbId = getTmdbId(segments[2]); if (!tmdbId) return buildStaticMetadata(requestUrl, previewLanguage); const payload = await fetchTmdbPayload( "/tv/" + tmdbId, apiKey, copy.tmdbLanguage, ); return buildWatchAnimeMetadata(requestUrl, payload, previewLanguage); } if ( segments[1] === "tv" && segments[3] === "s" && segments[5] === "e" && segments[6] ) { const tmdbId = getTmdbId(segments[2]); if (!tmdbId) return buildStaticMetadata(requestUrl, previewLanguage); const seasonNumber = Number(segments[4]); const episodeNumber = Number(segments[6]); const payload = await fetchTmdbPayload( "/tv/" + tmdbId, apiKey, copy.tmdbLanguage, ); let episodePayload = null; if (Number.isFinite(seasonNumber) && Number.isFinite(episodeNumber)) { try { episodePayload = await fetchTmdbPayload( `/tv/${tmdbId}/season/${seasonNumber}/episode/${episodeNumber}`, apiKey, copy.tmdbLanguage, ); } catch (_error) { episodePayload = null; } } return buildWatchEpisodeMetadata( requestUrl, payload, segments[1] === "anime" ? "anime" : "tv", previewLanguage, seasonNumber, episodeNumber, episodePayload, ); } } if (segments[0] === "collection" && segments[1] && apiKey) { const payload = await fetchTmdbPayload( `/collection/${segments[1]}`, apiKey, copy.tmdbLanguage, ); return buildCollectionMetadata(requestUrl, payload, previewLanguage); } if (segments[0] === "person" && segments[1] && apiKey) { const payload = await fetchTmdbPayload( `/person/${segments[1]}`, apiKey, copy.tmdbLanguage, ); return buildPersonMetadata(requestUrl, payload, previewLanguage); } if (segments[0] === "genre" && segments[1] && segments[2]) { return buildGenreMetadata( requestUrl, segments[1], segments[2], previewLanguage, ); } if (segments[0] === "provider" && segments[1]) { return buildProviderMetadata( requestUrl, segments[1], segments[2], segments[3], previewLanguage, ); } if (segments[0] === "wrapped") { return buildWrappedMetadata(requestUrl, segments[1], previewLanguage); } if (segments[0] === "watchparty") { const watchPartyApiBase = getWatchPartyApiBase(env || {}); if (segments[1] === "room" && segments[2] && watchPartyApiBase) { try { const payload = await fetchWatchPartyPayload( watchPartyApiBase, `/api/watchparty/room/${segments[2]}`, ); if (payload?.room) { return buildWatchPartyMetadata( requestUrl, payload.room, previewLanguage, "room", ); } } catch (_error) { return buildStaticMetadata(requestUrl, previewLanguage); } } if (segments[1] === "join" && segments[2] && watchPartyApiBase) { try { const payload = await fetchWatchPartyPayload( watchPartyApiBase, `/api/watchparty/info/${segments[2]}`, ); if (payload?.room) { return buildWatchPartyMetadata( requestUrl, { ...payload.room, code: segments[2].toUpperCase(), }, previewLanguage, "join", ); } } catch (_error) { return buildStaticMetadata(requestUrl, previewLanguage); } } } return buildStaticMetadata(requestUrl, previewLanguage); } export async function buildSocialPreviewResponse( context, forcedMediaType = null, ) { const requestUrl = new URL(context.request.url); const pathname = normalizePathname(requestUrl.pathname); if ( !["GET", "HEAD"].includes(context.request.method) || shouldBypassPreview(pathname) ) { return getSpaResponse(context); } const spaResponse = await getSpaResponse(context); if (!spaResponse.ok) { return spaResponse; } try { const previewLanguage = resolvePreviewLanguage(requestUrl); const metadata = await resolveRouteMetadata( requestUrl, context.env || {}, previewLanguage, forcedMediaType, ); if (!metadata) { return spaResponse; } const html = await spaResponse.text(); const headers = new Headers(spaResponse.headers); headers.set("content-type", "text/html; charset=UTF-8"); headers.delete("content-length"); return new Response(injectMetadata(html, metadata), { status: spaResponse.status, statusText: spaResponse.statusText, headers, }); } catch (error) { console.error("[SOCIAL PREVIEW] metadata injection failed:", error); return spaResponse; } }