Skip to content

Commit e8371f1

Browse files
authored
Merge pull request chigwell#230 from vsem-azamat/fix/forward-result-ids
feat(messages): return destination IDs for native forwards
2 parents ef941e6 + 295289b commit e8371f1

2 files changed

Lines changed: 90 additions & 15 deletions

File tree

‎telegram_mcp/tools/messages.py‎

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1468,6 +1468,10 @@ async def forward_message(
14681468
14691469
Telegram validates sender and topic permissions; errors never fall back to
14701470
another sender or topic. Discovery is opt-in and does not change defaults.
1471+
1472+
When topic_id, send_as, drop_author or silent is used, the result also lists
1473+
the destination message IDs Telegram returned for this request, or says
1474+
that none were returned.
14711475
"""
14721476
try:
14731477
if topic_id is not None and (type(topic_id) is not int or topic_id <= 0):
@@ -1497,30 +1501,47 @@ async def forward_message(
14971501
ids_to_forward = sibling_ids
14981502
expanded_from_album = True
14991503

1504+
destination_note = ""
15001505
if topic_id is not None or send_as is not None or drop_author or silent:
15011506
sender = await resolve_input_entity(send_as, cl) if send_as is not None else None
1502-
await cl(
1503-
functions.messages.ForwardMessagesRequest(
1504-
from_peer=from_entity,
1505-
id=ids_to_forward if isinstance(ids_to_forward, list) else [ids_to_forward],
1506-
to_peer=to_entity,
1507-
top_msg_id=topic_id,
1508-
send_as=sender,
1509-
drop_author=drop_author,
1510-
silent=silent,
1511-
)
1507+
request = functions.messages.ForwardMessagesRequest(
1508+
from_peer=from_entity,
1509+
id=ids_to_forward if isinstance(ids_to_forward, list) else [ids_to_forward],
1510+
to_peer=to_entity,
1511+
top_msg_id=topic_id,
1512+
send_as=sender,
1513+
drop_author=drop_author,
1514+
silent=silent,
1515+
)
1516+
result = await cl(request)
1517+
# Correlate only this request's random IDs, in request order; never
1518+
# infer destination IDs from unrelated updates in the response.
1519+
returned_ids = {
1520+
update.random_id: update.id
1521+
for update in getattr(result, "updates", None) or []
1522+
if isinstance(update, types.UpdateMessageID)
1523+
}
1524+
destination_ids = [
1525+
returned_ids[random_id]
1526+
for random_id in request.random_id
1527+
if random_id in returned_ids
1528+
]
1529+
destination_note = (
1530+
f" Destination message IDs: {destination_ids or 'not returned by Telegram'}."
15121531
)
15131532
else:
15141533
await cl.forward_messages(to_entity, ids_to_forward, from_entity)
15151534
count = len(ids_to_forward) if isinstance(ids_to_forward, list) else 1
15161535
if count == 1:
1517-
return f"Message {message_id} forwarded from {from_chat_id} to {to_chat_id}."
1518-
if expanded_from_album:
1519-
return (
1536+
summary = f"Message {message_id} forwarded from {from_chat_id} to {to_chat_id}."
1537+
elif expanded_from_album:
1538+
summary = (
15201539
f"Album of {count} messages forwarded from {from_chat_id} "
15211540
f"to {to_chat_id} (auto-expanded from message {message_id})."
15221541
)
1523-
return f"{count} messages forwarded from {from_chat_id} to {to_chat_id}."
1542+
else:
1543+
summary = f"{count} messages forwarded from {from_chat_id} to {to_chat_id}."
1544+
return summary + destination_note
15241545
except Exception as e:
15251546
return log_and_format_error(
15261547
"forward_message",

‎tests/test_forward_message_routing.py‎

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def __init__(self):
2626
"sender": types.InputPeerChannel(33, 303),
2727
}
2828
self.album = []
29+
self.build_updates = None # callable(request) -> updates echoing its random_ids
2930

3031
async def resolve(self, identifier, client):
3132
assert client is self
@@ -39,6 +40,10 @@ async def __call__(self, request):
3940
bytes(request) # Exercise the real SDK serializer, not a fake TL schema.
4041
if self.error:
4142
raise self.error
43+
if self.build_updates:
44+
return types.Updates(
45+
self.build_updates(request), [], [], datetime.now(timezone.utc), 1
46+
)
4247
return self.result
4348

4449
async def forward_messages(self, *args, **kwargs):
@@ -91,7 +96,10 @@ async def test_legacy_album_and_explicit_batch_are_unchanged(client, message_id,
9196
)
9297
async def test_routed_forward_uses_native_request(client, route):
9398
result = await messages.forward_message("source", [10, 12], "destination", **route)
94-
assert result == "2 messages forwarded from source to destination."
99+
assert result == (
100+
"2 messages forwarded from source to destination. "
101+
"Destination message IDs: not returned by Telegram."
102+
)
95103
assert client.legacy == []
96104
assert client.reads == []
97105
assert len(client.requests) == 1
@@ -252,3 +260,49 @@ async def test_discovery_readonly_account_fanout(client, monkeypatch):
252260
result = await messages.get_send_as("destination")
253261
assert "[first]" in result and "[second]" in result
254262
assert len(client.requests) == 2
263+
264+
265+
@pytest.mark.asyncio
266+
async def test_native_forward_reports_request_correlated_destination_ids(client):
267+
def updates(request):
268+
first, second = request.random_id
269+
return [
270+
types.UpdateMessageID(id=999, random_id=first ^ second ^ 1), # unrelated request
271+
types.UpdateMessageID(id=701, random_id=second), # out of order
272+
types.UpdateNewMessage( # never inferred from
273+
message=types.Message(id=555, peer_id=types.PeerUser(22), message="other"),
274+
pts=1,
275+
pts_count=1,
276+
),
277+
types.UpdateMessageID(id=700, random_id=first),
278+
]
279+
280+
client.build_updates = updates
281+
result = await messages.forward_message("source", [10, 12], "destination", topic_id=7)
282+
assert result == (
283+
"2 messages forwarded from source to destination. Destination message IDs: [700, 701]."
284+
)
285+
assert client.legacy == []
286+
287+
288+
@pytest.mark.asyncio
289+
async def test_native_forward_reports_only_ids_telegram_returned(client):
290+
def updates(request):
291+
return [types.UpdateMessageID(id=701, random_id=request.random_id[1])]
292+
293+
client.build_updates = updates
294+
result = await messages.forward_message("source", [10, 12], "destination", silent=True)
295+
assert result.endswith("Destination message IDs: [701].")
296+
297+
298+
@pytest.mark.asyncio
299+
async def test_native_forward_states_when_no_ids_were_returned(client):
300+
def updates(request):
301+
return [types.UpdateMessageID(id=999, random_id=request.random_id[0] ^ 1)]
302+
303+
client.build_updates = updates
304+
result = await messages.forward_message("source", 10, "destination", drop_author=True)
305+
assert result == (
306+
"Message 10 forwarded from source to destination. "
307+
"Destination message IDs: not returned by Telegram."
308+
)

0 commit comments

Comments
 (0)