Skip to content

Commit cdebd09

Browse files
committed
Fix get_chat_history method to work properly with min_id and max_id
1 parent e70b421 commit cdebd09

1 file changed

Lines changed: 105 additions & 46 deletions

File tree

pyrogram/methods/messages/get_chat_history.py

Lines changed: 105 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616
# You should have received a copy of the GNU Lesser General Public License
1717
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
1818

19-
from datetime import datetime
20-
from typing import Union, AsyncGenerator
19+
import datetime
20+
from typing import AsyncIterator, Union
2121

2222
import pyrogram
23-
from pyrogram import types, raw, utils
23+
from pyrogram import raw, types, utils
2424

2525

2626
async def get_chunk(
@@ -30,32 +30,67 @@ async def get_chunk(
3030
limit: int = 0,
3131
offset: int = 0,
3232
from_message_id: int = 0,
33-
from_date: datetime = utils.zero_datetime(),
33+
from_date: datetime.datetime = utils.zero_datetime(),
3434
min_id: int = 0,
3535
max_id: int = 0,
36-
reverse: bool = False
37-
):
38-
from_message_id = from_message_id or (1 if reverse else 0)
39-
40-
messages = await client.invoke(
36+
reverse: bool = False,
37+
) -> list[types.Message]:
38+
"""
39+
Get chunk of messages from chat history.
40+
41+
:param client: Pyrogram client instance.
42+
:param chat_id: Chat identifier.
43+
:param limit: Maximum number of messages to retrieve.
44+
:param offset: Offset for pagination.
45+
:param from_message_id: Starting message ID.
46+
:param from_date: Starting date.
47+
:param min_id: Minimum message ID (inclusive).
48+
:param max_id: Maximum message ID (inclusive).
49+
:param reverse: If True, retrieve messages from oldest to newest.
50+
:returns: List of messages.
51+
"""
52+
# Telegram API requires `offset_id` as starting point, boundaries alone don't work
53+
api_from_message_id: int = from_message_id
54+
api_min_id: int = min_id
55+
api_max_id: int = max_id
56+
api_offset: int = offset
57+
58+
# Telegram API works backwards from `offset_id`, so we need proper starting points
59+
# when only boundaries are provided without explicit starting message ID
60+
if (min_id or max_id) and not from_message_id:
61+
if max_id:
62+
# Start from `max_id`+1 to include max_id in results (API uses exclusive upper bound)
63+
api_from_message_id = max_id + 1
64+
elif min_id:
65+
# Start from latest messages (0 means latest) and let `min_id` filter from below
66+
api_from_message_id = 0
67+
68+
# When both boundaries are set, always start from the upper boundary for efficiency
69+
if min_id and max_id and not from_message_id:
70+
api_from_message_id = max_id + 1
71+
72+
messages: raw.base.messages.Messages = await client.invoke(
4173
raw.functions.messages.GetHistory(
42-
peer=await client.resolve_peer(chat_id),
43-
offset_id=from_message_id,
44-
offset_date=utils.datetime_to_timestamp(from_date),
45-
add_offset=offset * (-1 if reverse else 1) - (limit if reverse else 0),
74+
peer=await client.resolve_peer(chat_id), # type: ignore[arg-type]
75+
offset_id=api_from_message_id,
76+
offset_date=int(from_date.timestamp()),
77+
add_offset=api_offset,
4678
limit=limit,
47-
max_id=max_id,
48-
min_id=min_id,
49-
hash=0
79+
max_id=api_max_id,
80+
min_id=api_min_id,
81+
hash=0,
5082
),
51-
sleep_threshold=60
83+
sleep_threshold=60,
5284
)
5385

54-
messages = await utils.parse_messages(client, messages, replies=0)
86+
parsed_messages = await utils.parse_messages(client, messages, replies=0)
87+
88+
# Telegram API returns messages in descending order by default (newest first)
5589
if reverse:
56-
messages.reverse()
90+
# Make sure the order is ascending (oldest first)
91+
parsed_messages.reverse()
92+
return parsed_messages
5793

58-
return messages
5994

6095
class GetChatHistory:
6196
async def get_chat_history(
@@ -64,11 +99,11 @@ async def get_chat_history(
6499
limit: int = 0,
65100
offset: int = 0,
66101
offset_id: int = 0,
67-
offset_date: datetime = utils.zero_datetime(),
102+
offset_date: datetime.datetime = utils.zero_datetime(),
68103
min_id: int = 0,
69104
max_id: int = 0,
70-
reverse: bool = False
71-
) -> AsyncGenerator["types.Message", None]:
105+
reverse: bool = False,
106+
) -> AsyncIterator[types.Message]:
72107
"""Get messages from a chat history.
73108
74109
The messages are returned in reverse chronological order.
@@ -86,20 +121,21 @@ async def get_chat_history(
86121
By default, no limit is applied and all messages are returned.
87122
88123
offset (``int``, *optional*):
89-
Sequential number of the first message to be returned..
124+
Sequential number of the first message to be returned.
90125
Negative values are also accepted and become useful in case you set offset_id or offset_date.
91126
92127
offset_id (``int``, *optional*):
93128
Identifier of the first message to be returned.
129+
Note: This parameter is deprecated and should not be used. Use min_id/max_id instead for proper filtering.
94130
95131
offset_date (:py:obj:`~datetime.datetime`, *optional*):
96132
Pass a date as offset to retrieve only older messages starting from that date.
97133
98134
min_id (``int``, *optional*):
99-
If a positive value was provided, the method will return only messages with IDs more than min_id.
135+
If a positive value was provided, the method will return only messages with IDs more than or equal to min_id (inclusive).
100136
101137
max_id (``int``, *optional*):
102-
If a positive value was provided, the method will return only messages with IDs less than max_id.
138+
If a positive value was provided, the method will return only messages with IDs less than or equal to max_id (inclusive).
103139
104140
reverse (``bool``, *optional*):
105141
Pass True to retrieve the messages from oldest to newest.
@@ -113,34 +149,57 @@ async def get_chat_history(
113149
async for message in app.get_chat_history(chat_id):
114150
print(message.text)
115151
"""
116-
current = 0
117-
total = limit or (1 << 31) - 1
118-
limit = min(100, total)
119-
120-
while True:
121-
messages = await get_chunk(
152+
current: int = 0
153+
total: int = limit or (1 << 31) - 1
154+
chunk_limit: int = min(100, total)
155+
156+
# Telegram API requires different parameter setup for reverse vs normal iteration
157+
# because `GetHistory` always returns messages in descending order by default
158+
if reverse:
159+
# For reverse (oldest to newest): start from `min_id` and work upward
160+
from_message_id: int = min_id if min_id else 1
161+
# Adjust boundaries to make them inclusive: API treats boundaries as exclusive
162+
api_min_id: int = (min_id - 1) if min_id else 0
163+
api_max_id: int = (max_id + 1) if max_id else 0
164+
# Negative offset moves the starting point backwards, required for reverse iteration
165+
current_offset: int = offset - chunk_limit
166+
else:
167+
# For normal (newest to oldest): start from `max_id` and work downward
168+
from_message_id = max_id if max_id else 0
169+
# Adjust boundaries to make them inclusive: API treats boundaries as exclusive
170+
api_min_id = (min_id - 1) if min_id else 0
171+
api_max_id = (max_id + 1) if max_id else 0
172+
current_offset = offset
173+
174+
while current < total:
175+
remaining: int = total - current
176+
current_chunk_limit: int = min(chunk_limit, remaining)
177+
178+
messages: list[types.Message] = await get_chunk(
122179
client=self,
123180
chat_id=chat_id,
124-
limit=limit,
125-
offset=offset,
126-
from_message_id=offset_id,
181+
limit=current_chunk_limit,
182+
offset=current_offset,
183+
from_message_id=from_message_id,
127184
from_date=offset_date,
128-
max_id=max_id,
129-
min_id=min_id,
130-
reverse=reverse
185+
min_id=api_min_id,
186+
max_id=api_max_id,
187+
reverse=reverse,
131188
)
132189

190+
# If no messages were returned, we can stop iterating
133191
if not messages:
134-
return
135-
136-
offset_id = messages[-1].id
137-
if reverse:
138-
offset_id += 1
192+
break
139193

194+
# Yield messages
140195
for message in messages:
141196
yield message
142-
143197
current += 1
144198

145-
if current >= total:
146-
return
199+
# Update pagination parameters for next chunk iteration
200+
if reverse:
201+
# For reverse: continue from next message after the last one we processed
202+
from_message_id = messages[-1].id + 1
203+
else:
204+
# For normal: continue from the last message ID we processed
205+
from_message_id = messages[-1].id

0 commit comments

Comments
 (0)