reverse_engineered_twitter_api/
search.rs

1use crate::BEARER_TOKEN;
2use serde_json::json;
3use std::cmp;
4
5use super::{
6    types::{parse_legacy_tweet, Data, Tweet},
7    ReAPI,
8};
9
10const SEARCH_URL: &str = "https://twitter.com/i/api/graphql/nK1dw4oV3k4w5TdtcAdSww/SearchTimeline";
11
12impl ReAPI {
13    pub async fn search(
14        &self,
15        query: &str,
16        limit: u8,
17        cursor: &str,
18    ) -> std::result::Result<Data, reqwest::Error> {
19        let limit = cmp::min(50u8, limit);
20
21        let mut variables = json!(
22            {
23                "rawQuery":     query.to_string(),
24                "count":        limit,
25                "querySource":  "typed_query",
26                "product":      "Top"
27            }
28        );
29        let features = json!(
30            {
31                "rweb_lists_timeline_redesign_enabled":                                    true,
32                "responsive_web_graphql_exclude_directive_enabled":                        true,
33                "verified_phone_label_enabled":                                            false,
34                "creator_subscriptions_tweet_preview_api_enabled":                         true,
35                "responsive_web_graphql_timeline_navigation_enabled":                      true,
36                "responsive_web_graphql_skip_user_profile_image_extensions_enabled":       false,
37                "tweetypie_unmention_optimization_enabled":                                true,
38                "responsive_web_edit_tweet_api_enabled":                                   true,
39                "graphql_is_translatable_rweb_tweet_is_translatable_enabled":              true,
40                "view_counts_everywhere_api_enabled":                                      true,
41                "longform_notetweets_consumption_enabled":                                 true,
42                "responsive_web_twitter_article_tweet_consumption_enabled":                false,
43                "tweet_awards_web_tipping_enabled":                                        false,
44                "freedom_of_speech_not_reach_fetch_enabled":                               true,
45                "standardized_nudges_misinfo":                                             true,
46                "tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled": true,
47                "longform_notetweets_rich_text_read_enabled":                              true,
48                "longform_notetweets_inline_media_enabled":                                true,
49                "responsive_web_media_download_video_enabled":                             false,
50                "responsive_web_enhance_cards_enabled":                                    false,
51            }
52        );
53        let field_toggles = json!(
54            {
55                "withArticleRichContentState": false
56            }
57        );
58        if cursor.ne("") {
59            variables["cursor"] = cursor.to_string().into();
60        }
61        variables["product"] = "Latest".into();
62        let q = [
63            ("variables", variables.to_string()),
64            ("features", features.to_string()),
65            ("fieldToggles", field_toggles.to_string()),
66        ];
67        let req = self
68            .client
69            .get(SEARCH_URL)
70            .header("Authorization", format!("Bearer {}", BEARER_TOKEN))
71            .header("X-CSRF-Token", self.csrf_token.to_owned())
72            .query(&q)
73            .build()
74            .unwrap();
75        let text = self
76            .client
77            .execute(req)
78            .await
79            .unwrap()
80            .text()
81            .await
82            .unwrap();
83
84        println!("text:{}", text);
85        let res: Data = serde_json::from_str(&text).unwrap();
86        return Ok(res);
87    }
88
89    pub async fn search_tweets(
90        &self,
91        query: &str,
92        limit: u8,
93        cursor: &str,
94    ) -> Result<(Vec<Tweet>, String), reqwest::Error> {
95        let mut tweets: Vec<Tweet> = vec![];
96        
97        let search_result = self.search(query, limit, cursor).await;
98        let mut cursor = String::from("");
99        match search_result {
100            Ok(res) => {
101                let instructions = res
102                    .data
103                    .search_by_raw_query
104                    .search_timeline
105                    .timeline
106                    .instructions
107                    .unwrap();
108                for item in instructions {
109                    if item.instruction_type.ne("TimelineAddEntries")
110                        && item.instruction_type.ne("TimelineReplaceEntry")
111                    {
112                        continue;
113                    }
114                    if item.entry.is_some() {
115                        let entry = item.entry.unwrap();
116                        let cursor_type = entry.content.cursor_type.unwrap_or("".to_string());
117                        if cursor_type.eq("Bottom") {
118                            if entry.content.value.is_some() {
119                                cursor = entry.content.value.unwrap();
120                                continue;
121                            }
122                        }
123                    }
124                    for entry in item.entries {
125                        if entry.content.item_content.is_none() {
126                            continue;
127                        }
128                        let item = entry.content.item_content.unwrap();
129                        if item.tweet_display_type.eq("Tweet") {
130                            let core = item.tweet_results.result.core;
131                            if core.is_none() {
132                                continue;
133                            }
134                            let u = core.unwrap().user_results.result.legacy.unwrap();
135                            let t = item.tweet_results.result.legacy;
136                            if let Some(tweet) = parse_legacy_tweet(&u, &t) {
137                                tweets.push(tweet)
138                            }
139                        } else if entry
140                            .content
141                            .cursor_type
142                            .unwrap_or("".to_string())
143                            .eq("Bottom")
144                        {
145                            cursor = entry.content.value.unwrap();
146                        }
147                    }
148                }
149                Ok((tweets, cursor))
150            }
151            Err(e) => Err(e),
152        }
153    }
154}