roboat/presence/
mod.rs

1use crate::{presence::request_types::UserPresenceResponse, Client, RoboatError};
2use serde::{Deserialize, Serialize};
3mod request_types;
4
5const REGISTER_PRESENCE_API: &str = "https://presence.roblox.com/v1/presence/register-app-presence";
6const USER_PRESENCE_API: &str = "https://presence.roblox.com/v1/presence/users";
7
8/// Presence of user
9#[allow(missing_docs)]
10#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
11pub enum PresenceType {
12    #[default]
13    Offline,
14    Online,
15    InGame,
16    InStudio,
17    Invisible,
18}
19
20impl TryFrom<u8> for PresenceType {
21    type Error = RoboatError;
22
23    fn try_from(v: u8) -> Result<Self, Self::Error> {
24        match v {
25            0 => Ok(Self::Offline),
26            1 => Ok(Self::Online),
27            2 => Ok(Self::InGame),
28            3 => Ok(Self::InStudio),
29            4 => Ok(Self::Invisible),
30            _ => Err(RoboatError::MalformedResponse),
31        }
32    }
33}
34
35impl Client {
36    /// Registers presence on the website (makes you appear to be online). Endpoint called is
37    /// <https://presence.roblox.com/v1/presence/register-app-presence>
38    ///
39    /// # Notes
40    /// * Requires a valid roblosecurity.
41    /// * Will repeat once if the x-csrf-token is invalid.
42    /// * Normally repeats every 15 seconds when viewing the Roblox homepage.
43    ///
44    /// # Return Value Notes
45    /// * Will return `Ok(())` if presence was successfully registered.
46    ///
47    /// # Errors
48    /// * All errors under [Standard Errors](#standard-errors).
49    /// * All errors under [Auth Required Errors](#auth-required-errors).
50    /// * All errors under [X-CSRF-TOKEN Required Errors](#x-csrf-token-required-errors).
51    ///
52    /// # Example
53    ///
54    /// ```no_run
55    /// use roboat::ClientBuilder;
56    ///
57    /// const ROBLOSECURITY: &str = "roblosecurity";
58    ///
59    /// # #[tokio::main]
60    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
61    /// let client = ClientBuilder::new().roblosecurity(ROBLOSECURITY.to_string()).build();
62    ///
63    /// match client.register_presence().await {
64    ///    Ok(_) => println!("Successfully registered presence!"),
65    ///    Err(e) => println!("Error: {}", e),
66    /// }
67    /// # Ok(())
68    /// # }
69    /// ```
70    pub async fn register_presence(&self) -> Result<(), RoboatError> {
71        match self.register_presence_internal().await {
72            Ok(x) => Ok(x),
73            Err(e) => match e {
74                RoboatError::InvalidXcsrf(new_xcsrf) => {
75                    self.set_xcsrf(new_xcsrf).await;
76
77                    self.register_presence_internal().await
78                }
79                _ => Err(e),
80            },
81        }
82    }
83
84    /// Fetch presences of users on roblox like (Offline, Online, In Game, Last Location). Endpoint called is
85    /// <https://presence.roblox.com/v1/presence/users>
86    ///
87    /// # Notes
88    /// * valid roblosecurity is optional for more info about the game the user is in
89    /// * If user is in game and either their joins are turned off or you don't have a valid cookie information like place_id will always be None
90    /// * Can handle up to 50 users at once
91    /// * Doesnt need xcsrf token.
92    /// * This API is ratelimited
93    ///
94    /// # Return Value Notes
95    /// * Will return `Ok(UserPresenceResponse)` if presence was successfully fetched.
96    ///
97    /// # Errors
98    /// * All errors under [Standard Errors](#standard-errors).
99    /// * All errors under [Auth Required Errors](#auth-required-errors).
100    ///
101    /// # Example
102    ///
103    /// ```no_run
104    /// use roboat::ClientBuilder;
105    ///
106    /// const ROBLOSECURITY: &str = "roblosecurity";
107    ///
108    /// # #[tokio::main]
109    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
110    /// let client = ClientBuilder::new().roblosecurity(ROBLOSECURITY.to_string()).build();
111    /// let users = vec![1, 35958674918];
112    /// match client.fetch_user_presence(users).await {
113    ///    Ok(user_statuses) => println!("Successfully registered presence: {:?}", user_statuses),
114    ///    Err(e) => println!("Error: {}", e),
115    /// }
116    /// # Ok(())
117    /// # }
118    /// ```
119    pub async fn fetch_users_presence(
120        &self,
121        users: Vec<u64>,
122    ) -> Result<UserPresenceResponse, RoboatError> {
123        match self.fetch_users_presence_internal(users).await {
124            Ok(x) => Ok(x),
125            Err(e) => Err(e),
126        }
127    }
128}
129
130mod internal {
131    use super::REGISTER_PRESENCE_API;
132    use crate::{
133        presence::{request_types::UserPresenceResponse, USER_PRESENCE_API},
134        Client, RoboatError, XCSRF_HEADER,
135    };
136    use reqwest::header;
137
138    impl Client {
139        pub(super) async fn register_presence_internal(&self) -> Result<(), RoboatError> {
140            let cookie = self.cookie_string()?;
141
142            let json = serde_json::json!({
143                "location": "Home",
144            });
145
146            let request_result = self
147                .reqwest_client
148                .post(REGISTER_PRESENCE_API)
149                .header(header::COOKIE, cookie)
150                .header(XCSRF_HEADER, self.xcsrf().await)
151                .json(&json)
152                .send()
153                .await;
154
155            let _ = Self::validate_request_result(request_result).await?;
156
157            // We don't care about the response, just that it's a status code 200.
158            Ok(())
159        }
160
161        pub(super) async fn fetch_users_presence_internal(
162            &self,
163            users: Vec<u64>,
164        ) -> Result<UserPresenceResponse, RoboatError> {
165            let json = serde_json::json!({
166                "userIds": users,
167            });
168
169            // NOTE: Cookie is optional here
170            let request = self
171                .cookie_string()
172                .map(|cookie| {
173                    self.reqwest_client
174                        .post(USER_PRESENCE_API)
175                        .json(&json)
176                        .header(header::COOKIE, cookie)
177                })
178                .unwrap_or_else(|_| self.reqwest_client.post(USER_PRESENCE_API).json(&json));
179
180            let request_result = request.send().await;
181
182            let response = Self::validate_request_result(request_result).await?;
183            let presense_json = Self::parse_to_raw::<UserPresenceResponse>(response).await?;
184            Ok(presense_json)
185        }
186    }
187}