retrommo_fetch/
lib.rs

1#![doc = include_str!("../README.md")]
2#![forbid(unsafe_code)]
3
4pub mod entry;
5pub mod error;
6pub mod leaderboard;
7pub mod player;
8pub mod prelude;
9
10use reqwest::StatusCode;
11
12use crate::{entry::LeaderboardEntry, error::Error, leaderboard::Leaderboard, player::Player};
13
14pub(crate) const API_URL: &str = "https://play.retro-mmo.com";
15
16pub type OnlineList = Vec<String>;
17pub type LeaderboardPage = Vec<LeaderboardEntry>;
18
19/// Returns a specific player by their username.
20pub async fn get_player(username: &str) -> Result<Player, Error> {
21    let response = reqwest::get(format!("{API_URL}/users/{username}.json")).await;
22
23    if let Ok(player) = response {
24        let player = player.json::<Player>().await;
25
26        match player {
27            Ok(player) => Ok(player),
28            Err(e) => Err(Error::new(
29                StatusCode::INTERNAL_SERVER_ERROR,
30                Some(format!("Failed to parse player: {e}")),
31            )),
32        }
33    } else {
34        Err(Error::new(StatusCode::INTERNAL_SERVER_ERROR, Some("Failed to get player".to_string())))
35    }
36}
37
38/// Returns a list of online players on the server. Only includes their
39/// username.
40///
41/// If you wish to get all the online players as `Player` structs, use
42/// `get_online_players_full`.
43pub async fn get_online_players() -> Result<OnlineList, Error> {
44    let response = reqwest::get(format!("{API_URL}/players.json")).await;
45
46    if let Ok(players) = response {
47        let players = players.json::<OnlineList>().await;
48
49        match players {
50            Ok(players) => Ok(players),
51            Err(e) => Err(Error::new(
52                StatusCode::INTERNAL_SERVER_ERROR,
53                Some(format!("Failed to parse online players: {e}")),
54            )),
55        }
56    } else {
57        Err(Error::new(
58            StatusCode::INTERNAL_SERVER_ERROR,
59            Some("Failed to get online players".to_string()),
60        ))
61    }
62}
63
64/// Returns a list of Player structs for all the online players on the server.
65/// Note that this requires a separate API request for each player, due to the
66/// official API options available.
67///
68/// Use with caution so you don't slam the servers with unnessecary requests (or
69/// risk your IP being banned).
70pub async fn get_online_players_full() -> Result<Vec<Player>, Error> {
71    let mut players = Vec::new();
72
73    for player in get_online_players().await? {
74        players.push(get_player(&player).await?);
75    }
76
77    Ok(players)
78}
79
80/// Returns the total amount of registered accounts.
81pub async fn get_registered_player_count() -> Result<u64, Error> {
82    let response = reqwest::get(format!("{API_URL}/registered-users.json")).await;
83
84    if let Ok(count) = response {
85        let count = count.json::<u64>().await;
86
87        match count {
88            Ok(count) => Ok(count),
89            Err(e) => Err(Error::new(
90                StatusCode::INTERNAL_SERVER_ERROR,
91                Some(format!("Failed to parse registered user count: {e}")),
92            )),
93        }
94    } else {
95        Err(Error::new(
96            StatusCode::INTERNAL_SERVER_ERROR,
97            Some("Failed to get registered user count".to_string()),
98        ))
99    }
100}
101
102/// Returns a specific page of the leaderboard.
103///
104/// Currently there is no way to get more entries than what is given per page,
105/// so you will have to iterate over a range of pages to get full / partial
106/// leaderboard results. Be careful of slamming the servers with too many
107/// requests.
108pub async fn get_leaderboard_page(page: Option<u32>) -> Result<LeaderboardPage, Error> {
109    let response = if let Some(page) = page {
110        reqwest::get(format!("{API_URL}/leaderboards.json?page={page}")).await
111    } else {
112        reqwest::get(format!("{API_URL}/leaderboards.json")).await
113    };
114
115    if let Ok(leaderboard) = response {
116        let leaderboard = leaderboard.json::<LeaderboardPage>().await;
117
118        match leaderboard {
119            Ok(leaderboard) => Ok(leaderboard),
120            Err(e) => Err(Error::new(
121                StatusCode::INTERNAL_SERVER_ERROR,
122                Some(format!("Failed to parse leaderboard: {e}")),
123            )),
124        }
125    } else {
126        Err(Error::new(
127            StatusCode::INTERNAL_SERVER_ERROR,
128            Some("Failed to get leaderboard".to_string()),
129        ))
130    }
131}
132
133/// Helper method for getting the "front page" of the leaderboard -- or the top
134/// 100 -- to be exact.
135///
136/// This is no different than calling `get_leaderboard_page(Some(1))` yourself.
137pub async fn get_top_players() -> Result<LeaderboardPage, Error> {
138    let results = get_leaderboard_page(Some(1)).await?;
139
140    Ok(results)
141}
142
143/// Returns an iterator over the leaderboard pages, starting at 0. You can
144/// advance the iterator by calling `try_next()` or `next()` on the iterator
145/// returned from this function.
146///
147/// See `Leaderboard` for more information.
148pub fn get_leaderboard() -> Leaderboard {
149    Leaderboard::new()
150}
151
152#[cfg(test)]
153mod tests {
154    use chrono::DateTime;
155
156    use super::*;
157
158    #[tokio::test]
159    async fn test_get_player() {
160        let player = get_player("Gliss").await;
161        assert!(player.is_ok());
162    }
163
164    #[tokio::test]
165    async fn test_get_player_not_found() {
166        let player = get_player("a4iujtoisdjugfoiasjuhroighasoidg").await;
167        assert!(player.is_err());
168    }
169
170    #[tokio::test]
171    async fn test_get_player_eq() {
172        let player = get_player("Gliss").await;
173
174        if let Ok(player) = player {
175            assert_eq!(player.username, "Gliss");
176            assert_eq!(
177                player.registered_at,
178                DateTime::parse_from_rfc3339("2020-11-10T05:05:02.000Z").unwrap()
179            );
180        }
181    }
182
183    #[tokio::test]
184    async fn test_get_players_online() {
185        let players = get_online_players().await;
186        assert!(players.is_ok());
187    }
188
189    #[tokio::test]
190    async fn test_registered_users() {
191        let count = get_registered_player_count().await;
192        assert!(count.is_ok());
193        assert!(count.unwrap() > 0);
194    }
195
196    #[tokio::test]
197    async fn test_leaderboard() {
198        let leaderboard = get_leaderboard_page(None).await;
199        assert!(leaderboard.is_ok());
200        assert!(!leaderboard.unwrap().is_empty());
201    }
202
203    #[tokio::test]
204    async fn test_leaderboard_page() {
205        let leaderboard = get_leaderboard_page(Some(1)).await;
206        assert!(leaderboard.is_ok());
207        assert!(!leaderboard.unwrap().is_empty());
208    }
209}