I dug around more, ended up refactoring a bit of code took the liberty to push my changes to your branch. Managed to fix the nested entities stuff (and I'm wondering why the if offset == 0 never caused problems so far).
What's still missing is throwing an exception for two consequcitve block quotes in MarkdownV2.
Moreover, even though block quote parsing seems to be working correctly now, I think there is a bug on TG side. Sending a text of the form ">A\nB" leads to TG detecting a Message entity of length 2 instead of 1, which includes the "\n". You can reproduce this via
https://api.telegram.org/botTOKEN/sendMessage?chat_id=CHAT_ID&text=%3EA%0AB&parse_mode=MarkdownV2
If you try to compute the text_md_v2 for the resulting message, the B will be moved into the block quote.
For completeness, let me also share my renewed testing script:
Detailsimport asyncio import json from pathlib import Path from pprint import pprint from telegram import Message, Update, Bot from telegram.constants import ParseMode from telegram.ext import CallbackContext, Application, MessageHandler, filters def get_message(bot: Bot) -> Message: return Update.de_json(json.loads(Path("response_3.json").read_bytes()), bot).effective_message async def main(): async with Bot("TOKEN") as bot: message = get_message(bot) await run_tests(message) def get_formatted_text(message: Message, parse_mode: ParseMode) -> str: return getattr(message, f"text_{parse_mode.name.lower()}") async def run_tests(message: Message) -> None: print(repr(message.text)) pprint(repr(message.entities)) for parse_mode in (ParseMode.HTML, ParseMode.MARKDOWN_V2): print("-" * 80) print(f"Replying with {parse_mode}:") await message.get_bot().send_message( chat_id=1145108092, text=f"============ Replying with {parse_mode}:", ) formatted_text = get_formatted_text(message, parse_mode) print(repr(formatted_text)) reply = await message.get_bot().send_message( chat_id=1145108092, text=formatted_text, parse_mode=parse_mode, ) print(reply.text == message.text, repr(reply.text), repr(message.text), sep="\n") print( reply.entities == message.entities, repr(reply.entities), repr(message.entities), sep="\n", ) pprint(reply.parse_entities()) print( reply.text_html == message.text_html, repr(reply.text_html), repr(message.text_html), sep="\n", ) print( reply.text_markdown_v2 == message.text_markdown_v2, (reply.text_markdown_v2), (message.text_markdown_v2), sep="\n====\n", ) next_reply = await reply.reply_text( get_formatted_text(reply, parse_mode), parse_mode=parse_mode, quote=True ) await next_reply.reply_text( get_formatted_text(next_reply, parse_mode), parse_mode=parse_mode, quote=True ) if __name__ == "__main__": asyncio.run(main())
response.json:
{
"update_id": 219020534,
"message": {
"message_id": 31843,
"from": {
"id": 1145108092,
"is_bot": false,
"first_name": "Hinrich",
"last_name": "Mahler (@Bibo-Joshi)",
"username": "BiboJoshi",
"language_code": "de"
},
"chat": {
"id": 1145108092,
"first_name": "Hinrich",
"last_name": "Mahler (@Bibo-Joshi)",
"username": "BiboJoshi",
"type": "private"
},
"date": 1704553973,
"text": "block \\n quote\nABC\nblock \\n quote\nwith \\n line break\nDEF\nblock \\n quote\nwith \\n line breakp",
"entities": [
{
"offset": 0,
"length": 14,
"type": "blockquote"
},
{
"offset": 9,
"length": 5,
"type": "underline"
},
{
"offset": 19,
"length": 33,
"type": "blockquote"
},
{
"offset": 28,
"length": 5,
"type": "italic"
},
{
"offset": 28,
"length": 5,
"type": "strikethrough"
},
{
"offset": 57,
"length": 34,
"type": "blockquote"
},
{
"offset": 66,
"length": 5,
"type": "spoiler"
}
]
}
}