@@ -87,6 +87,29 @@ def _link_urls(msg):
8787 return out
8888
8989
90+ def get_reply_quote (msg ) -> Optional [dict ]:
91+ """Quoted fragment when a reply targets only *part* of the replied-to message.
92+
93+ Telegram lets you select a span of another message and reply to just that
94+ span. Telethon exposes it on msg.reply_to as quote_text (the selected text)
95+ and quote_offset (its UTF-16 character offset inside the original message).
96+ Returns {"text": ..., "offset": ...} for such a partial-quote reply, or None
97+ for a plain whole-message reply (or no reply at all). Independent of
98+ reply_to_msg_id so a cross-chat quote reply still surfaces its quote.
99+ """
100+ reply = getattr (msg , "reply_to" , None )
101+ if reply is None :
102+ return None
103+ quote_text = getattr (reply , "quote_text" , None )
104+ if not quote_text :
105+ return None
106+ quote = {"text" : sanitize_user_content (quote_text )}
107+ offset = getattr (reply , "quote_offset" , None )
108+ if offset is not None :
109+ quote ["offset" ] = offset
110+ return quote
111+
112+
90113def message_to_dict (msg ) -> dict :
91114 """API-complete but compact Telethon message view (omit empty fields).
92115
@@ -123,6 +146,9 @@ def message_to_dict(msg) -> dict:
123146 )
124147 if reply_to_id :
125148 d ["reply_to" ] = reply_to_id
149+ reply_quote = get_reply_quote (msg )
150+ if reply_quote :
151+ d ["reply_quote" ] = reply_quote # reply to a selected span of the original
126152
127153 fwd = getattr (msg , "fwd_from" , None )
128154 if fwd is not None :
@@ -184,6 +210,12 @@ def format_message_line(msg) -> str:
184210 )
185211 if reply_to_id :
186212 parts .append (f"reply to { reply_to_id } " )
213+ reply_quote = get_reply_quote (msg )
214+ if reply_quote :
215+ preview = reply_quote ["text" ].replace ("\n " , " " )
216+ if len (preview ) > 60 :
217+ preview = preview [:60 ] + "…"
218+ parts .append (f'quoting "{ preview } "' )
187219
188220 flags = []
189221 media_label = get_media_label (msg )
@@ -785,6 +817,9 @@ async def list_messages(
785817 reply_to_id = getattr (msg .reply_to , "reply_to_msg_id" , None ) if msg .reply_to else None
786818 if reply_to_id :
787819 record ["reply_to" ] = reply_to_id
820+ reply_quote = get_reply_quote (msg )
821+ if reply_quote :
822+ record ["reply_quote" ] = reply_quote
788823 engagement = get_engagement_dict (msg )
789824 if engagement :
790825 record ["engagement" ] = engagement
@@ -855,6 +890,9 @@ async def get_message_context(
855890 record ["grouped_id" ] = grouped_id
856891
857892 # Check if this message is a reply and get the replied message
893+ reply_quote = get_reply_quote (msg )
894+ if reply_quote :
895+ record ["reply_quote" ] = reply_quote
858896 if msg .reply_to and msg .reply_to .reply_to_msg_id :
859897 record ["reply_to" ] = msg .reply_to .reply_to_msg_id
860898 try :
@@ -1319,6 +1357,9 @@ async def search_messages(
13191357 }
13201358 if msg .reply_to and msg .reply_to .reply_to_msg_id :
13211359 record ["reply_to" ] = msg .reply_to .reply_to_msg_id
1360+ reply_quote = get_reply_quote (msg )
1361+ if reply_quote :
1362+ record ["reply_quote" ] = reply_quote
13221363 records .append (record )
13231364 return format_tool_result (records )
13241365 except Exception as e :
@@ -1435,6 +1476,9 @@ async def get_pinned_messages(chat_id: Union[int, str], account: str = None) ->
14351476 }
14361477 if msg .reply_to and msg .reply_to .reply_to_msg_id :
14371478 record ["reply_to" ] = msg .reply_to .reply_to_msg_id
1479+ reply_quote = get_reply_quote (msg )
1480+ if reply_quote :
1481+ record ["reply_quote" ] = reply_quote
14381482 records .append (record )
14391483
14401484 return format_tool_result (records )
0 commit comments