Cache Telegram avatars locally and refresh avatar file ids

This commit is contained in:
Ruslan Bakiev
2026-02-26 10:59:44 +07:00
parent ba3e5f7cac
commit 6bae0300c8
2 changed files with 27 additions and 1 deletions

View File

@@ -1,9 +1,13 @@
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 { prisma } from "../../../utils/prisma";
import { requireTelegramBotToken, telegramApiBase, telegramBotApi } from "../../../utils/telegram";
const TELEGRAM_FILE_MARKER = "tg-file:";
const AVATAR_CACHE_DIR = path.join(process.cwd(), ".data", "telegram-avatars");
type TelegramFileMeta = {
file_id: string;
@@ -17,6 +21,10 @@ function parseTelegramFileId(avatarUrl: string | null | undefined) {
return fileId || null;
}
function sanitizeCacheKey(input: string) {
return input.replace(/[^a-zA-Z0-9._-]/g, "_");
}
export default defineEventHandler(async (event) => {
const auth = await getAuthContext(event);
const query = getQuery(event);
@@ -45,6 +53,20 @@ export default defineEventHandler(async (event) => {
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 filePath = String(meta?.file_path ?? "").trim();
if (!filePath) {
@@ -62,6 +84,10 @@ export default defineEventHandler(async (event) => {
const contentLength = remote.headers.get("content-length");
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, "cache-control", "private, max-age=300");
if (contentLength) {

View File

@@ -248,7 +248,7 @@ async function maybeHydrateContact(contactId: string, profile: ContactProfile) {
}
const currentAvatar = asString(current.avatarUrl);
if (profile.avatarUrl && !currentAvatar) {
if (profile.avatarUrl && profile.avatarUrl !== currentAvatar) {
updates.avatarUrl = profile.avatarUrl;
}