Embed voting clips on landing page

This commit is contained in:
AzuTear
2026-06-25 09:54:22 +02:00
parent f851e500f7
commit fd81c968f6
5 changed files with 169 additions and 19 deletions
+100
View File
@@ -0,0 +1,100 @@
export type ClipEmbedKind = 'twitch' | 'youtube'
export interface ClipEmbed {
kind: ClipEmbedKind
src: string
title: string
}
export function buildClipEmbed(clipUrl: string, parentHost = currentParentHost()): ClipEmbed | null {
const url = parseUrl(clipUrl)
if (!url) return null
const twitchSlug = parseTwitchClipSlug(url)
if (twitchSlug && parentHost) {
const params = new URLSearchParams({ clip: twitchSlug, parent: parentHost })
return {
kind: 'twitch',
src: `https://clips.twitch.tv/embed?${params.toString()}`,
title: 'Twitch Clip Player',
}
}
const youtubeId = parseYoutubeVideoId(url)
if (youtubeId) {
return {
kind: 'youtube',
src: `https://www.youtube-nocookie.com/embed/${encodeURIComponent(youtubeId)}`,
title: 'YouTube Video Player',
}
}
return null
}
function currentParentHost() {
if (typeof window === 'undefined') return ''
return window.location.hostname
}
function parseUrl(value: string) {
try {
return new URL(value)
} catch {
return null
}
}
function parseTwitchClipSlug(url: URL) {
const host = normalizeHost(url.hostname)
const segments = pathSegments(url.pathname)
if (host === 'clips.twitch.tv') {
return sanitizeSlug(segments[0])
}
if (host === 'twitch.tv' && segments.length >= 3 && segments[1] === 'clip') {
return sanitizeSlug(segments[2])
}
return null
}
function parseYoutubeVideoId(url: URL) {
const host = normalizeHost(url.hostname)
const segments = pathSegments(url.pathname)
if (host === 'youtu.be') {
return sanitizeYoutubeId(segments[0])
}
if (host === 'youtube.com') {
if (segments[0] === 'watch') {
return sanitizeYoutubeId(url.searchParams.get('v'))
}
if (segments[0] === 'embed' || segments[0] === 'shorts' || segments[0] === 'live') {
return sanitizeYoutubeId(segments[1])
}
}
return null
}
function normalizeHost(hostname: string) {
return hostname.toLowerCase().replace(/^www\./, '').replace(/^m\./, '')
}
function pathSegments(pathname: string) {
return pathname.split('/').map((item) => item.trim()).filter(Boolean)
}
function sanitizeSlug(value: string | null | undefined) {
const slug = value?.trim()
return slug && /^[A-Za-z0-9_-]+$/.test(slug) ? slug : null
}
function sanitizeYoutubeId(value: string | null | undefined) {
const id = value?.trim()
return id && /^[A-Za-z0-9_-]{6,}$/.test(id) ? id : null
}