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}