1use crate::{DateTime, Gender, Paging, endpoint};
2
3pub const URL: &str = "https://users.roblox.com/v1";
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
8#[serde(rename_all = "camelCase")]
9pub struct ClientDetails {
10 pub id: u64,
11 pub name: String,
12 pub display_name: String,
13}
14
15#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
16#[serde(rename_all = "camelCase")]
17pub struct ClientAppLaunchInfo {
18 pub id: u64,
19 pub name: String,
20 pub display_name: String,
21 pub is_premium: bool,
22 pub country_code: String,
23 pub age_bracket: u64,
24}
25
26#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
27#[serde(rename_all = "camelCase")]
28pub struct UserDetails {
29 pub id: u64,
30 pub name: String,
31 pub display_name: String,
32 pub description: String,
33 pub created: DateTime,
34 #[serde(rename = "isBanned")]
35 pub is_terminated: bool,
36 #[serde(rename = "hasVerifiedBadge")]
37 pub is_verified: bool,
38}
39
40#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
41#[serde(rename_all = "camelCase")]
42pub struct UserById {
43 pub id: u64,
44 pub name: String,
45 pub display_name: String,
46 #[serde(rename = "hasVerifiedBadge")]
47 pub is_verified: bool,
48}
49
50#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
51#[serde(rename_all = "camelCase")]
52pub struct UserByName {
53 pub id: u64,
54 pub name: String,
55 #[serde(rename = "requestedUsername")]
56 pub requested_name: String,
57 pub display_name: String,
58 #[serde(rename = "hasVerifiedBadge")]
59 pub is_verified: bool,
60}
61
62#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
63#[serde(rename_all = "camelCase")]
64pub struct UserByKeyword {
65 pub id: u64,
66 pub name: String,
67 pub display_name: String,
68 #[serde(rename = "previousUsernames")]
69 pub previous_names: Vec<String>,
70 #[serde(rename = "hasVerifiedBadge")]
71 pub is_verified: bool,
72}
73
74#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
75pub struct NameHistory {
76 pub names: Vec<String>,
77 pub next_cursor: Option<String>,
78 pub previous_cursor: Option<String>,
79}
80
81#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
82pub struct UserSearchByKeyword {
83 #[serde(rename = "data")]
84 pub users: Vec<UserByKeyword>,
85 #[serde(rename = "nextPageCursor")]
86 pub next_cursor: Option<String>,
87 #[serde(rename = "previousPageCursor")]
88 pub previous_cursor: Option<String>,
89}
90
91endpoint! {
92 user_details(id: u64) -> UserDetails {
93 GET "{URL}/users/{id}";
94 }
95
96 user_username_history(id: u64, paging: Paging<'_>) -> NameHistory {
97 GET "{URL}/users/{id}/username-history";
98 paging_query { paging, limit = 10 }
99 types {
100 Username {
101 name: String,
102 }
103 Response {
104 data: Vec<Username>,
105 next_cursor("nextPageCursor"): Option<String>,
106 previous_cursor("previousPageCursor"): Option<String>,
107 }
108 }
109 map |r: Response| {
110 let names = r.data.iter().map(|x| x.name.clone()).collect();
111 NameHistory {
112 names,
113 next_cursor: r.next_cursor,
114 previous_cursor: r.previous_cursor,
115 }
116 }
117 }
118
119 users_by_id(ids: &[u64], exclude_terminated: bool) -> Vec<UserById> {
120 POST "{URL}/users";
121 types {
122 Request<'a> {
123 ids("userIds"): &'a [u64],
124 exclude_terminated("excludeBannedUsers"): bool,
125 }
126 Response {
127 data: Vec<UserById>,
128 }
129 }
130 body_serialize { Request { ids, exclude_terminated } }
131 map |r: Response| r.data
132 }
133
134 users_by_name(names: &[&str], exclude_terminated: bool) -> Vec<UserByName> {
135 POST "{URL}/usernames/users";
136 types {
137 Request<'a> {
138 names("usernames"): &'a [&'a str],
139 exclude_terminated("excludeBannedUsers"): bool,
140 }
141 Response {
142 data: Vec<UserByName>,
143 }
144 }
145 body_serialize { Request { names, exclude_terminated } }
146 map |r: Response| r.data
147 }
148
149 search_by_keyword(keyword: &str,session_id: Option<&str>,paging: Paging<'_>) -> UserSearchByKeyword {
151 GET "{URL}/users/search";
152 paging_query { paging, limit = 10 }
153 prelude {
154 let session_id_str = session_id.map(|s| s.to_string()).unwrap_or_default();
155 }
156 query {
157 "keyword" => keyword,
158 "sessionId" => &session_id_str,
159 }
160 }
161
162 authenticated_details() -> ClientDetails {
163 GET "{URL}/users/authenticated";
164 }
165
166 authenticated_age_bracket() -> u64 {
167 GET "{URL}/users/authenticated/age-bracket";
168 types {
169 Response {
170 age_bracket("ageBracket"): u64,
171 }
172 }
173 map |r: Response| r.age_bracket
174 }
175
176 authenticated_country_code() -> String {
177 GET "{URL}/users/authenticated/country-code";
178 types {
179 Response {
180 country_code("countryCode"): String,
181 }
182 }
183 map |r: Response| r.country_code
184 }
185
186 authenticated_roles() -> Vec<String> {
187 GET "{URL}/users/authenticated/roles";
188 types {
189 Response {
190 roles: Vec<String>,
191 }
192 }
193 map |r: Response| r.roles
194 }
195
196 authenticated_app_launch_info() -> ClientAppLaunchInfo {
197 GET "{URL}/users/authenticated/app-launch-info";
198 }
199
200 birthdate() -> DateTime {
201 GET "{URL}/birthdate";
202 types {
203 Response {
204 day("birthDay"): u8,
205 month("birthMonth"): u8,
206 year("birthYear"): i32,
207 }
208 }
209 map |b: Response| DateTime::from_ymd(b.year, b.month, b.day)
210 }
211
212 set_birthdate(birthdate: DateTime) -> () {
215 POST "{URL}/birthdate";
216 types {
217 Request {
218 day("birthDay"): u8,
219 month("birthMonth"): u8,
220 year("birthYear"): i32,
221 }
222 }
223 prelude {
224 let day = birthdate.day();
225 let month = birthdate.month();
226 let year = birthdate.year();
227 }
228 body_serialize { Request { day, month, year } }
229 }
230
231 description() -> String {
232 GET "{URL}/description";
233 types {
234 Response {
235 value("description"): String,
236 }
237 }
238 map |r: Response| r.value
239 }
240
241 set_description(description: &str) -> () {
242 POST "{URL}/description";
243 types {
244 Request<'a> {
245 value("description"): &'a str,
246 }
247 }
248 body_serialize { Request { value: description } }
249 }
250
251 gender() -> Gender {
252 GET "{URL}/gender";
253 types {
254 Response {
255 value("gender"): u8,
256 }
257 }
258 map |g: Response| Gender::from_repr(g.value).expect("failed to parse gender")
259 }
260
261 set_gender(gender: Gender) -> () {
262 POST "{URL}/gender";
263 types {
264 SetGenderRequest {
265 value("gender"): u8,
266 }
267 }
268 prelude {
269 let value = gender as u8;
270 }
271 body_serialize { SetGenderRequest { value } }
272
273 }
274
275 validate_display_name(display_name: &str, birthdate: DateTime) -> () {
276 GET "{URL}/display-names/validate";
277 prelude {
278 let birthdate = birthdate.to_string();
279 }
280 query {
281 "displayName" => display_name,
282 "birthdate" => &birthdate,
283 }
284 }
285
286 validate_display_name_by_id(id: u64, display_name: &str) -> () {
290 GET "{URL}/users/{id}/display-names/validate";
291 query {
292 "displayName" => display_name,
293 }
294 void
295 }
296
297 set_display_name(id: u64, display_name: &str) -> () {
301 PATCH "{URL}/users/{id}/display-names";
302 types {
303 Request<'a> {
304 display_name("newDisplayName"): &'a str,
305 }
306 }
307 body_serialize { Request { display_name } }
308 }
309}