roblox_api/api/groups/
v1.rs

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    /// How many users have the role
27    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    // TODO: all these fields are HashMap<String, bool>, but I can't figure a way to snakecase the hashmap keys, so.
145    #[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    /// GroupUser is the authenticated user
197    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
244/// Gets group membership information in the context of the authenticated user
245pub 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
263/// Gets the Group's name change history
264pub 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
304/// Gets groups that the authenticated user has requested to join
305pub 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
385/// Gets the permissions for a group's roleset. The authorized user must either be the group owner or the roleset being requested, except for guest roles, which can be viewed by all (members and guests).
386pub 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
406/// Gets all permissions for each role
407pub 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
482/// Gets a list of group wall posts
483pub 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}