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 #[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
253pub 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
416pub async fn set_birthdate(
419 client: &mut Client,
420 birthdate: DateTime,
421 ) -> 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 }
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 })
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
556pub 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
578pub 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}