diff --git a/app/api/opengraph/route.ts b/app/api/opengraph/route.ts new file mode 100644 index 0000000..22e7347 --- /dev/null +++ b/app/api/opengraph/route.ts @@ -0,0 +1,90 @@ +// HTML 파일로 부터 메타데이터 파싱 +const getMeta = (html: string, prop: string): string | null => { + const patterns = [ + new RegExp( + `]+(?:property|name)=["']${prop}["'][^>]+content=["']([^"']*)["']`, + 'i' + ), + new RegExp( + `]+content=["']([^"']*)["'][^>]+(?:property|name)=["']${prop}["']`, + 'i' + ), + ]; + for (const pattern of patterns) { + const match = html.match(pattern); + if (match?.[1]?.trim()) return match[1].trim(); + } + return null; +}; + +// Open Graph API 엔드포인트 +export const GET = async (request: Request) => { + const { searchParams } = new URL(request.url); + const url = searchParams.get('url'); + + if (!url) return Response.json({ error: 'url required' }, { status: 400 }); + + try { + new URL(url); + } catch { + return Response.json({ error: 'invalid url' }, { status: 400 }); + } + + try { + const res = await fetch(url, { + headers: { + 'User-Agent': + 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)', + Accept: 'text/html,application/xhtml+xml', + }, + signal: AbortSignal.timeout(5000), + }); + + if (!res.ok) + return Response.json({ error: 'fetch failed' }, { status: 502 }); + + const html = await res.text(); + const urlObj = new URL(url); + + const title = + getMeta(html, 'og:title') || + getMeta(html, 'twitter:title') || + html.match(/
+ {title} +
+ )} + {description && ( ++ {description} +
+ )} +대체 + // 커스텀 텍스트인 경우([text](url)) →
유지 후 카드 삽입 + const linkText = + aTag.children?.find((c: any) => c.type === 'text')?.value ?? ''; + const isUrlOnlyLink = linkText === href; + if (isUrlOnlyLink) { + parent.children.splice(index, 1, ogNode); + } else { + parent.children.splice(index + 1, 0, ogNode); + } } } }; -/** - * Open Graph 카드 노드 생성 - */ -export const createOpenGraph = (href: string) => { - return { - type: 'element', - tagName: 'a', - properties: { - className: 'open-graph', - href: href, - }, - children: [ - { - type: 'element', - tagName: 'img', - properties: { - src: `${href}`, - alt: 'Open Graph Image', - className: 'og-image', - }, - children: [], - }, - { - type: 'element', - tagName: 'div', - properties: { - className: 'og-container', - }, - children: [ - { - type: 'element', - tagName: 'h4', - properties: { - className: 'og-title', - }, - children: [ - { - type: 'text', - value: decodeURIComponent(href.split('/').pop()!).replaceAll( - '-', - ' ' - ), - }, - ], - }, - { - type: 'element', - tagName: 'span', - properties: { - className: 'og-content', - }, - children: [], - }, - { - type: 'element', - tagName: 'span', - properties: { - className: 'og-domain', - }, - children: [ - { - type: 'text', - value: '', - }, - ], - }, - ], - }, - ], - }; -}; - /** * 이미지 클릭 핸들러를 추가하는 함수 팩토리 * @param setSelectedImage - 선택된 이미지 URL을 설정하는 함수