1pub mod auth;
8pub mod client;
9pub mod local_mode;
10pub mod media;
11pub mod scopes;
12pub mod tier;
13pub mod types;
14
15pub use client::XApiHttpClient;
16pub use local_mode::session::ScraperSession;
17pub use local_mode::LocalModeXClient;
18pub use types::*;
19
20use std::path::Path;
21use std::sync::Arc;
22
23use crate::config::XApiConfig;
24use crate::error::XApiError;
25
26pub async fn create_local_client(config: &XApiConfig) -> Option<Arc<dyn XApiClient>> {
34 create_local_client_with_data_dir(config, None).await
35}
36
37pub async fn create_local_client_with_data_dir(
39 config: &XApiConfig,
40 data_dir: Option<&Path>,
41) -> Option<Arc<dyn XApiClient>> {
42 if config.provider_backend == "scraper" {
43 let client = match data_dir {
44 Some(dir) => LocalModeXClient::with_session(config.scraper_allow_mutations, dir).await,
45 None => LocalModeXClient::new(config.scraper_allow_mutations),
46 };
47 Some(Arc::new(client))
48 } else {
49 None
50 }
51}
52
53#[async_trait::async_trait]
58pub trait XApiClient: Send + Sync {
59 async fn search_tweets(
64 &self,
65 query: &str,
66 max_results: u32,
67 since_id: Option<&str>,
68 pagination_token: Option<&str>,
69 ) -> Result<SearchResponse, XApiError>;
70
71 async fn get_mentions(
75 &self,
76 user_id: &str,
77 since_id: Option<&str>,
78 pagination_token: Option<&str>,
79 ) -> Result<MentionResponse, XApiError>;
80
81 async fn post_tweet(&self, text: &str) -> Result<PostedTweet, XApiError>;
83
84 async fn reply_to_tweet(
86 &self,
87 text: &str,
88 in_reply_to_id: &str,
89 ) -> Result<PostedTweet, XApiError>;
90
91 async fn get_tweet(&self, tweet_id: &str) -> Result<Tweet, XApiError>;
93
94 async fn get_me(&self) -> Result<User, XApiError>;
96
97 async fn get_user_tweets(
99 &self,
100 user_id: &str,
101 max_results: u32,
102 pagination_token: Option<&str>,
103 ) -> Result<SearchResponse, XApiError>;
104
105 async fn get_user_by_username(&self, username: &str) -> Result<User, XApiError>;
107
108 async fn upload_media(
112 &self,
113 _data: &[u8],
114 _media_type: MediaType,
115 ) -> Result<MediaId, XApiError> {
116 Err(XApiError::MediaUploadError {
117 message: "upload_media not implemented".to_string(),
118 })
119 }
120
121 async fn post_tweet_with_media(
125 &self,
126 text: &str,
127 _media_ids: &[String],
128 ) -> Result<PostedTweet, XApiError> {
129 self.post_tweet(text).await
130 }
131
132 async fn reply_to_tweet_with_media(
136 &self,
137 text: &str,
138 in_reply_to_id: &str,
139 _media_ids: &[String],
140 ) -> Result<PostedTweet, XApiError> {
141 self.reply_to_tweet(text, in_reply_to_id).await
142 }
143
144 async fn quote_tweet(
146 &self,
147 _text: &str,
148 _quoted_tweet_id: &str,
149 ) -> Result<PostedTweet, XApiError> {
150 Err(XApiError::ApiError {
151 status: 0,
152 message: "not implemented".to_string(),
153 })
154 }
155
156 async fn like_tweet(&self, _user_id: &str, _tweet_id: &str) -> Result<bool, XApiError> {
158 Err(XApiError::ApiError {
159 status: 0,
160 message: "not implemented".to_string(),
161 })
162 }
163
164 async fn follow_user(&self, _user_id: &str, _target_user_id: &str) -> Result<bool, XApiError> {
166 Err(XApiError::ApiError {
167 status: 0,
168 message: "not implemented".to_string(),
169 })
170 }
171
172 async fn unfollow_user(
174 &self,
175 _user_id: &str,
176 _target_user_id: &str,
177 ) -> Result<bool, XApiError> {
178 Err(XApiError::ApiError {
179 status: 0,
180 message: "not implemented".to_string(),
181 })
182 }
183
184 async fn retweet(&self, _user_id: &str, _tweet_id: &str) -> Result<bool, XApiError> {
186 Err(XApiError::ApiError {
187 status: 0,
188 message: "not implemented".to_string(),
189 })
190 }
191
192 async fn unretweet(&self, _user_id: &str, _tweet_id: &str) -> Result<bool, XApiError> {
194 Err(XApiError::ApiError {
195 status: 0,
196 message: "not implemented".to_string(),
197 })
198 }
199
200 async fn delete_tweet(&self, _tweet_id: &str) -> Result<bool, XApiError> {
202 Err(XApiError::ApiError {
203 status: 0,
204 message: "not implemented".to_string(),
205 })
206 }
207
208 async fn get_home_timeline(
210 &self,
211 _user_id: &str,
212 _max_results: u32,
213 _pagination_token: Option<&str>,
214 ) -> Result<SearchResponse, XApiError> {
215 Err(XApiError::ApiError {
216 status: 0,
217 message: "not implemented".to_string(),
218 })
219 }
220
221 async fn unlike_tweet(&self, _user_id: &str, _tweet_id: &str) -> Result<bool, XApiError> {
223 Err(XApiError::ApiError {
224 status: 0,
225 message: "not implemented".to_string(),
226 })
227 }
228
229 async fn get_followers(
231 &self,
232 _user_id: &str,
233 _max_results: u32,
234 _pagination_token: Option<&str>,
235 ) -> Result<UsersResponse, XApiError> {
236 Err(XApiError::ApiError {
237 status: 0,
238 message: "not implemented".to_string(),
239 })
240 }
241
242 async fn get_following(
244 &self,
245 _user_id: &str,
246 _max_results: u32,
247 _pagination_token: Option<&str>,
248 ) -> Result<UsersResponse, XApiError> {
249 Err(XApiError::ApiError {
250 status: 0,
251 message: "not implemented".to_string(),
252 })
253 }
254
255 async fn get_user_by_id(&self, _user_id: &str) -> Result<User, XApiError> {
257 Err(XApiError::ApiError {
258 status: 0,
259 message: "not implemented".to_string(),
260 })
261 }
262
263 async fn get_liked_tweets(
265 &self,
266 _user_id: &str,
267 _max_results: u32,
268 _pagination_token: Option<&str>,
269 ) -> Result<SearchResponse, XApiError> {
270 Err(XApiError::ApiError {
271 status: 0,
272 message: "not implemented".to_string(),
273 })
274 }
275
276 async fn get_bookmarks(
278 &self,
279 _user_id: &str,
280 _max_results: u32,
281 _pagination_token: Option<&str>,
282 ) -> Result<SearchResponse, XApiError> {
283 Err(XApiError::ApiError {
284 status: 0,
285 message: "not implemented".to_string(),
286 })
287 }
288
289 async fn bookmark_tweet(&self, _user_id: &str, _tweet_id: &str) -> Result<bool, XApiError> {
291 Err(XApiError::ApiError {
292 status: 0,
293 message: "not implemented".to_string(),
294 })
295 }
296
297 async fn unbookmark_tweet(&self, _user_id: &str, _tweet_id: &str) -> Result<bool, XApiError> {
299 Err(XApiError::ApiError {
300 status: 0,
301 message: "not implemented".to_string(),
302 })
303 }
304
305 async fn get_users_by_ids(&self, _user_ids: &[&str]) -> Result<UsersResponse, XApiError> {
307 Err(XApiError::ApiError {
308 status: 0,
309 message: "not implemented".to_string(),
310 })
311 }
312
313 async fn get_tweet_liking_users(
315 &self,
316 _tweet_id: &str,
317 _max_results: u32,
318 _pagination_token: Option<&str>,
319 ) -> Result<UsersResponse, XApiError> {
320 Err(XApiError::ApiError {
321 status: 0,
322 message: "not implemented".to_string(),
323 })
324 }
325
326 async fn raw_request(
334 &self,
335 _method: &str,
336 _url: &str,
337 _query: Option<&[(String, String)]>,
338 _body: Option<&str>,
339 _headers: Option<&[(String, String)]>,
340 ) -> Result<RawApiResponse, XApiError> {
341 Err(XApiError::ApiError {
342 status: 0,
343 message: "raw_request not implemented".to_string(),
344 })
345 }
346}