Files
skills/docs/communication/messaging/telegram-client/src/utils/build-bodies.md
2026-05-29 08:20:56 +01:00

3.4 KiB

Build Bodies

Purpose

Pure, side-effect-free helpers that translate the camelCase public message / reply-markup shape used by callers into the snake_case wire shape that the Telegram Bot API expects. Splitting these helpers out of the client keeps the wire-format translation independently testable, makes it cheap to add new optional fields, and means every option permutation can be exercised without an HTTP round trip.

Telegram's Bot API mixes top-level fields (chat_id, parse_mode) with nested objects (reply_markup.inline_keyboard). Hand-rolling each conversion in the client would scatter snake-case knowledge across the codebase; centralising it here means the wire-format contract is one file to audit when Telegram releases a new option.

Features

  • buildSendMessageBody(message) — converts TelegramMessage → wire body
  • buildReplyMarkup(markup) — converts TelegramReplyMarkup → wire markup
  • MEDIA_METHOD_MAP — frozen record of TelegramMediaType → Bot API method
  • Drops optional fields when falsy/absent — minimises wire payload
  • Preserves caller-provided chatId type (string or number) — Telegram accepts both

Usage: Basic

import { buildSendMessageBody } from "@suaveplan/telegram-client";

const body = buildSendMessageBody({
  chatId: 100,
  text: "Hello",
  parseMode: "HTML",
});
// body === { chat_id: 100, text: "Hello", parse_mode: "HTML" }

Usage: Advanced

import {
  buildReplyMarkup,
  buildSendMessageBody,
  MEDIA_METHOD_MAP,
} from "@suaveplan/telegram-client";

// Compose a fully-featured message body with inline + custom keyboards.
const body = buildSendMessageBody({
  chatId: "@my_channel",
  text: "*Pick one*",
  parseMode: "MarkdownV2",
  disableWebPagePreview: true,
  replyToMessageId: 42,
  replyMarkup: {
    inlineKeyboard: [
      [
        { text: "Docs", url: "https://core.telegram.org/bots/api" },
        { text: "Yes", callbackData: "yes" },
      ],
    ],
    oneTimeKeyboard: true,
  },
});

// Pick the Bot API method for a media upload.
const method = MEDIA_METHOD_MAP.photo; // "sendPhoto"

API Reference

  • buildSendMessageBody(message: TelegramMessage): Record<string, unknown>
    • Always emits chat_id and text.
    • Emits parse_mode, disable_web_page_preview, disable_notification, reply_to_message_id, reply_markup only when the corresponding camelCase field is truthy.
  • buildReplyMarkup(markup: TelegramReplyMarkup): Record<string, unknown>
    • Emits inline_keyboard when inlineKeyboard is set; each button is { text, url?, callback_data? }.
    • Emits keyboard when keyboard is set; each button is { text, request_contact?, request_location? }.
    • Emits remove_keyboard: true / one_time_keyboard: true when requested.
  • MEDIA_METHOD_MAP: Record<TelegramMediaType, "send<Type>"> — frozen.

Implementation Notes

The helpers are pure: identical input → identical output, no global state, no Date.now()/Math.random(). That makes the test suite a flat property table — one expectation per option combination — and keeps coverage trivial. The "emit-only-when-truthy" rule is important: Telegram interprets disable_notification: false as the silent-mode opt-out, but a payload that never sent the field at all reverts to the chat's default. To match upstream behaviour exactly we drop the field when callers leave it undefined / false and only include it when the caller explicitly opts in.