1use serde::{Deserialize, Serialize};
2
3use crate::{DateTime, Error, Paging, client::Client};
4
5pub const URL: &str = "https://groups.roblox.com/v1";
6
7#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
8#[serde(rename_all = "camelCase")]
9pub struct GroupUser {
10 #[serde(rename = "userId")]
11 pub id: u64,
12 #[serde(rename = "username")]
13 pub name: String,
14 pub display_name: String,
15 #[serde(rename = "hasVerifiedBadge")]
16 pub is_verified: bool,
17}
18
19#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
20#[serde(rename_all = "camelCase")]
21pub struct GroupRole {
22 pub id: u64,
23 pub name: String,
24 pub rank: u8,
25
26 pub member_count: Option<u64>,
28 pub description: Option<String>,
29}
30
31#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
32pub struct UserRole {
33 pub user: GroupUser,
34 pub role: GroupRole,
35}
36
37#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
38pub struct GroupShout {
39 pub body: String,
40 pub poster: GroupUser,
41 pub created: DateTime,
42 pub updated: DateTime,
43}
44
45#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
46#[serde(rename_all = "camelCase")]
47pub struct NameHistory {
48 pub names: Vec<(String, DateTime)>,
49 pub next_cursor: Option<String>,
50 pub previous_cursor: Option<String>,
51}
52
53#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
54pub struct WallPost {
55 pub id: u64,
56 pub body: String,
57 pub created: DateTime,
58 pub updated: DateTime,
59 pub poster: GroupUser,
60}
61
62#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
63#[serde(rename_all = "camelCase")]
64pub struct WallPosts {
65 #[serde(rename = "data")]
66 pub posts: Vec<WallPost>,
67 pub next_cursor: Option<String>,
68 pub previous_cursor: Option<String>,
69}
70
71#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
72#[serde(rename_all = "camelCase")]
73pub struct PostPermissions {
74 pub view_wall: bool,
75 pub post_to_wall: bool,
76 pub delete_from_wall: bool,
77
78 pub view_status: bool,
79 pub post_to_status: bool,
80}
81
82#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
83#[serde(rename_all = "camelCase")]
84pub struct ForumsPermissions {
85 pub pin_posts: bool,
86 pub lock_posts: bool,
87 pub create_posts: bool,
88 pub remove_posts: bool,
89
90 pub create_comments: bool,
91 pub remove_comments: bool,
92
93 pub manage_categories: bool,
94}
95
96#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
97#[serde(rename_all = "camelCase")]
98pub struct ContentModerationPermissions {
99 pub manage_keyword_block_list: bool,
100 pub view_keyword_block_list: bool,
101}
102
103#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
104#[serde(rename_all = "camelCase")]
105pub struct MembershipPermissions {
106 pub change_rank: bool,
107 pub ban_members: bool,
108 pub invite_members: bool,
109 pub remove_members: bool,
110}
111
112#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
113#[serde(rename_all = "camelCase")]
114pub struct ManagementPermissions {
115 pub manage_clan: bool,
116 pub manage_relationships: bool,
117 pub view_audit_logs: bool,
118}
119
120#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
121#[serde(rename_all = "camelCase")]
122pub struct EconomyPermissions {
123 pub create_items: bool,
124 pub manage_items: bool,
125 pub advertise_group: bool,
126 pub add_group_places: bool,
127 pub spend_group_funds: bool,
128 pub manage_group_games: bool,
129
130 pub view_group_payouts: bool,
131 pub view_analytics: bool,
132}
133
134#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
135#[serde(rename_all = "camelCase")]
136pub struct OpenCloudPermissions {
137 pub use_cloud_authentication: bool,
138 pub administer_cloud_authentication: bool,
139}
140
141#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
142#[serde(rename_all = "camelCase")]
143pub struct Permissions {
144 #[serde(rename = "groupPostsPermissions")]
146 pub posts: PostPermissions,
147 #[serde(rename = "groupForumsPermissions")]
148 pub forums: ForumsPermissions,
149 #[serde(rename = "groupContentModerationPermissions")]
150 pub content_moderation: ContentModerationPermissions,
151 #[serde(rename = "groupMembershipPermissions")]
152 pub membership: MembershipPermissions,
153 #[serde(rename = "groupManagementPermissions")]
154 pub management: ManagementPermissions,
155 #[serde(rename = "groupEconomyPermissions")]
156 pub economy: EconomyPermissions,
157 #[serde(rename = "groupOpenCloudPermissions")]
158 pub open_cloud: OpenCloudPermissions,
159}
160
161#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
162#[serde(rename_all = "camelCase")]
163pub struct GroupInformation {
164 pub id: u64,
165 pub name: String,
166 pub description: String,
167
168 pub owner: Option<GroupUser>,
169 pub shout: Option<GroupShout>,
170
171 pub member_count: Option<u64>,
172 #[serde(rename = "isBuildersClubOnly")]
173 pub premium_only: bool,
174 #[serde(rename = "publicEntryAllowed")]
175 pub is_public: bool,
176 #[serde(rename = "hasVerifiedBadge")]
177 pub is_verified: bool,
178}
179
180#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
181pub struct NotificationPreference {
182 #[serde(rename = "type")]
183 pub name: String,
184 pub description: String,
185
186 pub kind: String,
187 pub enabled: bool,
188}
189
190#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
191#[serde(rename_all = "camelCase")]
192pub struct Membership {
193 #[serde(rename = "groupId")]
194 pub id: u64,
195
196 pub user_role: UserRole,
198 pub permissions: Permissions,
199
200 pub is_primary: bool,
201 pub is_pending_join: bool,
202
203 pub are_enemies_allowed: bool,
204 pub are_group_games_visible: bool,
205 pub are_group_funds_visible: bool,
206
207 pub can_configure: bool,
208
209 pub notification_preferences: Option<Vec<NotificationPreference>>,
210}
211
212#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
213pub struct RolePermissions {
214 #[serde(rename = "groupId")]
215 pub id: u64,
216 pub role: GroupRole,
217 pub permissions: Permissions,
218}
219
220#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
221#[serde(rename_all = "camelCase")]
222pub struct GroupUsers {
223 pub users: Vec<(GroupUser, GroupRole)>,
224 pub next_cursor: Option<String>,
225 pub previous_cursor: Option<String>,
226}
227
228pub async fn information(client: &mut Client, id: u64) -> Result<GroupInformation, Error> {
229 let result = client
230 .requestor
231 .client
232 .get(format!("{URL}/groups/{id}"))
233 .headers(client.requestor.default_headers.clone())
234 .send()
235 .await;
236
237 let response = client.validate_response(result).await?;
238 client
239 .requestor
240 .parse_json::<GroupInformation>(response)
241 .await
242}
243
244pub async fn membership(
246 client: &mut Client,
247 id: u64,
248 notification_preferences: bool,
249) -> Result<Membership, Error> {
250 let result = client
251 .requestor
252 .client
253 .get(format!("{URL}/groups/{id}/membership"))
254 .query(&[("includeNotificationPreferences", notification_preferences)])
255 .headers(client.requestor.default_headers.clone())
256 .send()
257 .await;
258
259 let response = client.validate_response(result).await?;
260 client.requestor.parse_json::<Membership>(response).await
261}
262
263pub async fn name_history(client: &mut Client, id: u64) -> Result<NameHistory, Error> {
265 let result = client
266 .requestor
267 .client
268 .get(format!("{URL}/groups/{id}/name-history"))
269 .headers(client.requestor.default_headers.clone())
270 .send()
271 .await;
272
273 #[derive(Debug, Deserialize)]
274 struct NameHistoryItem {
275 name: String,
276 created: DateTime,
277 }
278
279 #[derive(Debug, Deserialize)]
280 #[serde(rename_all = "camelCase")]
281 struct Response {
282 #[serde(rename = "data")]
283 items: Vec<NameHistoryItem>,
284 next_cursor: Option<String>,
285 previous_cursor: Option<String>,
286 }
287
288 let response = client.validate_response(result).await?;
289 let result = client.requestor.parse_json::<Response>(response).await?;
290
291 let names: Vec<(String, DateTime)> = result
292 .items
293 .into_iter()
294 .map(|x| (x.name, x.created))
295 .collect();
296
297 Ok(NameHistory {
298 names,
299 next_cursor: result.next_cursor,
300 previous_cursor: result.previous_cursor,
301 })
302}
303
304pub async fn pending_join_requests(client: &mut Client) -> Result<Vec<GroupInformation>, Error> {
306 let result = client
307 .requestor
308 .client
309 .get(format!("{URL}/user/groups/pending"))
310 .headers(client.requestor.default_headers.clone())
311 .send()
312 .await;
313
314 #[derive(Clone, Debug, Deserialize)]
315 struct Response {
316 #[serde(rename = "data")]
317 groups: Vec<GroupInformation>,
318 }
319
320 let response = client.validate_response(result).await?;
321 Ok(client
322 .requestor
323 .parse_json::<Response>(response)
324 .await?
325 .groups)
326}
327
328pub async fn roles(client: &mut Client, id: u64) -> Result<Vec<GroupRole>, Error> {
329 let result = client
330 .requestor
331 .client
332 .get(format!("{URL}/groups/{id}/roles"))
333 .headers(client.requestor.default_headers.clone())
334 .send()
335 .await;
336
337 #[derive(Clone, Debug, Deserialize)]
338 struct Response {
339 roles: Vec<GroupRole>,
340 }
341
342 let response = client.validate_response(result).await?;
343 Ok(client
344 .requestor
345 .parse_json::<Response>(response)
346 .await?
347 .roles)
348}
349
350pub async fn user_roles(
351 client: &mut Client,
352 id: u64,
353) -> Result<Vec<(GroupInformation, GroupRole)>, Error> {
354 let result = client
355 .requestor
356 .client
357 .get(format!("{URL}/users/{id}/groups/roles"))
358 .headers(client.requestor.default_headers.clone())
359 .send()
360 .await;
361
362 #[derive(Clone, Debug, Deserialize)]
363 struct GroupAndRole {
364 group: GroupInformation,
365 role: GroupRole,
366 }
367
368 #[derive(Clone, Debug, Deserialize)]
369 struct Response {
370 #[serde(rename = "data")]
371 items: Vec<GroupAndRole>,
372 }
373
374 let response = client.validate_response(result).await?;
375 let response = client.requestor.parse_json::<Response>(response).await?;
376
377 let mut roles = Vec::new();
378 for item in &response.items {
379 roles.push((item.group.clone(), item.role.clone()));
380 }
381
382 Ok(roles)
383}
384
385pub async fn roleset_permissions(
387 client: &mut Client,
388 id: u64,
389 roleset_id: u64,
390) -> Result<RolePermissions, Error> {
391 let result = client
392 .requestor
393 .client
394 .get(format!("{URL}/groups/{id}/roles/{roleset_id}/permissions"))
395 .headers(client.requestor.default_headers.clone())
396 .send()
397 .await;
398
399 let response = client.validate_response(result).await?;
400 client
401 .requestor
402 .parse_json::<RolePermissions>(response)
403 .await
404}
405
406pub async fn role_permissions(client: &mut Client, id: u64) -> Result<Vec<RolePermissions>, Error> {
408 let result = client
409 .requestor
410 .client
411 .get(format!("{URL}/groups/{id}/roles/permissions"))
412 .headers(client.requestor.default_headers.clone())
413 .send()
414 .await;
415
416 #[derive(Debug, Deserialize)]
417 struct Response {
418 #[serde(rename = "data")]
419 items: Vec<RolePermissions>,
420 }
421
422 let response = client.validate_response(result).await?;
423 Ok(client
424 .requestor
425 .parse_json::<Response>(response)
426 .await?
427 .items)
428}
429
430pub async fn users(client: &mut Client, id: u64, paging: Paging<'_>) -> Result<GroupUsers, Error> {
431 let limit = paging.limit.unwrap_or(10).to_string();
432 let sort_order = paging.order.unwrap_or_default().to_string();
433 let cursor = match paging.cursor {
434 Some(cursor) => cursor.to_string(),
435 None => String::new(),
436 };
437
438 let result = client
439 .requestor
440 .client
441 .get(format!("{URL}/groups/{id}/users"))
442 .query(&[
443 ("limit", limit),
444 ("sortOrder", sort_order),
445 ("cursor", cursor),
446 ])
447 .headers(client.requestor.default_headers.clone())
448 .send()
449 .await;
450
451 #[derive(Clone, Debug, Deserialize)]
452 struct User {
453 user: GroupUser,
454 role: GroupRole,
455 }
456
457 #[derive(Clone, Debug, Deserialize)]
458 struct Response {
459 #[serde(rename = "data")]
460 users: Vec<User>,
461 #[serde(rename = "nextPageCursor")]
462 next_cursor: Option<String>,
463 #[serde(rename = "previousPageCursor")]
464 previous_cursor: Option<String>,
465 }
466
467 let response = client.validate_response(result).await?;
468 let response = client.requestor.parse_json::<Response>(response).await?;
469
470 let mut users = Vec::new();
471 for user in response.users {
472 users.push((user.user, user.role))
473 }
474
475 Ok(GroupUsers {
476 users,
477 next_cursor: response.next_cursor,
478 previous_cursor: response.previous_cursor,
479 })
480}
481
482pub async fn wall_posts(
484 client: &mut Client,
485 id: u64,
486 paging: Paging<'_>,
487) -> Result<WallPosts, Error> {
488 let limit = paging.limit.unwrap_or(10).to_string();
489 let sort_order = paging.order.unwrap_or_default().to_string();
490 let cursor = match paging.cursor {
491 Some(cursor) => cursor.to_string(),
492 None => String::new(),
493 };
494
495 let result = client
496 .requestor
497 .client
498 .get(format!("{URL}/groups/{id}/wall/posts"))
499 .query(&[
500 ("limit", limit),
501 ("sortOrder", sort_order),
502 ("cursor", cursor),
503 ])
504 .headers(client.requestor.default_headers.clone())
505 .send()
506 .await;
507
508 let response = client.validate_response(result).await?;
509 client.requestor.parse_json::<WallPosts>(response).await
510}
511
512pub async fn join(client: &mut Client, id: u64) -> Result<(), Error> {
513 #[derive(Serialize)]
514 #[serde(rename_all = "camelCase")]
515 struct Request<'a> {
516 session_id: &'a str,
517 redemption_token: &'a str,
518 }
519
520 let result = client
521 .requestor
522 .client
523 .post(format!("{URL}/groups/{id}/users"))
524 .headers(client.requestor.default_headers.clone())
525 .json(&Request {
526 session_id: "",
527 redemption_token: "",
528 })
529 .send()
530 .await;
531
532 client.validate_response(result).await?;
533 Ok(())
534}
535
536pub async fn remove_join_request(client: &mut Client, id: u64, user_id: u64) -> Result<(), Error> {
537 #[derive(Serialize)]
538 struct Request {}
539
540 let result = client
541 .requestor
542 .client
543 .delete(format!("{URL}/groups/{id}/join-requests/users/{user_id}"))
544 .json(&Request {})
545 .headers(client.requestor.default_headers.clone())
546 .send()
547 .await;
548
549 client.validate_response(result).await?;
550 Ok(())
551}
552
553pub async fn remove(client: &mut Client, id: u64, user_id: u64) -> Result<(), Error> {
554 #[derive(Serialize)]
555 struct Request {}
556
557 let result = client
558 .requestor
559 .client
560 .delete(format!("{URL}/groups/{id}/users/{user_id}"))
561 .headers(client.requestor.default_headers.clone())
562 .json(&Request {})
563 .send()
564 .await;
565
566 client.validate_response(result).await?;
567 Ok(())
568}