Skip to Content
Telegram MCPComponentsTelegram Normalize

src/telegram/normalize.ts — Telegram Update → TelegramEvent

Pure mapper from the raw Telegram Bot API Update (telegraf re-exports the @telegraf/types definitions) into a unified discriminated union that the rest of the system consumes. No I/O, no telegraf instance required.

Public surface

ExportShapePurpose
TelegramEventTextEvent | PhotoEvent | DocumentEvent | CallbackEventThe union
TelegramEventKind'text' | 'photo' | 'document' | 'callback'Discriminator
normalize(update)(Update) => TelegramEvent | nullThe mapper

null means “ignore quietly” — the webhook still 200s but no event is forwarded.

Envelope

Every event carries:

FieldTypeSource
userIdnumbermessage.from.id / callback_query.from.id
usernamestring | nullfrom.username (Telegram omits for users who haven’t set one)
firstNamestring | nullfrom.first_name
chatIdnumbermessage.chat.id / callback_query.message.chat.id
messageIdnumberFor callbacks: the bot message the button was attached to (needed for edit_message/answerCallbackQuery)

Kind-specific fields

  • text: text: string
  • photo: fileId, width, height, caption?fileId is the largest size (Telegram returns multiple), selected by width * height area.
  • document: fileId, mimeType?, fileName?, fileSize?, caption? — any field Telegram omits surfaces as null.
  • callback: callbackData: string, callbackQueryId: string — only data callbacks are surfaced; game callbacks (no data) return null.

Dropped update kinds

normalize() returns null for:

  • edited_message, channel_post, edited_channel_post
  • message_reaction, message_reaction_count
  • inline_query, chosen_inline_result
  • shipping_query, pre_checkout_query
  • poll, poll_answer
  • my_chat_member, chat_member, chat_join_request, chat_boost, removed_chat_boost
  • messages whose payload we don’t surface (e.g. stickers, voice notes, video)
  • callbacks attached to inaccessible messages (Telegram’s date === 0 sentinel) or inline-mode callbacks with no message

This matches the allowed_updates whitelist sent during webhook registration (see scripts/register-webhook.ts), so in practice we only have to handle these for defense-in-depth.

Tests

test/unit/normalize.test.ts — 16 cases covering each kind, the largest-photo selection rule, optional-field nulling, all the documented drop paths, and the null-username fallback.

See also