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