import { NextRequest, NextResponse } from 'next/server' import { auth } from '@/auth' import { extractArticleFromHtml } from '@/lib/clip/extract-article' import { analyzeClipContent } from '@/lib/clip/analyze-clip' import { resolveClipLocale, wrapClipPlainParagraph } from '@/lib/clip/rtl-content' function isBlockedUrl(url: string): boolean { try { const parsed = new URL(url) const hostname = parsed.hostname.toLowerCase() const blocked = ['localhost', '127.0.0.1', '0.0.0.0', '::1', '169.254.169.254'] if (blocked.includes(hostname)) return true if (hostname.startsWith('10.') || hostname.startsWith('172.') || hostname.startsWith('192.168.')) return true if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') return true return false } catch { return true } } async function fetchPageHtml(url: string): Promise { const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), 15000) try { const response = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; MementoClipper/1.0)', Accept: 'text/html,application/xhtml+xml', }, signal: controller.signal, redirect: 'follow', }) if (!response.ok) return null const ct = response.headers.get('content-type') || '' if (!ct.includes('text/html') && !ct.includes('application/xhtml')) return null return await response.text() } catch { return null } finally { clearTimeout(timeoutId) } } export async function POST(request: NextRequest) { try { const session = await auth() if (!session?.user?.id) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const body = await request.json() const url = typeof body.url === 'string' ? body.url.trim() : '' const htmlInput = typeof body.html === 'string' ? body.html : '' const selection = typeof body.selection === 'string' ? body.selection.trim() : '' if (!url || isBlockedUrl(url)) { return NextResponse.json({ error: 'Invalid URL' }, { status: 400 }) } let title = '' let textContent = '' let contentHtml = '' if (body.mode === 'link') { title = new URL(url).hostname textContent = url contentHtml = `

${url}

` } else if (body.mode === 'selection' && selection) { title = typeof body.title === 'string' ? body.title : new URL(url).hostname textContent = selection const locale = resolveClipLocale(url, title, selection) contentHtml = wrapClipPlainParagraph(selection, locale) } else { const html = htmlInput || (await fetchPageHtml(url)) if (!html) { return NextResponse.json({ error: 'Could not fetch page content' }, { status: 422 }) } const extracted = extractArticleFromHtml(html, url) if (!extracted) { return NextResponse.json({ error: 'Could not extract readable article' }, { status: 422 }) } title = extracted.title || (typeof body.title === 'string' ? body.title : '') textContent = extracted.textContent contentHtml = extracted.content } const analysis = await analyzeClipContent({ url, title, textContent }) return NextResponse.json({ title: analysis.title, summary: analysis.summary, tags: analysis.tags, readingTime: analysis.readingTimeMinutes, content: contentHtml, excerpt: textContent.slice(0, 500), }) } catch (error) { console.error('[POST /api/clip/analyze]', error) return NextResponse.json({ error: 'Analysis failed' }, { status: 500 }) } }