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