import { NextRequest, NextResponse } from 'next/server' export async function GET(req: NextRequest) { const url = req.nextUrl.searchParams.get('url') if (!url) { return NextResponse.json({ error: 'Missing url' }, { status: 400 }) } try { const parsed = new URL(url) if (!['http:', 'https:'].includes(parsed.protocol)) { return NextResponse.json({ error: 'Invalid protocol' }, { status: 400 }) } const controller = new AbortController() const timeout = setTimeout(() => controller.abort(), 8000) const res = await fetch(url, { signal: controller.signal, headers: { 'User-Agent': 'Mozilla/5.0 (compatible; MementoBot/1.0)', 'Accept': 'text/html', }, redirect: 'follow', }) clearTimeout(timeout) if (!res.ok) { return NextResponse.json({ error: 'Fetch failed' }, { status: 502 }) } const html = await res.text() const data = extractMetadata(html, parsed) return NextResponse.json(data, { headers: { 'Cache-Control': 'public, s-maxage=86400' }, }) } catch { return NextResponse.json({ error: 'Failed to fetch' }, { status: 502 }) } } function extractMetadata(html: string, url: URL) { const getMeta = (pattern: RegExp): string | null => { const m = html.match(pattern) return m?.[1]?.trim() || null } const getMetaProperty = (prop: string): string | null => { return getMeta(new RegExp(`]+(?:property|name)=["']${prop}["'][^>]+content=["']([^"']+)["']`, 'i')) || getMeta(new RegExp(`]+content=["']([^"']+)["'][^>]+(?:property|name)=["']${prop}["']`, 'i')) } const title = getMetaProperty('og:title') || getMeta(new RegExp(']*>([^<]+)', 'i')) || null const description = getMetaProperty('og:description') || getMetaProperty('description') || getMetaProperty('twitter:description') || null const image = getMetaProperty('og:image') || getMetaProperty('twitter:image') || null const siteName = getMetaProperty('og:site_name') || null const favicon = image ? null : `https://www.google.com/s2/favicons?domain=${url.hostname}&sz=32` const finalImage = image ? (image.startsWith('http') ? image : new URL(image, url.origin).href) : null return { title: title ? decodeEntities(title) : null, description: description ? decodeEntities(description).slice(0, 200) : null, image: finalImage, favicon, siteName: siteName || url.hostname.replace('www.', ''), } } function decodeEntities(text: string): string { return text .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, "'") .replace(/'/g, "'") .replace(/ /g, ' ') }