import fastMetaTags, { userAgent } from 'fast-meta-tags'
export default async function getLinkPreview(url, opt) {
if (url.startsWith('http://')) url = 'https' + url.slice(4)
const data = await fastMetaTags(url, opt)
if (data === null) return null
const preview = parse(data)
const image = getImage(data)
preview.file = await getFile(image, opt)
return preview
}
function parse(data) {
return {
url: data.url,
favicon: data.favicon,
title: getTitle(data),
name: getSiteName(data),
description: getDescription(data)
}
}
function getSiteName(data) {
return (
getMetaContent(data.meta, {
name: 'og:site_name',
property: 'og:site_name',
}) || getUrlDomain(data.url)
)
}
function getTitle(data) {
return getMetaContent(data.meta, { name: 'og:title', property: 'og:title' }) || data.title
}
function getDescription(data) {
return getMetaContent(data.meta, { name: 'description', property: 'og:description' })
}
function getImage(data) {
return getMetaContent(data.meta, { name: 'og:image', property: 'og:image' })
}
async function getFile(src, opt) {
if (!src) return null
return opt?.noBuffer
? { src, name: toFilename(src, null), buffer: null }
: await getFileBuffer(src, opt)
}
async function getFileBuffer(src, opt) {
try {
const res = await fetch(src, {
headers: {
Accept: '*/*',
'User-Agent': opt?.userAgent || userAgent,
},
})
if (!res.ok) return null
const type = res.headers.get('Content-Type')
return { src, type, name: toFilename(src, type), buffer: await res.arrayBuffer() }
} catch {
return null
}
}
function getUrlDomain(url) {
try {
const parsedUrl = new URL(url)
return parsedUrl.hostname
} catch (error) {
return null
}
}
function getMetaContent(meta, filter) {
const entries = Object.entries(filter)
return meta.find((m) => {
for (const [k, v] of entries) {
if (m[k] === v) return true
}
})?.content
}
function toFilename(url, type) {
switch (type) {
case 'image/png':
return 'preview.png'
case 'image/jpg':
case 'image/jpeg':
return 'preview.jpeg'
case 'image/gif':
return 'preview.gif'
}
return decodeURIComponent(url?.split('/').pop())
}