twitter-v2 0.1.8

Twitter API v2
Documentation
use crate::api::TwitterApi;
use crate::api_result::ApiResult;
use crate::authorization::Authorization;
use crate::data::{ReplySettings, Tweet};
use crate::id::{IntoNumericId, IntoStringId, StringId};
use reqwest::Method;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use url::Url;

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
struct DraftTweetGeo {
    pub place_id: StringId,
}

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
struct DraftTweetMedia {
    pub media_ids: Vec<String>,
    pub tagged_user_ids: Vec<String>,
}

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
struct DraftTweetPoll {
    pub options: Vec<String>,
    pub duration_minutes: u64,
}

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
struct DraftTweetReply {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub exclude_reply_user_ids: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub in_reply_to_tweet_id: Option<String>,
}

#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
struct DraftTweet {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub direct_message_deep_link: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub for_super_followers_only: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub geo: Option<DraftTweetGeo>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub media: Option<DraftTweetMedia>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub poll: Option<DraftTweetPoll>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub quote_tweet_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reply: Option<DraftTweetReply>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reply_settings: Option<ReplySettings>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub text: Option<String>,
}

#[derive(Debug)]
pub struct TweetBuilder<A> {
    client: TwitterApi<A>,
    url: Url,
    tweet: DraftTweet,
}

impl<A> TweetBuilder<A>
where
    A: Authorization,
{
    pub(crate) fn new(client: &TwitterApi<A>, url: Url) -> Self {
        Self {
            client: client.clone(),
            url,
            tweet: Default::default(),
        }
    }
    pub fn text(&mut self, text: String) -> &mut Self {
        self.tweet.text = Some(text);
        self
    }
    pub fn direct_message_deep_link(&mut self, direct_message_deep_link: String) -> &mut Self {
        self.tweet.direct_message_deep_link = Some(direct_message_deep_link);
        self
    }
    pub fn for_super_followers_only(&mut self, for_super_followers_only: bool) -> &mut Self {
        self.tweet.for_super_followers_only = Some(for_super_followers_only);
        self
    }
    pub fn place_id(&mut self, place_id: impl IntoStringId) -> &mut Self {
        if let Some(geo) = self.tweet.geo.as_mut() {
            geo.place_id = place_id.into_id();
        } else {
            self.tweet.geo = Some(DraftTweetGeo {
                place_id: place_id.into_id(),
            });
        }
        self
    }
    pub fn add_media(
        &mut self,
        media_ids: impl IntoIterator<Item = impl IntoNumericId>,
        tagged_user_ids: impl IntoIterator<Item = impl IntoNumericId>,
    ) -> &mut Self {
        if let Some(media) = self.tweet.media.as_mut() {
            media
                .media_ids
                .extend(media_ids.into_iter().map(|id| id.to_string()));
            media
                .tagged_user_ids
                .extend(tagged_user_ids.into_iter().map(|id| id.to_string()));
        } else {
            self.tweet.media = Some(DraftTweetMedia {
                media_ids: media_ids.into_iter().map(|id| id.to_string()).collect(),
                tagged_user_ids: tagged_user_ids
                    .into_iter()
                    .map(|id| id.to_string())
                    .collect(),
            });
        }
        self
    }
    pub fn poll(
        &mut self,
        options: impl IntoIterator<Item = impl ToString>,
        duration: Duration,
    ) -> &mut Self {
        self.tweet.poll = Some(DraftTweetPoll {
            options: options
                .into_iter()
                .map(|option| option.to_string())
                .collect::<Vec<_>>(),
            duration_minutes: duration.as_secs() / 60,
        });
        self
    }
    pub fn quote_tweet_id(&mut self, id: impl IntoNumericId) -> &mut Self {
        self.tweet.quote_tweet_id = Some(id.to_string());
        self
    }
    pub fn add_exclude_reply_user_id(&mut self, user_id: impl IntoNumericId) -> &mut Self {
        self.add_exclude_reply_user_ids([user_id])
    }
    pub fn add_exclude_reply_user_ids(
        &mut self,
        user_ids: impl IntoIterator<Item = impl IntoNumericId>,
    ) -> &mut Self {
        let mut user_ids = user_ids
            .into_iter()
            .map(|id| id.to_string())
            .collect::<Vec<_>>();
        if let Some(reply) = self.tweet.reply.as_mut() {
            if let Some(exclude_reply_user_ids) = reply.exclude_reply_user_ids.as_mut() {
                exclude_reply_user_ids.append(&mut user_ids)
            } else {
                reply.exclude_reply_user_ids = Some(user_ids);
            }
        } else {
            self.tweet.reply = Some(DraftTweetReply {
                exclude_reply_user_ids: Some(user_ids),
                in_reply_to_tweet_id: None,
            });
        }
        self
    }
    pub fn in_reply_to_tweet_id(&mut self, tweet_id: impl IntoNumericId) -> &mut Self {
        if let Some(reply) = self.tweet.reply.as_mut() {
            reply.in_reply_to_tweet_id = Some(tweet_id.to_string());
        } else {
            self.tweet.reply = Some(DraftTweetReply {
                exclude_reply_user_ids: None,
                in_reply_to_tweet_id: Some(tweet_id.to_string()),
            });
        }
        self
    }
    pub fn reply_settings(&mut self, reply_settings: ReplySettings) -> &mut Self {
        self.tweet.reply_settings = Some(reply_settings);
        self
    }
    pub async fn send(&self) -> ApiResult<A, Tweet, ()> {
        self.client
            .send(
                self.client
                    .request(Method::POST, self.url.clone())
                    .json(&self.tweet),
            )
            .await
    }
}

impl<A> Clone for TweetBuilder<A> {
    fn clone(&self) -> Self {
        Self {
            client: self.client.clone(),
            url: self.url.clone(),
            tweet: self.tweet.clone(),
        }
    }
}