tail-fin-twitter 0.5.1

Twitter/X adapter for tail-fin: timeline, search, profile, bookmarks, likes, thread, post, like, follow, block, bookmark, reply, trending, lists, article, download, notifications
Documentation
use serde_json::json;
use tail_fin_common::BrowserSession;

use tail_fin_common::page::{ensure_on_domain, page_fetch};
use tail_fin_common::TailFinError;

use crate::auth::{build_headers, extract_ct0};
use crate::graphql::{build_graphql_url, default_features, resolve_query_id};
use crate::parsing::parse_timeline_response;
use crate::types::{TimelineResponse, TimelineType};

/// Fetch a page of the user's timeline.
///
/// - `timeline_type`: `ForYou` (algorithmic) or `Following` (chronological).
/// - `cursor`: Pass `None` for the first page, or `Some(cursor)` for pagination.
/// - `count`: Number of tweets to request (Twitter may return fewer).
pub async fn fetch_timeline(
    session: &BrowserSession,
    timeline_type: TimelineType,
    cursor: Option<&str>,
    count: usize,
) -> Result<TimelineResponse, TailFinError> {
    // Ensure we're on x.com for same-origin fetch
    ensure_on_domain(session, &["x.com", "twitter.com"]).await?;

    // Extract auth
    let ct0 = extract_ct0(session).await?;
    let headers = build_headers(&ct0);

    // Resolve queryId
    let endpoint = timeline_type.endpoint();
    let query_id = resolve_query_id(session, endpoint, timeline_type.fallback_query_id()).await?;

    // Build variables
    let mut variables = json!({
        "count": count,
        "includePromotedContent": true,
        "latestControlAvailable": true,
        "requestContext": "launch",
    });
    if let Some(c) = cursor {
        variables["cursor"] = json!(c);
    }

    let features = default_features();
    let url = build_graphql_url(&query_id, endpoint, &variables, &features);
    let method = timeline_type.method();

    // Execute request in page context
    let data = page_fetch(session, &url, method, &headers).await?;

    // Parse response
    parse_timeline_response(&data)
}