@@ -13,7 +13,12 @@ def get_media_label(msg) -> str:
1313 msg.message but the media stays in msg.media).
1414 """
1515 try :
16- # стикер/голос/видео/аудио/гиф — это тоже document, поэтому проверяем их РАНЬШЕ document
16+ # Веб-превью ссылки — НЕ вложение. Проверяем ПЕРВЫМ: у сообщения со
17+ # ссылкой Telethon отдаёт картинку превью через msg.photo, иначе она
18+ # ложно пометилась бы как "photo".
19+ if getattr (msg , "web_preview" , None ) is not None :
20+ return ""
21+ # Стикер/голос/видео/аудио/гиф — это тоже document, проверяем РАНЬШЕ document.
1722 sticker = getattr (msg , "sticker" , None )
1823 if sticker is not None :
1924 alt = ""
@@ -47,16 +52,163 @@ def get_media_label(msg) -> str:
4752 return "geo"
4853 if getattr (msg , "poll" , None ) is not None :
4954 return "poll"
50- # web-превью ссылки — это не вложение, не флагуем
51- if getattr (msg , "web_preview" , None ) is not None :
52- return ""
5355 if getattr (msg , "media" , None ) is not None :
5456 return "media"
5557 return ""
5658 except Exception :
5759 return ""
5860
5961
62+ def _inline_button_texts (msg ):
63+ """Тексты inline-кнопок сообщения (плоским списком), [] если нет."""
64+ out = []
65+ try :
66+ for row in (getattr (msg , "buttons" , None ) or []):
67+ for b in row :
68+ t = getattr (b , "text" , None )
69+ if t :
70+ out .append (t )
71+ except Exception :
72+ pass
73+ return out
74+
75+
76+ def _link_urls (msg ):
77+ """Явные URL из entities (скрытые за текстом ссылки), [] если нет."""
78+ out = []
79+ try :
80+ for e in (getattr (msg , "entities" , None ) or []):
81+ u = getattr (e , "url" , None )
82+ if u :
83+ out .append (u )
84+ except Exception :
85+ pass
86+ return out
87+
88+
89+ def message_to_dict (msg ) -> dict :
90+ """API-полный, но компактный вид сообщения Telethon (пустые поля опускаем).
91+
92+ Цель — чтобы вывод MCP по полноте соответствовал объекту API, а не терял
93+ данные (медиа, альбомы, пересылки, правки, кнопки, реакции и т.п.).
94+ Все эти поля уже приходят в объекте сообщения тем же запросом get_messages.
95+ """
96+ d = {"id" : msg .id , "sender" : get_sender_name (msg ), "date" : msg .date }
97+
98+ sender_id = getattr (msg , "sender_id" , None )
99+ if sender_id is not None :
100+ d ["sender_id" ] = sender_id
101+ if getattr (msg , "out" , False ):
102+ d ["out" ] = True
103+
104+ text = sanitize_user_content (msg .message ) if getattr (msg , "message" , None ) else ""
105+ if text :
106+ d ["text" ] = text
107+
108+ media_label = get_media_label (msg )
109+ if media_label :
110+ d ["media" ] = media_label
111+
112+ grouped_id = getattr (msg , "grouped_id" , None )
113+ if grouped_id :
114+ d ["grouped_id" ] = grouped_id # альбом: сообщения с одним grouped_id — одна группа
115+
116+ reply_to_id = getattr (msg .reply_to , "reply_to_msg_id" , None ) if getattr (msg , "reply_to" , None ) else None
117+ if reply_to_id :
118+ d ["reply_to" ] = reply_to_id
119+
120+ fwd = getattr (msg , "fwd_from" , None )
121+ if fwd is not None :
122+ finfo = {}
123+ fdate = getattr (fwd , "date" , None )
124+ if fdate :
125+ finfo ["date" ] = fdate
126+ fname = getattr (fwd , "from_name" , None )
127+ if fname :
128+ finfo ["from_name" ] = sanitize_name (fname )
129+ d ["forwarded" ] = finfo or True
130+
131+ via_bot_id = getattr (msg , "via_bot_id" , None )
132+ if via_bot_id :
133+ d ["via_bot_id" ] = via_bot_id
134+
135+ edit_date = getattr (msg , "edit_date" , None )
136+ if edit_date :
137+ d ["edited" ] = edit_date
138+
139+ if getattr (msg , "pinned" , False ):
140+ d ["pinned" ] = True
141+
142+ engagement = get_engagement_dict (msg )
143+ if engagement :
144+ d ["engagement" ] = engagement
145+
146+ replies = getattr (msg , "replies" , None )
147+ if replies is not None :
148+ cnt = getattr (replies , "replies" , None )
149+ if cnt is not None :
150+ d ["comments" ] = cnt
151+
152+ buttons = _inline_button_texts (msg )
153+ if buttons :
154+ d ["buttons" ] = buttons
155+
156+ urls = _link_urls (msg )
157+ if urls :
158+ d ["link_urls" ] = urls
159+
160+ action = getattr (msg , "action" , None )
161+ if action is not None :
162+ d ["action" ] = type (action ).__name__ # сервисное сообщение (вступил/закрепил/…)
163+
164+ ttl = getattr (msg , "ttl_period" , None )
165+ if ttl :
166+ d ["ttl_period" ] = ttl
167+
168+ return d
169+
170+
171+ def format_message_line (msg ) -> str :
172+ """Однострочный человекочитаемый вид сообщения со ВСЕМИ ключевыми флагами."""
173+ parts = [f"ID: { msg .id } " , get_sender_name (msg ), f"Date: { msg .date } " ]
174+
175+ reply_to_id = getattr (msg .reply_to , "reply_to_msg_id" , None ) if getattr (msg , "reply_to" , None ) else None
176+ if reply_to_id :
177+ parts .append (f"reply to { reply_to_id } " )
178+
179+ flags = []
180+ media_label = get_media_label (msg )
181+ if media_label :
182+ flags .append (f"📎 { media_label } " )
183+ grouped_id = getattr (msg , "grouped_id" , None )
184+ if grouped_id :
185+ flags .append (f"album:{ grouped_id } " )
186+ if getattr (msg , "fwd_from" , None ) is not None :
187+ flags .append ("forwarded" )
188+ if getattr (msg , "edit_date" , None ):
189+ flags .append ("edited" )
190+ if getattr (msg , "via_bot_id" , None ):
191+ flags .append ("via_bot" )
192+ if getattr (msg , "pinned" , False ):
193+ flags .append ("pinned" )
194+ btn = _inline_button_texts (msg )
195+ if btn :
196+ flags .append (f"buttons:{ len (btn )} " )
197+ action = getattr (msg , "action" , None )
198+ if action is not None :
199+ flags .append (f"service:{ type (action ).__name__ } " )
200+ if flags :
201+ parts .append (", " .join (flags ))
202+
203+ engagement_info = get_engagement_info (msg ).lstrip (" |" ).strip ()
204+ if engagement_info :
205+ parts .append (engagement_info )
206+
207+ raw = sanitize_user_content (msg .message ) if getattr (msg , "message" , None ) else ""
208+ safe_text = raw .replace ("\n " , "\\ n" ) if raw else "[empty]"
209+ return " | " .join (parts ) + f" | Message: { safe_text } "
210+
211+
60212@mcp .tool (annotations = ToolAnnotations (title = "Get Messages" , openWorldHint = True , readOnlyHint = True ))
61213@with_account (readonly = True )
62214@validate_id ("chat_id" )
@@ -79,21 +231,7 @@ async def get_messages(
79231 messages = await cl .get_messages (entity , limit = page_size , add_offset = offset )
80232 if not messages :
81233 return "No messages found for this page."
82- lines = []
83- for msg in messages :
84- sender_name = get_sender_name (msg )
85- reply_info = ""
86- if msg .reply_to and msg .reply_to .reply_to_msg_id :
87- reply_info = f" | reply to { msg .reply_to .reply_to_msg_id } "
88-
89- engagement_info = get_engagement_info (msg )
90- safe_text = sanitize_user_content (msg .message ).replace ("\n " , "\\ n" )
91- media_label = get_media_label (msg )
92- media_info = f" | 📎 { media_label } " if media_label else ""
93-
94- lines .append (
95- f"ID: { msg .id } | { sender_name } | Date: { msg .date } { reply_info } { engagement_info } { media_info } | Message: { safe_text } "
96- )
234+ lines = [format_message_line (msg ) for msg in messages ]
97235 return "\n " .join (lines )
98236 except Exception as e :
99237 return log_and_format_error (
@@ -1237,21 +1375,7 @@ async def get_history(chat_id: Union[int, str], limit: int = 100, account: str =
12371375 entity = await resolve_entity (chat_id , cl )
12381376 messages = await cl .get_messages (entity , limit = limit )
12391377
1240- records = []
1241- for msg in messages :
1242- record = {
1243- "id" : msg .id ,
1244- "sender" : get_sender_name (msg ),
1245- "date" : msg .date ,
1246- "text" : sanitize_user_content (msg .message ),
1247- }
1248- reply_to_id = getattr (msg .reply_to , "reply_to_msg_id" , None ) if msg .reply_to else None
1249- if reply_to_id :
1250- record ["reply_to" ] = reply_to_id
1251- media_label = get_media_label (msg )
1252- if media_label :
1253- record ["media" ] = media_label
1254- records .append (record )
1378+ records = [message_to_dict (msg ) for msg in messages ]
12551379 return format_tool_result (records )
12561380 except Exception as e :
12571381 return log_and_format_error ("get_history" , e , chat_id = chat_id , limit = limit )
0 commit comments