Skip to main content

twapi_v2/api/
get_2_users_id_timelines_reverse_chronological.rs

1use crate::fields::{
2    media_fields::MediaFields, place_fields::PlaceFields, poll_fields::PollFields,
3    tweet_fields::TweetFields, user_fields::UserFields,
4};
5use crate::responses::{errors::Errors, includes::Includes, meta::Meta, tweets::Tweets};
6use crate::{
7    api::{Authentication, TwapiOptions, execute_twitter, make_url},
8    error::Error,
9    headers::Headers,
10};
11use chrono::prelude::*;
12use itertools::Itertools;
13use reqwest::RequestBuilder;
14use serde::{Deserialize, Serialize};
15use std::collections::HashSet;
16
17const URL: &str = "/2/users/:id/timelines/reverse_chronological";
18
19#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Clone)]
20#[derive(Default)]
21pub enum Exclude {
22    #[serde(rename = "retweets")]
23    #[default]
24    Retweets,
25    #[serde(rename = "replies")]
26    Replies,
27}
28
29impl Exclude {
30    pub fn all() -> HashSet<Self> {
31        let mut result = HashSet::new();
32        result.insert(Self::Retweets);
33        result.insert(Self::Replies);
34        result
35    }
36}
37
38impl std::fmt::Display for Exclude {
39    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
40        match self {
41            Self::Retweets => write!(f, "retweets"),
42            Self::Replies => write!(f, "replies"),
43        }
44    }
45}
46
47
48#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Clone)]
49#[derive(Default)]
50pub enum Expansions {
51    #[serde(rename = "article.cover_media")]
52    #[default]
53    ArticleCoverMedia,
54    #[serde(rename = "article.media_entities")]
55    ArticleMediaEntities,
56    #[serde(rename = "attachments.media_keys")]
57    AttachmentsMediaKeys,
58    #[serde(rename = "attachments.media_source_tweet")]
59    AttachmentsMediaSourceTweet,
60    #[serde(rename = "attachments.poll_ids")]
61    AttachmentsPollIds,
62    #[serde(rename = "author_id")]
63    AuthorId,
64    #[serde(rename = "edit_history_tweet_ids")]
65    EditHistoryTweetIds,
66    #[serde(rename = "entities.mentions.username")]
67    EntitiesMentionsUsername,
68    #[serde(rename = "geo.place_id")]
69    GeoPlaceId,
70    #[serde(rename = "in_reply_to_user_id")]
71    InReplyToUserId,
72    #[serde(rename = "entities.note.mentions.username")]
73    EntitiesNoteMentionsUsername,
74    #[serde(rename = "referenced_tweets.id")]
75    ReferencedTweetsId,
76    #[serde(rename = "referenced_tweets.id.author_id")]
77    ReferencedTweetsIdAuthorId,
78}
79
80impl Expansions {
81    pub fn all() -> HashSet<Self> {
82        let mut result = HashSet::new();
83        result.insert(Self::ArticleCoverMedia);
84        result.insert(Self::ArticleMediaEntities);
85        result.insert(Self::AttachmentsMediaKeys);
86        result.insert(Self::AttachmentsMediaSourceTweet);
87        result.insert(Self::AttachmentsPollIds);
88        result.insert(Self::AuthorId);
89        result.insert(Self::EditHistoryTweetIds);
90        result.insert(Self::EntitiesMentionsUsername);
91        result.insert(Self::GeoPlaceId);
92        result.insert(Self::InReplyToUserId);
93        result.insert(Self::EntitiesNoteMentionsUsername);
94        result.insert(Self::ReferencedTweetsId);
95        result.insert(Self::ReferencedTweetsIdAuthorId);
96        result
97    }
98}
99
100impl std::fmt::Display for Expansions {
101    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
102        match self {
103            Self::ArticleCoverMedia => write!(f, "article.cover_media"),
104            Self::ArticleMediaEntities => write!(f, "article.media_entities"),
105            Self::AttachmentsMediaKeys => write!(f, "attachments.media_keys"),
106            Self::AttachmentsMediaSourceTweet => write!(f, "attachments.media_source_tweet"),
107            Self::AttachmentsPollIds => write!(f, "attachments.poll_ids"),
108            Self::AuthorId => write!(f, "author_id"),
109            Self::EditHistoryTweetIds => write!(f, "edit_history_tweet_ids"),
110            Self::EntitiesMentionsUsername => write!(f, "entities.mentions.username"),
111            Self::GeoPlaceId => write!(f, "geo.place_id"),
112            Self::InReplyToUserId => write!(f, "in_reply_to_user_id"),
113            Self::EntitiesNoteMentionsUsername => write!(f, "entities.note.mentions.username"),
114            Self::ReferencedTweetsId => write!(f, "referenced_tweets.id"),
115            Self::ReferencedTweetsIdAuthorId => write!(f, "referenced_tweets.id.author_id"),
116        }
117    }
118}
119
120
121#[derive(Debug, Clone, Default)]
122pub struct Api {
123    id: String,
124    end_time: Option<DateTime<Utc>>,
125    exclude: Option<HashSet<Exclude>>,
126    expansions: Option<HashSet<Expansions>>,
127    max_results: Option<usize>,
128    media_fields: Option<HashSet<MediaFields>>,
129    pagination_token: Option<String>,
130    place_fields: Option<HashSet<PlaceFields>>,
131    poll_fields: Option<HashSet<PollFields>>,
132    since_id: Option<String>,
133    start_time: Option<DateTime<Utc>>,
134    tweet_fields: Option<HashSet<TweetFields>>,
135    until_id: Option<String>,
136    user_fields: Option<HashSet<UserFields>>,
137    twapi_options: Option<TwapiOptions>,
138}
139
140impl Api {
141    pub fn new(id: &str) -> Self {
142        Self {
143            id: id.to_owned(),
144            ..Default::default()
145        }
146    }
147
148    pub fn all(id: &str) -> Self {
149        Self {
150            id: id.to_owned(),
151            exclude: Some(Exclude::all()),
152            expansions: Some(Expansions::all()),
153            media_fields: Some(MediaFields::all()),
154            place_fields: Some(PlaceFields::all()),
155            poll_fields: Some(PollFields::all()),
156            tweet_fields: Some(TweetFields::organic()),
157            user_fields: Some(UserFields::all()),
158            max_results: Some(100),
159            ..Default::default()
160        }
161    }
162
163    pub fn open(id: &str) -> Self {
164        Self {
165            id: id.to_owned(),
166            exclude: Some(Exclude::all()),
167            expansions: Some(Expansions::all()),
168            media_fields: Some(MediaFields::open()),
169            place_fields: Some(PlaceFields::all()),
170            poll_fields: Some(PollFields::all()),
171            tweet_fields: Some(TweetFields::open()),
172            user_fields: Some(UserFields::all()),
173            max_results: Some(100),
174            ..Default::default()
175        }
176    }
177
178    pub fn end_time(mut self, value: DateTime<Utc>) -> Self {
179        self.end_time = Some(value);
180        self
181    }
182
183    pub fn exclude(mut self, value: HashSet<Exclude>) -> Self {
184        self.exclude = Some(value);
185        self
186    }
187
188    pub fn expansions(mut self, value: HashSet<Expansions>) -> Self {
189        self.expansions = Some(value);
190        self
191    }
192
193    pub fn max_results(mut self, value: usize) -> Self {
194        self.max_results = Some(value);
195        self
196    }
197
198    pub fn media_fields(mut self, value: HashSet<MediaFields>) -> Self {
199        self.media_fields = Some(value);
200        self
201    }
202
203    pub fn pagination_token(mut self, value: &str) -> Self {
204        self.pagination_token = Some(value.to_owned());
205        self
206    }
207
208    pub fn place_fields(mut self, value: HashSet<PlaceFields>) -> Self {
209        self.place_fields = Some(value);
210        self
211    }
212
213    pub fn poll_fields(mut self, value: HashSet<PollFields>) -> Self {
214        self.poll_fields = Some(value);
215        self
216    }
217
218    pub fn since_id(mut self, value: &str) -> Self {
219        self.since_id = Some(value.to_owned());
220        self
221    }
222
223    pub fn start_time(mut self, value: DateTime<Utc>) -> Self {
224        self.start_time = Some(value);
225        self
226    }
227
228    pub fn tweet_fields(mut self, value: HashSet<TweetFields>) -> Self {
229        self.tweet_fields = Some(value);
230        self
231    }
232
233    pub fn until_id(mut self, value: &str) -> Self {
234        self.until_id = Some(value.to_owned());
235        self
236    }
237
238    pub fn user_fields(mut self, value: HashSet<UserFields>) -> Self {
239        self.user_fields = Some(value);
240        self
241    }
242
243    pub fn twapi_options(mut self, value: TwapiOptions) -> Self {
244        self.twapi_options = Some(value);
245        self
246    }
247
248    pub fn build(&self, authentication: &impl Authentication) -> RequestBuilder {
249        let mut query_parameters = vec![];
250        if let Some(end_time) = self.end_time.as_ref() {
251            query_parameters.push((
252                "end_time",
253                end_time.format("%Y-%m-%dT%H:%M:%SZ").to_string(),
254            ));
255        }
256        if let Some(exclude) = self.exclude.as_ref() {
257            query_parameters.push(("exclude", exclude.iter().join(",")));
258        }
259        if let Some(expansions) = self.expansions.as_ref() {
260            query_parameters.push(("expansions", expansions.iter().join(",")));
261        }
262        if let Some(max_results) = self.max_results.as_ref() {
263            query_parameters.push(("max_results", max_results.to_string()));
264        }
265        if let Some(media_fields) = self.media_fields.as_ref() {
266            query_parameters.push(("media.fields", media_fields.iter().join(",")));
267        }
268        if let Some(pagination_token) = self.pagination_token.as_ref() {
269            query_parameters.push(("pagination_token", pagination_token.to_string()));
270        }
271        if let Some(place_fields) = self.place_fields.as_ref() {
272            query_parameters.push(("place.fields", place_fields.iter().join(",")));
273        }
274        if let Some(poll_fields) = self.poll_fields.as_ref() {
275            query_parameters.push(("poll.fields", poll_fields.iter().join(",")));
276        }
277        if let Some(since_id) = self.since_id.as_ref() {
278            query_parameters.push(("since_id", since_id.to_string()));
279        }
280        if let Some(start_time) = self.start_time.as_ref() {
281            query_parameters.push((
282                "start_time",
283                start_time.format("%Y-%m-%dT%H:%M:%SZ").to_string(),
284            ));
285        }
286        if let Some(tweet_fields) = self.tweet_fields.as_ref() {
287            query_parameters.push(("tweet.fields", tweet_fields.iter().join(",")));
288        }
289        if let Some(until_id) = self.until_id.as_ref() {
290            query_parameters.push(("until_id", until_id.to_string()));
291        }
292        if let Some(user_fields) = self.user_fields.as_ref() {
293            query_parameters.push(("user.fields", user_fields.iter().join(",")));
294        }
295        let client = reqwest::Client::new();
296        let url = make_url(&self.twapi_options, &URL.replace(":id", &self.id));
297        let builder = client.get(&url).query(&query_parameters);
298        authentication.execute(
299            builder,
300            "GET",
301            &url,
302            &query_parameters
303                .iter()
304                .map(|it| (it.0, it.1.as_str()))
305                .collect::<Vec<_>>(),
306        )
307    }
308
309    pub async fn execute(
310        &self,
311        authentication: &impl Authentication,
312    ) -> Result<(Response, Headers), Error> {
313        execute_twitter(|| self.build(authentication), &self.twapi_options).await
314    }
315}
316
317#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)]
318pub struct Response {
319    #[serde(skip_serializing_if = "Option::is_none")]
320    pub data: Option<Vec<Tweets>>,
321    #[serde(skip_serializing_if = "Option::is_none")]
322    pub errors: Option<Vec<Errors>>,
323    #[serde(skip_serializing_if = "Option::is_none")]
324    pub includes: Option<Includes>,
325    #[serde(skip_serializing_if = "Option::is_none")]
326    pub meta: Option<Meta>,
327    #[serde(flatten)]
328    pub extra: std::collections::HashMap<String, serde_json::Value>,
329}
330
331impl Response {
332    pub fn is_empty_extra(&self) -> bool {
333        let res = self.extra.is_empty()
334            && self
335                .data
336                .as_ref()
337                .map(|it| it.iter().all(|item| item.is_empty_extra()))
338                .unwrap_or(true)
339            && self
340                .errors
341                .as_ref()
342                .map(|it| it.iter().all(|item| item.is_empty_extra()))
343                .unwrap_or(true)
344            && self
345                .includes
346                .as_ref()
347                .map(|it| it.is_empty_extra())
348                .unwrap_or(true)
349            && self
350                .meta
351                .as_ref()
352                .map(|it| it.is_empty_extra())
353                .unwrap_or(true);
354        if !res {
355            println!("Response {:?}", self.extra);
356        }
357        res
358    }
359}