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}