Telegram Send
Send messages, photos, documents, and videos to Telegram chats and channels via the Telegram Bot API.
Methods:
getMe— verify the bot token and fetch bot identity (smoke test)sendMessage— text with optional MarkdownV2 / HTML formattingsendPhoto— image by URL, Telegramfile_id, or local file pathsendDocument— arbitrary file (PDF, ZIP, …) up to 50 MBsendVideo— video by URL, Telegramfile_id, or local file path, with optionalwidth/heightso Telegram sizes the player correctlysendRichMessage— Bot API 10.2 block-based rich message (passthrough), with local-file attachments uploaded multipartgetFile— download a file the bot received, emitted as base64setWebhook/getWebhookInfo/deleteWebhook— manage the bot's webhook registrationsendMessagealso acceptsreplyMarkup(e.g. inline keyboards)
Also ships the @magistr/telegram-webhook serve webhook scheme: it verifies
Telegram's static X-Telegram-Bot-Api-Secret-Token header in constant time,
so Telegram can call a swamp serve webhook directly, and surfaces flat
callback / document / magnet views on the update for workflows.
The botToken (and optional webhookSecret) are sensitive
globalArguments routed to a vault automatically. Set defaultChatId on the model instance to avoid
repeating the chat target on every call.
A natural complement to @magistr/telegram-import, which goes the other
direction — importing Telegram channel exports into Obsidian.
2026.09.24.1
Added
- Ported from the homelab in-repo copy, which had drifted ahead of this package
and was shadowing it on
swamp serve:getFile(download a received file as base64),setWebhook/getWebhookInfo/deleteWebhook,sendRichMessage(the port deferred since 2026.08.01.1 astelegram-send-hardening-richmessage-port),replyMarkuponsendMessage, and the optional sensitivewebhookSecretglobal argument. - The
@magistr/telegram-webhookserve webhook scheme now ships in this package (extensions/webhooks/telegram_webhook.ts). It was never published before, so a registry install of this package could not serve/hooks/telegram.
Changed
- Every Bot API and file-download request now goes through one token-redacting
redactedFetch, including the newgetFiledownload URL (/file/bot<token>/...) and thesendRichMessagemultipart upload, which bypassed redaction in the homelab copy. richMessage/filesJSON arguments fail with a named error (richMessage is not valid JSON: ...) before any request is sent.- Upgrade
2026.09.19.2 -> 2026.09.24.1carries attributes over unchanged;webhookSecretdefaults to empty.
| Argument | Type | Description |
|---|---|---|
| fileId | string | Telegram file_id (from message.document.file_id / a webhook update) |
| fileName? | string | Original file name to record (Telegram's getFile does not return it) |
| mimeType? | string | Original MIME type to record |
| Argument | Type | Description |
|---|---|---|
| url | string | HTTPS URL Telegram should POST updates to |
| dropPendingUpdates | boolean | Discard updates queued before the webhook was set |
| allowedUpdates | array | Update types to receive (empty = Telegram default: all but |
| Argument | Type | Description |
|---|---|---|
| dropPendingUpdates | boolean |
| Argument | Type | Description |
|---|---|---|
| text | string | Message text (1-4096 characters) |
| disableWebPagePreview? | boolean | |
| disableNotification? | boolean | |
| replyToMessageId? | number |
| Argument | Type | Description |
|---|---|---|
| chatId? | string | |
| caption? | string | |
| disableNotification? | boolean |
| Argument | Type | Description |
|---|---|---|
| chatId? | string | |
| disableNotification? | boolean |
| Argument | Type | Description |
|---|---|---|
| chatId? | string | |
| caption? | string | |
| disableNotification? | boolean |
| Argument | Type | Description |
|---|---|---|
| chatId? | string | |
| caption? | string | |
| width? | number | Video width |
| height? | number | Video height |
| disableNotification? | boolean |
Resources
2026.09.19.2
Changed
- Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.09.17.1
Changed
- Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.08.20.1
Added
sendVideo— send a video by https URL, Telegramfile_id, or local file path (multipart upload), with optionalwidth/heightso Telegram sizes the player correctly instead of guessing the aspect ratio. MirrorssendDocumentexactly: same local-vs-remote branch viaisLocalPath, the samesentMessageresource mapping, and the same token-redacting error path.This closes a drift rather than inventing a feature: the method had been running in the homelab's own in-repo copy of the model (the printer-timelapse workflow calls it) but was never carried back into this published package, so registry consumers could not send video at all.
Fixture
fixtures/sendVideo.jsonplus two tests — a contract test pinning thesentMessagemapping, and one assertingwidth/heightactually reach the wire and are omitted when not supplied. The fixture is doc-derived like every other file infixtures/; no live capture (seePROVENANCE.md).Test helper
withEnvelopeCapturing— records each request's decoded JSON body so a test can assert what went ON THE WIRE, not just what came back. The existingwithEnvelopediscards the request, which would have let a droppedwidthpass unnoticed.
Still missing
sendRichMessageremains un-ported from the in-repo copy — tracked astelegram-send-hardening-richmessage-port. It is a much larger surface (block-basedarticleformatting plus multipartattach://media) and is deliberately left to its own change.
Modified 1 models
2026.08.01.1
Security hardening: closes the HIGH bot-token credential-leak tracked below as a
"Known gap" in the Unreleased entry (filed and planned as the issue-lifecycle
model telegram-send-hardening-richmessage-port).
- Added a module-private
redactToken(message, token)pure helper totelegram_send.ts. It replaces the live/bot<token>/URL segment with/bot<redacted>/, then applies a generic/bot[^/]+/regex backstop so any/bot.../path segment is scrubbed even if the token reaches the message reformatted (re-cased, percent-encoded, or otherwise transformed) rather than byte-for-byte.messageis accepted asunknownand safely coerced to a string — a fetch rejection is not guaranteed to be anErrorwith a string.message(it may be aDOMException, a thrown string, or an arbitrary non-Error value) — so no unsanitized shape can pass through unredacted. telegramJsonandtelegramMultipartnow wrap theirfetch()call in try/catch: a network-layer rejection (DNS failure, TLS error, connection reset) is caught and rethrown with its message redacted viaredactToken, preserving the original rejection ascausefor downstream diagnostics. Only thefetch()call itself is wrapped — theok:falseAPI-error throw (never carries the token, pinned GREEN and covered by property test c) is untouched.- Behavior-preserving otherwise: legitimate sends and the
ok:falseAPI-error path are unchanged. - Tests: flipped the two adversarial suite's former "HONEST GAP pin" tests
(
telegram_send_adversarial_test.ts,telegramJson/getMeandtelegramMultipart/sendPhoto) to assert the fetch-rejection message is now redacted (contains/bot<redacted>/, excludes the raw token, preserves.cause) instead of asserting verbatim propagation. Added directredactTokenunit tests: exact-token redaction, token-free passthrough, the generic backstop for a reformatted token, and non-Error/DOMException/ thrown-string/plain-object coercion (including a case where a non-Error value's own string form embeds the token). All 72 suite tests green; property suite green atFC_NUM_RUNS=5000. README.md: updated the Security note — the token-in-URL fetch-rejection gap is now redacted rather than an open gap.quality.yaml: the byte-frozen-source justification no longer applies (source is modified); ratchet re-measured live.- Deferred, tracked separately: porting
sendRichMessagefrom the homelab dev copy is OUT OF SCOPE for this security fix (its homelab source-of-truth is not in this read-only snapshot, so folding it in would be a blind, unverifiable port). It remains tracked by the issue-lifecycle modeltelegram-send-hardening-richmessage-portas a follow-up.
Release 2026.07.16.2 — align model versions with manifests
Maintenance release across the @magistr extensions. For most packages this
carries no functional change: the only edit is the model's version: field,
brought back in line with its manifest version so the published model type
version and the package version no longer drift.
Functional changes in this release are limited to:
anime-cron: normalizeTitle now strips a ": subtitle" suffix and a trailing parenthesized year before comparison, fixing dedup false-misses where the torrent title carries a subtitle or year that the AniList romaji does not.
arckit: first publish. Standalone ArcKit port — a 12-phase architecture governance state machine with 65 bundled templates, driven by a bundled skill.
Also tracks three extensions (kaiten, observability-agent, music-library) that previously existed only as untracked working-tree directories, recovered from stashes.
- Has README or module doc2/2earned
- README has a code example1/1earned
- README is substantive1/1earned
- Most symbols documented1/1earned
- No slow types (deprecated)1/1earned
- Dependencies pass trust audit2/2earned
- Has description1/1earned
- Platform support declared (or universal)2/2earned
- License declared1/1earned
- Verified public repository2/2earned