@@ -45,7 +45,8 @@ def _timeline_structure(payload: Any) -> tuple[list[dict[str, Any]], list[str],
4545 for group in _instruction_lists (payload ):
4646 instructions .extend (item for item in group if isinstance (item , dict ))
4747
48- ordered_ids : list [str ] = []
48+ # Collect pins first so instruction ordering cannot accidentally let a pinned ID
49+ # enter the normal lower-bound sequence.
4950 pinned_ids : set [str ] = set ()
5051 terminated_bottom = False
5152 for instruction in instructions :
@@ -54,15 +55,20 @@ def _timeline_structure(payload: Any) -> tuple[list[dict[str, Any]], list[str],
5455 post_id = _post_id (instruction .get ("entry" ))
5556 if post_id :
5657 pinned_ids .add (post_id )
57- elif kind == "TimelineAddEntries" :
58- entries = instruction .get ("entries" , [])
59- if isinstance (entries , list ):
60- for entry in entries :
61- post_id = _post_id (entry )
62- if post_id and post_id not in pinned_ids :
63- ordered_ids .append (post_id )
6458 elif kind == "TimelineTerminateTimeline" and str (instruction .get ("direction" , "" )) == "Bottom" :
6559 terminated_bottom = True
60+
61+ ordered_ids : list [str ] = []
62+ for instruction in instructions :
63+ if str (instruction .get ("type" , "" ) or "" ) != "TimelineAddEntries" :
64+ continue
65+ entries = instruction .get ("entries" , [])
66+ if not isinstance (entries , list ):
67+ continue
68+ for entry in entries :
69+ post_id = _post_id (entry )
70+ if post_id and post_id not in pinned_ids :
71+ ordered_ids .append (post_id )
6672 return instructions , ordered_ids , pinned_ids , terminated_bottom
6773
6874
@@ -189,16 +195,14 @@ async def _provider_page(
189195 next_cursor = get_cursor (payload , "Bottom" )
190196
191197 instructions , ordered_ids , pinned_ids , terminated_bottom = _timeline_structure (payload )
192- recognized = any (
193- str (item .get ("type" , "" )) in {
194- "TimelineAddEntries" ,
195- "TimelinePinEntry" ,
196- "TimelineTerminateTimeline" ,
197- "TimelineReplaceEntry" ,
198- }
199- for item in instructions
198+ has_add_entries = any (str (item .get ("type" , "" ) or "" ) == "TimelineAddEntries" for item in instructions )
199+ # Pin/replace instructions alone are not sufficient terminal proof. A normal
200+ # timeline page or an explicit Bottom termination is required.
201+ valid = bool (
202+ isinstance (payload , dict )
203+ and not payload .get ("errors" )
204+ and (has_add_entries or terminated_bottom )
200205 )
201- valid = isinstance (payload , dict ) and not payload .get ("errors" ) and recognized
202206 if valid :
203207 valid = _record_structural_proof (
204208 tweets = tweets ,
@@ -209,16 +213,12 @@ async def _provider_page(
209213 # The pinned twscrape paginator itself treats absence of a Bottom cursor as the
210214 # end of a non-error page. An explicit Bottom termination is even stronger.
211215 exhausted = bool (valid and (terminated_bottom or not next_cursor ))
212- page = recovery ._ProviderPage (
216+ return recovery ._ProviderPage (
213217 tweets = tweets ,
214218 next_cursor = str (next_cursor ) if next_cursor else None ,
215219 exhausted = exhausted ,
216220 valid_response = valid ,
217221 )
218- page .ordered_tweet_ids = tuple (ordered_ids )
219- page .pinned_tweet_ids = frozenset (pinned_ids )
220- page .terminated_bottom = terminated_bottom
221- return page
222222
223223
224224def install () -> None :
0 commit comments