# 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 ```ts 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 ```ts 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` - 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` - 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">` — 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.