// GIPHY v1 client — Google closed Tenor to new API clients in Jan 2026, so // the picker uses GIPHY's Developer API instead. Provision a free key at // https://developers.giphy.com/dashboard/ and expose it via the renderer // env var VITE_GIPHY_API_KEY (e.g. in apps/desktop/.env.local). When unset, // every call throws — the picker degrades to a friendly // "GIFs sind nicht konfiguriert" message via the consumer's catch. // // The file is still named `tenor.ts` for import-path stability; rename later // if it bothers anyone. const ENDPOINT = 'https://api.giphy.com/v1/gifs'; const RECENT_KEY = 'chatapp.gifRecent.v1'; const RECENT_MAX = 24; const API_KEY = ( (import.meta as unknown as { env?: { VITE_GIPHY_API_KEY?: string } }).env ?.VITE_GIPHY_API_KEY ?? '' ).trim(); export interface GifResult { id: string; // Animated full-size URL (typically <2 MB). url: string; // Small preview shown in the picker grid. previewUrl: string; width: number; height: number; description: string; } interface GiphyImage { url?: string; width?: string; height?: string; } interface GiphyResult { id: string; title?: string; images?: { original?: GiphyImage; fixed_height?: GiphyImage; fixed_height_small?: GiphyImage; preview_gif?: GiphyImage; }; } function mapResult(r: GiphyResult): GifResult | null { const full = r.images?.original ?? r.images?.fixed_height; const preview = r.images?.fixed_height_small ?? r.images?.preview_gif ?? r.images?.fixed_height ?? full; if (!full?.url || !preview?.url) return null; return { id: r.id, url: full.url, previewUrl: preview.url, width: full.width ? Number(full.width) : 0, height: full.height ? Number(full.height) : 0, description: r.title ?? '', }; } async function fetchGiphy(path: string, params: Record): Promise { if (!API_KEY) { throw new Error('giphy_api_key_missing'); } const search = new URLSearchParams({ api_key: API_KEY, rating: 'pg-13', ...params }); const res = await fetch(`${ENDPOINT}/${path}?${search.toString()}`); if (!res.ok) throw new Error('giphy http ' + res.status); const json = (await res.json()) as { data?: GiphyResult[] }; return (json.data ?? []).map(mapResult).filter((x): x is GifResult => x !== null); } export function isGifProviderConfigured(): boolean { return API_KEY.length > 0; } // Back-compat alias for the original Tenor function name. New code should // use isGifProviderConfigured. export const isTenorConfigured = isGifProviderConfigured; export async function searchGifs(query: string, locale: string = 'de'): Promise { if (!query.trim()) return featuredGifs(locale); return fetchGiphy('search', { q: query, limit: '30', lang: locale }); } export async function featuredGifs(_locale: string = 'de'): Promise { return fetchGiphy('trending', { limit: '30' }); } export function getRecentGifs(): GifResult[] { try { const raw = window.localStorage.getItem(RECENT_KEY); if (!raw) return []; const parsed = JSON.parse(raw) as unknown; if (!Array.isArray(parsed)) return []; return parsed.filter( (x): x is GifResult => x != null && typeof x === 'object' && typeof (x as GifResult).url === 'string', ); } catch { return []; } } export function rememberRecentGif(gif: GifResult): void { const current = getRecentGifs().filter((g) => g.id !== gif.id); const next = [gif, ...current].slice(0, RECENT_MAX); try { window.localStorage.setItem(RECENT_KEY, JSON.stringify(next)); } catch { /* quota */ } }