Cache Telegram avatars locally and refresh avatar file ids
This commit is contained in:
@@ -1,9 +1,13 @@
|
|||||||
import { getQuery, setHeader } from "h3";
|
import { getQuery, setHeader } from "h3";
|
||||||
|
import { existsSync } from "node:fs";
|
||||||
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
import { getAuthContext } from "../../../utils/auth";
|
import { getAuthContext } from "../../../utils/auth";
|
||||||
import { prisma } from "../../../utils/prisma";
|
import { prisma } from "../../../utils/prisma";
|
||||||
import { requireTelegramBotToken, telegramApiBase, telegramBotApi } from "../../../utils/telegram";
|
import { requireTelegramBotToken, telegramApiBase, telegramBotApi } from "../../../utils/telegram";
|
||||||
|
|
||||||
const TELEGRAM_FILE_MARKER = "tg-file:";
|
const TELEGRAM_FILE_MARKER = "tg-file:";
|
||||||
|
const AVATAR_CACHE_DIR = path.join(process.cwd(), ".data", "telegram-avatars");
|
||||||
|
|
||||||
type TelegramFileMeta = {
|
type TelegramFileMeta = {
|
||||||
file_id: string;
|
file_id: string;
|
||||||
@@ -17,6 +21,10 @@ function parseTelegramFileId(avatarUrl: string | null | undefined) {
|
|||||||
return fileId || null;
|
return fileId || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sanitizeCacheKey(input: string) {
|
||||||
|
return input.replace(/[^a-zA-Z0-9._-]/g, "_");
|
||||||
|
}
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const auth = await getAuthContext(event);
|
const auth = await getAuthContext(event);
|
||||||
const query = getQuery(event);
|
const query = getQuery(event);
|
||||||
@@ -45,6 +53,20 @@ export default defineEventHandler(async (event) => {
|
|||||||
throw createError({ statusCode: 404, statusMessage: "telegram avatar is missing" });
|
throw createError({ statusCode: 404, statusMessage: "telegram avatar is missing" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cacheKey = sanitizeCacheKey(fileId);
|
||||||
|
const cacheBodyPath = path.join(AVATAR_CACHE_DIR, `${cacheKey}.img`);
|
||||||
|
const cacheMetaPath = path.join(AVATAR_CACHE_DIR, `${cacheKey}.meta`);
|
||||||
|
|
||||||
|
if (existsSync(cacheBodyPath) && existsSync(cacheMetaPath)) {
|
||||||
|
const cachedBody = await readFile(cacheBodyPath);
|
||||||
|
const cachedContentType = (await readFile(cacheMetaPath, "utf8")).trim() || "image/jpeg";
|
||||||
|
|
||||||
|
setHeader(event, "content-type", cachedContentType);
|
||||||
|
setHeader(event, "cache-control", "private, max-age=300");
|
||||||
|
setHeader(event, "content-length", cachedBody.length);
|
||||||
|
return cachedBody;
|
||||||
|
}
|
||||||
|
|
||||||
const meta = await telegramBotApi<TelegramFileMeta>("getFile", { file_id: fileId });
|
const meta = await telegramBotApi<TelegramFileMeta>("getFile", { file_id: fileId });
|
||||||
const filePath = String(meta?.file_path ?? "").trim();
|
const filePath = String(meta?.file_path ?? "").trim();
|
||||||
if (!filePath) {
|
if (!filePath) {
|
||||||
@@ -62,6 +84,10 @@ export default defineEventHandler(async (event) => {
|
|||||||
const contentLength = remote.headers.get("content-length");
|
const contentLength = remote.headers.get("content-length");
|
||||||
const body = Buffer.from(await remote.arrayBuffer());
|
const body = Buffer.from(await remote.arrayBuffer());
|
||||||
|
|
||||||
|
await mkdir(AVATAR_CACHE_DIR, { recursive: true });
|
||||||
|
await writeFile(cacheBodyPath, body);
|
||||||
|
await writeFile(cacheMetaPath, contentType, "utf8");
|
||||||
|
|
||||||
setHeader(event, "content-type", contentType);
|
setHeader(event, "content-type", contentType);
|
||||||
setHeader(event, "cache-control", "private, max-age=300");
|
setHeader(event, "cache-control", "private, max-age=300");
|
||||||
if (contentLength) {
|
if (contentLength) {
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ async function maybeHydrateContact(contactId: string, profile: ContactProfile) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const currentAvatar = asString(current.avatarUrl);
|
const currentAvatar = asString(current.avatarUrl);
|
||||||
if (profile.avatarUrl && !currentAvatar) {
|
if (profile.avatarUrl && profile.avatarUrl !== currentAvatar) {
|
||||||
updates.avatarUrl = profile.avatarUrl;
|
updates.avatarUrl = profile.avatarUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user