diff --git a/frontend/server/api/omni/telegram/avatar.get.ts b/frontend/server/api/omni/telegram/avatar.get.ts index 85b3676..0cc59fb 100644 --- a/frontend/server/api/omni/telegram/avatar.get.ts +++ b/frontend/server/api/omni/telegram/avatar.get.ts @@ -14,6 +14,29 @@ type TelegramFileMeta = { 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) { const raw = String(avatarUrl ?? "").trim(); if (!raw.startsWith(TELEGRAM_FILE_MARKER)) return null; @@ -25,6 +48,22 @@ function sanitizeCacheKey(input: string) { return input.replace(/[^a-zA-Z0-9._-]/g, "_"); } +async function fetchTelegramAvatarFileIdByExternalId(externalId: string) { + const getChatRes = await telegramBotApi("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("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) => { const auth = await getAuthContext(event); const query = getQuery(event); @@ -48,9 +87,36 @@ export default defineEventHandler(async (event) => { throw createError({ statusCode: 404, statusMessage: "contact not found" }); } - const fileId = parseTelegramFileId(contact.avatarUrl); + let fileId = parseTelegramFileId(contact.avatarUrl); 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); diff --git a/frontend/server/graphql/schema.ts b/frontend/server/graphql/schema.ts index 8937bad..807308f 100644 --- a/frontend/server/graphql/schema.ts +++ b/frontend/server/graphql/schema.ts @@ -123,9 +123,11 @@ function resolveContactMessageAudioUrl(message: { function resolveContactAvatarUrl(contact: { id: string; avatarUrl: string | null; -}) { +}, hasTelegramChannel: boolean) { 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)) { return `/api/omni/telegram/avatar?contactId=${encodeURIComponent(contact.id)}`; } @@ -493,11 +495,13 @@ async function getContacts(auth: AuthContext | null) { if (total === 0) return true; return (visibleInboxesByContactId.get(c.id) ?? 0) > 0; }) - .map((c) => ({ + .map((c) => { + const channels = Array.from(channelsByContactId.get(c.id) ?? []); + return { id: c.id, name: c.name, - avatar: resolveContactAvatarUrl(c), - channels: Array.from(channelsByContactId.get(c.id) ?? []), + avatar: resolveContactAvatarUrl(c, channels.includes("Telegram")), + channels, lastContactAt: c.messages[0]?.occurredAt?.toISOString?.() ?? c.updatedAt.toISOString(), lastMessageText: c.messages[0]?.content ?? "", 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)!) : false, description: c.note?.content ?? "", - })); + }; + }); } async function getCommunications(auth: AuthContext | null) {