mirror of
https://github.com/dergigi/boris.git
synced 2025-12-18 15:14:20 +01:00
feat(services): youtubeMetaService with 7d localStorage cache and ID extraction
This commit is contained in:
77
src/services/youtubeMetaService.ts
Normal file
77
src/services/youtubeMetaService.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
export type Caption = { start: number; dur: number; text: string }
|
||||
export type YouTubeMeta = {
|
||||
title: string
|
||||
description?: string
|
||||
captions: Caption[]
|
||||
transcript?: string
|
||||
lang: string
|
||||
isAuto?: boolean
|
||||
source: 'youtube'
|
||||
}
|
||||
|
||||
type CachedMeta = {
|
||||
data: YouTubeMeta
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
const TTL_MS = 7 * 24 * 60 * 60 * 1000 // 7 days
|
||||
|
||||
function cacheKey(videoId: string, lang: string) {
|
||||
return `yt_meta_${videoId}_${lang}`
|
||||
}
|
||||
|
||||
function load(videoId: string, lang: string): YouTubeMeta | null {
|
||||
try {
|
||||
const raw = localStorage.getItem(cacheKey(videoId, lang))
|
||||
if (!raw) return null
|
||||
const { data, timestamp } = JSON.parse(raw) as CachedMeta
|
||||
if (Date.now() - timestamp > TTL_MS) {
|
||||
localStorage.removeItem(cacheKey(videoId, lang))
|
||||
return null
|
||||
}
|
||||
return data
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function save(videoId: string, lang: string, data: YouTubeMeta) {
|
||||
try {
|
||||
const value: CachedMeta = { data, timestamp: Date.now() }
|
||||
localStorage.setItem(cacheKey(videoId, lang), JSON.stringify(value))
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
export function extractYouTubeId(url: string): string | null {
|
||||
try {
|
||||
const u = new URL(url)
|
||||
if (u.hostname === 'youtu.be') {
|
||||
return u.pathname.slice(1)
|
||||
}
|
||||
if (u.searchParams.get('v')) return u.searchParams.get('v')
|
||||
const parts = u.pathname.split('/').filter(Boolean)
|
||||
// /shorts/:id or /embed/:id
|
||||
if ((parts[0] === 'shorts' || parts[0] === 'embed') && parts[1]) return parts[1]
|
||||
return null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function getYouTubeMeta(videoId: string, lang = 'en'): Promise<YouTubeMeta | null> {
|
||||
const cached = load(videoId, lang)
|
||||
if (cached) return cached
|
||||
const res = await fetch(`/api/youtube-meta?videoId=${encodeURIComponent(videoId)}&lang=${encodeURIComponent(lang)}`, {
|
||||
headers: {
|
||||
'x-ui-locale': lang
|
||||
}
|
||||
})
|
||||
if (!res.ok) return null
|
||||
const data = (await res.json()) as YouTubeMeta
|
||||
save(videoId, lang, data)
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user