roblox_api/api/users/
v1.rs

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