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}