feat(telegram): ingest and render inbound voice messages

This commit is contained in:
Ruslan Bakiev
2026-02-23 12:21:53 +07:00
parent c94c229a1a
commit acd974766a
4 changed files with 225 additions and 5 deletions

View File

@@ -51,6 +51,69 @@ function normalizeString(value: unknown) {
return normalized || null;
}
function normalizeNumber(value: unknown) {
if (typeof value === "number" && Number.isFinite(value)) return value;
if (typeof value === "string") {
const parsed = Number(value);
if (Number.isFinite(parsed)) return parsed;
}
return null;
}
type TelegramMediaInfo = {
kind: "voice" | "audio" | "video_note" | null;
fileId: string | null;
durationSec: number | null;
mimeType: string | null;
title: string | null;
};
function pickTelegramMedia(message: JsonObject): TelegramMediaInfo {
const voice = asObject(message.voice);
if (Object.keys(voice).length > 0) {
return {
kind: "voice",
fileId: normalizeString(voice.file_id),
durationSec: normalizeNumber(voice.duration),
mimeType: normalizeString(voice.mime_type),
title: "Voice message",
};
}
const audio = asObject(message.audio);
if (Object.keys(audio).length > 0) {
const performer = normalizeString(audio.performer);
const title = normalizeString(audio.title) ?? normalizeString(audio.file_name);
const combinedTitle = performer && title ? `${performer} - ${title}` : title ?? performer;
return {
kind: "audio",
fileId: normalizeString(audio.file_id),
durationSec: normalizeNumber(audio.duration),
mimeType: normalizeString(audio.mime_type),
title: combinedTitle,
};
}
const videoNote = asObject(message.video_note);
if (Object.keys(videoNote).length > 0) {
return {
kind: "video_note",
fileId: normalizeString(videoNote.file_id),
durationSec: normalizeNumber(videoNote.duration),
mimeType: null,
title: "Video note",
};
}
return {
kind: null,
fileId: null,
durationSec: null,
mimeType: null,
title: null,
};
}
function detectDirection(message: JsonObject, chat: JsonObject, from: JsonObject): "IN" | "OUT" {
if (typeof message.outgoing === "boolean") return message.outgoing ? "OUT" : "IN";
if (typeof message.is_outgoing === "boolean") return message.is_outgoing ? "OUT" : "IN";
@@ -113,7 +176,19 @@ export function parseTelegramBusinessUpdate(raw: unknown): OmniInboundEnvelopeV1
? String(fallbackContactSource.id)
: null;
const text = cropText(message.text) ?? cropText(message.caption);
const media = pickTelegramMedia(message);
const text =
cropText(message.text) ??
cropText(message.caption) ??
(media.kind === "voice"
? "[voice message]"
: media.kind === "video_note"
? "[video note]"
: media.kind === "audio"
? media.title
? `[audio] ${media.title}`
: "[audio]"
: null);
const businessConnectionId =
message.business_connection_id != null
@@ -148,6 +223,11 @@ export function parseTelegramBusinessUpdate(raw: unknown): OmniInboundEnvelopeV1
contactExternalId,
text,
businessConnectionId,
mediaKind: media.kind,
mediaFileId: media.fileId,
mediaDurationSec: media.durationSec,
mediaMimeType: media.mimeType,
mediaTitle: media.title,
updateId: updateId != null ? String(updateId) : null,
chatTitle: typeof chat.title === "string" ? chat.title : null,
chatUsername: normalizeString(chat.username),