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