Move avatar profile resolution into telegram backend
This commit is contained in:
2
frontend
2
frontend
Submodule frontend updated: b01072f52f...3518c80b92
@@ -2,6 +2,7 @@ import { Pool } from "pg";
|
|||||||
import type { OmniInboundEnvelopeV1 } from "./types";
|
import type { OmniInboundEnvelopeV1 } from "./types";
|
||||||
|
|
||||||
const TELEGRAM_FILE_MARKER = "tg-file:";
|
const TELEGRAM_FILE_MARKER = "tg-file:";
|
||||||
|
const TELEGRAM_API_BASE_DEFAULT = "https://api.telegram.org";
|
||||||
|
|
||||||
type AvatarStateRow = {
|
type AvatarStateRow = {
|
||||||
avatar_fingerprint: string | null;
|
avatar_fingerprint: string | null;
|
||||||
@@ -28,6 +29,64 @@ function parseTelegramFileId(avatarUrl: string | null) {
|
|||||||
return fileId || null;
|
return fileId || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseTelegramUserId(value: string | null) {
|
||||||
|
const raw = asString(value);
|
||||||
|
if (!raw || !/^\d+$/.test(raw)) return null;
|
||||||
|
const userId = Number.parseInt(raw, 10);
|
||||||
|
if (!Number.isFinite(userId) || userId <= 0) return null;
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function telegramApiBase() {
|
||||||
|
return asString(process.env.TELEGRAM_API_BASE) ?? TELEGRAM_API_BASE_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
type TelegramUserProfilePhotosResponse = {
|
||||||
|
ok?: boolean;
|
||||||
|
result?: {
|
||||||
|
photos?: Array<Array<{ file_id?: string }>>;
|
||||||
|
};
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function pickProfilePhotoFileId(payload: TelegramUserProfilePhotosResponse) {
|
||||||
|
const groups = payload.result?.photos;
|
||||||
|
if (!Array.isArray(groups) || !groups.length) return null;
|
||||||
|
const firstGroup = groups[0];
|
||||||
|
if (!Array.isArray(firstGroup) || !firstGroup.length) return null;
|
||||||
|
|
||||||
|
for (let index = firstGroup.length - 1; index >= 0; index -= 1) {
|
||||||
|
const fileId = asString(firstGroup[index]?.file_id);
|
||||||
|
if (fileId) return fileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveProfileAvatarFileId(contactExternalId: string | null) {
|
||||||
|
const userId = parseTelegramUserId(contactExternalId);
|
||||||
|
if (!userId) return null;
|
||||||
|
|
||||||
|
const token = asString(process.env.TELEGRAM_BOT_TOKEN);
|
||||||
|
if (!token) return null;
|
||||||
|
|
||||||
|
const response = await fetch(`${telegramApiBase().replace(/\/+$/, "")}/bot${token}/getUserProfilePhotos`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "content-type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
user_id: userId,
|
||||||
|
offset: 0,
|
||||||
|
limit: 1,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) return null;
|
||||||
|
const payload = (await response.json()) as TelegramUserProfilePhotosResponse;
|
||||||
|
if (!payload.ok) return null;
|
||||||
|
|
||||||
|
return pickProfilePhotoFileId(payload);
|
||||||
|
}
|
||||||
|
|
||||||
const pool = new Pool({
|
const pool = new Pool({
|
||||||
connectionString: requiredEnv("TELEGRAM_PROFILE_STATE_DATABASE_URL"),
|
connectionString: requiredEnv("TELEGRAM_PROFILE_STATE_DATABASE_URL"),
|
||||||
});
|
});
|
||||||
@@ -101,8 +160,19 @@ async function detectAvatarChange(input: {
|
|||||||
export async function applyAvatarProfileState(envelope: OmniInboundEnvelopeV1): Promise<OmniInboundEnvelopeV1> {
|
export async function applyAvatarProfileState(envelope: OmniInboundEnvelopeV1): Promise<OmniInboundEnvelopeV1> {
|
||||||
const payload = envelope.payloadNormalized;
|
const payload = envelope.payloadNormalized;
|
||||||
const contactExternalId = asString(payload.contactExternalId);
|
const contactExternalId = asString(payload.contactExternalId);
|
||||||
const contactAvatarUrl = asString(payload.contactAvatarUrl);
|
let contactAvatarUrl = asString(payload.contactAvatarUrl);
|
||||||
const contactAvatarFingerprint = asString(payload.contactAvatarFingerprint);
|
let contactAvatarFingerprint = asString(payload.contactAvatarFingerprint);
|
||||||
|
|
||||||
|
if (!contactAvatarUrl) {
|
||||||
|
const profileFileId = await resolveProfileAvatarFileId(contactExternalId);
|
||||||
|
if (profileFileId) {
|
||||||
|
contactAvatarUrl = `${TELEGRAM_FILE_MARKER}${profileFileId}`;
|
||||||
|
if (!contactAvatarFingerprint) {
|
||||||
|
contactAvatarFingerprint = profileFileId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const avatarFileId = parseTelegramFileId(contactAvatarUrl);
|
const avatarFileId = parseTelegramFileId(contactAvatarUrl);
|
||||||
|
|
||||||
const avatarState = await detectAvatarChange({
|
const avatarState = await detectAvatarChange({
|
||||||
@@ -117,6 +187,7 @@ export async function applyAvatarProfileState(envelope: OmniInboundEnvelopeV1):
|
|||||||
...payload,
|
...payload,
|
||||||
contactAvatarChanged: avatarState.changed,
|
contactAvatarChanged: avatarState.changed,
|
||||||
contactAvatarUrl: avatarState.changed ? contactAvatarUrl : null,
|
contactAvatarUrl: avatarState.changed ? contactAvatarUrl : null,
|
||||||
|
contactAvatarFingerprint,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user