use std::sync::Arc;
use super::super::analytics_loop::{AnalyticsError, EngagementFetcher, ProfileFetcher};
use super::super::loop_helpers::{
ContentLoopError, LoopError, LoopTweet, MentionsFetcher, ThreadPoster, TweetSearcher,
};
use super::super::posting_queue::PostExecutor;
use super::super::target_loop::{TargetTweetFetcher, TargetUserManager};
use super::helpers::{
search_response_to_loop_tweets, toolkit_to_analytics_error, toolkit_to_content_error,
toolkit_to_loop_error,
};
use crate::x_api::XApiClient;
pub struct XApiSearchAdapter {
client: Arc<dyn XApiClient>,
}
impl XApiSearchAdapter {
pub fn new(client: Arc<dyn XApiClient>) -> Self {
Self { client }
}
}
#[async_trait::async_trait]
impl TweetSearcher for XApiSearchAdapter {
async fn search_tweets(&self, query: &str) -> Result<Vec<LoopTweet>, LoopError> {
let response = crate::toolkit::read::search_tweets(&*self.client, query, 20, None, None)
.await
.map_err(toolkit_to_loop_error)?;
Ok(search_response_to_loop_tweets(response))
}
}
pub struct XApiMentionsAdapter {
client: Arc<dyn XApiClient>,
own_user_id: String,
}
impl XApiMentionsAdapter {
pub fn new(client: Arc<dyn XApiClient>, own_user_id: String) -> Self {
Self {
client,
own_user_id,
}
}
}
#[async_trait::async_trait]
impl MentionsFetcher for XApiMentionsAdapter {
async fn get_mentions(&self, since_id: Option<&str>) -> Result<Vec<LoopTweet>, LoopError> {
let response =
crate::toolkit::read::get_mentions(&*self.client, &self.own_user_id, since_id, None)
.await
.map_err(toolkit_to_loop_error)?;
Ok(search_response_to_loop_tweets(response))
}
}
pub struct XApiTargetAdapter {
client: Arc<dyn XApiClient>,
}
impl XApiTargetAdapter {
pub fn new(client: Arc<dyn XApiClient>) -> Self {
Self { client }
}
}
#[async_trait::async_trait]
impl TargetTweetFetcher for XApiTargetAdapter {
async fn fetch_user_tweets(&self, user_id: &str) -> Result<Vec<LoopTweet>, LoopError> {
let response = crate::toolkit::read::get_user_tweets(&*self.client, user_id, 10, None)
.await
.map_err(toolkit_to_loop_error)?;
Ok(search_response_to_loop_tweets(response))
}
}
#[async_trait::async_trait]
impl TargetUserManager for XApiTargetAdapter {
async fn lookup_user(&self, username: &str) -> Result<(String, String), LoopError> {
let user = crate::toolkit::read::get_user_by_username(&*self.client, username)
.await
.map_err(toolkit_to_loop_error)?;
Ok((user.id, user.username))
}
}
pub struct XApiProfileAdapter {
client: Arc<dyn XApiClient>,
}
impl XApiProfileAdapter {
pub fn new(client: Arc<dyn XApiClient>) -> Self {
Self { client }
}
}
#[async_trait::async_trait]
impl ProfileFetcher for XApiProfileAdapter {
async fn get_profile_metrics(
&self,
) -> Result<super::super::analytics_loop::ProfileMetrics, AnalyticsError> {
let user = crate::toolkit::read::get_me(&*self.client)
.await
.map_err(toolkit_to_analytics_error)?;
Ok(super::super::analytics_loop::ProfileMetrics {
follower_count: user.public_metrics.followers_count as i64,
following_count: user.public_metrics.following_count as i64,
tweet_count: user.public_metrics.tweet_count as i64,
})
}
}
#[async_trait::async_trait]
impl EngagementFetcher for XApiProfileAdapter {
async fn get_tweet_metrics(
&self,
tweet_id: &str,
) -> Result<super::super::analytics_loop::TweetMetrics, AnalyticsError> {
let tweet = crate::toolkit::read::get_tweet(&*self.client, tweet_id)
.await
.map_err(toolkit_to_analytics_error)?;
Ok(super::super::analytics_loop::TweetMetrics {
likes: tweet.public_metrics.like_count as i64,
retweets: tweet.public_metrics.retweet_count as i64,
replies: tweet.public_metrics.reply_count as i64,
impressions: tweet.public_metrics.impression_count as i64,
})
}
}
pub struct XApiPostExecutorAdapter {
client: Arc<dyn XApiClient>,
}
impl XApiPostExecutorAdapter {
pub fn new(client: Arc<dyn XApiClient>) -> Self {
Self { client }
}
}
#[async_trait::async_trait]
impl PostExecutor for XApiPostExecutorAdapter {
async fn execute_reply(
&self,
tweet_id: &str,
content: &str,
media_ids: &[String],
) -> Result<String, String> {
let media = if media_ids.is_empty() {
None
} else {
Some(media_ids)
};
crate::toolkit::write::reply_to_tweet(&*self.client, content, tweet_id, media)
.await
.map(|posted| posted.id)
.map_err(|e| e.to_string())
}
async fn execute_tweet(&self, content: &str, media_ids: &[String]) -> Result<String, String> {
let media = if media_ids.is_empty() {
None
} else {
Some(media_ids)
};
crate::toolkit::write::post_tweet(&*self.client, content, media)
.await
.map(|posted| posted.id)
.map_err(|e| e.to_string())
}
}
pub struct XApiThreadPosterAdapter {
client: Arc<dyn XApiClient>,
}
impl XApiThreadPosterAdapter {
pub fn new(client: Arc<dyn XApiClient>) -> Self {
Self { client }
}
}
#[async_trait::async_trait]
impl ThreadPoster for XApiThreadPosterAdapter {
async fn post_tweet(&self, content: &str) -> Result<String, ContentLoopError> {
crate::toolkit::write::post_tweet(&*self.client, content, None)
.await
.map(|posted| posted.id)
.map_err(toolkit_to_content_error)
}
async fn reply_to_tweet(
&self,
in_reply_to: &str,
content: &str,
) -> Result<String, ContentLoopError> {
crate::toolkit::write::reply_to_tweet(&*self.client, content, in_reply_to, None)
.await
.map(|posted| posted.id)
.map_err(toolkit_to_content_error)
}
}