/** * OpenGraph metadata fetcher service * Fetches OpenGraph metadata from URLs and caches results */ export interface OpenGraphData { title?: string; description?: string; image?: string; url?: string; siteName?: string; type?: string; cachedAt: number; } const CACHE_DURATION = 7 * 24 * 60 * 60 * 1000; // 7 days const CACHE_KEY_PREFIX = 'opengraph_'; /** * Fetch OpenGraph metadata from a URL * Uses a CORS proxy if needed, caches results in localStorage */ export async function fetchOpenGraph(url: string): Promise { // Check cache first const cached = getCachedOpenGraph(url); if (cached && Date.now() - cached.cachedAt < CACHE_DURATION) { return cached; } try { // Try to fetch the page HTML // Note: Direct fetch may fail due to CORS, so we'll use a simple approach // In production, you might want to use a backend proxy or service const response = await fetch(url, { method: 'GET', headers: { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'User-Agent': 'Mozilla/5.0 (compatible; aitherboard/1.0)' }, mode: 'cors', cache: 'no-cache' }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const html = await response.text(); const ogData = parseOpenGraph(html, url); // Cache the result if (ogData) { cacheOpenGraph(url, ogData); } return ogData; } catch (error) { console.warn('Failed to fetch OpenGraph data:', error); // Return cached data even if expired, or null return cached || null; } } /** * Parse OpenGraph metadata from HTML */ function parseOpenGraph(html: string, url: string): OpenGraphData | null { const og: Partial = { cachedAt: Date.now() }; // Extract OpenGraph meta tags const ogTitleMatch = html.match(/]*>([^<]+)<\/title>/i); if (titleMatch) { og.title = decodeHtmlEntities(titleMatch[1].trim()); } } if (!og.description) { const metaDescriptionMatch = html.match(/ CACHE_DURATION) { keysToRemove.push(key); } } catch { keysToRemove.push(key); } } } keysToRemove.forEach(key => localStorage.removeItem(key)); }