apex_legends_api/
lib.rs

1use std::time::Duration;
2
3use reqwest::{header::HeaderMap, StatusCode};
4
5pub mod data_types;
6mod http;
7
8/// Default time to wait after a 429 error code
9pub const DEFAULT_RATE: f32 = 3.0;
10
11/// Simple function to standarize error messages given a Status Code
12fn handle_error<T>(status: StatusCode) -> Result<T, String> {
13    // Documentation: https://apexlegendsapi.com/#errors
14    match status {
15        StatusCode::TOO_MANY_REQUESTS => Err(String::from(
16            "Too many requests: wait 1 or 2 seconds please",
17        )),
18        StatusCode::UNAUTHORIZED => Err(String::from(
19            "The API key is incorrect, please contact the bot administrator",
20        )),
21        StatusCode::NOT_FOUND => Err(String::from(
22            "Either apexlegendsapi.com is not available or the user does not exist",
23        )),
24        StatusCode::INTERNAL_SERVER_ERROR => {
25            Err(String::from("There was an internal server error"))
26        }
27        _ => Err(format!("{}", status)),
28    }
29}
30
31/// Given an optional header, return the value of the header if it exists or DEFAULT_RATE
32/// The header is `x-current-rate`
33fn get_rate(header: Option<HeaderMap>) -> f32 {
34    if let Some(h) = header {
35        if let Some(value) = h.get("x-current-rate") {
36            let default = DEFAULT_RATE.to_string();
37            let string_value = value.to_str().unwrap_or(default.as_str());
38
39            return string_value.parse().unwrap_or(DEFAULT_RATE);
40        } else {
41            return DEFAULT_RATE;
42        }
43    } else {
44        return DEFAULT_RATE;
45    }
46}
47
48/// Gets information about a User. This version automatically retries if the API returns code 429 (too many requests).
49/// For the sleep time it reads the `x-current-rate` header or uses the DEFAULT_RATE
50/// See [https://apexlegendsapi.com/#player-statistics](https://apexlegendsapi.com/#player-statistics)
51///
52/// # Arguments
53///
54/// * `username` - The Origin username of the player
55/// * `api_key` - The API key for [https://apexlegendsstatus.com](https://apexlegendsstatus.com)
56/// * `retry` - Wether to retry after a timeout or error out immediately
57///
58/// # Examples
59/// ```
60/// use std::env;
61///
62/// #[tokio::test]
63/// async fn user() {
64///     dotenv::dotenv().expect("Could not load .env file");
65///
66///     let user_name = env::var("USERNAME").expect("Expected key USERNAME");
67///     let api_key = env::var("API_KEY").expect("Expected key API_KEY");
68///
69///     // This example automatically handles the 429 error code (too many requests)
70///     match apex_legends::get_user_retry(String::from(&user_name), &api_key, true).await {
71///        Ok(data) => {
72///            println!(
73///                "You are level {}, and you have {} kills.",
74///                data.global.level, data.stats.br_kills.value
75///            );
76///        }
77///        Err(e) => {
78///            println!("there was an error!: {}", e);
79///        }
80///     }
81/// }
82/// ```
83pub async fn get_user_retry(
84    username: String,
85    api_key: &str,
86    retry: bool,
87) -> Result<data_types::ApexUser, String> {
88    match http::get_request(format!(
89        "https://api.mozambiquehe.re/bridge?version=5&platform=PC&player={}&auth={}",
90        username, api_key
91    ))
92    .await
93    {
94        Ok(resp) => match serde_json::from_str(&resp) {
95            Ok(data) => Ok(data),
96            Err(e) => Err(format!("{}", e)),
97        },
98        Err(e) => {
99            if let Some(code) = e.0.status() {
100                if code == StatusCode::TOO_MANY_REQUESTS && retry {
101                    let time = get_rate(e.1);
102
103                    tokio::time::sleep(Duration::from_secs_f32(time)).await;
104
105                    get_user(username, api_key).await
106                } else {
107                    handle_error::<data_types::ApexUser>(code)
108                }
109            } else {
110                Err(format!("{}", e.0))
111            }
112        }
113    }
114}
115
116/// Gets information about a User. This version does not handle code 429 (too many requests)
117/// See [https://apexlegendsapi.com/#player-statistics](https://apexlegendsapi.com/#player-statistics)
118///
119/// # Arguments
120///
121/// * `username` - The Origin username of the player
122/// * `api_key` - The API key for [https://apexlegendsstatus.com](https://apexlegendsstatus.com)
123///
124/// # Examples
125/// ```
126/// use std::env;
127///
128/// #[tokio::test]
129/// async fn user() {
130///     dotenv::dotenv().expect("Could not load .env file");
131///
132///     let user_name = env::var("USERNAME").expect("Expected key USERNAME");
133///     let api_key = env::var("API_KEY").expect("Expected key API_KEY");
134///
135///     // This example will fail if the API returns the 429 error code (too many requests)
136///     match apex_legends::get_user(String::from(&user_name), &api_key).await {
137///        Ok(data) => {
138///            println!(
139///                "You are level {}, and you have {} kills.",
140///                data.global.level, data.stats.br_kills.value
141///            );
142///        }
143///        Err(e) => {
144///            println!("there was an error!: {}", e);
145///        }
146///     }
147/// }
148/// ```
149pub async fn get_user(username: String, api_key: &str) -> Result<data_types::ApexUser, String> {
150    match http::get_request(format!(
151        "https://api.mozambiquehe.re/bridge?version=5&platform=PC&player={}&auth={}",
152        username, api_key
153    ))
154    .await
155    {
156        Ok(resp) => match serde_json::from_str(&resp) {
157            Ok(data) => Ok(data),
158            Err(e) => Err(format!("{}", e)),
159        },
160        Err(e) => {
161            if let Some(code) = e.0.status() {
162                handle_error::<data_types::ApexUser>(code)
163            } else {
164                Err(format!("{}", e.0))
165            }
166        }
167    }
168}
169
170/// Gets information about the recent games.
171/// You must be whitelisted to use this API. It has a strict limit of 5 uniques players queried per hour.
172/// See [https://apexlegendsapi.com/#match-history](https://apexlegendsapi.com/#match-history)
173///
174/// * `user_id` - The player's UID
175/// * `api_key` - The API key for [https://apexlegendsstatus.com](https://apexlegendsstatus.com)
176pub async fn get_recent_games(
177    user_id: String,
178    api_key: &str,
179) -> Result<Vec<data_types::ApexGame>, String> {
180    match http::get_request(format!(
181        "https://api.mozambiquehe.re/games?auth={}&uid={}",
182        api_key, user_id
183    ))
184    .await
185    {
186        Ok(resp) => match serde_json::from_str(&resp) {
187            Ok(data) => Ok(data),
188            Err(e) => Err(format!("{}", e)),
189        },
190        Err(e) => {
191            if let Some(code) = e.0.status() {
192                handle_error::<Vec<data_types::ApexGame>>(code)
193            } else {
194                Err("There was an error getting your recent matches.".to_string())
195            }
196        }
197    }
198}
199
200/// Returns a player's UID from a given name, but also works with Playstation and Xbox players
201/// See [https://apexlegendsapi.com/#name-to-uid](https://apexlegendsapi.com/#name-to-uid)
202///
203/// ## Warning
204/// This function does not work as intended:
205///    It will not retry because the API returns 200 OK instead of code 429,
206///    so there is no way to check if error 429 has occurred
207///
208/// # Parameters
209/// * `user_id` - The player's UID
210/// * `api_key` - The API key for [https://apexlegendsstatus.com](https://apexlegendsstatus.com)
211/// * `retry` - Wether to retry after a timeout or error out immediately
212pub async fn get_uid_from_username_retry(
213    username: String,
214    api_key: &str,
215    retry: bool,
216) -> Result<data_types::ApexProfile, String> {
217    match http::get_request(format!(
218        "https://api.mozambiquehe.re/nametouid?player={}&platform=PC&auth={}",
219        username, api_key
220    ))
221    .await
222    {
223        Ok(resp) => match serde_json::from_str(&resp) {
224            Ok(data) => Ok(data),
225            Err(e) => match serde_json::from_str::<data_types::ApexError>(&resp) {
226                Ok(err) => Err(err.message),
227                Err(_) => Err(format!("{}", e)),
228            },
229        },
230        Err(e) => {
231            if let Some(code) = e.0.status() {
232                if code == StatusCode::TOO_MANY_REQUESTS && retry {
233                    tokio::time::sleep(Duration::from_secs_f32(get_rate(e.1))).await;
234
235                    get_uid_from_username(username, api_key).await
236                } else {
237                    handle_error::<data_types::ApexProfile>(code)
238                }
239            } else {
240                Err("There was an error getting the user id.".to_string())
241            }
242        }
243    }
244}
245
246/// Returns a player's UID from a given name, but also works with Playstation and Xbox players
247/// See [https://apexlegendsapi.com/#name-to-uid](https://apexlegendsapi.com/#name-to-uid)
248///
249/// # Parameters
250/// * `user_id` - The player's UID
251/// * `api_key` - The API key for [https://apexlegendsstatus.com](https://apexlegendsstatus.com)
252pub async fn get_uid_from_username(
253    username: String,
254    api_key: &str,
255) -> Result<data_types::ApexProfile, String> {
256    match http::get_request(format!(
257        "https://api.mozambiquehe.re/nametouid?player={}&platform=PC&auth={}",
258        username, api_key
259    ))
260    .await
261    {
262        Ok(resp) => match serde_json::from_str(&resp) {
263            Ok(data) => Ok(data),
264            Err(e) => match serde_json::from_str::<data_types::ApexError>(&resp) {
265                Ok(err) => Err(err.message),
266                Err(_) => Err(format!("{}", e)),
267            },
268        },
269        Err(e) => {
270            if let Some(code) = e.0.status() {
271                handle_error::<data_types::ApexProfile>(code)
272            } else {
273                Err("There was an error getting the user id.".to_string())
274            }
275        }
276    }
277}
278
279/// The map rotation API will return the current and next map for Battle Royale and Arenas, for both pubs and ranked modes.
280/// Control map rotation is also available.
281/// See [https://apexlegendsapi.com/#map-rotation](https://apexlegendsapi.com/#map-rotation)
282///
283/// # Parameters
284/// * `api_key` - The API key for [https://apexlegendsstatus.com](https://apexlegendsstatus.com)
285/// * `retry` - Wether to retry after a timeout or error out immediately
286pub async fn get_map_rotation_retry(
287    api_key: &str,
288    retry: bool,
289) -> Result<data_types::ApexMapRotation, String> {
290    match http::get_request(format!(
291        "https://api.mozambiquehe.re/maprotation?version=2&auth={}",
292        api_key
293    ))
294    .await
295    {
296        Ok(resp) => match serde_json::from_str(&resp) {
297            Ok(data) => Ok(data),
298            Err(_) => Err("Unable to deserialize JSON.".to_string()),
299        },
300        Err(e) => {
301            if let Some(code) = e.0.status() {
302                if code == StatusCode::TOO_MANY_REQUESTS && retry {
303                    tokio::time::sleep(Duration::from_secs_f32(get_rate(e.1))).await;
304
305                    get_map_rotation(api_key).await
306                } else {
307                    handle_error::<data_types::ApexMapRotation>(code)
308                }
309            } else {
310                Err("There was an error getting the map rotation.".to_string())
311            }
312        }
313    }
314}
315
316/// The map rotation API will return the current and next map for Battle Royale and Arenas, for both pubs and ranked modes.
317/// Control map rotation is also available.
318/// See [https://apexlegendsapi.com/#map-rotation](https://apexlegendsapi.com/#map-rotation)
319///
320/// # Parameters
321/// * `api_key` - The API key for [https://apexlegendsstatus.com](https://apexlegendsstatus.com)
322pub async fn get_map_rotation(api_key: &str) -> Result<data_types::ApexMapRotation, String> {
323    match http::get_request(format!(
324        "https://api.mozambiquehe.re/maprotation?version=2&auth={}",
325        api_key
326    ))
327    .await
328    {
329        Ok(resp) => match serde_json::from_str(&resp) {
330            Ok(data) => Ok(data),
331            Err(_) => Err("Unable to deserialize JSON.".to_string()),
332        },
333        Err(e) => {
334            if let Some(code) = e.0.status() {
335                handle_error::<data_types::ApexMapRotation>(code)
336            } else {
337                Err("There was an error getting the map rotation.".to_string())
338            }
339        }
340    }
341}