diff --git a/backend/src/embeds.ts b/backend/src/embeds.ts new file mode 100644 index 0000000..7d03d5f --- /dev/null +++ b/backend/src/embeds.ts @@ -0,0 +1,144 @@ +import { getVideoById } from "./services/db"; +import { getStreamUrl, getObjectUrl } from "./services/rustfs"; +import type { VideoEntry } from "./types/video"; +import { readFileSync } from "fs"; + +const BASE_URL = process.env.BASE_URL || "https://surf.nathan.rip"; + +function escapeHtml(s: string): string { + return s + .replace(/&/g, "&") + .replace(/"/g, """) + .replace(//g, ">"); +} + +function formatRunTime(seconds: number): string { + const mins = Math.floor(seconds / 60); + const secs = seconds % 60; + return `${mins}:${secs.toFixed(3).padStart(6, "0")}`; +} + +function mapDisplayName(mapName: string): string { + return mapName.replace(/_/g, " ").toUpperCase(); +} + +function pageTitle(video: VideoEntry): string { + return `${mapDisplayName(video.mapName)} - ${video.playerName} · surf.nathan.rip`; +} + +function ogTitle(video: VideoEntry): string { + return mapDisplayName(video.mapName); +} + +function ogDescription(video: VideoEntry): string { + const parts: string[] = [formatRunTime(video.runTime)]; + if (video.tier != null) parts.push(`Tier ${video.tier}`); + return `${video.playerName} on ${mapDisplayName(video.mapName)} · ${parts.join(" · ")}`; +} + +function buildOgsHtml(video: VideoEntry): string { + const runUrl = `${BASE_URL}/run/${video.id}`; + const title = ogTitle(video); + const description = ogDescription(video); + const oembedUrl = `${BASE_URL}/api/oembed/${video.id}`; + + const thumbnailUrl = video.thumbnailKey + ? getObjectUrl(video.thumbnailKey) + : undefined; + const videoStreamUrl = `${BASE_URL}${getStreamUrl(video.id)}`; + + let tags = + ` \n` + + ` \n` + + ` \n` + + ` \n` + + ` \n` + + ` `; + + if (thumbnailUrl) { + tags += + `\n \n` + + ` \n` + + ` `; + } + + tags += + `\n \n` + + ` \n` + + ` \n` + + ` `; + + tags += + `\n \n` + + ` \n` + + ` `; + + if (thumbnailUrl) { + tags += `\n `; + } + + return tags; +} + +export interface OembedResponse { + type: string; + version: string; + title: string; + author_name: string; + author_url: string; + provider_name: string; + provider_url: string; + thumbnail_url?: string; + html?: string; +} + +export function getOembed(id: string): OembedResponse | null { + const video = getVideoById(id); + if (!video) return null; + + const thumbnailUrl = video.thumbnailKey + ? getObjectUrl(video.thumbnailKey) + : undefined; + + return { + type: "video", + version: "1.0", + title: ogTitle(video), + author_name: video.playerName, + author_url: `https://dashboard.momentum-mod.org/maps/${video.mapName}`, + provider_name: "surf.nathan.rip", + provider_url: BASE_URL, + thumbnail_url: thumbnailUrl, + }; +} + +export function renderRunPage( + id: string, + staticDir: string, +): { html: string; contentType: string } | null { + const indexPath = `${staticDir}/index.html`; + + let indexHtml: string; + try { + indexHtml = readFileSync(indexPath, "utf-8"); + } catch { + return null; + } + + const video = getVideoById(id); + if (!video) { + return { html: indexHtml, contentType: "text/html" }; + } + + const ogTags = buildOgsHtml(video); + const title = pageTitle(video); + const enhancedHtml = indexHtml + .replace("", `${ogTags}\n `) + .replace( + /