1use chrono::{DateTime, FixedOffset};
2use serde::Deserialize;
3
4#[derive(Debug, Deserialize)]
5pub struct ExpandedURL {
6 pub expanded_url: String,
7}
8
9#[derive(Debug, Deserialize)]
10pub struct Url {
11 pub urls: Vec<ExpandedURL>,
12}
13
14#[derive(Debug, Deserialize)]
15pub struct EntitiesOfLegacyUser {
16 pub url: Option<Url>,
17}
18
19#[derive(Debug, Deserialize)]
20pub struct LegacyUser {
21 pub created_at: String,
22 pub description: String,
23 pub entities: EntitiesOfLegacyUser,
24 pub favourites_count: i128,
25 pub followers_count: i128,
26 pub friends_count: i128,
27 pub id_str: Option<String>,
28 pub listed_count: i128,
29 pub name: String,
30 pub location: String,
31 pub pinned_tweet_ids_str: Vec<String>,
32 pub profile_banner_url: String,
33 pub profile_image_url_https: String,
34 pub protected: Option<bool>,
35 pub screen_name: String,
36 pub statuses_count: i128,
37 pub verified: bool,
38}
39
40#[derive(Debug, Deserialize)]
41pub struct UserResult {
42 pub is_blue_verified: bool,
43 pub legacy: Option<LegacyUser>,
44}
45
46#[derive(Debug, Deserialize)]
47pub struct UserResults {
48 pub result: UserResult,
49}
50
51#[derive(Debug, Deserialize)]
52pub struct ResultCore {
53 pub user_results: UserResults,
54}
55
56#[derive(Debug, Deserialize)]
57pub struct Views {
58 pub count: String,
59}
60
61#[derive(Debug, Deserialize)]
62pub struct NoteTweetResult {
63 pub text: String,
64}
65
66#[derive(Debug, Deserialize)]
67pub struct NoteTweetResults {
68 pub result: NoteTweetResult,
69}
70
71#[derive(Debug, Deserialize)]
72pub struct NoteTweet {
73 pub note_tweet_results: NoteTweetResults,
74}
75
76#[derive(Debug, Deserialize)]
77pub struct QuotedStatusResult {
78 pub result: Box<Result>,
79}
80
81#[derive(Debug, Deserialize)]
82pub struct Result {
83 pub __typename: String,
84 pub core: Option<ResultCore>,
85 pub views: Views,
86 pub note_tweet: Option<NoteTweet>,
87 pub quoted_status_result: Option<QuotedStatusResult>,
88 pub legacy: LegacyTweet,
89}
90
91#[derive(Debug, Deserialize)]
92pub struct TweetRresults {
93 pub result: Result,
94}
95
96#[derive(Debug, Deserialize)]
97pub struct ItemContent {
98 #[serde(rename(serialize = "tweetDisplayType", deserialize = "tweetDisplayType"))]
99 pub tweet_display_type: String,
100 pub tweet_results: TweetRresults,
101 #[serde(rename(serialize = "userDisplayType", deserialize = "userDisplayType"))]
102 pub user_display_yype: Option<String>,
103 pub user_results: Option<TweetRresults>,
104}
105
106#[derive(Debug, Deserialize)]
107pub struct Item {
108 #[serde(rename(serialize = "itemContent", deserialize = "itemContent"))]
109 pub item_content: ItemContent,
110}
111
112#[derive(Debug, Deserialize)]
113pub struct EntryContent {
114 #[serde(rename(serialize = "cursorType", deserialize = "cursorType"))]
115 pub cursor_type: Option<String>,
116 pub value: Option<String>,
117 pub items: Option<Vec<Item>>,
118 #[serde(rename(serialize = "itemContent", deserialize = "itemContent"))]
119 pub item_content: Option<ItemContent>,
120}
121
122#[derive(Debug, Deserialize)]
123pub struct Entry {
124 pub content: EntryContent,
125}
126
127#[derive(Debug, Deserialize)]
128pub struct Instruction {
129 #[serde(rename(serialize = "type", deserialize = "type"))]
130 pub instruction_type: String,
131 pub entries: Vec<Entry>,
132 pub entry: Option<Entry>,
133}
134
135#[derive(Debug, Deserialize)]
136pub struct Timeline {
137 pub instructions: Option<Vec<Instruction>>,
138}
139
140#[derive(Debug, Deserialize)]
141pub struct TimelineResult {
142 pub timeline: Timeline,
143}
144
145#[derive(Debug, Deserialize)]
146pub struct SearchTimeline {
147 pub search_timeline: TimelineResult,
148}
149
150#[derive(Debug, Deserialize)]
151pub struct SearchByRawQuery {
152 pub search_by_raw_query: SearchTimeline,
153}
154
155#[derive(Debug, Deserialize)]
156pub struct Data {
157 pub data: SearchByRawQuery,
158}
159
160#[derive(Debug, Deserialize)]
162pub struct Mention {
163 pub id: String,
164 pub username: String,
165 pub name: String,
166}
167
168#[derive(Debug, Deserialize)]
170pub struct Photo {
171 pub id: String,
172 pub url: Option<String>,
173}
174
175#[derive(Debug, Deserialize)]
177pub struct Video {
178 pub id: String,
179 pub preview: String,
180 pub url: Option<String>,
181}
182
183#[derive(Debug, Deserialize)]
185pub struct GIF {
186 pub id: String,
187 pub preview: String,
188 pub url: Option<String>,
189}
190
191#[derive(Debug, Clone, Deserialize)]
192pub struct BoundingBox {
193 pub _type: String,
194 pub coordinates: Vec<Vec<Vec<f64>>>,
195}
196
197#[derive(Debug, Clone, Deserialize)]
198pub struct Place {
199 pub id: String,
200 pub place_type: String,
201 pub name: String,
202 pub full_name: String,
203 pub country_code: String,
204 pub country: String,
205 pub bounding_box: BoundingBox,
206}
207
208pub struct Tweet {
209 pub converation_id: String,
210 pub gifs: Vec<GIF>,
211 pub hash_tags: Vec<String>,
212 pub html: String,
213 pub id: String,
214 pub in_reply_to_status: Option<Box<Tweet>>,
215 pub in_reply_to_status_id: Option<String>,
216 pub is_quoted: bool,
217 pub is_pin: bool,
218 pub is_reply: bool,
219 pub is_retweet: bool,
220 pub is_self_thread: bool,
221 pub likes: i128,
222 pub name: String,
223 pub mentions: Vec<Mention>,
224 pub permanent_url: String,
225 pub photos: Vec<Photo>,
226 pub place: Option<Place>,
227 pub quoted_status: Option<Box<Tweet>>,
228 pub quoted_status_id: Option<String>,
229 pub replies: i128,
230 pub retweets: i128,
231 pub retweeted_status: Option<Box<Tweet>>,
232 pub retweeted_status_id: String,
233 pub text: String,
234 pub thread: Vec<Box<Tweet>>,
235 pub time_parsed: DateTime<FixedOffset>,
236 pub timestamp: i64,
237 pub urls: Vec<String>,
238 pub user_id: String,
239 pub username: String,
240 pub videos: Vec<Video>,
241 pub views: i128,
242 pub sensitive_content: bool,
243}
244
245#[derive(Debug, Deserialize)]
246pub struct HashTag {
247 pub text: String,
248}
249
250#[derive(Debug, Deserialize)]
251pub struct TweetMedia {
252 pub id_str: String,
253 pub media_url_https: String,
254 #[serde(rename(serialize = "type", deserialize = "type"))]
255 pub media_type: String,
256 pub url: Option<String>,
257 pub ext_sensitive_media_warning: Option<ExtSensitiveMediaWarning>,
258 pub video_info: Option<VideoInfo>,
259}
260
261#[derive(Debug, Deserialize)]
262pub struct Urls {
263 pub expanded_url: String,
264 pub url: Option<String>,
265}
266
267#[derive(Debug, Deserialize)]
268pub struct UserMentions {
269 pub id_str: String,
270 pub name: String,
271 pub screen_name: String,
272}
273
274#[derive(Debug, Deserialize)]
275pub struct Entities {
276 pub hashtags: Vec<HashTag>,
277 pub media: Option<Vec<TweetMedia>>,
278 pub urls: Vec<Urls>,
279 pub user_mentions: Vec<UserMentions>,
280}
281
282#[derive(Debug, Deserialize)]
283pub struct ExtSensitiveMediaWarning {
284 pub adult_content: bool,
285 pub graphic_violence: bool,
286 pub other: bool,
287}
288
289#[derive(Debug, Deserialize)]
290pub struct Variant {
291 pub bitrate: Option<i64>,
292 pub url: String,
293}
294
295#[derive(Debug, Deserialize)]
296pub struct VideoInfo {
297 pub variants: Vec<Variant>,
298}
299
300#[derive(Debug, Deserialize)]
301pub struct ExtendedMedia {
302 pub id_str: String,
303 pub media_url_https: String,
304 pub ext_sensitive_media_warning: Option<ExtSensitiveMediaWarning>,
305 #[serde(rename(serialize = "type", deserialize = "type"))]
306 pub ext_type: String,
307 pub url: Option<String>,
308 pub video_info: VideoInfo,
309}
310
311#[derive(Debug, Deserialize)]
312pub struct ExtendedEntities {
313 pub media: Vec<ExtendedMedia>,
314}
315
316#[derive(Debug, Deserialize)]
317pub struct RetweetedStatusResult {
318 pub result: Option<Box<Result>>,
319}
320
321#[derive(Debug, Deserialize)]
322pub struct SelfThread {
323 pub id_str: String,
324}
325
326#[derive(Debug, Deserialize)]
327pub struct ExtViews {
328 pub state: String,
329 pub count: String,
330}
331
332#[derive(Debug, Deserialize)]
333pub struct LegacyTweet {
334 pub conversation_id_str: String,
335 pub created_at: String,
336 pub favorite_count: i128,
337 pub full_text: String,
338 pub entities: Entities,
339 pub extended_entities: Option<ExtendedEntities>,
340 pub id_str: String,
341 pub in_reply_to_status_id_str: Option<String>,
342 pub place: Option<Place>,
343 pub reply_count: i128,
344 pub retweet_count: i128,
345 pub retweeted_status_id_str: Option<String>,
346 pub retweeted_status_result: Option<RetweetedStatusResult>,
347 pub quoted_status_id_str: Option<String>,
348 pub self_thread: Option<SelfThread>,
349 pub time: Option<String>,
350 pub user_id_str: String,
351 pub ext_views: Option<ExtViews>,
352}
353
354pub fn parse_legacy_tweet(u: &LegacyUser, t: &LegacyTweet) -> Option<Tweet> {
355 let tweet_id = &t.id_str;
356 if tweet_id.eq("") {
357 return Option::None;
358 }
359 let id = t.id_str.to_owned();
360 let name = u.name.to_owned();
361 let likes = t.favorite_count;
362 let user_id = t.user_id_str.to_owned();
363 let username = u.screen_name.to_owned();
364 let converation_id = t.conversation_id_str.to_owned();
365 let permanent_url = format!("https://twitter.com/{}/status/{}", username, tweet_id);
366 let replies = t.reply_count;
367 let retweets = t.retweet_count;
368 let text = t.full_text.to_owned();
369 let is_quoted = t
370 .quoted_status_id_str
371 .as_ref()
372 .unwrap_or(&"".to_string())
373 .ne("");
374 let quoted_status_id = t.quoted_status_id_str.to_owned();
375 let is_reply = t
376 .in_reply_to_status_id_str
377 .as_ref()
378 .unwrap_or(&"".to_string())
379 .ne("");
380 let in_reply_to_status_id = t.in_reply_to_status_id_str.to_owned();
381 let is_retweet = (t.in_reply_to_status_id_str.is_some()
382 && t.in_reply_to_status_id_str
383 .as_ref()
384 .unwrap_or(&"".to_string())
385 .ne(""))
386 || (t.retweeted_status_result.is_some()
387 && t.retweeted_status_result.as_ref().unwrap().result.is_some());
388 let retweeted_status_id = t
389 .retweeted_status_id_str
390 .as_ref()
391 .unwrap_or(&String::from(""))
392 .to_string();
393 let mut views = 0i128;
394
395 if t.ext_views.is_some() {
396 views = t.ext_views.as_ref().unwrap().count.parse::<i128>().unwrap();
397 }
398
399 let hash_tags: Vec<String> = t
400 .entities
401 .hashtags
402 .iter()
403 .map(|i| i.text.to_owned())
404 .collect();
405 let mentions: Vec<Mention> = t
406 .entities
407 .user_mentions
408 .iter()
409 .map(|i| Mention {
410 id: i.id_str.to_owned(),
411 username: i.screen_name.to_owned(),
412 name: i.name.to_owned(),
413 })
414 .collect();
415 let mut photos: Vec<Photo> = vec![];
416 let mut videos: Vec<Video> = vec![];
417 let mut gifs: Vec<GIF> = vec![];
418 let mut sensitive_content = false;
419 for i in t.entities.media.as_ref().unwrap_or(&vec![]).iter() {
420 match i.media_type.as_str() {
421 "photo" => photos.push(Photo {
422 id: i.id_str.to_owned(),
423 url: Some(i.media_url_https.to_owned()),
424 }),
425 "animated_gif" | "video" => {
426 let mut url = "";
427 let mut max_bitrate = 0;
428 if i.video_info.is_some() {
429 let video_info = i.video_info.as_ref().unwrap();
430 for variant in &video_info.variants {
431 let bitrate = variant.bitrate.unwrap_or(0);
432 if bitrate > max_bitrate {
433 max_bitrate = bitrate;
434 url = variant.url.strip_suffix("?tag=10").unwrap();
435 }
436 }
437 }
438 if i.media_type.as_str().eq("video") {
439 videos.push(Video {
440 id: i.id_str.to_owned(),
441 preview: i.media_url_https.to_owned(),
442 url: Some(url.to_string()),
443 })
444 } else {
445 gifs.push(GIF {
446 id: i.id_str.to_owned(),
447 preview: i.media_url_https.to_owned(),
448 url: Some(url.to_string()),
449 })
450 }
451 }
452 _ => {}
453 }
454 if !sensitive_content && i.ext_sensitive_media_warning.is_some() {
455 let warning = i.ext_sensitive_media_warning.as_ref().unwrap();
456 sensitive_content = warning.adult_content || warning.graphic_violence || warning.other;
457 }
458 }
459 let mut urls: Vec<String> = vec![];
460 for i in t.entities.urls.iter() {
461 urls.push(i.expanded_url.to_owned());
462 }
463
464 let mut time_parsed = chrono::offset::Utc::now().fixed_offset();
465 if t.time.is_some() {
466 time_parsed = DateTime::parse_from_rfc2822(&t.time.as_ref().unwrap()).unwrap();
467 }
468 let timestamp = time_parsed.timestamp();
469 let html = t.full_text.to_owned();
470
471 let mut retweeted_status: Option<Box<Tweet>> = Option::None;
472 if t.retweeted_status_result.is_some() {
473 let core = &t
474 .retweeted_status_result
475 .as_ref()
476 .unwrap()
477 .result
478 .as_ref()
479 .unwrap()
480 .core;
481 if let Some(core) = core {
482 let legacy_u = core.user_results.result.legacy.as_ref().unwrap();
483 let legacy_t = &t
484 .retweeted_status_result
485 .as_ref()
486 .unwrap()
487 .result
488 .as_ref()
489 .unwrap()
490 .legacy;
491 retweeted_status = Some(Box::new(parse_legacy_tweet(&legacy_u, &legacy_t).unwrap()));
492 }
493 }
494 let tweet = Tweet {
495 converation_id,
496 id,
497 likes,
498 name,
499 permanent_url,
500 replies,
501 retweets,
502 text,
503 user_id,
504 username,
505 place: t.place.clone(),
506 is_pin: false,
507 is_self_thread: false,
508 thread: vec![],
509 is_quoted,
510 quoted_status_id,
511 quoted_status: Option::None,
512 is_reply,
513 in_reply_to_status_id,
514 in_reply_to_status: Option::None,
515 is_retweet,
516 retweeted_status_id,
517 retweeted_status,
518 views,
519 hash_tags,
520 mentions,
521 gifs,
522 videos,
523 photos,
524 urls,
525 time_parsed,
526 timestamp,
527 sensitive_content,
528 html,
529 };
530 Some(tweet)
531}