Repository navigation
fix(types): keep plain JSON text in RichBlockPreformatted - #2647
Conversation
RichText.de_json treated any string that parses to a JSON object as a
serialized RichText. A pre block holding a code sample like {"foo": "bar"}
has no "type" key, so the lookup fell through to None and the block lost its
text. Sending that message back failed with RICH_MESSAGE_CONTENT_REQUIRED.
Only a dict that resolves to a known RichText counts as serialized now, and
anything else keeps the original string. The type dispatch moved into
_from_dict so the string branch can fall back to it.
Fixes eternnoir#2646.
|
What I did to fix it locally in the meantime is add if 'type' not in parsed:
return json_stringbefore the if isinstance(json_string, str):
return json_string |
|
The approach made by Telegram:
is really a pain-in-the-ass.... |
There was a problem hiding this comment.
🟢 Approval recommended
No active review issues are reported for the change preserving JSON-shaped plain text in rich-message blocks.
No open issues 🎉
What changed in this PR
Fixes JSON-shaped plain text being lost from preformatted rich-message blocks, addressing #2646 in the shared type handling used by synchronous and asynchronous bots.
Changes:
- Preserves JSON-object strings as plain text unless they resolve to a known RichText type.
- Extracts type dispatch into
_from_dict. - Adds regression coverage for JSON code samples while confirming serialized RichText still deserializes.
| File | Description |
|---|---|
tests/test_types.py |
Adds tests for preserving JSON-shaped text and deserializing valid RichText. |
telebot/types.py |
Adds a plain-text fallback and extracts dictionary-based RichText dispatch. |
Review effort: Balanced
If it's a string, treat it as a string. There shouldn't be any special handling required for that. Assign it to 'text' and don't try to serialize it or anything if the instance is string. There shouldn't be a situation where a valid RichText object arrives to the 'text' variable as a string, it would be a dict or something else. |
|
Check for string instance first, if it is then return it as-is. Otherwise it's an array or a RichText* instance so then you can de-json it. Otherwise you'll always have instances where things match when they're not supposed to, for example dumping json of a Telegram message while debugging a bot. If you're dumping the json of a rich message then it's going to look like a valid rich message because... it is. But it's also a string meant to be sent, so it should just be returned and not processed. I ran into this issue when I was just sending json in a rich message to test all of the rich message features and make sure I implemented them correctly. But no matter what conditions you test the string for, it's still a string. It should just be returned. Otherwise you can have anyone break a bot by sending a rich message's json to it and that shouldn't be possible. |
|
RichText is JsonDeserializable. It may come as JSON string, that is the root of evil. Thus we cannot learly dedicate string from json-string. |
A JSON object whose "type" matches a known RichText but that is missing the
rest of the shape, e.g. {"type": "bold"}, is ordinary text the user typed, but
it raised KeyError: 'text' out of RichTextBold.de_json.
Fall back to the original string when resolving the parsed object raises, which
keeps the plain-text branch total.
|
On the objection that a string cannot be cleanly separated from a JSON-string: agreed in general, but the current branch already narrows "serialized RichText" to objects that resolve to a known type, and that leaves one hole I hit while testing.
c5285aa makes that branch total: if resolving the parsed object raises, the original string is returned. So the rule is now "a JSON object is RichText only if it fully resolves", not "only if its type looks right". Test added for the partial-shape case; suite is 309 passed, 73 skipped. |
|
I'm really tired of this pain made by Tg. Let's try keeping this another "optimization", may be it will cover the pain better. |
Description
RichText.de_jsontreated any string that parses to a JSON object as a serialized RichText. Apreblock holding a code sample like{"foo": "bar"}has notypekey, so the dispatch fell through toNoneand the block lost its text:Sending that message back then fails with
RICH_MESSAGE_CONTENT_REQUIRED(#2646).Only a dict that resolves to a known RichText counts as serialized now; anything else keeps the original string. The type dispatch moved into
_from_dictso the string branch can fall back to it. The[56]case from #2635 still works, and so do{},{"a": [1, 2]}and{"type": "not_a_richtext"}.Describe your tests
Added
test_json_richtext_plain_json_object_stays_textintests/test_types.py. It covers the preformatted block from the issue plus the other JSON-shaped strings, and asserts a real serialized RichText still deserializes.Full suite:
308 passed, 73 skipped.Python version: 3.12.3
OS: macOS 27.2
Checklist: