1use rspc::Type;
2use serde::{Deserialize, Serialize};
3use strum_macros::{Display, EnumString};
4
5use crate::{
6 users::{whoami, MinimalGroupUser},
7 util::{
8 jar::RequestJar,
9 paging::{get_page, PageLimit, SortOrder},
10 responses::DataWrapper,
11 Error,
12 },
13};
14
15use super::{permissions::GroupPermissions, roles::GroupRole};
16
17#[derive(Debug, Serialize, Deserialize, Clone, Type)]
18#[serde(rename_all = "camelCase")]
19pub struct Group {
20 pub id: i64,
21 pub name: String,
22 pub description: String,
23 pub owner: MinimalGroupUser,
24 pub shout: Option<GroupShout>,
25 pub member_count: Option<i64>,
26 pub is_builders_club_only: bool,
27 pub public_entry_allowed: bool,
28 pub is_locked: Option<bool>,
29 pub has_verified_badge: bool,
30}
31
32#[derive(Debug, Serialize, Deserialize, Clone, Type)]
33#[serde(rename_all = "camelCase")]
34pub struct MinimalGroup {
35 pub id: i64,
36 pub name: String,
37 pub member_count: i64,
38 pub has_verified_badge: bool,
39}
40
41#[derive(Debug, Serialize, Deserialize, Clone, Type)]
42#[serde(rename_all = "camelCase")]
43pub struct GroupShout {
44 pub body: String,
45 pub poster: MinimalGroupUser,
46 pub created: String,
47 pub updated: String,
48}
49
50pub async fn group_by_id(jar: &RequestJar, group_id: i64) -> Result<Group, Box<Error>> {
55 let url = format!("https://groups.roblox.com/v1/groups/{}", group_id);
56 let response = jar.get_json::<Group>(&url).await?;
57 Ok(response)
58}
59
60#[derive(Debug, Serialize, Deserialize, Clone, Type)]
61#[serde(rename_all = "camelCase")]
62pub struct GroupAuditLogEntry {
63 pub actor: GroupAuditLogActor,
64 pub action_type: GroupAuditLogActionType,
65 pub created: String,
67}
68
69#[derive(Debug, Serialize, Deserialize, Clone, Type)]
70#[serde(rename_all = "camelCase")]
71pub struct GroupAuditLogActor {
72 pub user: MinimalGroupUser,
73 pub created: String,
74 pub updated: String,
75}
76
77#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Display, EnumString, Type)]
78pub enum GroupAuditLogActionType {
79 DeletePost,
81 RemoveMember,
82 AcceptJoinRequest,
83 DeclineJoinRequest,
84 PostStatus,
85 ChangeRank,
86 BuyAd,
87 SendAllyRequest,
88 CreateEnemy,
89 AcceptAllyRequest,
90 DeclineAllyRequest,
91 DeleteAlly,
92 DeleteEnemy,
93 AddGroupPlace,
94 RemoveGroupPlace,
95 CreateItems,
96 ConfigureItems,
97 SpendGroupFunds,
98 ChangeOwner,
99 Delete,
100 AdjustCurrencyAmounts,
101 Abandon,
102 Claim,
103 Rename,
104 ChangeDescription,
105 InviteToClan,
106 KickFromClan,
107 CancelClanInvite,
108 BuyClan,
109 CreateGroupAsset,
110 UpdateGroupAsset,
111 ConfigureGroupAsset,
112 RevertGroupAsset,
113 CreateGroupDeveloperProduct,
114 ConfigureGroupGame,
115 Lock,
116 Unlock,
117 CreateGamePass,
118 CreateBadge,
119 ConfigureBadge,
120 SavePlace,
121 PublishPlace,
122 UpdateRolesetRank,
123 UpdateRolesetData,
124}
125
126pub async fn audit_log(
132 jar: &RequestJar,
133 group_id: i64,
134 limit: PageLimit,
135 user_id: Option<i64>,
136 sort_order: Option<SortOrder>,
137 ) -> Result<Vec<GroupAuditLogEntry>, Box<Error>> {
139 let mut url = format!("https://groups.roblox.com/v1/groups/{}/audit-log", group_id);
140 if user_id.is_some() {
141 let user_id = user_id.unwrap();
142 url = format!("{}?userId={}", url, user_id);
143 }
144 url = format!("{}&sortOrder={}", url, sort_order.unwrap_or(SortOrder::Asc));
145
146 let response = get_page(jar, url.as_str(), limit, None).await?; Ok(response)
148}
149
150#[derive(Debug, Serialize, Deserialize, Clone, Type)]
151#[serde(rename_all = "camelCase")]
152pub struct GroupNameHistoryEntry {
153 pub name: String,
154 pub created: String,
155}
156
157pub async fn name_history(
163 jar: &RequestJar,
164 group_id: i64,
165 limit: PageLimit,
166 sort_order: Option<SortOrder>,
167 ) -> Result<Vec<GroupNameHistoryEntry>, Box<Error>> {
169 let mut url = format!(
170 "https://groups.roblox.com/v1/groups/{}/name-history",
171 group_id
172 );
173 url = format!("{}?sortOrder={}", url, sort_order.unwrap_or(SortOrder::Asc));
174
175 let response = get_page(jar, url.as_str(), limit, None).await?; Ok(response)
177}
178
179#[derive(Debug, Serialize, Deserialize, Clone, Type)]
180#[serde(rename_all = "camelCase")]
181pub struct GroupSettings {
182 pub is_approval_required: bool,
183 pub is_builders_club_required: bool,
184 pub are_enemies_allowed: bool,
185 pub are_group_funds_visible: bool,
186 pub are_group_games_visible: bool,
187 pub is_group_name_change_enabled: bool,
188}
189
190pub async fn settings(jar: &RequestJar, group_id: i64) -> Result<GroupSettings, Box<Error>> {
196 let url = format!("https://groups.roblox.com/v1/groups/{}/settings", group_id);
197 let response = jar.get_json::<GroupSettings>(&url).await?;
198 Ok(response)
199}
200
201#[derive(Debug, Serialize, Deserialize, Clone, Type)]
202#[serde(rename_all = "camelCase")]
203pub struct GroupSettingsUpdateRequest {
204 pub is_approval_required: Option<bool>,
205 pub are_enemies_allowed: Option<bool>,
206 pub are_group_funds_visible: Option<bool>,
207 pub are_group_games_visible: Option<bool>,
208}
209
210#[derive(Debug, Serialize, Deserialize, Clone, Type)]
211#[serde(rename_all = "camelCase")]
212pub struct GroupSettingsUpdateResponse {}
213
214pub async fn update_settings(
221 jar: &RequestJar,
222 group_id: i64,
223 request: GroupSettingsUpdateRequest,
224) -> Result<GroupSettingsUpdateResponse, Box<Error>> {
225 let url = format!("https://groups.roblox.com/v1/groups/{}/settings", group_id);
226 let response = jar
227 .patch_json::<GroupSettingsUpdateResponse, GroupSettingsUpdateRequest>(&url, request)
228 .await?;
229 Ok(response)
230}
231
232#[derive(Debug, Serialize, Deserialize, Clone, Type)]
235#[serde(rename_all = "camelCase")]
236pub struct GroupComplianceItem {
237 pub can_view_group: bool,
238 pub group_id: i64,
239}
240
241#[derive(Debug, Serialize, Deserialize, Clone, Type)]
242#[serde(rename_all = "camelCase")]
243pub struct GroupComplianceResponse {
244 pub groups: Vec<GroupComplianceItem>,
245}
246
247#[derive(Debug, Serialize, Deserialize, Clone, Type)]
248#[serde(rename_all = "camelCase")]
249pub struct GroupComplianceRequest {
250 pub group_ids: Vec<i64>,
251}
252
253pub async fn compliance(
258 jar: &RequestJar,
259 group_ids: Vec<i64>,
260) -> Result<GroupComplianceResponse, Box<Error>> {
261 let url = format!("https://groups.roblox.com/v1/groups/policies");
262 let request = GroupComplianceRequest { group_ids };
263 let response = jar
264 .post_json::<GroupComplianceResponse, GroupComplianceRequest>(&url, request)
265 .await?;
266 Ok(response)
267}
268
269#[derive(Debug, Serialize, Deserialize, Clone, Type)]
270#[serde(rename_all = "camelCase")]
271pub struct NewDescriptionRequest {
272 pub description: String,
273}
274
275#[derive(Debug, Serialize, Deserialize, Clone, Type)]
276#[serde(rename_all = "camelCase")]
277pub struct NewDescriptionResponse {
278 pub new_description: String,
279}
280
281pub async fn update_description(
288 jar: &RequestJar,
289 group_id: i64,
290 description: String,
291) -> Result<NewDescriptionResponse, Box<Error>> {
292 let url = format!(
293 "https://groups.roblox.com/v1/groups/{}/description",
294 group_id
295 );
296 let request = NewDescriptionRequest { description };
297 let response = jar
298 .patch_json::<NewDescriptionResponse, NewDescriptionRequest>(&url, request)
299 .await?;
300 Ok(response)
301}
302
303#[derive(Debug, Serialize, Deserialize, Clone, Type)]
304#[serde(rename_all = "camelCase")]
305pub struct NewNameRequest {
306 pub name: String,
307}
308
309#[derive(Debug, Serialize, Deserialize, Clone, Type)]
310#[serde(rename_all = "camelCase")]
311pub struct NewNameResponse {
312 pub new_name: String,
313}
314
315pub async fn update_name(
325 jar: &RequestJar,
326 group_id: i64,
327 name: String,
328) -> Result<NewNameResponse, Box<Error>> {
329 let url = format!(
330 "https://groups.roblox.com/v1/groups/{}/description",
331 group_id
332 );
333 let request = NewNameRequest { name };
334 let response = jar
335 .patch_json::<NewNameResponse, NewNameRequest>(&url, request)
336 .await?;
337 Ok(response)
338}
339
340#[derive(Debug, Serialize, Deserialize, Clone, Type)]
345#[serde(rename_all = "camelCase")]
346pub struct GroupMembership {
347 pub group_id: i64,
348 pub is_primary: bool,
349 pub is_pending_join: bool,
350 pub group_role: Option<GroupMembershipUserRole>,
351 pub permissions: GroupPermissions,
352 pub are_group_games_visible: bool,
353 pub are_group_funds_visible: bool,
354 pub are_enemies_allowed: bool,
355 pub can_configure: bool,
356}
357
358#[derive(Debug, Serialize, Deserialize, Clone, Type)]
359#[serde(rename_all = "camelCase")]
360pub struct GroupMembershipUserRole {
361 pub user: MinimalGroupUser,
362 pub role: GroupRole,
363}
364
365pub async fn membership(jar: &RequestJar, group_id: i64) -> Result<GroupMembership, Box<Error>> {
370 let url = format!(
371 "https://groups.roblox.com/v1/groups/{}/membership",
372 group_id
373 );
374 let response = jar.get_json::<GroupMembership>(&url).await?;
375 Ok(response)
376}
377
378pub async fn members(
383 jar: &RequestJar,
384 group_id: i64,
385 limit: PageLimit,
386 sort_order: Option<SortOrder>,
387) -> Result<Vec<GroupMembershipUserRole>, Box<Error>> {
388 let url = format!(
389 "https://groups.roblox.com/v1/groups/{}/users?sortOrder={}",
390 group_id,
391 sort_order.unwrap_or(SortOrder::Asc).get_sort_order_string()
392 );
393 let response = get_page(jar, url.as_str(), limit, None).await?;
395 Ok(response)
396}
397
398pub async fn pending_requests(jar: &RequestJar) -> Result<Vec<Group>, Box<Error>> {
406 let url = format!("https://groups.roblox.com/v1/user/groups/pending");
407 let response = jar
408 .get_json::<DataWrapper<Vec<Group>>>(url.as_str())
409 .await?;
410 Ok(response.data)
411}
412
413#[derive(Debug, Serialize, Deserialize, Clone, Type)]
414#[serde(rename_all = "camelCase")]
415pub struct FriendGroupsGroupItem {
416 pub group: Group,
417 pub role: GroupRole,
418 pub is_primary_group: Option<bool>,
419}
420
421#[derive(Debug, Serialize, Deserialize, Clone, Type)]
422#[serde(rename_all = "camelCase")]
423pub struct FriendGroupsItem {
424 pub user: MinimalGroupUser,
425 pub groups: Vec<FriendGroupsGroupItem>,
426}
427
428pub async fn friend_groups(jar: &RequestJar) -> Result<Vec<FriendGroupsItem>, Box<Error>> {
433 let user_id = whoami(jar).await?.id;
434 let url = format!(
435 "https://groups.roblox.com/v1/users/{}/friends/groups/roles",
436 user_id
437 );
438 let response = jar
439 .get_json::<DataWrapper<Vec<FriendGroupsItem>>>(url.as_str())
440 .await?;
441 Ok(response.data)
442}
443
444#[derive(Debug, Serialize, Deserialize, Clone, Type)]
445#[serde(rename_all = "camelCase")]
446pub struct UserMembershipsGroupItem {
447 pub group: MinimalGroup,
448 pub role: GroupRole,
449}
450
451pub async fn user_memberships(
457 jar: &RequestJar,
458 user_id: i64,
459) -> Result<Vec<UserMembershipsGroupItem>, Box<Error>> {
460 let url = format!(
461 "https://groups.roblox.com/v2/users/{}/groups/roles",
462 user_id
463 );
464 let response = jar
465 .get_json::<DataWrapper<Vec<UserMembershipsGroupItem>>>(url.as_str())
466 .await?;
467 Ok(response.data)
468}
469
470#[derive(Debug, Serialize, Deserialize, Clone, Type)]
471#[serde(rename_all = "camelCase")]
472pub struct GroupOwnershipChangeRequest {
473 pub user_id: i64,
474}
475
476pub async fn change_owner(jar: &RequestJar, group_id: i64, user_id: i64) -> Result<(), Box<Error>> {
486 let url = format!(
487 "https://groups.roblox.com/v1/groups/{}/change-owner",
488 group_id
489 );
490 let request = GroupOwnershipChangeRequest { user_id };
491 jar.post_json(url.as_str(), &request).await?;
492 Ok(())
493}
494
495pub async fn claim_ownership(jar: &RequestJar, group_id: i64) -> Result<(), Box<Error>> {
504 let url = format!(
505 "https://groups.roblox.com/v1/groups/{}/claim-ownership",
506 group_id
507 );
508 jar.post(url.as_str(), "".to_string()).await?;
509 Ok(())
510}