reverse_engineered_twitter_api/
search.rs1use 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}