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
2222import pyrogram
23- from pyrogram import types , raw , utils
23+ from pyrogram import raw , types , utils
2424
2525
2626async 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
6095class 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