Skip to content

Add block and media support to sendRichMessage - #438

Open
FernandoWerneck-VibX wants to merge 5 commits into
pengrad:masterfrom
Vibxtech:master
Open

Add block and media support to sendRichMessage#438
FernandoWerneck-VibX wants to merge 5 commits into
pengrad:masterfrom
Vibxtech:master

Conversation

@FernandoWerneck-VibX

Copy link
Copy Markdown

What

Completes the Bot API 10.1 rich message sending surface. InputRichMessage now supports blocks and media in addition to html/markdown, and rich messages carrying uploaded files work across sendRichMessage, sendRichMessageDraft and editMessageText.

Changes

Input block models — new package model.request.richmessages.inputrichblock: the InputRichBlock interface, its 21 concrete blocks and InputRichBlockListItem. Plus InputRichMessageMedia, and InputMediaVoiceNote, which was missing and is required by InputRichBlockVoiceNote.

Type constants are reused from RichBlockType rather than duplicated, and blocks reference the existing received types where the API specifies them (RichText, RichBlockCaption, RichBlockTableCell, Location).

SerializationRichBlock and RichText implementations declare their type discriminator as a computed property, which Gson does not serialize, and the registered adapters were JsonDeserializer only. RichTextTypeAdapter and RichBlockTypeAdapter now also implement JsonSerializer, and a new InputRichBlockSerializer covers the input hierarchy. Each delegates to the concrete runtime type and adds the discriminator back:

context.serialize(src, src.javaClass).asJsonObject.apply { addProperty("type", src.type) }

This does not recurse — the adapter is registered against the interface, while src.javaClass resolves to the concrete class's reflective adapter. RichText keeps the two special cases that mirror how it is parsed: RichTextPlain serializes as a bare string, RichTextArray as an array.

MultipartSendRichMessage inherits isMultipart() == false from AbstractSendRequest, so blocks holding uploads had no plumbing. RichMessageAttachments walks media and blocks, descending into list, blockquote, collage, slideshow and details, and collects the attach:// mapping from each InputMedia.

It runs from isMultipart()/getParameters(), both of which TelegramBotClient reads at send time, and rescans on every call, dropping the parts left over from the previous scan. Unlike SendMediaGroup, which collects in its constructor, this means a rich message populated after the request was built is still uploaded, replacing a block also removes its stale part, and re-sending a request picks up media added in between.

Where the API forbids uploads, the request stays non-multipart and fails fast instead of building a payload Telegram would reject:

  • sendRichMessageDraft"Direct upload of new files isn't supported."
  • editMessageText targeting an inline message — "Direct upload of new files isn't supported when an inline message is edited."

editMessageText — already accepted an InputRichMessage; it now collects attachments the same way.

Kotlin DSLsendRichMessage and sendRichMessageDraft extensions, following SendMessageExtension.kt.

ModelTest — added prefab InputMedia values. EqualsVerifier cannot instantiate InputMedia because of its final self-typed thisAsT field, so every new model holding one failed.

Testing

17 unit tests in RichMessageRequestTest covering the discriminator of every block type, snake_case field names, rich text serialization, nested blocks, and attachment collection — nested, message-level, post-construction mutation, block replacement, the non-multipart case, and both upload-rejection paths.

./gradlew clean check -PprTest passes on JDK 11.

FernandoWerneck-VibX and others added 5 commits August 6, 2026 22:20
InputRichMessage only exposed html/markdown, so rich messages could not be
described as blocks nor carry embedded media. Two lower-level defects blocked
adding them:

- RichBlock/RichText declare their `type` discriminator as a computed property,
  which Gson does not serialize, and the registered adapters were deserializers
  only. Every block sent would have gone out without a type.
- SendRichMessage inherits isMultipart() == false from AbstractSendRequest, so a
  block referencing a freshly uploaded file would serialize an attach:// URL with
  no matching multipart part. editMessageText, which already accepted an
  InputRichMessage, had the same defect.

Adds the InputRichBlock hierarchy (21 blocks plus InputRichBlockListItem),
InputRichMessageMedia and the missing InputMediaVoiceNote; makes the rich
text/block adapters serialize their discriminator; and collects uploads lazily at
send time, so a rich message populated after the request was built is still
uploaded.

Also adds Kotlin DSL extensions for sendRichMessage and sendRichMessageDraft.

ModelTest needed prefab InputMedia values: EqualsVerifier cannot instantiate
InputMedia because of its final self-typed field.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012tbomWzUs3YpXUn61Rwg17
The collect-once guard defeated the point of collecting lazily: anything that
read isMultipart() or getParameters() before the mutable InputRichMessage was
populated — logging, for instance — froze the request as non-multipart, and
re-sending a request after adding media kept sending attach:// references with no
file parts.

RichMessageAttachments.refresh now rescans on every call and drops the parts left
over from the previous scan, so replacing a block also removes its stale part.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012tbomWzUs3YpXUn61Rwg17
sendRichMessageDraft cannot upload new files. Collecting attachments for it made
the request multipart and sent attach:// parts that Telegram rejects. There is no
legitimate case to collect either: every InputMedia.addAttachment caller takes a
File or a byte array, so an already uploaded thumbnail never shows up there.

The draft now stays non-multipart and fails fast when its media would need an
upload, instead of building a request that cannot succeed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012tbomWzUs3YpXUn61Rwg17
The API states, for the rich_message parameter of editMessageText, that "direct
upload of new files isn't supported when an inline message is edited". Collecting
attachments unconditionally made those edits multipart, producing a request
Telegram rejects.

EditMessageText now tracks whether it targets an inline message and fails fast on
media that would need an upload, as sendRichMessageDraft already does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012tbomWzUs3YpXUn61Rwg17
Add block and media support to sendRichMessage
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant