Initial Commit

This commit is contained in:
2026-10-03 20:07:49 +02:00
commit a1346c98b1
427 changed files with 59320 additions and 0 deletions
+58
View File
@@ -0,0 +1,58 @@
const MAX_HTML_BYTES = 512 * 1024
function metaContent(html: string, keys: string[]): string | null {
for (const key of keys) {
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
const before = new RegExp(`<meta[^>]+(?:property|name)=["']${escaped}["'][^>]+content=["']([^"']+)["']`, 'i')
const after = new RegExp(`<meta[^>]+content=["']([^"']+)["'][^>]+(?:property|name)=["']${escaped}["']`, 'i')
const match = before.exec(html) ?? after.exec(html)
if (match?.[1]?.trim()) return match[1].trim()
}
return null
}
export interface LinkPreview {
image: string | null
title: string | null
}
const BLOCKED_HOST = /^(localhost|127\.|0\.0\.0\.0|::1|\[::1\]|169\.254\.|10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.|.*\.(local|internal|lan)$)/i
export function isBlockedHost(hostname: string): boolean {
return BLOCKED_HOST.test(hostname)
}
export async function fetchLinkPreview(url: string): Promise<LinkPreview> {
try {
const hostname = new URL(url).hostname
if (isBlockedHost(hostname)) return { image: null, title: null }
const res = await fetch(url, {
signal: AbortSignal.timeout(5000),
redirect: 'follow',
headers: {
'user-agent': 'Mozilla/5.0 (compatible; 4Brett-LinkPreview/1.0)',
'accept': 'text/html,application/xhtml+xml',
},
})
if (!res.ok) return { image: null, title: null }
const contentType = res.headers.get('content-type') ?? ''
if (!contentType.includes('text/html') && !contentType.includes('application/xhtml')) {
return { image: null, title: null }
}
const html = (await res.text()).slice(0, MAX_HTML_BYTES)
const base = res.url || url
const image = metaContent(html, ['og:image', 'og:image:url', 'og:image:secure_url', 'twitter:image', 'twitter:image:src'])
const title = metaContent(html, ['og:title', 'twitter:title'])
?? /<title[^>]*>([^<]+)<\/title>/i.exec(html)?.[1]?.trim()
?? null
return {
image: image ? new URL(image, base).href.slice(0, 2048) : null,
title: title?.slice(0, 500) ?? null,
}
}
catch {
return { image: null, title: null }
}
}