twitter_v2/requests/
tweet.rs

1use crate::api::TwitterApi;
2use crate::api_result::ApiResult;
3use crate::authorization::Authorization;
4use crate::data::{ReplySettings, Tweet};
5use crate::id::{IntoNumericId, IntoStringId, StringId};
6use reqwest::Method;
7use serde::{Deserialize, Serialize};
8use std::time::Duration;
9use url::Url;
10
11#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
12struct DraftTweetGeo {
13    pub place_id: StringId,
14}
15
16#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
17struct DraftTweetMedia {
18    pub media_ids: Vec<String>,
19    pub tagged_user_ids: Vec<String>,
20}
21
22#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
23struct DraftTweetPoll {
24    pub options: Vec<String>,
25    pub duration_minutes: u64,
26}
27
28#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
29struct DraftTweetReply {
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub exclude_reply_user_ids: Option<Vec<String>>,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub in_reply_to_tweet_id: Option<String>,
34}
35
36#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
37struct DraftTweet {
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub direct_message_deep_link: Option<String>,
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub for_super_followers_only: Option<bool>,
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub geo: Option<DraftTweetGeo>,
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub media: Option<DraftTweetMedia>,
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub poll: Option<DraftTweetPoll>,
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub quote_tweet_id: Option<String>,
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub reply: Option<DraftTweetReply>,
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub reply_settings: Option<ReplySettings>,
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub text: Option<String>,
56}
57
58#[derive(Debug)]
59pub struct TweetBuilder<A> {
60    client: TwitterApi<A>,
61    url: Url,
62    tweet: DraftTweet,
63}
64
65impl<A> TweetBuilder<A>
66where
67    A: Authorization,
68{
69    pub(crate) fn new(client: &TwitterApi<A>, url: Url) -> Self {
70        Self {
71            client: client.clone(),
72            url,
73            tweet: Default::default(),
74        }
75    }
76    pub fn text(&mut self, text: String) -> &mut Self {
77        self.tweet.text = Some(text);
78        self
79    }
80    pub fn direct_message_deep_link(&mut self, direct_message_deep_link: String) -> &mut Self {
81        self.tweet.direct_message_deep_link = Some(direct_message_deep_link);
82        self
83    }
84    pub fn for_super_followers_only(&mut self, for_super_followers_only: bool) -> &mut Self {
85        self.tweet.for_super_followers_only = Some(for_super_followers_only);
86        self
87    }
88    pub fn place_id(&mut self, place_id: impl IntoStringId) -> &mut Self {
89        if let Some(geo) = self.tweet.geo.as_mut() {
90            geo.place_id = place_id.into_id();
91        } else {
92            self.tweet.geo = Some(DraftTweetGeo {
93                place_id: place_id.into_id(),
94            });
95        }
96        self
97    }
98    pub fn add_media(
99        &mut self,
100        media_ids: impl IntoIterator<Item = impl IntoNumericId>,
101        tagged_user_ids: impl IntoIterator<Item = impl IntoNumericId>,
102    ) -> &mut Self {
103        if let Some(media) = self.tweet.media.as_mut() {
104            media
105                .media_ids
106                .extend(media_ids.into_iter().map(|id| id.to_string()));
107            media
108                .tagged_user_ids
109                .extend(tagged_user_ids.into_iter().map(|id| id.to_string()));
110        } else {
111            self.tweet.media = Some(DraftTweetMedia {
112                media_ids: media_ids.into_iter().map(|id| id.to_string()).collect(),
113                tagged_user_ids: tagged_user_ids
114                    .into_iter()
115                    .map(|id| id.to_string())
116                    .collect(),
117            });
118        }
119        self
120    }
121    pub fn poll(
122        &mut self,
123        options: impl IntoIterator<Item = impl ToString>,
124        duration: Duration,
125    ) -> &mut Self {
126        self.tweet.poll = Some(DraftTweetPoll {
127            options: options
128                .into_iter()
129                .map(|option| option.to_string())
130                .collect::<Vec<_>>(),
131            duration_minutes: duration.as_secs() / 60,
132        });
133        self
134    }
135    pub fn quote_tweet_id(&mut self, id: impl IntoNumericId) -> &mut Self {
136        self.tweet.quote_tweet_id = Some(id.to_string());
137        self
138    }
139    pub fn add_exclude_reply_user_id(&mut self, user_id: impl IntoNumericId) -> &mut Self {
140        self.add_exclude_reply_user_ids([user_id])
141    }
142    pub fn add_exclude_reply_user_ids(
143        &mut self,
144        user_ids: impl IntoIterator<Item = impl IntoNumericId>,
145    ) -> &mut Self {
146        let mut user_ids = user_ids
147            .into_iter()
148            .map(|id| id.to_string())
149            .collect::<Vec<_>>();
150        if let Some(reply) = self.tweet.reply.as_mut() {
151            if let Some(exclude_reply_user_ids) = reply.exclude_reply_user_ids.as_mut() {
152                exclude_reply_user_ids.append(&mut user_ids)
153            } else {
154                reply.exclude_reply_user_ids = Some(user_ids);
155            }
156        } else {
157            self.tweet.reply = Some(DraftTweetReply {
158                exclude_reply_user_ids: Some(user_ids),
159                in_reply_to_tweet_id: None,
160            });
161        }
162        self
163    }
164    pub fn in_reply_to_tweet_id(&mut self, tweet_id: impl IntoNumericId) -> &mut Self {
165        if let Some(reply) = self.tweet.reply.as_mut() {
166            reply.in_reply_to_tweet_id = Some(tweet_id.to_string());
167        } else {
168            self.tweet.reply = Some(DraftTweetReply {
169                exclude_reply_user_ids: None,
170                in_reply_to_tweet_id: Some(tweet_id.to_string()),
171            });
172        }
173        self
174    }
175    pub fn reply_settings(&mut self, reply_settings: ReplySettings) -> &mut Self {
176        self.tweet.reply_settings = Some(reply_settings);
177        self
178    }
179    pub async fn send(&self) -> ApiResult<A, Tweet, ()> {
180        self.client
181            .send(
182                self.client
183                    .request(Method::POST, self.url.clone())
184                    .json(&self.tweet),
185            )
186            .await
187    }
188}
189
190impl<A> Clone for TweetBuilder<A> {
191    fn clone(&self) -> Self {
192        Self {
193            client: self.client.clone(),
194            url: self.url.clone(),
195            tweet: self.tweet.clone(),
196        }
197    }
198}