feat: make author clickable

This commit is contained in:
2023-08-12 18:32:56 +02:00
parent d2a02fcf34
commit 2b4173d759
27 changed files with 257 additions and 174 deletions

95
lib/cache/image.ts vendored
View File

@@ -1,4 +1,4 @@
import { hash, isLocalImage } from "@lib/string.ts";
import { hash, isLocalImage, rgbToHex } from "@lib/string.ts";
import * as cache from "@lib/cache/cache.ts";
import {
ImageMagick,
@@ -8,32 +8,45 @@ import { createLogger } from "@lib/log.ts";
import { generateThumbhash } from "@lib/thumbhash.ts";
import { SILVERBULLET_SERVER } from "@lib/env.ts";
type ImageCacheOptions = {
type ImageCacheOptionsBasic = {
url: string;
mediaType?: string;
};
interface ImageCacheOptionsDimensions extends ImageCacheOptionsBasic {
width: number;
height: number;
mediaType?: string;
suffix?: string;
};
}
interface ImageCacheOptionsSuffix extends ImageCacheOptionsBasic {
suffix: string;
}
type ImageCacheOptions = ImageCacheOptionsDimensions | ImageCacheOptionsSuffix;
const CACHE_KEY = "images";
const log = createLogger("cache/image");
function getCacheKey(
{ url: _url, width, height, suffix }: ImageCacheOptions,
opts: ImageCacheOptions,
) {
const isLocal = isLocalImage(_url);
const url = new URL(isLocal ? `${SILVERBULLET_SERVER}/${_url}` : _url);
const isLocal = isLocalImage(opts.url);
const url = new URL(
isLocal ? `${SILVERBULLET_SERVER}/${opts.url}` : opts.url,
);
const _suffix = suffix || `${width}:${height}`;
const _suffix = "suffix" in opts
? opts.suffix
: `${opts.width}:${opts.height}`;
return `${CACHE_KEY}:${url.hostname}:${
const cacheId = `${CACHE_KEY}:${url.hostname}:${
url.pathname.replaceAll("/", ":")
}:${_suffix}`
.replace(
"::",
":",
);
return cacheId;
}
export function createThumbhash(
@@ -53,7 +66,21 @@ export function createThumbhash(
"RGBA",
);
if (!bytes) return;
const hash = generateThumbhash(bytes, _image.width, _image.height);
const [hash, average] = generateThumbhash(
bytes,
_image.width,
_image.height,
);
if (average) {
cache.set(
getCacheKey({
url,
suffix: "average",
}),
rgbToHex(average.r, average.g, average.b),
);
}
if (hash) {
const b64 = btoa(String.fromCharCode(...hash));
@@ -61,8 +88,6 @@ export function createThumbhash(
getCacheKey({
url,
suffix: "thumbnail",
width: _image.width,
height: _image.height,
}),
b64,
);
@@ -91,18 +116,26 @@ function verifyImage(
}
export function getThumbhash({ url }: { url: string }) {
return cache.get<Uint8Array>(
getCacheKey({
url,
suffix: "thumbnail",
width: 200,
height: 200,
}),
return Promise.all(
[
cache.get<Uint8Array>(
getCacheKey({
url,
suffix: "thumbnail",
}),
),
cache.get<string>(
getCacheKey({
url,
suffix: "average",
}),
),
] as const,
);
}
export async function getImage({ url, width, height }: ImageCacheOptions) {
const cacheKey = getCacheKey({ url, width, height });
export async function getImage(opts: ImageCacheOptions) {
const cacheKey = getCacheKey(opts);
const pointerCacheRaw = await cache.get<string>(cacheKey);
if (!pointerCacheRaw) return;
@@ -122,31 +155,29 @@ export async function getImage({ url, width, height }: ImageCacheOptions) {
export async function setImage(
buffer: Uint8Array,
{ url, width, height, mediaType }: ImageCacheOptions,
opts: ImageCacheOptions,
) {
const clone = new Uint8Array(buffer);
const imageCorrect = await verifyImage(clone);
if (!imageCorrect) {
log.info("failed to store image", { url });
log.info("failed to store image", { url: opts.url });
return;
}
const cacheKey = getCacheKey({ url, width, height });
const cacheKey = getCacheKey(opts);
const pointerId = await hash(cacheKey);
await cache.set(`image:${pointerId}`, clone);
cache.expire(pointerId, 60 * 60 * 24);
cache.expire(cacheKey, 60 * 60 * 24);
await cache.set(`image:${pointerId}`, clone, { expires: 60 * 60 * 24 });
await cache.set(
cacheKey,
JSON.stringify({
id: pointerId,
url,
width,
height,
mediaType,
...("suffix" in opts
? { suffix: opts.suffix }
: { width: opts.width, height: opts.height }),
}),
{ expires: 60 * 60 * 24 },
);
}