[APP-2066] Migrate from Tenor to KLIPY (#10240)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Spence Pope
2026-04-17 08:43:13 -04:00
committed by GitHub
parent a97b15b204
commit a77b6e3525
12 changed files with 411 additions and 50 deletions
+104
View File
@@ -23,6 +23,7 @@ export const embedPlayerSources = [
'vimeo',
'giphy',
'tenor',
'klipy',
'flickr',
'bandcamp',
] as const
@@ -44,6 +45,7 @@ export type EmbedPlayerType =
| 'vimeo_video'
| 'giphy_gif'
| 'tenor_gif'
| 'klipy_gif'
| 'flickr_album'
| 'bandcamp_album'
| 'bandcamp_track'
@@ -55,6 +57,7 @@ export const externalEmbedLabels: Record<EmbedPlayerSource, string> = {
twitch: 'Twitch',
giphy: 'GIPHY',
tenor: 'Tenor',
klipy: 'KLIPY',
spotify: 'Spotify',
appleMusic: 'Apple Music',
soundcloud: 'SoundCloud',
@@ -391,6 +394,20 @@ export function parseEmbedPlayerFromUrl(
}
}
const klipyGif = parseKlipyGif(urlp)
if (klipyGif.success) {
const {playerUri, dimensions} = klipyGif
return {
type: 'klipy_gif',
source: 'klipy',
isGif: true,
hideDetails: true,
playerUri,
dimensions,
}
}
// this is a standard flickr path! we can use the embedder for albums and groups, so validate the path
if (urlp.hostname === 'www.flickr.com' || urlp.hostname === 'flickr.com') {
let i = urlp.pathname.length - 1
@@ -628,3 +645,90 @@ export function isTenorGifUri(url: URL | string) {
return false
}
}
export function parseKlipyGif(urlp: URL):
| {success: false}
| {
success: true
playerUri: string
dimensions: {height: number; width: number}
} {
if (urlp.hostname !== 'static.klipy.com') {
return {success: false}
}
if (!urlp.pathname.startsWith('/ii/')) {
return {success: false}
}
const h = urlp.searchParams.get('hh')
const w = urlp.searchParams.get('ww')
if (!h || !w) {
return {success: false}
}
const dimensions = {
height: Number(h),
width: Number(w),
}
// Validate dimensions are valid positive numbers
if (
isNaN(dimensions.height) ||
isNaN(dimensions.width) ||
dimensions.height <= 0 ||
dimensions.width <= 0
) {
return {success: false}
}
const playerUrl = new URL(urlp.href)
playerUrl.hostname = 'k.gifs.bsky.app'
// On web, swap the gif filename for a video format so the <video>
// element can play it. Klipy uses different filename slugs per
// format (unlike Tenor's ID-based scheme), so the slugs are
// embedded as query params at composition time by resolveGif().
if (IS_WEB) {
const webmSlug = playerUrl.searchParams.get('webm')
const mp4Slug = playerUrl.searchParams.get('mp4')
const slug = IS_WEB_SAFARI ? mp4Slug : webmSlug
const ext = IS_WEB_SAFARI ? 'mp4' : 'webm'
// Without a slug we can't produce a playable video URL on web,
// so fall back to the link card instead of returning a broken player.
if (!slug) {
return {success: false}
}
const parts = playerUrl.pathname.split('/')
parts[parts.length - 1] = `${slug}.${ext}`
playerUrl.pathname = parts.join('/')
}
// Strip all metadata params — only the path matters for the CDN
playerUrl.searchParams.delete('hh')
playerUrl.searchParams.delete('ww')
playerUrl.searchParams.delete('mp4')
playerUrl.searchParams.delete('webm')
return {
success: true,
playerUri: playerUrl.href,
dimensions,
}
}
export function isKlipyGifUri(url: URL | string) {
try {
return parseKlipyGif(typeof url === 'string' ? new URL(url) : url).success
} catch {
// Invalid URL
return false
}
}
export function isGifEmbed(url: URL | string) {
return isTenorGifUri(url) || isKlipyGifUri(url)
}