Hydrate Telegram avatars on demand in frontend API
This commit is contained in:
@@ -14,6 +14,29 @@ type TelegramFileMeta = {
|
|||||||
file_path?: string;
|
file_path?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type TelegramChatPhoto = {
|
||||||
|
small_file_id?: string;
|
||||||
|
big_file_id?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TelegramGetChatResponse = {
|
||||||
|
ok?: boolean;
|
||||||
|
result?: {
|
||||||
|
photo?: TelegramChatPhoto;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type TelegramProfilePhotoSize = {
|
||||||
|
file_id?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TelegramGetUserProfilePhotosResponse = {
|
||||||
|
ok?: boolean;
|
||||||
|
result?: {
|
||||||
|
photos?: TelegramProfilePhotoSize[][];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
function parseTelegramFileId(avatarUrl: string | null | undefined) {
|
function parseTelegramFileId(avatarUrl: string | null | undefined) {
|
||||||
const raw = String(avatarUrl ?? "").trim();
|
const raw = String(avatarUrl ?? "").trim();
|
||||||
if (!raw.startsWith(TELEGRAM_FILE_MARKER)) return null;
|
if (!raw.startsWith(TELEGRAM_FILE_MARKER)) return null;
|
||||||
@@ -25,6 +48,22 @@ function sanitizeCacheKey(input: string) {
|
|||||||
return input.replace(/[^a-zA-Z0-9._-]/g, "_");
|
return input.replace(/[^a-zA-Z0-9._-]/g, "_");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchTelegramAvatarFileIdByExternalId(externalId: string) {
|
||||||
|
const getChatRes = await telegramBotApi<TelegramGetChatResponse["result"]>("getChat", {
|
||||||
|
chat_id: externalId,
|
||||||
|
});
|
||||||
|
const fromChat = String(getChatRes?.photo?.small_file_id ?? getChatRes?.photo?.big_file_id ?? "").trim();
|
||||||
|
if (fromChat) return fromChat;
|
||||||
|
|
||||||
|
const getUserPhotosRes = await telegramBotApi<TelegramGetUserProfilePhotosResponse["result"]>("getUserProfilePhotos", {
|
||||||
|
user_id: externalId,
|
||||||
|
limit: 1,
|
||||||
|
});
|
||||||
|
const firstPhotoSizes = Array.isArray(getUserPhotosRes?.photos?.[0]) ? getUserPhotosRes.photos[0] : [];
|
||||||
|
const candidate = String(firstPhotoSizes.at(-1)?.file_id ?? firstPhotoSizes[0]?.file_id ?? "").trim();
|
||||||
|
return candidate || null;
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
@@ -48,9 +87,36 @@ export default defineEventHandler(async (event) => {
|
|||||||
throw createError({ statusCode: 404, statusMessage: "contact not found" });
|
throw createError({ statusCode: 404, statusMessage: "contact not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileId = parseTelegramFileId(contact.avatarUrl);
|
let fileId = parseTelegramFileId(contact.avatarUrl);
|
||||||
if (!fileId) {
|
if (!fileId) {
|
||||||
throw createError({ statusCode: 404, statusMessage: "telegram avatar is missing" });
|
const identity = await prisma.omniContactIdentity.findFirst({
|
||||||
|
where: {
|
||||||
|
teamId: auth.teamId,
|
||||||
|
contactId,
|
||||||
|
channel: "TELEGRAM",
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
externalId: true,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
updatedAt: "desc",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!identity?.externalId) {
|
||||||
|
throw createError({ statusCode: 404, statusMessage: "telegram identity is missing" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchedFileId = await fetchTelegramAvatarFileIdByExternalId(identity.externalId);
|
||||||
|
if (!fetchedFileId) {
|
||||||
|
throw createError({ statusCode: 404, statusMessage: "telegram avatar is missing" });
|
||||||
|
}
|
||||||
|
|
||||||
|
fileId = fetchedFileId;
|
||||||
|
await prisma.contact.update({
|
||||||
|
where: { id: contactId },
|
||||||
|
data: { avatarUrl: `${TELEGRAM_FILE_MARKER}${fileId}` },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const cacheKey = sanitizeCacheKey(fileId);
|
const cacheKey = sanitizeCacheKey(fileId);
|
||||||
|
|||||||
@@ -123,9 +123,11 @@ function resolveContactMessageAudioUrl(message: {
|
|||||||
function resolveContactAvatarUrl(contact: {
|
function resolveContactAvatarUrl(contact: {
|
||||||
id: string;
|
id: string;
|
||||||
avatarUrl: string | null;
|
avatarUrl: string | null;
|
||||||
}) {
|
}, hasTelegramChannel: boolean) {
|
||||||
const raw = String(contact.avatarUrl ?? "").trim();
|
const raw = String(contact.avatarUrl ?? "").trim();
|
||||||
if (!raw) return "";
|
if (!raw) {
|
||||||
|
return hasTelegramChannel ? `/api/omni/telegram/avatar?contactId=${encodeURIComponent(contact.id)}` : "";
|
||||||
|
}
|
||||||
if (raw.startsWith(TELEGRAM_AUDIO_FILE_MARKER)) {
|
if (raw.startsWith(TELEGRAM_AUDIO_FILE_MARKER)) {
|
||||||
return `/api/omni/telegram/avatar?contactId=${encodeURIComponent(contact.id)}`;
|
return `/api/omni/telegram/avatar?contactId=${encodeURIComponent(contact.id)}`;
|
||||||
}
|
}
|
||||||
@@ -493,11 +495,13 @@ async function getContacts(auth: AuthContext | null) {
|
|||||||
if (total === 0) return true;
|
if (total === 0) return true;
|
||||||
return (visibleInboxesByContactId.get(c.id) ?? 0) > 0;
|
return (visibleInboxesByContactId.get(c.id) ?? 0) > 0;
|
||||||
})
|
})
|
||||||
.map((c) => ({
|
.map((c) => {
|
||||||
|
const channels = Array.from(channelsByContactId.get(c.id) ?? []);
|
||||||
|
return {
|
||||||
id: c.id,
|
id: c.id,
|
||||||
name: c.name,
|
name: c.name,
|
||||||
avatar: resolveContactAvatarUrl(c),
|
avatar: resolveContactAvatarUrl(c, channels.includes("Telegram")),
|
||||||
channels: Array.from(channelsByContactId.get(c.id) ?? []),
|
channels,
|
||||||
lastContactAt: c.messages[0]?.occurredAt?.toISOString?.() ?? c.updatedAt.toISOString(),
|
lastContactAt: c.messages[0]?.occurredAt?.toISOString?.() ?? c.updatedAt.toISOString(),
|
||||||
lastMessageText: c.messages[0]?.content ?? "",
|
lastMessageText: c.messages[0]?.content ?? "",
|
||||||
lastMessageChannel: c.messages[0]?.channel ? mapChannel(c.messages[0].channel) : "",
|
lastMessageChannel: c.messages[0]?.channel ? mapChannel(c.messages[0].channel) : "",
|
||||||
@@ -505,7 +509,8 @@ async function getContacts(auth: AuthContext | null) {
|
|||||||
? (!readAtByContactId.has(c.id) || c.messages[0].occurredAt > readAtByContactId.get(c.id)!)
|
? (!readAtByContactId.has(c.id) || c.messages[0].occurredAt > readAtByContactId.get(c.id)!)
|
||||||
: false,
|
: false,
|
||||||
description: c.note?.content ?? "",
|
description: c.note?.content ?? "",
|
||||||
}));
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getCommunications(auth: AuthContext | null) {
|
async function getCommunications(auth: AuthContext | null) {
|
||||||
|
|||||||
Reference in New Issue
Block a user