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;
}
}