mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
Comment the image generator
This commit is contained in:
@@ -9,41 +9,36 @@ import { format } from "@tusbar/cache-control";
|
|||||||
const cache = new TTLCache({ max: 10000, ttl: 1000 * 60 * 60 })
|
const cache = new TTLCache({ max: 10000, ttl: 1000 * 60 * 60 })
|
||||||
import fnv from "fnv-plus"
|
import fnv from "fnv-plus"
|
||||||
|
|
||||||
// import type { Endpoints } from "@octokit/types";
|
|
||||||
|
|
||||||
// let repoRegex = new RegExp("https?://github\.com/(?<repo>[a-zA-Z0-9]+/[a-zA-Z0-9]+)/?")
|
|
||||||
|
|
||||||
const fontFile = await fetch('https://og-playground.vercel.app/inter-latin-ext-700-normal.woff');
|
const fontFile = await fetch('https://og-playground.vercel.app/inter-latin-ext-700-normal.woff');
|
||||||
const fontData: ArrayBuffer = await fontFile.arrayBuffer();
|
const fontData: ArrayBuffer = await fontFile.arrayBuffer();
|
||||||
|
|
||||||
// const height = 630;
|
|
||||||
// const width = 1200;
|
|
||||||
const defaultWidth = 800;
|
const defaultWidth = 800;
|
||||||
const defaultRatio = 0.5
|
const defaultRatio = 0.5
|
||||||
// const defaultWidth = 800;
|
|
||||||
|
|
||||||
|
// JSX stub
|
||||||
const h = (type: any, props: any) => { return { type, props } }
|
const h = (type: any, props: any) => { return { type, props } }
|
||||||
|
|
||||||
type a = RequestHandler;
|
type a = RequestHandler;
|
||||||
/** @type {RequestHandler} */
|
/** @type {RequestHandler} */
|
||||||
export async function GET({ url, request }) {
|
export async function GET({ url, request }) {
|
||||||
|
// First, get the information about the post
|
||||||
|
// We have the slug and date of the post, which we can use to look up the post
|
||||||
const slug = url.searchParams.get('slug')
|
const slug = url.searchParams.get('slug')
|
||||||
let dateParts = url.searchParams.get('date')?.split(/[\/-]/)?.map((p: string) => parseInt(p, 10))
|
let dateParts = url.searchParams.get('date')?.split(/[\/-]/)?.map((p: string) => parseInt(p, 10))
|
||||||
if (dateParts && dateParts.length > 3) {
|
if (dateParts && dateParts.length > 3) {
|
||||||
throw error(404, 'Post not found (bad date)')
|
throw error(404, 'Post not found (bad date)')
|
||||||
}
|
}
|
||||||
|
// Next, get the width and ratio of the image
|
||||||
|
// to determine the size of the image
|
||||||
const width = Number(url.searchParams.get('width'))
|
const width = Number(url.searchParams.get('width'))
|
||||||
const ratio = Number(url.searchParams.get('ratio'))
|
const ratio = Number(url.searchParams.get('ratio'))
|
||||||
|
// If the width or ratio is too big, don't render the image to prevent DoS attacks
|
||||||
if (width > 10000 || ratio > 50) {
|
if (width > 10000 || ratio > 50) {
|
||||||
throw error(400, 'Image too big')
|
throw error(400, 'Image too big')
|
||||||
}
|
}
|
||||||
let image;
|
let image;
|
||||||
|
|
||||||
// let start = new Date(dateParts[0] || 1, dateParts[1] || 0, dateParts[2] || 0);
|
// Look up the post in the database
|
||||||
// // @ts-ignore
|
|
||||||
// let end = new Date(...dateParts);
|
|
||||||
// console.log(dateParts)
|
|
||||||
|
|
||||||
// get post with metadata
|
|
||||||
const page = pages
|
const page = pages
|
||||||
.filter((post) => slug === post.slug)
|
.filter((post) => slug === post.slug)
|
||||||
.filter((post) => {
|
.filter((post) => {
|
||||||
@@ -61,16 +56,21 @@ export async function GET({ url, request }) {
|
|||||||
throw error(404, 'Post not found')
|
throw error(404, 'Post not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate a cache key based on the post's canonical URL, reading time, width, and ratio
|
||||||
|
// Caching the image based on this key ensures that the image is not regenerated every time
|
||||||
|
// The cache key is also used for browser caching
|
||||||
let cache_key = fnv.hash(page.canonical + "\x00" + page.readingTime.text + "\x00" + width + "\x00" + ratio).str()
|
let cache_key = fnv.hash(page.canonical + "\x00" + page.readingTime.text + "\x00" + width + "\x00" + ratio).str()
|
||||||
|
|
||||||
let recieved_etag = request.headers.get("if-none-match");
|
let received_etag = request.headers.get("if-none-match");
|
||||||
|
// If the client has a cached version of the image, return a 304 Not Modified response, indicating that the image has not changed
|
||||||
if (recieved_etag == cache_key) {
|
// This means we don't even have to have the image cached in memory
|
||||||
console.log("304")
|
if (received_etag == cache_key) {
|
||||||
return new Response(null, { status: 304 })
|
return new Response(null, { status: 304 })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the image is not cached, generate the image and cache it
|
||||||
if (!cache.has(cache_key)) {
|
if (!cache.has(cache_key)) {
|
||||||
|
// First, render the HTML / JSX-based template
|
||||||
let template = h("div", {
|
let template = h("div", {
|
||||||
style: {
|
style: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -114,6 +114,7 @@ export async function GET({ url, request }) {
|
|||||||
children: `Published on ${new Date(page.date).toLocaleDateString()} by Jade Ellis · ${page.readingTime.text}`
|
children: `Published on ${new Date(page.date).toLocaleDateString()} by Jade Ellis · ${page.readingTime.text}`
|
||||||
})]
|
})]
|
||||||
});
|
});
|
||||||
|
// Then, convert the vdom to SVG using satori
|
||||||
const svg = await satori(template, {
|
const svg = await satori(template, {
|
||||||
fonts: [
|
fonts: [
|
||||||
{
|
{
|
||||||
@@ -126,6 +127,7 @@ export async function GET({ url, request }) {
|
|||||||
width: defaultWidth,
|
width: defaultWidth,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Then, convert the SVG to a PNG image using resvg
|
||||||
const resvg = new Resvg(svg, {
|
const resvg = new Resvg(svg, {
|
||||||
fitTo: {
|
fitTo: {
|
||||||
mode: 'width',
|
mode: 'width',
|
||||||
@@ -134,20 +136,25 @@ export async function GET({ url, request }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
image = resvg.render().asPng();
|
image = resvg.render().asPng();
|
||||||
|
// Finally, save the image to the cache
|
||||||
cache.set(cache_key, image);
|
cache.set(cache_key, image);
|
||||||
} else {
|
} else {
|
||||||
|
// If the image is cached, return it
|
||||||
image = cache.get(cache_key) as Buffer
|
image = cache.get(cache_key) as Buffer
|
||||||
}
|
}
|
||||||
|
// Finally, return the image as a response
|
||||||
return new Response(image, {
|
return new Response(image, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'image/png',
|
'Content-Type': 'image/png',
|
||||||
|
// Cache the image for 24 hours
|
||||||
'Cache-Control': format({
|
'Cache-Control': format({
|
||||||
public: true,
|
public: true,
|
||||||
// immutable: true
|
// immutable: true
|
||||||
maxAge: 60 * 60 * 24
|
maxAge: 60 * 60 * 24
|
||||||
}),
|
}),
|
||||||
|
// Set the cache key as the ETag
|
||||||
'ETag': cache_key,
|
'ETag': cache_key,
|
||||||
|
// Allow cross-origin requests to serve the image
|
||||||
'Cross-Origin-Resource-Policy': 'cross-origin'
|
'Cross-Origin-Resource-Policy': 'cross-origin'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user