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

95 lines
3.4 KiB
Markdown

# 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<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.