From c14dc2258c2f29c2bace65c5994edc6cc9ce845c Mon Sep 17 00:00:00 2001 From: Hipposgrumm Date: Tue, 29 Jul 2025 20:44:59 -0600 Subject: [PATCH] Clan System, please contact me if you have any questions. Please only contact me if you're Clan System /j --- README.md | 14 +- src/Controllers/Common/GroupController.cs | 545 ++++++++++++++++++-- src/Controllers/Common/ProfileController.cs | 8 +- src/Model/DBContext.cs | 20 +- src/Model/Group.cs | 24 +- src/Model/GroupJoinRequest.cs | 15 + src/Model/GroupViking.cs | 35 ++ src/Model/Viking.cs | 2 +- src/Schema/AssignRoleRequest.cs | 25 + src/Schema/AssignRoleResult.cs | 16 + src/Schema/AssignRoleStatus.cs | 18 + src/Schema/AuthorizeJoinRequest.cs | 25 + src/Schema/AuthorizeJoinResult.cs | 13 + src/Schema/AuthorizeJoinStatus.cs | 24 + src/Schema/CreateGroupRequest.cs | 40 ++ src/Schema/CreateGroupResult.cs | 16 + src/Schema/CreateGroupStatus.cs | 32 ++ src/Schema/EditGroupRequest.cs | 37 ++ src/Schema/EditGroupResult.cs | 16 + src/Schema/EditGroupStatus.cs | 28 + src/Schema/GetGroupsRequest.cs | 40 ++ src/Schema/GetGroupsResult.cs | 16 + src/Schema/GetPendingJoinRequest.cs | 16 + src/Schema/GetPendingJoinResult.cs | 14 + src/Schema/GroupJoinRequestStatus.cs | 16 + src/Schema/GroupJoinResult.cs | 15 + src/Schema/GroupMember.cs | 35 ++ src/Schema/GroupsFilter.cs | 16 + src/Schema/JoinGroupRequest.cs | 31 ++ src/Schema/JoinGroupStatus.cs | 28 + src/Schema/LeaveGroupRequest.cs | 19 + src/Schema/LeaveGroupResult.cs | 13 + src/Schema/LeaveGroupStatus.cs | 12 + src/Schema/PendingJoinRequest.cs | 22 + src/Schema/RemoveMemberRequest.cs | 22 + src/Schema/RemoveMemberResult.cs | 13 + src/Schema/RemoveMemberStatus.cs | 16 + src/Schema/RolePermission.cs | 16 + src/Schema/UserProfileGroupData.cs | 2 +- src/Schema/UserRole.cs | 12 + 40 files changed, 1257 insertions(+), 70 deletions(-) create mode 100644 src/Model/GroupJoinRequest.cs create mode 100644 src/Model/GroupViking.cs create mode 100644 src/Schema/AssignRoleRequest.cs create mode 100644 src/Schema/AssignRoleResult.cs create mode 100644 src/Schema/AssignRoleStatus.cs create mode 100644 src/Schema/AuthorizeJoinRequest.cs create mode 100644 src/Schema/AuthorizeJoinResult.cs create mode 100644 src/Schema/AuthorizeJoinStatus.cs create mode 100644 src/Schema/CreateGroupRequest.cs create mode 100644 src/Schema/CreateGroupResult.cs create mode 100644 src/Schema/CreateGroupStatus.cs create mode 100644 src/Schema/EditGroupRequest.cs create mode 100644 src/Schema/EditGroupResult.cs create mode 100644 src/Schema/EditGroupStatus.cs create mode 100644 src/Schema/GetGroupsRequest.cs create mode 100644 src/Schema/GetGroupsResult.cs create mode 100644 src/Schema/GetPendingJoinRequest.cs create mode 100644 src/Schema/GetPendingJoinResult.cs create mode 100644 src/Schema/GroupJoinRequestStatus.cs create mode 100644 src/Schema/GroupJoinResult.cs create mode 100644 src/Schema/GroupMember.cs create mode 100644 src/Schema/GroupsFilter.cs create mode 100644 src/Schema/JoinGroupRequest.cs create mode 100644 src/Schema/JoinGroupStatus.cs create mode 100644 src/Schema/LeaveGroupRequest.cs create mode 100644 src/Schema/LeaveGroupResult.cs create mode 100644 src/Schema/LeaveGroupStatus.cs create mode 100644 src/Schema/PendingJoinRequest.cs create mode 100644 src/Schema/RemoveMemberRequest.cs create mode 100644 src/Schema/RemoveMemberResult.cs create mode 100644 src/Schema/RemoveMemberStatus.cs create mode 100644 src/Schema/RolePermission.cs create mode 100644 src/Schema/UserRole.cs diff --git a/README.md b/README.md index afc55f1..f7d6ba2 100644 --- a/README.md +++ b/README.md @@ -79,13 +79,13 @@ Almost everything: - hideouts - farms - minigames +- clans - MMO (using sodoff-mmo) ### What doesn't work - play as Guest - friends -- clans - in-game messaging system (Terrible Mail) @@ -94,10 +94,13 @@ Almost everything: #### Fully implemented - AcceptMission - AddBattleItems +- AssignRole - AuthenticateUser +- CreateGroup - CreatePet - DeleteAccountNotification - DeleteProfile +- EditGroup - FuseItems - GetAchievementsByUserID - GetAllActivePetsByuserId @@ -108,12 +111,17 @@ Almost everything: - GetDetailedChildList - GetGameData - GetGameDataByUser +- GetGroups +- GetGroupsByGroupType +- GetGroupsByUserID - GetImage - GetImageByUserId - GetItem - GetKeyValuePair - GetKeyValuePairByUserID +- GetMembersByGroupID - GetMMOServerInfoWithZone (uses resource xml as response) +- GetPendingJoinRequests - GetPetAchievementsByUserID - GetSelectedRaisedPet - GetStore @@ -127,6 +135,8 @@ Almost everything: - GetUserRoomItemPositions - GetUserUpcomingMissionState - IsValidApiToken_V2 +- JoinGroup (V1 & V2) (User-given message for join request disabled, at least for now) +- LeaveGroup - LoginChild - LoginParent - PurchaseItems (V1) @@ -152,7 +162,6 @@ Almost everything: #### Implemented enough (probably) - GetCommonInventory (V1 - returns the viking's inventory if it is called with a viking; otherwise returns 8 viking slots) -- GetGroupsByGroupType (only useful for Eat My Dust at the moment) - GetQuestions (doesn't return all questions, probably doesn't need to) - GetRules (doesn't return any rules, probably doesn't need to) - GetSubscriptionInfo (always returns member, with end date 10 years from now) @@ -171,7 +180,6 @@ Almost everything: - GetTopAchievementPointUsers (ignores type [all, buddy, hall of fame, ...] and mode [overall, monthly, weekly] properties) - GetUserAchievements (used by Magic & Mythies) - GetUserRoomList (room categories are not implemented, but it's enough for SoD) -- JoinGroup (for Eat My Dust only) - ProcessRewardedItems (gives gems, but doesn't give gold, gold is not yet implemented) - SellItems (gives gems, but doesn't give gold, gold is not yet implemented) - SetUserAchievementTask (returns a real reward but still use task placeholder) diff --git a/src/Controllers/Common/GroupController.cs b/src/Controllers/Common/GroupController.cs index 8ee0d9c..ddd6ceb 100644 --- a/src/Controllers/Common/GroupController.cs +++ b/src/Controllers/Common/GroupController.cs @@ -5,7 +5,57 @@ using sodoff.Schema; using sodoff.Util; namespace sodoff.Controllers.Common; + public class GroupController : Controller { + private static readonly List RolePermissions; + + static GroupController() { + RolePermissions = new List(); + for (GroupType type = GroupType.Public; type <= GroupType.Private; type++) { + // Anything commented out is not implemented. + RolePermissions.Add(new RolePermission { + GroupType = type, + Role = UserRole.Member, + Permissions = [ + "Delete Own Msg", + //"Post Message" + ] + }); + RolePermissions.Add(new RolePermission { + GroupType = type, + Role = UserRole.Elder, + Permissions = [ + //"Invite", + "Approve Join Request", + //"Post News", + //"Delete Own Msg", + //"Delete Any Msg", + //"Delete News", + //"Post Message", + "Remove Member" + ] + }); + RolePermissions.Add(new RolePermission { + GroupType = type, + Role = UserRole.Leader, + Permissions = [ + //"Invite", + "Approve Join Request", + "Assign Leader", + "Assign Elder", + "Demote Elder", + "Edit Group", + //"Post News", + //"Delete Own Msg", + //"Delete Any Msg", + //"Delete News", + //"Post Message", + "Remove Member" + ] + }); + } + } + public static readonly Schema.Group EMD_Dragons = new Schema.Group { GroupID = "8e68214a-c801-4759-8461-d01f28484134", Name = "Dragons", @@ -24,56 +74,7 @@ public class GroupController : Controller { public GroupController(DBContext ctx) { this.ctx = ctx; - } - [HttpPost] - [Produces("application/xml")] - [Route("GroupWebService.asmx/JoinGroup")] - [VikingSession] - public IActionResult JoinGroup(Viking viking, [FromForm] string apiKey, [FromForm] string groupID) { - AddEMDGroups(); - uint version = ClientVersion.GetVersion(apiKey); - - // Only implemented for EMD so far. - if (version == ClientVersion.EMD) { - if (viking.Groups.Any(g => { - // Check for loyalty. - string id = g.GroupID.ToString(); - return id == EMD_Dragons.GroupID || id == EMD_Scorpions.GroupID; - })) { - return Ok(new JoinGroupResult { GroupStatus = GroupMembershipStatus.ALREADY_MEMBER }); - } - groupID = groupID.ToUpper(); - Model.Group? group = ctx.Groups.FirstOrDefault(g => g.GroupID.ToString() == groupID); - if (group != null) { - group.Vikings.Add(viking); - ctx.SaveChanges(); - return Ok(new JoinGroupResult { GroupStatus = GroupMembershipStatus.APPROVED }); - } - } - return Ok(new JoinGroupResult { GroupStatus = GroupMembershipStatus.REJECTED }); - } - - [HttpPost] - [Produces("application/xml")] - [Route("GroupWebService.asmx/GetGroupsByGroupType")] - [VikingSession] - public Schema.Group[] GetGroupsByGroupType([FromForm] string apiKey, [FromForm] string groupType) { - AddEMDGroups(); - List groups = new List(); - foreach (Model.Group group in ctx.Groups) { - if (group.ApiKey == apiKey && group.Type.ToString() == groupType) groups.Add(new Schema.Group { - GroupID = group.GroupID.ToString(), - Name = group.Name, - Color = group.Color, - Logo = group.Logo, - Type = group.Type - }); - } - return groups.ToArray(); - } - - private void AddEMDGroups() { bool changed = false; Guid DragonString = new Guid(EMD_Dragons.GroupID); Guid ScorpionString = new Guid(EMD_Scorpions.GroupID); @@ -84,7 +85,8 @@ public class GroupController : Controller { Color = EMD_Dragons.Color, Logo = EMD_Dragons.Logo, Type = GroupType.System, - ApiKey = "dd602cf1-cc98-4738-9a0a-56dde3026947" + GameID = ClientVersion.EMD, + MaxMemberLimit = int.MaxValue }); changed = true; } @@ -95,10 +97,451 @@ public class GroupController : Controller { Color = EMD_Scorpions.Color, Logo = EMD_Scorpions.Logo, Type = GroupType.System, - ApiKey = "dd602cf1-cc98-4738-9a0a-56dde3026947" + GameID = ClientVersion.EMD, + MaxMemberLimit = int.MaxValue }); changed = true; } if (changed) ctx.SaveChanges(); } + + [HttpPost] + [Produces("application/xml")] + [Route("V2/GroupWebService.asmx/CreateGroup")] + [VikingSession] + public IActionResult CreateGroup(Viking viking, [FromForm] string apiKey, [FromForm] string groupCreateRequest) { + uint gameId = ClientVersion.GetGameID(apiKey); + + if (viking.GroupRoles.Any(g => g.GameID == gameId)) { + return Ok(new CreateGroupResult { Success = false, Status = CreateGroupStatus.CreatorIsNotApproved }); + } + + CreateGroupRequest request = XmlUtil.DeserializeXml(groupCreateRequest); + request.Name = request.Name.Trim(); + + // Cue the gauntlet of validity checks. + if (request.Type <= GroupType.System) return Ok(new CreateGroupResult { Success = false, Status = CreateGroupStatus.GroupTypeIsInvalid }); + //if (request.MaxMemberLimit < 4) return Ok(new CreateGroupResult { Success = false, Status = CreateGroupStatus.GroupMaxMemberLimitInvalid }); // Not actually used by the game. + if (request.Name.Length == 0) return Ok(new CreateGroupResult { Success = false, Status = CreateGroupStatus.GroupNameIsEmpty }); + if (request.Description.Length == 0) return Ok(new CreateGroupResult { Success = false, Status = CreateGroupStatus.GroupDescriptionIsEmpty }); + if (viking.GroupRoles.Any(g => g.Name.Equals(request.Name, StringComparison.InvariantCultureIgnoreCase))) return Ok(new CreateGroupResult { Success = false, Status = CreateGroupStatus.GroupNameIsDuplicate }); + + Model.Group group = new Model.Group { + Name = request.Name, + Description = request.Description, + Logo = request.Logo, + Color = request.Color, + Type = request.Type, + CreateDate = DateTime.Now, + GameID = gameId, + MaxMemberLimit = 50, + GroupID = Guid.NewGuid(), + Vikings = new List() + }; + + ctx.Groups.Add(group); + group.Vikings.Add(new GroupViking { + Viking = viking, + Group = group, + UserRole = UserRole.Leader, + JoinDate = group.CreateDate + }); + group.LastActiveTime = group.CreateDate; + ctx.SaveChanges(); + + return Ok(new CreateGroupResult { + Success = true, + Status = CreateGroupStatus.Success, + Group = new Schema.Group { + Name = group.Name, + Description = group.Description, + Logo = group.Logo, + Color = group.Color, + Type = group.Type, + OwnerID = viking.Uid.ToString(), + Points = 0,//group.Points, + Active = false,//group.Vikings.Count >= 4, + MemberLimit = group.MaxMemberLimit, + GroupID = group.GroupID.ToString() + } + // TODO: Delete after 15 days of less than 4 members. + }); + } + + [HttpPost] + [Produces("application/xml")] + [Route("V2/GroupWebService.asmx/EditGroup")] + [VikingSession] + public IActionResult EditGroup(Viking viking, [FromForm] string apiKey, [FromForm] string groupEditRequest) { + EditGroupRequest request = XmlUtil.DeserializeXml(groupEditRequest); + request.Name = request.Name.Trim(); + + GroupViking? vikingRole = viking.GroupRoles.FirstOrDefault(gv => gv.Group.GroupID.ToString() == request.GroupID); + if (vikingRole == null) { + return Ok(new EditGroupResult { Success = false, Status = EditGroupStatus.GroupNotFound }); + } else if (vikingRole.UserRole < UserRole.Elder) { + return Ok(new EditGroupResult { Success = false, Status = EditGroupStatus.PermissionDenied }); + } + + // Cue the gauntlet of validity checks. + if (request.Type != null && request.Type <= GroupType.System) return Ok(new EditGroupResult { Success = false, Status = EditGroupStatus.GroupTypeIsInvalid }); + //if (request.MaxMemberLimit < 4) return Ok(new EditGroupResult { Success = false, Status = EditGroupStatus.GroupMaxMemberLimitInvalid }); // Not actually used by the game. + if ((request.Name?.Length ?? 0) == 0) request.Name = vikingRole.Name; + if ((request.Description?.Length ?? 0) == 0) request.Description = vikingRole.Description; + if (request.Name != vikingRole.Name && viking.GroupRoles.Any(g => g.Name.Equals(request.Name, StringComparison.InvariantCultureIgnoreCase))) return Ok(new EditGroupResult { Success = false, Status = EditGroupStatus.GroupNameIsDuplicate }); + + vikingRole.Group.Name = request.Name; + vikingRole.Group.Description = request.Description; + if (request.Type != null) vikingRole.Group.Type = (GroupType)request.Type; + if ((request.Color?.Length ?? 0) > 0) vikingRole.Group.Color = request.Color; + if ((request.Logo?.Length ?? 0) > 0) vikingRole.Group.Logo = request.Logo; + ctx.SaveChanges(); + + return Ok(new EditGroupResult { + Success = true, + Status = EditGroupStatus.Success, + NewRolePermissions = RolePermissions + }); + } + + [HttpPost] + [Produces("application/xml")] + [Route("GroupWebService.asmx/JoinGroup")] + [VikingSession] + public IActionResult JoinGroupV1(Viking viking, [FromForm] string apiKey, [FromForm] string groupID) { + uint gameId = ClientVersion.GetGameID(apiKey); + + // Check for loyalty. + if (viking.GroupRoles.Any(g => g.GameID == gameId)) { + return Ok(new JoinGroupResult { GroupStatus = GroupMembershipStatus.SELF_BLOCKED }); + } + Model.Group? group = ctx.Groups.FirstOrDefault(g => g.GroupID.ToString().Equals(groupID, StringComparison.OrdinalIgnoreCase)); + if (group != null) { + // This check is only on this side to prevent people from attempting to circumvent the join limit. + if (group.Type <= GroupType.System || group.Vikings.Count < group.MaxMemberLimit) { + group.Vikings.Add(new GroupViking { + Viking = viking, + Group = group, + UserRole = UserRole.Member + }); + ctx.SaveChanges(); + return Ok(new JoinGroupResult { GroupStatus = GroupMembershipStatus.APPROVED }); + } + } + return Ok(new JoinGroupResult { GroupStatus = GroupMembershipStatus.REJECTED }); + } + + [HttpPost] + [Produces("application/xml")] + [Route("V2/GroupWebService.asmx/JoinGroup")] + [VikingSession] + public IActionResult JoinGroup(Viking viking, [FromForm] string apiKey, [FromForm] string groupJoinRequest) { + uint gameId = ClientVersion.GetGameID(apiKey); + + JoinGroupRequest request = XmlUtil.DeserializeXml(groupJoinRequest); + Model.Group? group = ctx.Groups.FirstOrDefault(g => g.GroupID.ToString() == request.GroupID.ToUpper()); + if (group != null) { + if (group.Type >= GroupType.Private) { + return Ok(new GroupJoinResult { Success = false, Status = JoinGroupStatus.GroupTypeIsNotPublic }); + } + GroupViking? existing = viking.GroupRoles.FirstOrDefault(g => g.GameID == gameId); + if (existing != null) { + if (existing.Group == group) + return Ok(new GroupJoinResult { Success = false, Status = JoinGroupStatus.UserAlreadyMemberOfTheGroup }); + existing.Group.Vikings.Remove(existing); + if (!existing.Group.Vikings.Any()) ctx.Groups.Remove(existing.Group); + } + if (group.Type == GroupType.MembersOnly) { + if (!group.JoinRequests.Any(r => r.Viking == viking)) + group.JoinRequests.Add(new GroupJoinRequest { + Group = group, + Viking = viking, + //Message = request.Message // For future implemention, once moderation is possible. + }); + ctx.SaveChanges(); + return Ok(new GroupJoinResult { Success = false, Status = JoinGroupStatus.JoinRequestPending }); + } + + if (group.Vikings.Count < group.MaxMemberLimit) { + GroupViking joinee = new GroupViking { + Viking = viking, + Group = group, + UserRole = UserRole.Member, + JoinDate = DateTime.Now + }; + group.Vikings.Add(joinee); + group.LastActiveTime = joinee.JoinDate; + ctx.SaveChanges(); + return Ok(new GroupJoinResult { Success = true, Status = JoinGroupStatus.Success }); + } else { + return Ok(new GroupJoinResult { Success = false, Status = JoinGroupStatus.GroupIsFull }); + } + } + return Ok(new GroupJoinResult { Success = false, Status = JoinGroupStatus.Error }); + } + + [HttpPost] + [Produces("application/xml")] + [Route("V2/GroupWebService.asmx/LeaveGroup")] + [VikingSession] + public IActionResult LeaveGroup(Viking viking, [FromForm] string groupLeaveRequest) { + LeaveGroupRequest request = XmlUtil.DeserializeXml(groupLeaveRequest); + GroupViking? vikingRole = viking.GroupRoles.FirstOrDefault(g => g.Group.GroupID.ToString() == request.GroupID); + if (vikingRole != null) { + GroupViking? targetRole = null; + if (viking.Uid.ToString().Equals(request.UserID, StringComparison.CurrentCultureIgnoreCase)) { + targetRole = vikingRole.Group.Vikings.FirstOrDefault(gv => gv.Viking == viking); + } else if (vikingRole.UserRole >= UserRole.Elder) { + targetRole = vikingRole.Group.Vikings.FirstOrDefault(gv => gv.Viking.Uid.ToString() == request.UserID); + } else { + return Ok(new LeaveGroupResult { Success = false, Status = LeaveGroupStatus.Error }); + } + if (targetRole != null) { + vikingRole.Group.Vikings.Remove(targetRole); + if (!vikingRole.Group.Vikings.Any()) ctx.Groups.Remove(vikingRole.Group); + ctx.SaveChanges(); + return Ok(new LeaveGroupResult { Success = true, Status = LeaveGroupStatus.Success }); + } + return Ok(new LeaveGroupResult { Success = false, Status = LeaveGroupStatus.UserNotAMemberOfTheGroup }); + } + return Ok(new LeaveGroupResult { Success = false, Status = LeaveGroupStatus.Error }); + } + + [HttpPost] + [Produces("application/xml")] + [Route("V2/GroupWebService.asmx/GetGroups")] + public IActionResult GetGroups([FromForm] string apiKey, [FromForm] string getGroupsRequest) { + uint gameId = ClientVersion.GetGameID(apiKey); + + GetGroupsRequest request = XmlUtil.DeserializeXml(getGroupsRequest); + IEnumerable groups = ctx.Groups; + if (request.ForUserID != null) { + Viking? target = ctx.Vikings.FirstOrDefault(v => request.ForUserID.ToUpper() == v.Uid.ToString()); + if (target == null) return Ok(new GetGroupsResult { Success = false }); + groups = target.GroupRoles.Select(gv => gv.Group); + } else { + groups = groups.Where(g => g.Type == GroupType.Public || g.Type == GroupType.MembersOnly); + } + if (request.Name != null) { + groups = groups.Where(g => g.Name?.Contains(request.Name, StringComparison.InvariantCultureIgnoreCase) == true); + } + int skip = 0; + if (request.PageSize != null) { + if ((request.PageNo ?? 0) > 1) skip = (int)((request.PageNo! - 1) * request.PageSize); + if (skip > 0) groups = groups.Skip(skip); + groups = groups.Take((int) request.PageSize); + } + groups = groups.Where(g => g.GameID == gameId).OrderByDescending(g => g.Points); + + return Ok(new GetGroupsResult { + Success = true, + Groups = groups.Select((g, i) => { + Schema.Group group = new Schema.Group { + Name = g.Name, + Description = g.Description, + GroupID = g.GroupID.ToString(), + OwnerID = (g.Vikings.FirstOrDefault(v => v.UserRole == UserRole.Leader)?.Viking?.Uid ?? Guid.Empty).ToString(), + Color = g.Color, + Logo = g.Logo, + Type = g.Type, + Rank = i+skip+1, + MemberLimit = g.MaxMemberLimit + }; + if (request.IncludeMemberCount) group.TotalMemberCount = g.Vikings.Count; + return group; + }).ToArray(), + RolePermissions = RolePermissions + }); + } + + [HttpPost] + [Produces("application/xml")] + [Route("V2/GroupWebService.asmx/RemoveMember")] + [VikingSession] + public IActionResult RemoveMember(Viking viking, [FromForm] string removeMemberRequest) { + RemoveMemberRequest request = XmlUtil.DeserializeXml(removeMemberRequest); + GroupViking? vikingRole = viking.GroupRoles.FirstOrDefault(g => g.Group.GroupID.ToString() == request.GroupID); + if (vikingRole != null) { + if (vikingRole.UserRole < UserRole.Elder) { + return Ok(new RemoveMemberResult { Success = false, Status = RemoveMemberStatus.UserHasNoPermission }); + } + GroupViking? targetRole = vikingRole.Group.Vikings.FirstOrDefault(gv => gv.Viking.Uid.ToString() == request.RemoveUserID); + if (targetRole != null) { + vikingRole.Group.Vikings.Remove(targetRole); + if (!vikingRole.Group.Vikings.Any()) ctx.Groups.Remove(vikingRole.Group); + ctx.SaveChanges(); + return Ok(new RemoveMemberResult { Success = true, Status = RemoveMemberStatus.Success }); + } + return Ok(new RemoveMemberResult { Success = false, Status = RemoveMemberStatus.UserNotAMemberOfTheGroup }); + } + return Ok(new RemoveMemberResult { Success = false, Status = RemoveMemberStatus.Error }); + } + + [HttpPost] + [Produces("application/xml")] + [Route("V2/GroupWebService.asmx/AuthorizeJoinRequest")] + [VikingSession] + public IActionResult AuthorizeJoinRequest(Viking viking, [FromForm] string apiKey, [FromForm] string authorizeJoinRequest) { + uint gameId = ClientVersion.GetGameID(apiKey); + + AuthorizeJoinRequest request = XmlUtil.DeserializeXml(authorizeJoinRequest); + GroupViking? vikingRole = viking.GroupRoles.FirstOrDefault(g => g.Group.GroupID.ToString() == request.GroupID); + if (vikingRole != null) { + if (vikingRole.UserRole < UserRole.Elder) { + return Ok(new AuthorizeJoinResult { Success = false, Status = AuthorizeJoinStatus.ApproverHasNoPermission }); + } + Viking? target = ctx.Vikings.FirstOrDefault(v => v.Uid.ToString() == request.UserID); + if (target != null) { + GroupViking? existing = target.GroupRoles.FirstOrDefault(g => g.GameID == gameId); + if (existing != null) { + if (existing.Group == vikingRole.Group) { + return Ok(new AuthorizeJoinResult { Success = false, Status = AuthorizeJoinStatus.UserAlreadyMemberOfTheGroup }); + } else { + return Ok(new AuthorizeJoinResult { Success = false, Status = AuthorizeJoinStatus.UserHasNoJoinRequest }); + } + } + if (vikingRole.Group.Vikings.Count < vikingRole.Group.MaxMemberLimit) { + if (request.Approved) { + GroupViking joinee = new GroupViking { + Viking = target, + Group = vikingRole.Group, + UserRole = UserRole.Member, + JoinDate = DateTime.Now + }; + vikingRole.Group.Vikings.Add(joinee); + vikingRole.Group.LastActiveTime = joinee.JoinDate; + } + GroupJoinRequest? joinRequest = ctx.GroupJoinRequests.Find(target.Id, vikingRole.GroupID); + if (joinRequest != null) ctx.GroupJoinRequests.Remove(joinRequest); + ctx.SaveChanges(); + return Ok(new AuthorizeJoinResult { Success = true, Status = AuthorizeJoinStatus.Success }); + } else { + return Ok(new AuthorizeJoinResult { Success = false, Status = AuthorizeJoinStatus.GroupIsFull }); + } + } else { + return Ok(new AuthorizeJoinResult { Success = false, Status = AuthorizeJoinStatus.Error }); + } + } + return Ok(new AuthorizeJoinResult { Success = false, Status = AuthorizeJoinStatus.ApproverNotInThisGroup }); + } + + [HttpPost] + [Produces("application/xml")] + [Route("V2/GroupWebService.asmx/AssignRole")] + [VikingSession] + public IActionResult AssignRole(Viking viking, [FromForm] string assignRoleRequest) { + AssignRoleRequest request = XmlUtil.DeserializeXml(assignRoleRequest); + GroupViking? vikingRole = viking.GroupRoles.FirstOrDefault(g => g.Group.GroupID.ToString() == request.GroupID); + if (vikingRole != null) { + if (vikingRole.UserRole < UserRole.Elder) { + return Ok(new AssignRoleResult { Success = false, Status = AssignRoleStatus.ApproverHasNoPermission }); + } + GroupViking? targetRole = vikingRole.Group.Vikings.FirstOrDefault(gv => gv.Viking.Uid.ToString() == request.MemberID); + if (targetRole != null) { + if (targetRole.UserRole == request.NewRole) + return Ok(new AssignRoleResult { Success = false, Status = AssignRoleStatus.MemberAlreadyInTheRole }); + if (vikingRole.UserRole == UserRole.Leader) { // Disallow leader from simply demoting themself. + if (viking == targetRole.Viking) + return Ok(new AssignRoleResult { Success = false, Status = AssignRoleStatus.ApproverHasNoPermission }); + } else if (viking != targetRole.Viking || request.NewRole > vikingRole.UserRole) { // Disallow Elders from promoting themselves to leader, or promoting anyone else to elder, but allow them to demote themselves. + return Ok(new AssignRoleResult { Success = false, Status = AssignRoleStatus.ApproverHasNoPermission }); + } + targetRole.UserRole = request.NewRole; + if (request.NewRole == UserRole.Leader) vikingRole.UserRole = UserRole.Elder; // This is the only way a leader can demote themself. + ctx.SaveChanges(); + return Ok(new AssignRoleResult { Success = true, Status = AssignRoleStatus.Success }); + } else { + return Ok(new AssignRoleResult { Success = false, Status = AssignRoleStatus.MemberNotPartOfTheGroup }); + } + } + return Ok(new AssignRoleResult { Success = false, Status = AssignRoleStatus.ApproverNotMemberOfTheGroup }); + } + + [HttpPost] + [Produces("application/xml")] + [Route("V2/GroupWebService.asmx/GetPendingJoinRequest")] + [VikingSession] + public IActionResult GetPendingJoinRequests(Viking viking, [FromForm] string getPendingJoinRequest) { + GetPendingJoinRequest request = XmlUtil.DeserializeXml(getPendingJoinRequest); + GroupViking? vikingRole = viking.GroupRoles.FirstOrDefault(g => g.Group.GroupID.ToString() == request.GroupID); + if (vikingRole?.UserRole >= UserRole.Elder) { + return Ok(new GetPendingJoinResult { + Success = true, + Requests = vikingRole.Group.JoinRequests + .Select(r => { + PendingJoinRequest req = new PendingJoinRequest { + UserID = r.Viking.Uid.ToString(), + GroupID = vikingRole.Group.GroupID.ToString(), + StatusID = GroupJoinRequestStatus.Pending, + Message = r.Message ?? "Hello! Please invite me to your Crew!" // Defualt from Math Blaster btw + }; + req.FromUserID = req.UserID; + return req; + }).ToArray() + }); + } + return Ok(new GetPendingJoinResult { Success = false }); + } + + [HttpPost] + [Produces("application/xml")] + [Route("GroupWebService.asmx/GetGroupsByUserID")] + public Schema.Group[] GetGroupsByUserID([FromForm] string apiKey, [FromForm] string userId) { + uint gameId = ClientVersion.GetGameID(apiKey); + + Viking? viking = ctx.Vikings.FirstOrDefault(v => v.Uid.ToString() == userId); + if (viking == null) return []; + + return viking.GroupRoles.Where(gv => gv.GameID == gameId).Select(gv => new Schema.Group { + GroupID = gv.Group.GroupID.ToString(), + Name = gv.Name, + Color = gv.Color, + Logo = gv.Logo, + MemberLimit = gv.Group.MaxMemberLimit + }).ToArray(); + } + + [HttpPost] + [Produces("application/xml")] + [Route("GroupWebService.asmx/GetGroupsByGroupType")] + public Schema.Group[] GetGroupsByGroupType([FromForm] string apiKey, [FromForm] string groupType) { + uint gameId = ClientVersion.GetGameID(apiKey); + + List groups = new List(); + foreach (Model.Group group in ctx.Groups) { + if (group.GameID == gameId && group.Type.ToString() == groupType) groups.Add(new Schema.Group { + GroupID = group.GroupID.ToString(), + Name = group.Name, + Color = group.Color, + Logo = group.Logo, + Type = group.Type, + MemberLimit = group.MaxMemberLimit + }); + } + return groups.ToArray(); + } + + [HttpPost] + [Produces("application/xml")] + [Route("GroupWebService.asmx/GetMembersByGroupID")] + public GroupMember[] GetMembersByGroupID([FromForm] string groupID) { + groupID = groupID.ToUpper(); + Model.Group? group = ctx.Groups.FirstOrDefault( + g => g.GroupID.ToString() == groupID + ); + if (group == null) return []; + + return group.Vikings.Select(v => new GroupMember{ + DisplayName = v.Viking.AvatarSerialized != null + ? XmlUtil.DeserializeXml(v.Viking.AvatarSerialized).DisplayName + : v.Viking.Name, + UserID = v.Viking.Uid.ToString(), + JoinDate = v.JoinDate, + RoleID = (int) v.UserRole, + Online = false, // There's no way to check this. + Rank = 0, + GroupID = group.GroupID.ToString(), + Points = group.Points + }).ToArray(); + } } diff --git a/src/Controllers/Common/ProfileController.cs b/src/Controllers/Common/ProfileController.cs index c6928c2..68da31a 100644 --- a/src/Controllers/Common/ProfileController.cs +++ b/src/Controllers/Common/ProfileController.cs @@ -152,18 +152,18 @@ public class ProfileController : Controller { UserGameCurrency currency = achievementService.GetUserCurrency(viking); - ICollection groups = viking.Groups; + ICollection groups = viking.GroupRoles; UserProfileGroupData[] groupData = new UserProfileGroupData[groups.Count]; int i = 0; - foreach (Model.Group group in groups) { + foreach (GroupViking group in groups) { groupData[i] = new UserProfileGroupData { - GroupID = group.GroupID.ToString(), + GroupID = group.Group.GroupID.ToString(), Name = group.Name, Color = group.Color, Logo = group.Logo, TypeID = (int)group.Type, - RoleID = 0 + RoleID = group.UserRole }; i++; } diff --git a/src/Model/DBContext.cs b/src/Model/DBContext.cs index 1832763..9d0a38c 100644 --- a/src/Model/DBContext.cs +++ b/src/Model/DBContext.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using sodoff.Configuration; +using System.Text.Json; namespace sodoff.Model; public class DBContext : DbContext { @@ -26,6 +27,8 @@ public class DBContext : DbContext { public DbSet Neighborhoods { get; set; } = null!; // we had a brief debate on whether it's neighborhoods or neighborheed public DbSet Groups { get; set; } = null!; + public DbSet GroupViking { get; set; } = null!; + public DbSet GroupJoinRequests { get; set; } = null!; public DbSet Ratings { get; set; } = null!; public DbSet RatingRanks { get; set; } = null!; public DbSet UserMissionData { get; set; } = null!; @@ -66,6 +69,9 @@ public class DBContext : DbContext { } protected override void OnModelCreating(ModelBuilder builder) { + builder.Entity().HasKey(["VikingID", "GroupID"]); + builder.Entity().HasKey(["VikingID", "GroupID"]); + // Sessions builder.Entity().HasOne(s => s.User) .WithMany(e => e.Sessions) @@ -144,8 +150,8 @@ public class DBContext : DbContext { builder.Entity().HasOne(v => v.Neighborhood) .WithOne(e => e.Viking); - builder.Entity().HasMany(v => v.Groups) - .WithMany(e => e.Vikings); + builder.Entity().HasMany(v => v.GroupRoles) + .WithOne(g => g.Viking); builder.Entity().HasMany(v => v.Ratings) .WithOne(r => r.Viking); @@ -279,7 +285,15 @@ public class DBContext : DbContext { // Groups builder.Entity().HasMany(r => r.Vikings) - .WithMany(e => e.Groups); + .WithOne(v => v.Group); + builder.Entity().HasOne(r => r.Group) + .WithMany(g => g.Vikings); + builder.Entity().HasOne(r => r.Viking) + .WithMany(g => g.GroupRoles); + builder.Entity().HasMany(r => r.JoinRequests) + .WithOne(r => r.Group); + builder.Entity().HasOne(r => r.Group) + .WithMany(g => g.JoinRequests); // Rating builder.Entity().HasOne(r => r.Viking) diff --git a/src/Model/Group.cs b/src/Model/Group.cs index c06d450..b83b14f 100644 --- a/src/Model/Group.cs +++ b/src/Model/Group.cs @@ -3,23 +3,33 @@ using System.ComponentModel.DataAnnotations; namespace sodoff.Model; -// Implementation for EMD, add whatever else if needed public class Group { [Key] public int Id { get; set; } - [Required] public Guid GroupID { get; set; } - public string Name { get; set; } + public string? Name { get; set; } + + public string? Description { get; set; } public GroupType Type { get; set; } - public string Color { get; set; } + public string? Logo { get; set; } - public string Logo { get; set; } + public string? Color { get; set; } - public string ApiKey { get; set; } + public int Points { get; set; } - public virtual ICollection Vikings { get; set; } = null!; + public DateTime CreateDate { get; set; } + + public DateTime? LastActiveTime { get; set; } + + public uint GameID { get; set; } + + public int MaxMemberLimit { get; set; } + + public virtual ICollection Vikings { get; set; } = null!; + + public virtual ICollection JoinRequests { get; set; } = null!; } diff --git a/src/Model/GroupJoinRequest.cs b/src/Model/GroupJoinRequest.cs new file mode 100644 index 0000000..fc84077 --- /dev/null +++ b/src/Model/GroupJoinRequest.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; + +namespace sodoff.Model; + +public class GroupJoinRequest { + [Key] + public int VikingID { get; set; } + [Key] + public int GroupID { get; set; } + + public string? Message { get; set; } + + public virtual Viking Viking { get; set; } = null!; + public virtual Group Group { get; set; } = null!; +} diff --git a/src/Model/GroupViking.cs b/src/Model/GroupViking.cs new file mode 100644 index 0000000..51529f9 --- /dev/null +++ b/src/Model/GroupViking.cs @@ -0,0 +1,35 @@ +using sodoff.Schema; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace sodoff.Model; + +public class GroupViking { + [Key] + public int VikingID { get; set; } + [Key] + public int GroupID { get; set; } + public UserRole UserRole { get; set; } + public DateTime JoinDate { get; set; } + + public virtual Viking Viking { get; set; } = null!; + public virtual Group Group { get; set; } = null!; + + [NotMapped] + public string Name { get { return Group.Name; } } + + [NotMapped] + public string? Description { get { return Group.Description; } } + + [NotMapped] + public GroupType Type { get { return Group.Type; } } + + [NotMapped] + public string Logo { get { return Group.Logo; } } + + [NotMapped] + public string Color { get { return Group.Color; } } + + [NotMapped] + public uint GameID { get { return Group.GameID; } } +} diff --git a/src/Model/Viking.cs b/src/Model/Viking.cs index a787107..d67f0df 100644 --- a/src/Model/Viking.cs +++ b/src/Model/Viking.cs @@ -39,7 +39,7 @@ public class Viking { public virtual ICollection Parties { get; set; } = null!; public virtual ICollection MMORoles { get; set; } = null!; public virtual Neighborhood? Neighborhood { get; set; } = null!; - public virtual ICollection Groups { get; set; } = null!; + public virtual ICollection GroupRoles { get; set; } = null!; public virtual ICollection Ratings { get; set; } = null!; public virtual Dragon? SelectedDragon { get; set; } public virtual ICollection UserMissions { get; set; } = null!; diff --git a/src/Schema/AssignRoleRequest.cs b/src/Schema/AssignRoleRequest.cs new file mode 100644 index 0000000..6022310 --- /dev/null +++ b/src/Schema/AssignRoleRequest.cs @@ -0,0 +1,25 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "AssignRoleRequest", IsNullable = true)] +[Serializable] +public class AssignRoleRequest { + [XmlElement(ElementName = "UserID")] + public string UserID; + + [XmlElement(ElementName = "MemberID")] + public string MemberID; + + [XmlElement(ElementName = "NewRole")] + public UserRole NewRole; + + [XmlElement(ElementName = "GroupID")] + public string GroupID; + + [XmlElement(ElementName = "ProductGroupID")] + public int? ProductGroupID; + + [XmlElement(ElementName = "ProductID")] + public int? ProductID; +} diff --git a/src/Schema/AssignRoleResult.cs b/src/Schema/AssignRoleResult.cs new file mode 100644 index 0000000..59e9526 --- /dev/null +++ b/src/Schema/AssignRoleResult.cs @@ -0,0 +1,16 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "AssignRoleResult", IsNullable = true)] +[Serializable] +public class AssignRoleResult { + [XmlElement(ElementName = "Success")] + public bool Success; + + [XmlElement(ElementName = "InitiatorNewRole", IsNullable = true)] + public UserRole? InitiatorNewRole; + + [XmlElement(ElementName = "Status")] + public AssignRoleStatus Status; +} diff --git a/src/Schema/AssignRoleStatus.cs b/src/Schema/AssignRoleStatus.cs new file mode 100644 index 0000000..dc0c1a8 --- /dev/null +++ b/src/Schema/AssignRoleStatus.cs @@ -0,0 +1,18 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +public enum AssignRoleStatus { + [XmlEnum("1")] + Success = 1, + [XmlEnum("2")] + Error, + [XmlEnum("3")] + ApproverNotMemberOfTheGroup, + [XmlEnum("4")] + ApproverHasNoPermission, + [XmlEnum("5")] + MemberAlreadyInTheRole, + [XmlEnum("6")] + MemberNotPartOfTheGroup +} diff --git a/src/Schema/AuthorizeJoinRequest.cs b/src/Schema/AuthorizeJoinRequest.cs new file mode 100644 index 0000000..8847d49 --- /dev/null +++ b/src/Schema/AuthorizeJoinRequest.cs @@ -0,0 +1,25 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "AuthorizeJoinRequest", IsNullable = true)] +[Serializable] +public class AuthorizeJoinRequest { + [XmlElement(ElementName = "Approved")] + public bool Approved; + + [XmlElement(ElementName = "JoineeID")] + public string JoineeID; + + [XmlElement(ElementName = "UserID")] + public string UserID; + + [XmlElement(ElementName = "GroupID")] + public string GroupID; + + [XmlElement(ElementName = "ProductGroupID")] + public int? ProductGroupID; + + [XmlElement(ElementName = "ProductID")] + public int? ProductID; +} diff --git a/src/Schema/AuthorizeJoinResult.cs b/src/Schema/AuthorizeJoinResult.cs new file mode 100644 index 0000000..aadddc7 --- /dev/null +++ b/src/Schema/AuthorizeJoinResult.cs @@ -0,0 +1,13 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "AuthorizeJoinResult", IsNullable = true)] +[Serializable] +public class AuthorizeJoinResult { + [XmlElement(ElementName = "Success")] + public bool Success; + + [XmlElement(ElementName = "Status")] + public AuthorizeJoinStatus Status; +} diff --git a/src/Schema/AuthorizeJoinStatus.cs b/src/Schema/AuthorizeJoinStatus.cs new file mode 100644 index 0000000..ad40cf1 --- /dev/null +++ b/src/Schema/AuthorizeJoinStatus.cs @@ -0,0 +1,24 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +public enum AuthorizeJoinStatus { + [XmlEnum("1")] + Success = 1, + [XmlEnum("2")] + Error, + [XmlEnum("3")] + GroupIsFull, + [XmlEnum("4")] + GroupTypeIsNotPublic, + [XmlEnum("5")] + UserAlreadyMemberOfTheGroup, + [XmlEnum("6")] + GroupTypeIsPublic, + [XmlEnum("7")] + ApproverNotInThisGroup, + [XmlEnum("8")] + ApproverHasNoPermission, + [XmlEnum("9")] + UserHasNoJoinRequest +} diff --git a/src/Schema/CreateGroupRequest.cs b/src/Schema/CreateGroupRequest.cs new file mode 100644 index 0000000..890602b --- /dev/null +++ b/src/Schema/CreateGroupRequest.cs @@ -0,0 +1,40 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "CreateGroupRequest", IsNullable = true)] +[Serializable] +public class CreateGroupRequest { + [XmlElement(ElementName = "Name")] + public string Name; + + [XmlElement(ElementName = "Description")] + public string Description; + + [XmlElement(ElementName = "Type")] + public GroupType Type; + + [XmlElement(ElementName = "Logo")] + public string Logo; + + [XmlElement(ElementName = "Color")] + public string Color; + + [XmlElement(ElementName = "UserID")] + public string UserID; + + [XmlElement(ElementName = "DefaultLeaderID")] + public string DefaultLeaderID; + + [XmlElement(ElementName = "ParentGroupID")] + public string ParentGroupID; + + [XmlElement(ElementName = "ProductGroupID")] + public int? ProductGroupID; + + [XmlElement(ElementName = "ProductID")] + public int? ProductID; + + [XmlElement(ElementName = "MaxMemberLimit")] + public int? MaxMemberLimit; +} diff --git a/src/Schema/CreateGroupResult.cs b/src/Schema/CreateGroupResult.cs new file mode 100644 index 0000000..c08cfc3 --- /dev/null +++ b/src/Schema/CreateGroupResult.cs @@ -0,0 +1,16 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "CreateGroupResult", IsNullable = true)] +[Serializable] +public class CreateGroupResult { + [XmlElement(ElementName = "Success")] + public bool Success; + + [XmlElement(ElementName = "Status")] + public CreateGroupStatus Status; + + [XmlElement(ElementName = "Group")] + public Group Group; +} \ No newline at end of file diff --git a/src/Schema/CreateGroupStatus.cs b/src/Schema/CreateGroupStatus.cs new file mode 100644 index 0000000..72c965c --- /dev/null +++ b/src/Schema/CreateGroupStatus.cs @@ -0,0 +1,32 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +public enum CreateGroupStatus { + [XmlEnum("1")] + Success = 1, + [XmlEnum("2")] + Error, + [XmlEnum("3")] + GroupNameInvalid, + [XmlEnum("4")] + GroupDescriptionInvalid, + [XmlEnum("5")] + GroupNameIsDuplicate, + [XmlEnum("6")] + GroupOwnerHasInSufficientCurrency, + [XmlEnum("7")] + UnableToGetCreatorDetails, + [XmlEnum("8")] + CreatorIsNotApproved, + [XmlEnum("9")] + GroupNameIsEmpty, + [XmlEnum("10")] + GroupDescriptionIsEmpty, + [XmlEnum("11")] + GroupTypeIsInvalid, + [XmlEnum("12")] + GroupMaxMemberLimitInvalid, + [XmlEnum("13")] + AutomaticItemPurchaseFailed +} diff --git a/src/Schema/EditGroupRequest.cs b/src/Schema/EditGroupRequest.cs new file mode 100644 index 0000000..cec0ca4 --- /dev/null +++ b/src/Schema/EditGroupRequest.cs @@ -0,0 +1,37 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "EditGroupRequest", IsNullable = true)] +[Serializable] +public class EditGroupRequest { + [XmlElement(ElementName = "GroupID")] + public string GroupID; + + [XmlElement(ElementName = "UserID")] + public string UserID; + + [XmlElement(ElementName = "Name")] + public string Name; + + [XmlElement(ElementName = "Description")] + public string Description; + + [XmlElement(ElementName = "Type")] + public GroupType? Type; + + [XmlElement(ElementName = "Logo")] + public string Logo; + + [XmlElement(ElementName = "Color")] + public string Color; + + [XmlElement(ElementName = "ProductGroupID")] + public int? ProductGroupID; + + [XmlElement(ElementName = "ProductID")] + public int? ProductID; + + [XmlElement(ElementName = "MaxMemberLimit")] + public int? MaxMemberLimit; +} diff --git a/src/Schema/EditGroupResult.cs b/src/Schema/EditGroupResult.cs new file mode 100644 index 0000000..0c88ce1 --- /dev/null +++ b/src/Schema/EditGroupResult.cs @@ -0,0 +1,16 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "EditGroupResult", IsNullable = true)] +[Serializable] +public class EditGroupResult { + [XmlElement(ElementName = "Success")] + public bool Success; + + [XmlElement(ElementName = "Status")] + public EditGroupStatus Status; + + [XmlElement(ElementName = "NewRolePermissions", IsNullable = true)] + public List NewRolePermissions; +} diff --git a/src/Schema/EditGroupStatus.cs b/src/Schema/EditGroupStatus.cs new file mode 100644 index 0000000..9ed099c --- /dev/null +++ b/src/Schema/EditGroupStatus.cs @@ -0,0 +1,28 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +public enum EditGroupStatus { + [XmlEnum("1")] + Success = 1, + [XmlEnum("2")] + Error, + [XmlEnum("3")] + UnableToGetUserDetails, + [XmlEnum("4")] + GroupNameInvalid, + [XmlEnum("5")] + GroupDescriptionInvalid, + [XmlEnum("6")] + GroupNameIsDuplicate, + [XmlEnum("7")] + GroupTypeIsInvalid, + [XmlEnum("8")] + PermissionDenied, + [XmlEnum("9")] + GroupNotFound, + [XmlEnum("10")] + GroupMaxMemberLimitInvalid, + [XmlEnum("11")] + GroupOwnerHasInSufficientCurrency +} diff --git a/src/Schema/GetGroupsRequest.cs b/src/Schema/GetGroupsRequest.cs new file mode 100644 index 0000000..d196ce7 --- /dev/null +++ b/src/Schema/GetGroupsRequest.cs @@ -0,0 +1,40 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "GetGroupsRequest", IsNullable = true)] +[Serializable] +public class GetGroupsRequest { + [XmlElement(ElementName = "UserID")] + public string UserID; + + [XmlElement(ElementName = "ForUserID")] + public string ForUserID; + + [XmlElement(ElementName = "GroupID")] + public Guid? GroupID; + + [XmlElement(ElementName = "Name")] + public string Name; + + [XmlElement(ElementName = "ProductID")] + public int? ProductID; + + [XmlElement(ElementName = "ProductGroupID")] + public int? ProductGroupID; + + [XmlElement(ElementName = "IncludeMemberCount")] + public bool IncludeMemberCount; + + [XmlElement(ElementName = "IncludeMinFields")] + public bool IncludeMinFields; + + [XmlElement(ElementName = "PageNo")] + public int? PageNo; + + [XmlElement(ElementName = "PageSize")] + public int? PageSize; + + [XmlElement(ElementName = "GroupsFilter")] + public GroupsFilter GroupsFilter; +} diff --git a/src/Schema/GetGroupsResult.cs b/src/Schema/GetGroupsResult.cs new file mode 100644 index 0000000..1d2dbd7 --- /dev/null +++ b/src/Schema/GetGroupsResult.cs @@ -0,0 +1,16 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "GetGroupsResult", IsNullable = true)] +[Serializable] +public class GetGroupsResult { + [XmlElement(ElementName = "Success")] + public bool Success; + + [XmlElement(ElementName = "Groups")] + public Group[] Groups; + + [XmlElement(ElementName = "RolePermissions", IsNullable = true)] + public List RolePermissions; +} diff --git a/src/Schema/GetPendingJoinRequest.cs b/src/Schema/GetPendingJoinRequest.cs new file mode 100644 index 0000000..f7198f3 --- /dev/null +++ b/src/Schema/GetPendingJoinRequest.cs @@ -0,0 +1,16 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "GetPendingJoinRequest", IsNullable = true)] +[Serializable] +public class GetPendingJoinRequest { + [XmlElement(ElementName = "UserID")] + public string UserID; + + [XmlElement(ElementName = "GroupID")] + public string GroupID; + + [XmlElement(ElementName = "ProductGroupID")] + public int? ProductGroupID; +} diff --git a/src/Schema/GetPendingJoinResult.cs b/src/Schema/GetPendingJoinResult.cs new file mode 100644 index 0000000..42420bf --- /dev/null +++ b/src/Schema/GetPendingJoinResult.cs @@ -0,0 +1,14 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "GetPendingJoinResult", IsNullable = true)] +[Serializable] +public class GetPendingJoinResult { + // Token: 0x04000A48 RID: 2632 + [XmlElement(ElementName = "Success")] + public bool Success; + + [XmlElement(ElementName = "Requests")] + public PendingJoinRequest[] Requests; +} diff --git a/src/Schema/GroupJoinRequestStatus.cs b/src/Schema/GroupJoinRequestStatus.cs new file mode 100644 index 0000000..83d079b --- /dev/null +++ b/src/Schema/GroupJoinRequestStatus.cs @@ -0,0 +1,16 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +public enum GroupJoinRequestStatus { + [XmlEnum("1")] + Pending = 1, + [XmlEnum("2")] + Approved, + [XmlEnum("3")] + Rejected, + [XmlEnum("4")] + Cancelled, + [XmlEnum("5")] + PendingAccountOwnerRequest +} \ No newline at end of file diff --git a/src/Schema/GroupJoinResult.cs b/src/Schema/GroupJoinResult.cs new file mode 100644 index 0000000..c7ca46a --- /dev/null +++ b/src/Schema/GroupJoinResult.cs @@ -0,0 +1,15 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "GroupJoinResult", IsNullable = true)] +[Serializable] +public class GroupJoinResult { + // Token: 0x040009C3 RID: 2499 + [XmlElement(ElementName = "Success")] + public bool Success; + + // Token: 0x040009C4 RID: 2500 + [XmlElement(ElementName = "Status")] + public JoinGroupStatus Status; +} \ No newline at end of file diff --git a/src/Schema/GroupMember.cs b/src/Schema/GroupMember.cs new file mode 100644 index 0000000..b49a906 --- /dev/null +++ b/src/Schema/GroupMember.cs @@ -0,0 +1,35 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "GroupMember", IsNullable = true)] +[Serializable] +public class GroupMember { + [XmlElement(ElementName = "UserID", IsNullable = false)] + public string UserID; + + [XmlElement(ElementName = "GroupID", IsNullable = false)] + public string GroupID; + + [XmlElement(ElementName = "DisplayName", IsNullable = false)] + public string DisplayName; + + [XmlElement(ElementName = "JoinDate")] + public DateTime JoinDate; + + [XmlElement(ElementName = "Online")] + public bool Online; + + [XmlElement(ElementName = "RoleID", IsNullable = true)] + public int? RoleID; + + [XmlElement(ElementName = "Points", IsNullable = true)] + public int? Points; + + [XmlElement(ElementName = "Rank", IsNullable = true)] + public int? Rank; + + [XmlElement(ElementName = "RankTrend", IsNullable = true)] + public int? RankTrend; +} + diff --git a/src/Schema/GroupsFilter.cs b/src/Schema/GroupsFilter.cs new file mode 100644 index 0000000..0623ba2 --- /dev/null +++ b/src/Schema/GroupsFilter.cs @@ -0,0 +1,16 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "GroupsFilter", IsNullable = true)] +[Serializable] +public class GroupsFilter { + [XmlElement(ElementName = "GroupType")] + public GroupType? GroupType; + + [XmlElement(ElementName = "Locale")] + public string Locale; + + [XmlElement(ElementName = "PointTypeID")] + public int? PointTypeID; +} diff --git a/src/Schema/JoinGroupRequest.cs b/src/Schema/JoinGroupRequest.cs new file mode 100644 index 0000000..891befc --- /dev/null +++ b/src/Schema/JoinGroupRequest.cs @@ -0,0 +1,31 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "JoinGroupRequest", IsNullable = true)] +[Serializable] +public class JoinGroupRequest { + [XmlElement(ElementName = "UserID")] + public string UserID; + + [XmlElement(ElementName = "GroupID")] + public string GroupID; + + [XmlElement(ElementName = "ProductGroupID")] + public int? ProductGroupID; + + [XmlElement(ElementName = "ProductID")] + public int? ProductID; + + [XmlElement(ElementName = "Message")] + public string Message; + + [XmlElement(ElementName = "JoinRequestStatus")] + public GroupJoinRequestStatus? JoinRequestStatus; + + [XmlElement(ElementName = "JoinByInvite")] + public bool JoinByInvite; + + [XmlElement(ElementName = "InviterID")] + public string InviterID; +} diff --git a/src/Schema/JoinGroupStatus.cs b/src/Schema/JoinGroupStatus.cs new file mode 100644 index 0000000..699ad33 --- /dev/null +++ b/src/Schema/JoinGroupStatus.cs @@ -0,0 +1,28 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +public enum JoinGroupStatus { + [XmlEnum("1")] + Success = 1, + [XmlEnum("2")] + Error, + [XmlEnum("3")] + GroupIsFull, + [XmlEnum("4")] + GroupTypeIsNotPublic, + [XmlEnum("5")] + UserAlreadyMemberOfTheGroup, + [XmlEnum("6")] + GroupTypeIsPublic, + [XmlEnum("7")] + MessageInvalid, + [XmlEnum("8")] + JoinRequestPending, + [XmlEnum("9")] + InviteNeededToJoin, + [XmlEnum("10")] + NoValidInvite, + [XmlEnum("11")] + InviterHasNoPermission +} diff --git a/src/Schema/LeaveGroupRequest.cs b/src/Schema/LeaveGroupRequest.cs new file mode 100644 index 0000000..2eb775f --- /dev/null +++ b/src/Schema/LeaveGroupRequest.cs @@ -0,0 +1,19 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "LeaveGroupRequest", IsNullable = true)] +[Serializable] +public class LeaveGroupRequest { + [XmlElement(ElementName = "UserID")] + public string UserID; + + [XmlElement(ElementName = "GroupID")] + public string GroupID; + + [XmlElement(ElementName = "ProductGroupID")] + public int? ProductGroupID; + + [XmlElement(ElementName = "ProductID")] + public int? ProductID; +} diff --git a/src/Schema/LeaveGroupResult.cs b/src/Schema/LeaveGroupResult.cs new file mode 100644 index 0000000..c14624b --- /dev/null +++ b/src/Schema/LeaveGroupResult.cs @@ -0,0 +1,13 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "LeaveGroupResult", IsNullable = true)] +[Serializable] +public class LeaveGroupResult { + [XmlElement(ElementName = "Success")] + public bool Success; + + [XmlElement(ElementName = "Status")] + public LeaveGroupStatus Status; +} diff --git a/src/Schema/LeaveGroupStatus.cs b/src/Schema/LeaveGroupStatus.cs new file mode 100644 index 0000000..209b04a --- /dev/null +++ b/src/Schema/LeaveGroupStatus.cs @@ -0,0 +1,12 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +public enum LeaveGroupStatus { + [XmlEnum("1")] + Success = 1, + [XmlEnum("2")] + Error, + [XmlEnum("3")] + UserNotAMemberOfTheGroup +} diff --git a/src/Schema/PendingJoinRequest.cs b/src/Schema/PendingJoinRequest.cs new file mode 100644 index 0000000..0af54c2 --- /dev/null +++ b/src/Schema/PendingJoinRequest.cs @@ -0,0 +1,22 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "PendingJoinRequest", IsNullable = true)] +[Serializable] +public class PendingJoinRequest { + [XmlElement(ElementName = "UserID")] + public string UserID; + + [XmlElement(ElementName = "GroupID")] + public string GroupID; + + [XmlElement(ElementName = "FromUserID")] + public string FromUserID; + + [XmlElement(ElementName = "Message")] + public string Message; + + [XmlElement(ElementName = "StatusID", IsNullable = true)] + public GroupJoinRequestStatus? StatusID; +} diff --git a/src/Schema/RemoveMemberRequest.cs b/src/Schema/RemoveMemberRequest.cs new file mode 100644 index 0000000..35397f6 --- /dev/null +++ b/src/Schema/RemoveMemberRequest.cs @@ -0,0 +1,22 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "RemoveMemberRequest", IsNullable = true)] +[Serializable] +public class RemoveMemberRequest { + [XmlElement(ElementName = "RemoveUserID")] + public string RemoveUserID; + + [XmlElement(ElementName = "UserID")] + public string UserID; + + [XmlElement(ElementName = "GroupID")] + public string GroupID; + + [XmlElement(ElementName = "ProductGroupID")] + public int? ProductGroupID; + + [XmlElement(ElementName = "ProductID")] + public int? ProductID; +} diff --git a/src/Schema/RemoveMemberResult.cs b/src/Schema/RemoveMemberResult.cs new file mode 100644 index 0000000..a014e22 --- /dev/null +++ b/src/Schema/RemoveMemberResult.cs @@ -0,0 +1,13 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "RemoveMemberResult", IsNullable = true)] +[Serializable] +public class RemoveMemberResult { + [XmlElement(ElementName = "Success")] + public bool Success; + + [XmlElement(ElementName = "Status")] + public RemoveMemberStatus Status; +} diff --git a/src/Schema/RemoveMemberStatus.cs b/src/Schema/RemoveMemberStatus.cs new file mode 100644 index 0000000..90f3338 --- /dev/null +++ b/src/Schema/RemoveMemberStatus.cs @@ -0,0 +1,16 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +public enum RemoveMemberStatus { + [XmlEnum("1")] + Success = 1, + [XmlEnum("2")] + Error, + [XmlEnum("3")] + UserNotAMemberOfTheGroup, + [XmlEnum("4")] + UserHasNoPermission, + [XmlEnum("5")] + InvalidParameters +} diff --git a/src/Schema/RolePermission.cs b/src/Schema/RolePermission.cs new file mode 100644 index 0000000..93c65ba --- /dev/null +++ b/src/Schema/RolePermission.cs @@ -0,0 +1,16 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "RP", IsNullable = true)] +[Serializable] +public class RolePermission { + [XmlElement(ElementName = "G")] + public GroupType GroupType; + + [XmlElement(ElementName = "R")] + public UserRole Role; + + [XmlElement(ElementName = "P")] + public List Permissions; +} diff --git a/src/Schema/UserProfileGroupData.cs b/src/Schema/UserProfileGroupData.cs index 207edf4..acba960 100644 --- a/src/Schema/UserProfileGroupData.cs +++ b/src/Schema/UserProfileGroupData.cs @@ -9,7 +9,7 @@ public class UserProfileGroupData public string GroupID; [XmlElement(ElementName = "RoleID", IsNullable = true)] - public int? RoleID; + public UserRole? RoleID; [XmlElement(ElementName = "TypeID", IsNullable = true)] public int? TypeID; diff --git a/src/Schema/UserRole.cs b/src/Schema/UserRole.cs new file mode 100644 index 0000000..9680b50 --- /dev/null +++ b/src/Schema/UserRole.cs @@ -0,0 +1,12 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +public enum UserRole { + [XmlEnum("1")] + Member = 1, + [XmlEnum("2")] + Elder, + [XmlEnum("3")] + Leader +}