tail-fin-twitter 0.1.0

Twitter/X adapter for tail-fin: timeline, search via GraphQL API
Documentation
pub mod actions;
pub mod auth;
pub mod util;
pub mod graphql;
#[cfg(feature = "http")]
pub mod http;
pub mod parsing;
pub mod search;
pub mod timeline;
pub mod types;

use night_fury_core::BrowserSession;
use tail_fin_common::TailFinError;

pub use util::extract_tweet_id;
pub use types::{
    ActionResult, MediaItem, Notification, TimelineResponse, TimelineType, Trend, Tweet,
    TwitterUser, UserProfile,
};

#[cfg(feature = "http")]
pub use http::TwitterHttpClient;

/// Shared trait for operations available on both browser and HTTP clients.
pub trait TwitterApi {
    /// Fetch a page of the user's home timeline.
    fn timeline(
        &self,
        kind: TimelineType,
        count: usize,
        cursor: Option<&str>,
    ) -> impl std::future::Future<Output = Result<TimelineResponse, TailFinError>> + Send;

    /// Search for tweets matching the given query.
    fn search(
        &self,
        query: &str,
        count: usize,
    ) -> impl std::future::Future<Output = Result<Vec<Tweet>, TailFinError>> + Send;
}

/// High-level Twitter client backed by a NightFury browser session.
///
/// The browser must already be navigated to (or able to navigate to) x.com
/// with an active login session.
pub struct TwitterClient {
    session: BrowserSession,
}

impl TwitterClient {
    /// Create a new TwitterClient wrapping the given browser session.
    pub fn new(session: BrowserSession) -> Self {
        Self { session }
    }

    /// Get a reference to the underlying browser session.
    pub fn session(&self) -> &BrowserSession {
        &self.session
    }

    /// Post a new tweet.
    pub async fn post(&self, text: &str) -> Result<ActionResult, TailFinError> {
        actions::browser_post(&self.session, text).await
    }

    /// Like a tweet by URL.
    pub async fn like(&self, tweet_url: &str) -> Result<ActionResult, TailFinError> {
        actions::browser_like(&self.session, tweet_url).await
    }

    /// Follow a user by screen name.
    pub async fn follow(&self, username: &str) -> Result<ActionResult, TailFinError> {
        actions::browser_follow(&self.session, username, true).await
    }

    /// Unfollow a user by screen name.
    pub async fn unfollow(&self, username: &str) -> Result<ActionResult, TailFinError> {
        actions::browser_follow(&self.session, username, false).await
    }

    /// Delete a tweet by URL.
    pub async fn delete(&self, tweet_url: &str) -> Result<ActionResult, TailFinError> {
        actions::browser_delete(&self.session, tweet_url).await
    }

    /// Block a user by screen name.
    pub async fn block(&self, username: &str) -> Result<ActionResult, TailFinError> {
        actions::browser_block(&self.session, username, true).await
    }

    /// Unblock a user by screen name.
    pub async fn unblock(&self, username: &str) -> Result<ActionResult, TailFinError> {
        actions::browser_block(&self.session, username, false).await
    }

    /// Reply to a tweet.
    pub async fn reply(&self, tweet_url: &str, text: &str) -> Result<ActionResult, TailFinError> {
        actions::browser_reply(&self.session, tweet_url, text).await
    }

    /// Get trending topics.
    pub async fn trending(&self, count: usize) -> Result<Vec<Trend>, TailFinError> {
        actions::browser_trending(&self.session, count).await
    }

    /// Download media URLs from a user's media tab.
    pub async fn download(
        &self,
        username: &str,
        count: usize,
    ) -> Result<Vec<MediaItem>, TailFinError> {
        actions::browser_download(&self.session, username, count).await
    }
}

impl TwitterApi for TwitterClient {
    async fn timeline(
        &self,
        kind: TimelineType,
        count: usize,
        cursor: Option<&str>,
    ) -> Result<TimelineResponse, TailFinError> {
        timeline::fetch_timeline(&self.session, kind, cursor, count).await
    }

    async fn search(&self, query: &str, count: usize) -> Result<Vec<Tweet>, TailFinError> {
        search::search_tweets(&self.session, query, count).await
    }
}