Skip to content

Commit 5309cdf

Browse files
authored
Merge pull request chigwell#133 from artgas1/feat/full-message-fidelity-upstream
feat: full-fidelity message data in get_history / get_messages
2 parents 2f303de + 3bdad95 commit 5309cdf

1 file changed

Lines changed: 164 additions & 34 deletions

File tree

telegram_mcp/tools/messages.py

Lines changed: 164 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@ def get_media_label(msg) -> str:
1313
msg.message but the media stays in msg.media).
1414
"""
1515
try:
16-
# стикер/голос/видео/аудио/гиф — это тоже document, поэтому проверяем их РАНЬШЕ document
16+
# Link web preview is NOT an attachment. Check it FIRST: for a message with a
17+
# link, Telethon returns the preview image via msg.photo; otherwise it would
18+
# be incorrectly classified as a "photo".
19+
if getattr(msg, "web_preview", None) is not None:
20+
return ""
21+
# Sticker/voice/video/audio/GIF are also represented as documents, so check
22+
# them BEFORE the generic document handler.
1723
sticker = getattr(msg, "sticker", None)
1824
if sticker is not None:
1925
alt = ""
@@ -47,16 +53,168 @@ def get_media_label(msg) -> str:
4753
return "geo"
4854
if getattr(msg, "poll", None) is not None:
4955
return "poll"
50-
# web-превью ссылки — это не вложение, не флагуем
51-
if getattr(msg, "web_preview", None) is not None:
52-
return ""
5356
if getattr(msg, "media", None) is not None:
5457
return "media"
5558
return ""
5659
except Exception:
5760
return ""
5861

5962

63+
def _inline_button_texts(msg):
64+
"""Inline button texts of the message (flat list), [] if none."""
65+
out = []
66+
try:
67+
for row in getattr(msg, "buttons", None) or []:
68+
for b in row:
69+
t = getattr(b, "text", None)
70+
if t:
71+
out.append(t)
72+
except Exception:
73+
pass
74+
return out
75+
76+
77+
def _link_urls(msg):
78+
"""Explicit URLs from entities (links hidden behind text), [] if none."""
79+
out = []
80+
try:
81+
for e in getattr(msg, "entities", None) or []:
82+
u = getattr(e, "url", None)
83+
if u:
84+
out.append(u)
85+
except Exception:
86+
pass
87+
return out
88+
89+
90+
def message_to_dict(msg) -> dict:
91+
"""API-complete but compact Telethon message view (omit empty fields).
92+
93+
The goal is for the MCP output to match the API object in completeness, rather
94+
than losing data such as media, albums, forwards, edits, buttons, reactions,
95+
and so on. All these fields are already present in the message object returned
96+
by the same get_messages request.
97+
"""
98+
d = {"id": msg.id, "sender": get_sender_name(msg), "date": msg.date}
99+
100+
sender_id = getattr(msg, "sender_id", None)
101+
if sender_id is not None:
102+
d["sender_id"] = sender_id
103+
if getattr(msg, "out", False):
104+
d["out"] = True
105+
106+
text = sanitize_user_content(msg.message) if getattr(msg, "message", None) else ""
107+
if text:
108+
d["text"] = text
109+
110+
media_label = get_media_label(msg)
111+
if media_label:
112+
d["media"] = media_label
113+
114+
grouped_id = getattr(msg, "grouped_id", None)
115+
if grouped_id:
116+
d["grouped_id"] = grouped_id # album: messages sharing one grouped_id form a single group
117+
118+
reply_to_id = (
119+
getattr(msg.reply_to, "reply_to_msg_id", None) if getattr(msg, "reply_to", None) else None
120+
)
121+
if reply_to_id:
122+
d["reply_to"] = reply_to_id
123+
124+
fwd = getattr(msg, "fwd_from", None)
125+
if fwd is not None:
126+
finfo = {}
127+
fdate = getattr(fwd, "date", None)
128+
if fdate:
129+
finfo["date"] = fdate
130+
fname = getattr(fwd, "from_name", None)
131+
if fname:
132+
finfo["from_name"] = sanitize_name(fname)
133+
d["forwarded"] = finfo or True
134+
135+
via_bot_id = getattr(msg, "via_bot_id", None)
136+
if via_bot_id:
137+
d["via_bot_id"] = via_bot_id
138+
139+
edit_date = getattr(msg, "edit_date", None)
140+
if edit_date:
141+
d["edited"] = edit_date
142+
143+
if getattr(msg, "pinned", False):
144+
d["pinned"] = True
145+
146+
engagement = get_engagement_dict(msg)
147+
if engagement:
148+
d["engagement"] = engagement
149+
150+
replies = getattr(msg, "replies", None)
151+
if replies is not None:
152+
cnt = getattr(replies, "replies", None)
153+
if cnt is not None:
154+
d["comments"] = cnt
155+
156+
buttons = _inline_button_texts(msg)
157+
if buttons:
158+
d["buttons"] = buttons
159+
160+
urls = _link_urls(msg)
161+
if urls:
162+
d["link_urls"] = urls
163+
164+
action = getattr(msg, "action", None)
165+
if action is not None:
166+
d["action"] = type(action).__name__ # service message (joined/pinned/…)
167+
168+
ttl = getattr(msg, "ttl_period", None)
169+
if ttl:
170+
d["ttl_period"] = ttl
171+
172+
return d
173+
174+
175+
def format_message_line(msg) -> str:
176+
"""Single-line human-readable message representation with ALL key flags."""
177+
parts = [f"ID: {msg.id}", get_sender_name(msg), f"Date: {msg.date}"]
178+
179+
reply_to_id = (
180+
getattr(msg.reply_to, "reply_to_msg_id", None) if getattr(msg, "reply_to", None) else None
181+
)
182+
if reply_to_id:
183+
parts.append(f"reply to {reply_to_id}")
184+
185+
flags = []
186+
media_label = get_media_label(msg)
187+
if media_label:
188+
flags.append(f"📎 {media_label}")
189+
grouped_id = getattr(msg, "grouped_id", None)
190+
if grouped_id:
191+
flags.append(f"album:{grouped_id}")
192+
if getattr(msg, "fwd_from", None) is not None:
193+
flags.append("forwarded")
194+
if getattr(msg, "edit_date", None):
195+
flags.append("edited")
196+
if getattr(msg, "via_bot_id", None):
197+
flags.append("via_bot")
198+
if getattr(msg, "pinned", False):
199+
flags.append("pinned")
200+
btn = _inline_button_texts(msg)
201+
if btn:
202+
flags.append(f"buttons:{len(btn)}")
203+
action = getattr(msg, "action", None)
204+
if action is not None:
205+
flags.append(f"service:{type(action).__name__}")
206+
if flags:
207+
parts.append(", ".join(flags))
208+
209+
engagement_info = get_engagement_info(msg).lstrip(" |").strip()
210+
if engagement_info:
211+
parts.append(engagement_info)
212+
213+
raw = sanitize_user_content(msg.message) if getattr(msg, "message", None) else ""
214+
safe_text = raw.replace("\n", "\\n") if raw else "[empty]"
215+
return " | ".join(parts) + f" | Message: {safe_text}"
216+
217+
60218
@mcp.tool(annotations=ToolAnnotations(title="Get Messages", openWorldHint=True, readOnlyHint=True))
61219
@with_account(readonly=True)
62220
@validate_id("chat_id")
@@ -79,21 +237,7 @@ async def get_messages(
79237
messages = await cl.get_messages(entity, limit=page_size, add_offset=offset)
80238
if not messages:
81239
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-
)
240+
lines = [format_message_line(msg) for msg in messages]
97241
return "\n".join(lines)
98242
except Exception as e:
99243
return log_and_format_error(
@@ -1237,21 +1381,7 @@ async def get_history(chat_id: Union[int, str], limit: int = 100, account: str =
12371381
entity = await resolve_entity(chat_id, cl)
12381382
messages = await cl.get_messages(entity, limit=limit)
12391383

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)
1384+
records = [message_to_dict(msg) for msg in messages]
12551385
return format_tool_result(records)
12561386
except Exception as e:
12571387
return log_and_format_error("get_history", e, chat_id=chat_id, limit=limit)

0 commit comments

Comments
 (0)