reverse_engineered_twitter_api/
types.rs

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// Mention type.
161#[derive(Debug, Deserialize)]
162pub struct Mention {
163    pub id: String,
164    pub username: String,
165    pub name: String,
166}
167
168// Photo type.
169#[derive(Debug, Deserialize)]
170pub struct Photo {
171    pub id: String,
172    pub url: Option<String>,
173}
174
175// Video type.
176#[derive(Debug, Deserialize)]
177pub struct Video {
178    pub id: String,
179    pub preview: String,
180    pub url: Option<String>,
181}
182
183// GIF type.
184#[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}