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)— convertsTelegramMessage→ wire bodybuildReplyMarkup(markup)— convertsTelegramReplyMarkup→ wire markupMEDIA_METHOD_MAP— frozen record ofTelegramMediaType→ Bot API method- Drops optional fields when falsy/absent — minimises wire payload
- Preserves caller-provided
chatIdtype (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_idandtext. - Emits
parse_mode,disable_web_page_preview,disable_notification,reply_to_message_id,reply_markuponly when the corresponding camelCase field is truthy.
- Always emits
buildReplyMarkup(markup: TelegramReplyMarkup): Record<string, unknown>- Emits
inline_keyboardwheninlineKeyboardis set; each button is{ text, url?, callback_data? }. - Emits
keyboardwhenkeyboardis set; each button is{ text, request_contact?, request_location? }. - Emits
remove_keyboard: true/one_time_keyboard: truewhen requested.
- Emits
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.