Skip to main content

roblox_api/api/users/
v1.rs

1use crate::{DateTime, Gender, Paging, endpoint};
2
3pub const URL: &str = "https://users.roblox.com/v1";
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
8#[serde(rename_all = "camelCase")]
9pub struct ClientDetails {
10    pub id: u64,
11    pub name: String,
12    pub display_name: String,
13}
14
15#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
16#[serde(rename_all = "camelCase")]
17pub struct ClientAppLaunchInfo {
18    pub id: u64,
19    pub name: String,
20    pub display_name: String,
21    pub is_premium: bool,
22    pub country_code: String,
23    pub age_bracket: u64,
24}
25
26#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
27#[serde(rename_all = "camelCase")]
28pub struct UserDetails {
29    pub id: u64,
30    pub name: String,
31    pub display_name: String,
32    pub description: String,
33    pub created: DateTime,
34    #[serde(rename = "isBanned")]
35    pub is_terminated: bool,
36    #[serde(rename = "hasVerifiedBadge")]
37    pub is_verified: bool,
38}
39
40#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
41#[serde(rename_all = "camelCase")]
42pub struct UserById {
43    pub id: u64,
44    pub name: String,
45    pub display_name: String,
46    #[serde(rename = "hasVerifiedBadge")]
47    pub is_verified: bool,
48}
49
50#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
51#[serde(rename_all = "camelCase")]
52pub struct UserByName {
53    pub id: u64,
54    pub name: String,
55    #[serde(rename = "requestedUsername")]
56    pub requested_name: String,
57    pub display_name: String,
58    #[serde(rename = "hasVerifiedBadge")]
59    pub is_verified: bool,
60}
61
62#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
63#[serde(rename_all = "camelCase")]
64pub struct UserByKeyword {
65    pub id: u64,
66    pub name: String,
67    pub display_name: String,
68    #[serde(rename = "previousUsernames")]
69    pub previous_names: Vec<String>,
70    #[serde(rename = "hasVerifiedBadge")]
71    pub is_verified: bool,
72}
73
74#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
75pub struct NameHistory {
76    pub names: Vec<String>,
77    pub next_cursor: Option<String>,
78    pub previous_cursor: Option<String>,
79}
80
81#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
82pub struct UserSearchByKeyword {
83    #[serde(rename = "data")]
84    pub users: Vec<UserByKeyword>,
85    #[serde(rename = "nextPageCursor")]
86    pub next_cursor: Option<String>,
87    #[serde(rename = "previousPageCursor")]
88    pub previous_cursor: Option<String>,
89}
90
91endpoint! {
92    user_details(id: u64) -> UserDetails {
93        GET "{URL}/users/{id}";
94    }
95
96    user_username_history(id: u64, paging: Paging<'_>) -> NameHistory {
97        GET "{URL}/users/{id}/username-history";
98        paging_query { paging, limit = 10 }
99        types {
100            Username {
101                name: String,
102            }
103            Response {
104                data: Vec<Username>,
105                next_cursor("nextPageCursor"): Option<String>,
106                previous_cursor("previousPageCursor"): Option<String>,
107            }
108        }
109        map |r: Response| {
110            let names = r.data.iter().map(|x| x.name.clone()).collect();
111            NameHistory {
112                names,
113                next_cursor: r.next_cursor,
114                previous_cursor: r.previous_cursor,
115            }
116        }
117    }
118
119    users_by_id(ids: &[u64], exclude_terminated: bool) -> Vec<UserById> {
120        POST "{URL}/users";
121        types {
122            Request<'a> {
123                ids("userIds"): &'a [u64],
124                exclude_terminated("excludeBannedUsers"): bool,
125            }
126            Response {
127                data: Vec<UserById>,
128            }
129        }
130        body_serialize { Request { ids, exclude_terminated } }
131        map |r: Response| r.data
132    }
133
134    users_by_name(names: &[&str], exclude_terminated: bool) -> Vec<UserByName> {
135        POST "{URL}/usernames/users";
136        types {
137            Request<'a> {
138                names("usernames"): &'a [&'a str],
139                exclude_terminated("excludeBannedUsers"): bool,
140            }
141            Response {
142                data: Vec<UserByName>,
143            }
144        }
145        body_serialize { Request { names, exclude_terminated } }
146        map |r: Response| r.data
147    }
148
149    // this api seems to be dysfunctional, try using search::omni instead
150    search_by_keyword(keyword: &str,session_id: Option<&str>,paging: Paging<'_>) -> UserSearchByKeyword {
151        GET "{URL}/users/search";
152        paging_query { paging, limit = 10 }
153        prelude {
154            let session_id_str = session_id.map(|s| s.to_string()).unwrap_or_default();
155        }
156        query {
157            "keyword" => keyword,
158            "sessionId" => &session_id_str,
159        }
160    }
161
162    authenticated_details() -> ClientDetails {
163        GET "{URL}/users/authenticated";
164    }
165
166    authenticated_age_bracket() -> u64 {
167        GET "{URL}/users/authenticated/age-bracket";
168        types {
169            Response {
170                age_bracket("ageBracket"): u64,
171            }
172        }
173        map |r: Response| r.age_bracket
174    }
175
176    authenticated_country_code() -> String {
177        GET "{URL}/users/authenticated/country-code";
178        types {
179            Response {
180                country_code("countryCode"): String,
181            }
182        }
183        map |r: Response| r.country_code
184    }
185
186    authenticated_roles() -> Vec<String> {
187        GET "{URL}/users/authenticated/roles";
188        types {
189            Response {
190                roles: Vec<String>,
191            }
192        }
193        map |r: Response| r.roles
194    }
195
196    authenticated_app_launch_info() -> ClientAppLaunchInfo {
197        GET "{URL}/users/authenticated/app-launch-info";
198    }
199
200    birthdate() -> DateTime {
201        GET "{URL}/birthdate";
202        types {
203            Response {
204                day("birthDay"): u8,
205                month("birthMonth"): u8,
206                year("birthYear"): i32,
207            }
208        }
209        map |b: Response| DateTime::from_ymd(b.year, b.month, b.day)
210    }
211
212    // According to documentation there's supposed to be a password,
213    // yet the roblox website doesn't use it..
214    set_birthdate(birthdate: DateTime) -> () {
215        POST "{URL}/birthdate";
216        types {
217            Request {
218                day("birthDay"): u8,
219                month("birthMonth"): u8,
220                year("birthYear"): i32,
221            }
222        }
223        prelude {
224            let day = birthdate.day();
225            let month = birthdate.month();
226            let year = birthdate.year();
227        }
228        body_serialize { Request { day, month, year } }
229    }
230
231    description() -> String {
232        GET "{URL}/description";
233        types {
234            Response {
235                value("description"): String,
236            }
237        }
238        map |r: Response| r.value
239    }
240
241    set_description(description: &str) -> () {
242        POST "{URL}/description";
243        types {
244            Request<'a> {
245                value("description"): &'a str,
246            }
247        }
248        body_serialize { Request { value: description } }
249    }
250
251    gender() -> Gender {
252        GET "{URL}/gender";
253        types {
254            Response {
255                value("gender"): u8,
256            }
257        }
258        map |g: Response| Gender::from_repr(g.value).expect("failed to parse gender")
259    }
260
261    set_gender(gender: Gender) -> () {
262        POST "{URL}/gender";
263         types {
264            SetGenderRequest {
265                value("gender"): u8,
266            }
267        }
268        prelude {
269            let value = gender as u8;
270        }
271        body_serialize { SetGenderRequest { value } }
272
273    }
274
275    validate_display_name(display_name: &str, birthdate: DateTime) -> () {
276        GET "{URL}/display-names/validate";
277        prelude {
278            let birthdate = birthdate.to_string();
279        }
280        query {
281            "displayName" => display_name,
282            "birthdate" => &birthdate,
283        }
284    }
285
286    // TODO: I don't know what `id` is for, as this api only seems to be used for the client only,
287    // there's also currently no way to require id from Client, perhaps we should authenticate
288    // on from_cookie method, and store the ClientDetails in the Client
289    validate_display_name_by_id(id: u64, display_name: &str) -> () {
290        GET "{URL}/users/{id}/display-names/validate";
291        query {
292            "displayName" => display_name,
293        }
294        void
295    }
296
297    // TODO: I don't know what `id` is for, as this api only seems to be used for the client only,
298    // there's also currently no way to require id from Client, perhaps we should authenticate
299    // on from_cookie method, and store the ClientDetails in the Client
300    set_display_name(id: u64, display_name: &str) -> () {
301        PATCH "{URL}/users/{id}/display-names";
302        types {
303            Request<'a> {
304                display_name("newDisplayName"): &'a str,
305            }
306        }
307        body_serialize { Request { display_name } }
308    }
309}