Track Telegram avatar changes in telegram backend state DB
This commit is contained in:
113
telegram_backend/src/profile-state.ts
Normal file
113
telegram_backend/src/profile-state.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { mkdirSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import Database from "better-sqlite3";
|
||||
import type { OmniInboundEnvelopeV1 } from "./types";
|
||||
|
||||
const TELEGRAM_FILE_MARKER = "tg-file:";
|
||||
const DEFAULT_STATE_DB_PATH = path.join(process.cwd(), ".data", "telegram_backend", "state.sqlite");
|
||||
|
||||
type AvatarStateRow = {
|
||||
avatarFingerprint: string | null;
|
||||
};
|
||||
|
||||
function asString(value: unknown) {
|
||||
if (typeof value !== "string") return null;
|
||||
const normalized = value.trim();
|
||||
return normalized || null;
|
||||
}
|
||||
|
||||
function stateDbPath() {
|
||||
const fromEnv = asString(process.env.TELEGRAM_PROFILE_STATE_DB_PATH);
|
||||
return fromEnv ?? DEFAULT_STATE_DB_PATH;
|
||||
}
|
||||
|
||||
function parseTelegramFileId(avatarUrl: string | null) {
|
||||
const raw = asString(avatarUrl);
|
||||
if (!raw || !raw.startsWith(TELEGRAM_FILE_MARKER)) return null;
|
||||
const fileId = raw.slice(TELEGRAM_FILE_MARKER.length).trim();
|
||||
return fileId || null;
|
||||
}
|
||||
|
||||
function openStateDb() {
|
||||
const dbPath = stateDbPath();
|
||||
const dbDir = path.dirname(dbPath);
|
||||
mkdirSync(dbDir, { recursive: true });
|
||||
|
||||
const db = new Database(dbPath);
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS telegram_contact_profile_state (
|
||||
contact_external_id TEXT PRIMARY KEY,
|
||||
avatar_fingerprint TEXT,
|
||||
avatar_file_id TEXT,
|
||||
updated_at TEXT NOT NULL
|
||||
)
|
||||
`);
|
||||
return db;
|
||||
}
|
||||
|
||||
const db = openStateDb();
|
||||
const selectStateStatement = db.prepare(`
|
||||
SELECT avatar_fingerprint AS avatarFingerprint
|
||||
FROM telegram_contact_profile_state
|
||||
WHERE contact_external_id = ?
|
||||
`);
|
||||
const upsertStateStatement = db.prepare(`
|
||||
INSERT INTO telegram_contact_profile_state (
|
||||
contact_external_id,
|
||||
avatar_fingerprint,
|
||||
avatar_file_id,
|
||||
updated_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT(contact_external_id) DO UPDATE SET
|
||||
avatar_fingerprint = excluded.avatar_fingerprint,
|
||||
avatar_file_id = excluded.avatar_file_id,
|
||||
updated_at = excluded.updated_at
|
||||
`);
|
||||
|
||||
function detectAvatarChange(input: {
|
||||
contactExternalId: string | null;
|
||||
avatarFileId: string | null;
|
||||
avatarFingerprint: string | null;
|
||||
}) {
|
||||
const contactExternalId = asString(input.contactExternalId);
|
||||
const avatarFileId = asString(input.avatarFileId);
|
||||
const avatarFingerprint = asString(input.avatarFingerprint) ?? avatarFileId;
|
||||
|
||||
if (!contactExternalId || !avatarFileId || !avatarFingerprint) {
|
||||
return { changed: false };
|
||||
}
|
||||
|
||||
const row = selectStateStatement.get(contactExternalId) as AvatarStateRow | undefined;
|
||||
const previousFingerprint = asString(row?.avatarFingerprint);
|
||||
const changed = previousFingerprint !== avatarFingerprint;
|
||||
|
||||
if (changed) {
|
||||
upsertStateStatement.run(contactExternalId, avatarFingerprint, avatarFileId, new Date().toISOString());
|
||||
}
|
||||
|
||||
return { changed };
|
||||
}
|
||||
|
||||
export function applyAvatarProfileState(envelope: OmniInboundEnvelopeV1): OmniInboundEnvelopeV1 {
|
||||
const payload = envelope.payloadNormalized;
|
||||
const contactExternalId = asString(payload.contactExternalId);
|
||||
const contactAvatarUrl = asString(payload.contactAvatarUrl);
|
||||
const contactAvatarFingerprint = asString(payload.contactAvatarFingerprint);
|
||||
const avatarFileId = parseTelegramFileId(contactAvatarUrl);
|
||||
|
||||
const avatarState = detectAvatarChange({
|
||||
contactExternalId,
|
||||
avatarFileId,
|
||||
avatarFingerprint: contactAvatarFingerprint,
|
||||
});
|
||||
|
||||
return {
|
||||
...envelope,
|
||||
payloadNormalized: {
|
||||
...payload,
|
||||
contactAvatarChanged: avatarState.changed,
|
||||
contactAvatarUrl: avatarState.changed ? contactAvatarUrl : null,
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user