roblox_api/api/users/
v1.rs

1use crate::{DateTime, Error, Paging, client::Client};
2use serde::{Deserialize, Serialize};
3
4pub const URL: &str = "https://users.roblox.com/v1";
5
6#[repr(u8)]
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum Gender {
9    None = 1,
10    Male = 2,
11    Female = 3,
12}
13
14impl Gender {
15    fn from_u8(value: u8) -> Self {
16        match value {
17            2 => Self::Male,
18            3 => Self::Female,
19            _ => Self::None,
20        }
21    }
22}
23
24#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
25pub struct ClientDetails {
26    pub id: u64,
27    pub name: String,
28    #[serde(rename = "displayName")]
29    pub display_name: String,
30}
31
32#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
33pub struct UserDetails {
34    pub id: u64,
35    pub name: String,
36    #[serde(rename = "displayName")]
37    pub display_name: String,
38    pub description: String,
39    #[serde(rename = "created")]
40    pub created_date: DateTime,
41    #[serde(rename = "isBanned")]
42    pub is_terminated: bool,
43    #[serde(rename = "hasVerifiedBadge")]
44    pub is_verified: bool,
45}
46
47#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
48pub struct UserById {
49    pub id: u64,
50    pub name: String,
51    #[serde(rename = "displayName")]
52    pub display_name: String,
53    #[serde(rename = "hasVerifiedBadge")]
54    pub is_verified: bool,
55}
56
57#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
58pub struct UserByName {
59    pub id: u64,
60    pub name: String,
61    #[serde(rename = "requestedUsername")]
62    pub requested_name: String,
63    #[serde(rename = "displayName")]
64    pub display_name: String,
65    #[serde(rename = "hasVerifiedBadge")]
66    pub is_verified: bool,
67}
68
69#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
70pub struct UserByKeyword {
71    pub id: u64,
72    pub name: String,
73    #[serde(rename = "displayName")]
74    pub display_name: String,
75    #[serde(rename = "previousUsernames")]
76    pub previous_names: Vec<String>,
77    #[serde(rename = "hasVerifiedBadge")]
78    pub is_verified: bool,
79}
80
81#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
82pub struct NameHistory {
83    pub names: Vec<String>,
84    pub next_cursor: Option<String>,
85    pub previous_cursor: Option<String>,
86}
87
88#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
89pub struct UserSearchByKeyword {
90    #[serde(rename = "data")]
91    pub users: Vec<UserByKeyword>,
92    #[serde(rename = "nextPageCursor")]
93    pub next_cursor: Option<String>,
94    #[serde(rename = "previousPageCursor")]
95    pub previous_cursor: Option<String>,
96}
97
98pub async fn user_details(client: &mut Client, id: u64) -> Result<UserDetails, Error> {
99    let result = client
100        .requestor
101        .client
102        .get(format!("{URL}/users/{id}"))
103        .headers(client.requestor.default_headers.clone())
104        .send()
105        .await;
106
107    let response = client.validate_response(result).await?;
108    client.requestor.parse_json::<UserDetails>(response).await
109}
110
111pub async fn user_username_history(
112    client: &mut Client,
113    id: u64,
114    paging: Paging<'_>,
115) -> Result<NameHistory, Error> {
116    let limit = paging.limit.unwrap_or(10);
117    let sort_order = paging.order.unwrap_or_default().to_string();
118    let cursor = match paging.cursor {
119        Some(cursor) => format!("&cursor={cursor}"),
120        None => String::new(),
121    };
122
123    let result = client
124        .requestor
125        .client
126        .get(format!(
127            "{URL}/users/{id}/username-history?limit={limit}&sortOrder={sort_order}{cursor}"
128        ))
129        .headers(client.requestor.default_headers.clone())
130        .send()
131        .await;
132
133    // no they don't have anything else in here
134    #[derive(Debug, Deserialize)]
135    struct Username {
136        name: String,
137    }
138
139    #[derive(Debug, Deserialize)]
140    struct Response {
141        #[serde(rename = "data")]
142        names: Vec<Username>,
143        #[serde(rename = "nextPageCursor")]
144        next_cursor: Option<String>,
145        #[serde(rename = "previousPageCursor")]
146        previous_cursor: Option<String>,
147    }
148
149    let response = client.validate_response(result).await?;
150    let result = client.requestor.parse_json::<Response>(response).await?;
151
152    let names = result.names.iter().map(|x| x.name.clone()).collect();
153    Ok(NameHistory {
154        names,
155        next_cursor: result.next_cursor,
156        previous_cursor: result.previous_cursor,
157    })
158}
159
160pub async fn users_by_id(
161    client: &mut Client,
162    ids: &[u64],
163    exclude_terminated: bool,
164) -> Result<Vec<UserById>, Error> {
165    #[derive(Debug, Serialize)]
166    struct Request<'a> {
167        #[serde(rename = "userIds")]
168        ids: &'a [u64],
169        #[serde(rename = "excludeBannedUsers")]
170        exclude_terminated: bool,
171    }
172
173    let result = client
174        .requestor
175        .client
176        .post(format!("{URL}/users"))
177        .headers(client.requestor.default_headers.clone())
178        .json(&Request {
179            ids,
180            exclude_terminated,
181        })
182        .send()
183        .await;
184
185    #[derive(Debug, Deserialize)]
186    struct Response {
187        #[serde(rename = "data")]
188        users: Vec<UserById>,
189    }
190
191    let response = client.validate_response(result).await?;
192    Ok(client
193        .requestor
194        .parse_json::<Response>(response)
195        .await?
196        .users)
197}
198
199pub async fn users_by_name(
200    client: &mut Client,
201    names: &[&str],
202    exclude_terminated: bool,
203) -> Result<Vec<UserByName>, Error> {
204    #[derive(Debug, Serialize)]
205    struct Request<'a> {
206        #[serde(rename = "usernames")]
207        names: &'a [&'a str],
208        #[serde(rename = "excludeBannedUsers")]
209        exclude_terminated: bool,
210    }
211
212    let result = client
213        .requestor
214        .client
215        .post(format!("{URL}/usernames/users"))
216        .headers(client.requestor.default_headers.clone())
217        .json(&Request {
218            names,
219            exclude_terminated,
220        })
221        .send()
222        .await;
223
224    #[derive(Debug, Deserialize)]
225    struct Response {
226        #[serde(rename = "data")]
227        names: Vec<UserByName>,
228    }
229
230    let response = client.validate_response(result).await?;
231    Ok(client
232        .requestor
233        .parse_json::<Response>(response)
234        .await?
235        .names)
236}
237
238// this api seems to be dysfunctional, try using search::omni instead
239pub async fn search_by_keyword(
240    client: &mut Client,
241    keyword: &str,
242    session_id: Option<&str>,
243    paging: Paging<'_>,
244) -> Result<UserSearchByKeyword, Error> {
245    let limit = paging.limit.unwrap_or(10);
246    let cursor = match paging.cursor {
247        Some(cursor) => format!("&cursor={cursor}"),
248        None => String::new(),
249    };
250
251    let session_id = match session_id {
252        Some(session_id) => format!("&session_id={session_id}"),
253        None => String::new(),
254    };
255
256    let result = client
257        .requestor
258        .client
259        .get(format!(
260            "{URL}/users/search?keyword={keyword}&limit={limit}{cursor}{session_id}"
261        ))
262        .headers(client.requestor.default_headers.clone())
263        .send()
264        .await;
265
266    let response = client.validate_response(result).await?;
267    client
268        .requestor
269        .parse_json::<UserSearchByKeyword>(response)
270        .await
271}
272
273pub async fn authenticated_details(client: &mut Client) -> Result<ClientDetails, Error> {
274    let result = client
275        .requestor
276        .client
277        .get(format!("{URL}/users/authenticated"))
278        .headers(client.requestor.default_headers.clone())
279        .send()
280        .await;
281
282    let response = client.validate_response(result).await?;
283    client.requestor.parse_json::<ClientDetails>(response).await
284}
285
286pub async fn authenticated_age_bracket(client: &mut Client) -> Result<u64, Error> {
287    let result = client
288        .requestor
289        .client
290        .get(format!("{URL}/users/authenticated/age-bracket"))
291        .headers(client.requestor.default_headers.clone())
292        .send()
293        .await;
294
295    #[derive(Debug, Deserialize)]
296    struct Response {
297        #[serde(rename = "ageBracket")]
298        age_bracket: u64,
299    }
300
301    let response = client.validate_response(result).await?;
302    Ok(client
303        .requestor
304        .parse_json::<Response>(response)
305        .await?
306        .age_bracket)
307}
308
309pub async fn authenticated_country_code(client: &mut Client) -> Result<String, Error> {
310    let result = client
311        .requestor
312        .client
313        .get(format!("{URL}/users/authenticated/country-code"))
314        .headers(client.requestor.default_headers.clone())
315        .send()
316        .await;
317
318    #[derive(Debug, Deserialize)]
319    struct Response {
320        #[serde(rename = "countryCode")]
321        country_code: String,
322    }
323
324    let response = client.validate_response(result).await?;
325    Ok(client
326        .requestor
327        .parse_json::<Response>(response)
328        .await?
329        .country_code)
330}
331
332pub async fn authenticated_roles(client: &mut Client) -> Result<Vec<String>, Error> {
333    let result = client
334        .requestor
335        .client
336        .get(format!("{URL}/users/authenticated/roles"))
337        .headers(client.requestor.default_headers.clone())
338        .send()
339        .await;
340
341    #[derive(Debug, Deserialize)]
342    struct Response {
343        roles: Vec<String>,
344    }
345
346    let response = client.validate_response(result).await?;
347    Ok(client
348        .requestor
349        .parse_json::<Response>(response)
350        .await?
351        .roles)
352}
353
354pub async fn birthdate(client: &mut Client) -> Result<DateTime, Error> {
355    let result = client
356        .requestor
357        .client
358        .get(format!("{URL}/birthdate"))
359        .headers(client.requestor.default_headers.clone())
360        .send()
361        .await;
362
363    #[derive(Debug, Deserialize)]
364    struct Response {
365        #[serde(rename = "birthDay")]
366        day: u8,
367        #[serde(rename = "birthMonth")]
368        month: u8,
369        #[serde(rename = "birthYear")]
370        year: i32,
371    }
372
373    let response = client.validate_response(result).await?;
374    let birthdate = client.requestor.parse_json::<Response>(response).await?;
375
376    Ok(DateTime::from_ymd(
377        birthdate.year,
378        birthdate.month,
379        birthdate.day,
380    ))
381}
382
383// According to documentation there's supposed to be a password,
384// yet the roblox website doesn't use it..
385pub async fn set_birthdate(
386    client: &mut Client,
387    birthdate: DateTime,
388    //password: &str,
389) -> Result<(), Error> {
390    #[derive(Debug, Serialize)]
391    struct Request {
392        #[serde(rename = "birthDay")]
393        day: u8,
394        #[serde(rename = "birthMonth")]
395        month: u8,
396        #[serde(rename = "birthYear")]
397        year: i32,
398        //pub password: &'a str,
399    }
400
401    let result = client
402        .requestor
403        .client
404        .post(format!("{URL}/birthdate"))
405        .headers(client.requestor.default_headers.clone())
406        .json(&Request {
407            day: birthdate.day(),
408            month: birthdate.month(),
409            year: birthdate.year(),
410            //password,
411        })
412        .send()
413        .await;
414
415    client.validate_response(result).await?;
416    Ok(())
417}
418
419pub async fn description(client: &mut Client) -> Result<String, Error> {
420    let result = client
421        .requestor
422        .client
423        .get(format!("{URL}/description"))
424        .headers(client.requestor.default_headers.clone())
425        .send()
426        .await;
427
428    #[derive(Debug, Deserialize)]
429    struct Response {
430        #[serde(rename = "description")]
431        value: String,
432    }
433
434    let response = client.validate_response(result).await?;
435    let description = client.requestor.parse_json::<Response>(response).await?;
436
437    Ok(description.value)
438}
439
440pub async fn set_description(client: &mut Client, description: &str) -> Result<(), Error> {
441    #[derive(Debug, Serialize)]
442    struct Request<'a> {
443        #[serde(rename = "description")]
444        value: &'a str,
445    }
446
447    let result = client
448        .requestor
449        .client
450        .post(format!("{URL}/description"))
451        .headers(client.requestor.default_headers.clone())
452        .json(&Request { value: description })
453        .send()
454        .await;
455
456    client.validate_response(result).await?;
457    Ok(())
458}
459
460pub async fn gender(client: &mut Client) -> Result<Gender, Error> {
461    let result = client
462        .requestor
463        .client
464        .get(format!("{URL}/gender"))
465        .headers(client.requestor.default_headers.clone())
466        .send()
467        .await;
468
469    #[derive(Debug, Deserialize)]
470    struct Response {
471        #[serde(rename = "gender")]
472        value: u8,
473    }
474
475    let response = client.validate_response(result).await?;
476    let gender: Response = client.requestor.parse_json(response).await?;
477
478    Ok(Gender::from_u8(gender.value))
479}
480
481pub async fn set_gender(client: &mut Client, gender: Gender) -> Result<(), Error> {
482    #[derive(Debug, Serialize)]
483    struct Request {
484        #[serde(rename = "gender")]
485        value: u8,
486    }
487
488    let result = client
489        .requestor
490        .client
491        .post(format!("{URL}/gender"))
492        .headers(client.requestor.default_headers.clone())
493        .json(&Request {
494            value: gender as u8,
495        })
496        .send()
497        .await;
498
499    client.validate_response(result).await?;
500    Ok(())
501}
502
503pub async fn validate_display_name(
504    client: &mut Client,
505    display_name: &str,
506    birthdate: DateTime,
507) -> Result<(), Error> {
508    let result = client
509        .requestor
510        .client
511        .get(format!(
512            "{URL}/display-names/validate?displayName={display_name}&birthdate={}",
513            birthdate.to_string()
514        ))
515        .headers(client.requestor.default_headers.clone())
516        .send()
517        .await;
518
519    client.validate_response(result).await?;
520    Ok(())
521}
522
523// TODO: I don't know what `id` is for, as this api only seems to be used for the client only,
524// there's also currently no way to require id from Client, perhaps we should authenticate
525// on from_cookie method, and store the ClientDetails in the Client
526pub async fn validate_display_name_by_id(
527    client: &mut Client,
528    id: u64,
529    display_name: &str,
530) -> Result<(), Error> {
531    let result = client
532        .requestor
533        .client
534        .get(format!(
535            "{URL}/users/{id}/display-names/validate?displayName={display_name}"
536        ))
537        .headers(client.requestor.default_headers.clone())
538        .send()
539        .await;
540
541    client.validate_response(result).await?;
542    Ok(())
543}
544
545// TODO: I don't know what `id` is for, as this api only seems to be used for the client only,
546// there's also currently no way to require id from Client, perhaps we should authenticate
547// on from_cookie method, and store the ClientDetails in the Client
548pub async fn set_display_name(
549    client: &mut Client,
550    id: u64,
551    display_name: &str,
552) -> Result<(), Error> {
553    #[derive(Debug, Serialize)]
554    struct Request<'a> {
555        #[serde(rename = "newDisplayName")]
556        display_name: &'a str,
557    }
558
559    let result = client
560        .requestor
561        .client
562        .patch(format!(
563            "{URL}/users/{id}/display-names?displayName={display_name}"
564        ))
565        .headers(client.requestor.default_headers.clone())
566        .json(&Request { display_name })
567        .send()
568        .await;
569
570    client.validate_response(result).await?;
571    Ok(())
572}