Feed cursor pagination (#49)
* Expose promoted tweets in feed output * Add cursor-based feed pagination output
This commit is contained in:
@@ -153,22 +153,28 @@ class TwitterClient:
|
||||
|
||||
# ── Read operations ──────────────────────────────────────────────
|
||||
|
||||
def fetch_home_timeline(self, count=20):
|
||||
# type: (int) -> List[Tweet]
|
||||
def fetch_home_timeline(self, count=20, include_promoted=False, cursor=None, return_cursor=False):
|
||||
# type: (int, bool, Optional[str], bool) -> Any
|
||||
"""Fetch home timeline tweets."""
|
||||
return self._fetch_timeline(
|
||||
"HomeTimeline",
|
||||
count,
|
||||
lambda data: _deep_get(data, "data", "home", "home_timeline_urt", "instructions"),
|
||||
include_promoted=include_promoted,
|
||||
start_cursor=cursor,
|
||||
return_cursor=return_cursor,
|
||||
)
|
||||
|
||||
def fetch_following_feed(self, count=20):
|
||||
# type: (int) -> List[Tweet]
|
||||
def fetch_following_feed(self, count=20, include_promoted=False, cursor=None, return_cursor=False):
|
||||
# type: (int, bool, Optional[str], bool) -> Any
|
||||
"""Fetch chronological following feed."""
|
||||
return self._fetch_timeline(
|
||||
"HomeLatestTimeline",
|
||||
count,
|
||||
lambda data: _deep_get(data, "data", "home", "home_timeline_urt", "instructions"),
|
||||
include_promoted=include_promoted,
|
||||
start_cursor=cursor,
|
||||
return_cursor=return_cursor,
|
||||
)
|
||||
|
||||
def fetch_bookmarks(self, count=50):
|
||||
@@ -732,8 +738,8 @@ class TwitterClient:
|
||||
|
||||
# ── Internal: timeline / user list fetchers ──────────────────────
|
||||
|
||||
def _fetch_timeline(self, operation_name, count, get_instructions, extra_variables=None, override_base_variables=False, field_toggles=None, use_post=False):
|
||||
# type: (str, int, Callable[[Any], Any], Optional[Dict[str, Any]], bool, Optional[Dict[str, Any]], bool) -> List[Tweet]
|
||||
def _fetch_timeline(self, operation_name, count, get_instructions, extra_variables=None, override_base_variables=False, field_toggles=None, use_post=False, include_promoted=False, start_cursor=None, return_cursor=False):
|
||||
# type: (str, int, Callable[[Any], Any], Optional[Dict[str, Any]], bool, Optional[Dict[str, Any]], bool, bool, Optional[str], bool) -> Any
|
||||
"""Generic timeline fetcher with pagination and deduplication.
|
||||
|
||||
Args:
|
||||
@@ -751,7 +757,8 @@ class TwitterClient:
|
||||
|
||||
tweets = [] # type: List[Tweet]
|
||||
seen_ids = set() # type: Set[str]
|
||||
cursor = None # type: Optional[str]
|
||||
cursor = start_cursor # type: Optional[str]
|
||||
continuation_cursor = None # type: Optional[str]
|
||||
attempts = 0
|
||||
max_attempts = int(math.ceil(count / 20.0)) + 2
|
||||
|
||||
@@ -763,7 +770,7 @@ class TwitterClient:
|
||||
else:
|
||||
variables = {
|
||||
"count": min(count - len(tweets) + 5, 40),
|
||||
"includePromotedContent": False,
|
||||
"includePromotedContent": include_promoted,
|
||||
"latestControlAvailable": True,
|
||||
"requestContext": "launch",
|
||||
}
|
||||
@@ -784,10 +791,13 @@ class TwitterClient:
|
||||
tweets.append(tweet)
|
||||
|
||||
if not next_cursor:
|
||||
continuation_cursor = None
|
||||
break
|
||||
if next_cursor == cursor:
|
||||
logger.debug("Timeline pagination stopped because cursor did not advance: %s", next_cursor)
|
||||
continuation_cursor = None
|
||||
break
|
||||
continuation_cursor = next_cursor
|
||||
cursor = next_cursor
|
||||
|
||||
if not new_tweets:
|
||||
@@ -799,6 +809,8 @@ class TwitterClient:
|
||||
logger.debug("Sleeping %.1fs between requests", jitter)
|
||||
time.sleep(jitter)
|
||||
|
||||
if return_cursor:
|
||||
return tweets[:count], continuation_cursor
|
||||
return tweets[:count]
|
||||
|
||||
def _fetch_user_list(self, operation_name, user_id, count, get_instructions):
|
||||
|
||||
Reference in New Issue
Block a user