elk/server/api/og-image/[url].ts

122 lines
3.2 KiB
TypeScript
Raw Normal View History

2022-12-12 14:55:57 +00:00
import opengraph from 'opengraph-io'
2022-12-12 19:06:57 +00:00
// This API-Endpoint will be cached via nuxt.config.ts -> nitro.routeRules['/api/og-image/**']
2022-12-12 13:26:30 +00:00
2022-12-12 15:27:33 +00:00
type OpenGraphClient = ReturnType<typeof opengraph>
2022-12-12 14:55:57 +00:00
2022-12-12 15:27:33 +00:00
let openGraphClient: OpenGraphClient
function getOpenGraphClient(): OpenGraphClient {
2022-12-12 20:51:50 +00:00
const appId = useRuntimeConfig().opengraphApi
if (typeof appId !== 'string')
2022-12-12 15:27:33 +00:00
throw new Error('Missing NUXT_OPENGRAPH_API environment variable.')
if (!openGraphClient)
2022-12-12 20:51:50 +00:00
openGraphClient = opengraph({ appId, fullRender: true })!
2022-12-12 14:55:57 +00:00
return openGraphClient
2022-12-12 13:26:30 +00:00
}
2022-12-10 22:09:11 +00:00
2022-12-12 15:00:26 +00:00
function extractOgImageUrl(html: string): string {
2022-12-12 20:58:03 +00:00
const match = html.match(/<meta[^>]*property="og:image"[^>]*content="([^"]+)"|<meta[^>]*content="([^"]+)"[^>]*property="og:image"/)
return match?.[1] ?? match?.[2] ?? ''
2022-12-12 15:00:26 +00:00
}
2022-12-12 15:53:43 +00:00
async function resolveOgImageUrlManually(cardUrl: string): Promise<string> {
const html = await $fetch<string>(cardUrl)
const ogImageUrl = extractOgImageUrl(html)
if (!ogImageUrl) {
// Throw an error so we can try to apply another fallback
throw new Error('Could not find og:image in html.')
}
return ogImageUrl
}
2022-12-10 22:09:11 +00:00
export default defineEventHandler(async (event) => {
2022-12-12 20:51:50 +00:00
const config = useRuntimeConfig()
2022-12-12 15:53:43 +00:00
const { url } = getRouterParams(event)
const cardUrl = decodeURIComponent(url)
2022-12-10 22:09:11 +00:00
if (!cardUrl) {
2022-12-12 21:46:37 +00:00
sendError(event, {
2022-12-10 22:09:11 +00:00
statusCode: 422,
2022-12-12 21:46:37 +00:00
fatal: false,
message: 'Missing cardUrl.',
name: 'OgImageError',
unhandled: false,
2022-12-10 22:09:11 +00:00
})
2022-12-12 21:46:37 +00:00
return
2022-12-10 22:09:11 +00:00
}
if (typeof cardUrl !== 'string') {
2022-12-12 21:46:37 +00:00
sendError(event, {
2022-12-10 22:09:11 +00:00
statusCode: 422,
2022-12-12 21:46:37 +00:00
fatal: false,
message: 'cardUrl must be string.',
name: 'OgImageError',
unhandled: false,
2022-12-10 22:09:11 +00:00
})
2022-12-12 21:46:37 +00:00
return
2022-12-10 22:09:11 +00:00
}
2022-12-12 15:53:43 +00:00
// If anything goes wrong, fail gracefully
2022-12-12 15:31:29 +00:00
try {
// First we want to try to get the og:image from the html
// But sometimes it is not included due to async JS loading
2022-12-12 15:53:43 +00:00
let ogImageUrl = await resolveOgImageUrlManually(cardUrl).catch(() =>
// Try another fallback
'',
)
2022-12-12 14:55:57 +00:00
2022-12-12 20:51:50 +00:00
if (config.opengraphApi) {
2022-12-12 15:53:43 +00:00
// If no og:image was found, try to get it from opengraph.io
2022-12-12 15:31:29 +00:00
if (!ogImageUrl) {
2022-12-12 17:43:38 +00:00
const response = await getOpenGraphClient().getSiteInfo(cardUrl).catch(() =>
// Try another fallback
null,
)
2022-12-12 15:00:26 +00:00
2022-12-12 15:31:29 +00:00
ogImageUrl = response?.openGraph?.image?.url || response?.hybridGraph?.image || ''
}
}
2022-12-12 14:55:57 +00:00
2022-12-12 21:13:23 +00:00
if (!ogImageUrl.startsWith('https')) {
// If the og:image is not https, we can't use it
2022-12-12 21:41:22 +00:00
sendError(event, {
2022-12-12 21:18:29 +00:00
statusCode: 404, // Must be 404 so the srcset can fallback to the default image
2022-12-12 21:41:22 +00:00
fatal: false,
message: 'og:image must be https.',
name: 'OgImageError',
unhandled: false,
2022-12-12 21:13:23 +00:00
})
2022-12-12 21:41:22 +00:00
return
2022-12-12 21:13:23 +00:00
}
2022-12-12 17:43:38 +00:00
if (!ogImageUrl) {
2022-12-12 20:59:35 +00:00
// If nothing helped, send 404 so the srcset can fallback to the default image
2022-12-12 21:41:22 +00:00
sendError(event, {
2022-12-12 20:59:35 +00:00
statusCode: 404,
2022-12-12 21:41:22 +00:00
fatal: false,
message: 'Could not find og:image.',
name: 'OgImageError',
unhandled: false,
2022-12-12 20:59:35 +00:00
})
2022-12-12 21:41:22 +00:00
return
2022-12-12 17:43:38 +00:00
}
await sendRedirect(event, ogImageUrl)
2022-12-12 15:31:29 +00:00
}
catch (error) {
throw createError({
statusCode: 500,
statusMessage: (error as Error)?.message || 'Unknown error.',
cause: error,
})
}
2022-12-10 22:09:11 +00:00
})
2022-12-12 13:26:30 +00:00