agent_twitter_client/
search.rs1use crate::api::requests::request_api;
2use crate::error::Result;
3use crate::timeline::search::{
4 parse_search_timeline_tweets, parse_search_timeline_users, SearchTimeline,
5};
6use crate::timeline::v1::{QueryProfilesResponse, QueryTweetsResponse};
7use reqwest::Method;
8use serde_json::json;
9use crate::api::client::TwitterClient;
10#[derive(Debug, Clone, Copy)]
11pub enum SearchMode {
12 Top,
13 Latest,
14 Photos,
15 Videos,
16 Users,
17}
18
19pub async fn fetch_search_tweets(
20 client: &TwitterClient,
21 query: &str,
22 max_tweets: i32,
23 search_mode: SearchMode,
24 cursor: Option<String>,
25) -> Result<QueryTweetsResponse> {
26 let timeline = get_search_timeline(client, query, max_tweets, search_mode, cursor).await?;
27
28 Ok(parse_search_timeline_tweets(&timeline))
29}
30
31pub async fn search_profiles(
32 client: &TwitterClient,
33 query: &str,
34 max_profiles: i32,
35 cursor: Option<String>,
36) -> Result<QueryProfilesResponse> {
37 let timeline =
38 get_search_timeline(client, query, max_profiles, SearchMode::Users, cursor).await?;
39
40 Ok(parse_search_timeline_users(&timeline))
41}
42
43async fn get_search_timeline(
44 client: &TwitterClient,
45 query: &str,
46 max_items: i32,
47 search_mode: SearchMode,
48 _cursor: Option<String>,
49) -> Result<SearchTimeline> {
50
51 let max_items = if max_items > 50 { 50 } else { max_items };
52
53 let mut variables = json!({
54 "rawQuery": query,
55 "count": max_items,
56 "querySource": "typed_query",
57 "product": "Top"
58 });
59
60 match search_mode {
62 SearchMode::Latest => {
63 variables["product"] = json!("Latest");
64 }
65 SearchMode::Photos => {
66 variables["product"] = json!("Photos");
67 }
68 SearchMode::Videos => {
69 variables["product"] = json!("Videos");
70 }
71 SearchMode::Users => {
72 variables["product"] = json!("People");
73 }
74 _ => {}
75 }
76
77 let features = json!({
78 "longform_notetweets_inline_media_enabled": true,
79 "responsive_web_enhance_cards_enabled": false,
80 "responsive_web_media_download_video_enabled": false,
81 "responsive_web_twitter_article_tweet_consumption_enabled": false,
82 "tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled": true,
83 "interactive_text_enabled": false,
84 "responsive_web_text_conversations_enabled": false,
85 "vibe_api_enabled": false,
86 "rweb_lists_timeline_redesign_enabled": true,
87 "responsive_web_graphql_exclude_directive_enabled": true,
88 "verified_phone_label_enabled": false,
89 "creator_subscriptions_tweet_preview_api_enabled": true,
90 "responsive_web_graphql_timeline_navigation_enabled": true,
91 "responsive_web_graphql_skip_user_profile_image_extensions_enabled": false,
92 "tweetypie_unmention_optimization_enabled": true,
93 "responsive_web_edit_tweet_api_enabled": true,
94 "graphql_is_translatable_rweb_tweet_is_translatable_enabled": true,
95 "view_counts_everywhere_api_enabled": true,
96 "longform_notetweets_consumption_enabled": true,
97 "tweet_awards_web_tipping_enabled": false,
98 "freedom_of_speech_not_reach_fetch_enabled": true,
99 "standardized_nudges_misinfo": true,
100 "longform_notetweets_rich_text_read_enabled": true,
101 "responsive_web_enhance_cards_enabled": false,
102 "subscriptions_verification_info_enabled": true,
103 "subscriptions_verification_info_reason_enabled": true,
104 "subscriptions_verification_info_verified_since_enabled": true,
105 "super_follow_badge_privacy_enabled": false,
106 "super_follow_exclusive_tweet_notifications_enabled": false,
107 "super_follow_tweet_api_enabled": false,
108 "super_follow_user_api_enabled": false,
109 "android_graphql_skip_api_media_color_palette": false,
110 "creator_subscriptions_subscription_count_enabled": false,
111 "blue_business_profile_image_shape_enabled": false,
112 "unified_cards_ad_metadata_container_dynamic_card_content_query_enabled": false
113 });
114
115 let field_toggles = json!({
116 "withArticleRichContentState": false
117 });
118
119 let params = [("variables", serde_json::to_string(&variables)?),
120 ("features", serde_json::to_string(&features)?),
121 ("fieldToggles", serde_json::to_string(&field_toggles)?)];
122
123 let query_string = params
124 .iter()
125 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
126 .collect::<Vec<_>>()
127 .join("&");
128
129 let mut headers = reqwest::header::HeaderMap::new();
130 client.auth.install_headers(&mut headers).await?;
131
132 let url = format!(
133 "https://api.twitter.com/graphql/gkjsKepM6gl_HmFWoWKfgg/SearchTimeline?{}",
134 query_string
135 );
136
137 let (response, _) = request_api::<SearchTimeline>(&client.client, &url, headers, Method::GET, None).await?;
138
139 Ok(response)
140}