diff --git a/src/Nellebot/BotOptions.cs b/src/Nellebot/BotOptions.cs index 8b9ddfc..2f60c6f 100644 --- a/src/Nellebot/BotOptions.cs +++ b/src/Nellebot/BotOptions.cs @@ -54,7 +54,11 @@ public class BotOptions public ulong MemberRoleId { get; init; } - public ulong[] MemberRoleIds { get; init; } = []; + public ulong[] MemberActivatingRoleIds { get; init; } = []; + + public ulong BeginnerRoleId { get; init; } + + public ulong[] BeginnerActivatingRoleIds { get; init; } = []; public ulong GhostRoleId { get; init; } diff --git a/src/Nellebot/CommandHandlers/QuarantineUser.cs b/src/Nellebot/CommandHandlers/QuarantineUser.cs index 55fbad4..696305c 100644 --- a/src/Nellebot/CommandHandlers/QuarantineUser.cs +++ b/src/Nellebot/CommandHandlers/QuarantineUser.cs @@ -77,7 +77,7 @@ public async Task Handle(QuarantineUserCommand request, CancellationToken cancel } DiscordInteraction? modalInteraction = null; - string? quarantineReason = null; + string? quarantineReason = request.Reason; if (ctx is SlashCommandContext slashCtx && request.Reason == null) { diff --git a/src/Nellebot/CommandHandlers/ValhallKickUser.cs b/src/Nellebot/CommandHandlers/ValhallKickUser.cs index ab8345e..4cdde8f 100644 --- a/src/Nellebot/CommandHandlers/ValhallKickUser.cs +++ b/src/Nellebot/CommandHandlers/ValhallKickUser.cs @@ -54,7 +54,7 @@ public async Task Handle(ValhallKickUserCommand request, CancellationToken cance } DiscordInteraction? modalInteraction = null; - string? kickReason = null; + string? kickReason = request.Reason; if (ctx is SlashCommandContext slashCtx && request.Reason == null) { diff --git a/src/Nellebot/Jobs/RoleMaintenanceJob.cs b/src/Nellebot/Jobs/RoleMaintenanceJob.cs index b21f8ae..8ec4136 100644 --- a/src/Nellebot/Jobs/RoleMaintenanceJob.cs +++ b/src/Nellebot/Jobs/RoleMaintenanceJob.cs @@ -42,9 +42,6 @@ public async Task Execute(IJobExecutionContext context) CancellationToken cancellationToken = context.CancellationToken; ulong guildId = _options.GuildId; - ulong memberRoleId = _options.MemberRoleId; - ulong[] memberRoleIds = _options.MemberRoleIds; - ulong ghostRoleId = _options.GhostRoleId; ulong quarantineRoleId = _options.QuarantineRoleId; DiscordGuild guild = _client.Guilds[guildId]; @@ -55,18 +52,11 @@ public async Task Execute(IJobExecutionContext context) _discordLogger.LogOperationMessage($"Downloaded {allMembers.Count} guild members."); - DiscordRole memberRole = guild.Roles[memberRoleId] - ?? throw new Exception($"Could not find member role with id {memberRoleId}"); - DiscordRole ghostRole = guild.Roles[ghostRoleId] - ?? throw new Exception($"Could not find ghost role with id {ghostRoleId}"); + await MaintainMemberRoles(guild, allMembers, quarantineRoleId, cancellationToken); - await AddMissingMemberRoles(allMembers, memberRoleIds, memberRole, quarantineRoleId, cancellationToken); + await MaintainBeginnerRoles(guild, allMembers, quarantineRoleId, cancellationToken); - await RemoveUnneededMemberRoles(allMembers, memberRoleIds, memberRole, quarantineRoleId, cancellationToken); - - await AddMissingGhostRoles(allMembers, ghostRole, cancellationToken); - - await RemoveUnneededGhostRoles(allMembers, ghostRole, cancellationToken); + await MaintainGhostRoles(guild, allMembers, cancellationToken); _discordLogger.LogOperationMessage($"Job finished: {Key}"); } @@ -77,42 +67,94 @@ public async Task Execute(IJobExecutionContext context) } } - private async Task AddMissingMemberRoles( + private async Task MaintainMemberRoles( + DiscordGuild guild, + List allMembers, + ulong quarantineRoleId, + CancellationToken cancellationToken) + { + ulong memberRoleId = _options.MemberRoleId; + ulong[] memberActivatingRoleIds = _options.MemberActivatingRoleIds; + DiscordRole memberRole = guild.Roles[memberRoleId] + ?? throw new Exception($"Could not find Member role with id {memberRoleId}"); + + await AddMissingActivatableRoles( + allMembers, + memberRole, + memberActivatingRoleIds, + quarantineRoleId, + cancellationToken); + + await RemoveUnneededActivatableRoles( + allMembers, + memberRole, + memberActivatingRoleIds, + quarantineRoleId, + cancellationToken); + } + + private async Task MaintainBeginnerRoles( + DiscordGuild guild, List allMembers, - ulong[] memberRoleIds, - DiscordRole memberRole, ulong quarantineRoleId, CancellationToken cancellationToken) { - List missingMemberRoleMembers = allMembers - .Where(m => !HasMemberRole(m) && HasMandatoryRoles(m) && !HasQuarantineRole(m)) + ulong beginnerRoleId = _options.BeginnerRoleId; + ulong[] beginnerActivatingRoleIds = _options.BeginnerActivatingRoleIds; + DiscordRole beginnerRole = guild.Roles[beginnerRoleId] + ?? throw new Exception($"Could not find Beginner role with id {beginnerRoleId}"); + + await AddMissingActivatableRoles( + allMembers, + beginnerRole, + beginnerActivatingRoleIds, + quarantineRoleId, + cancellationToken); + + await RemoveUnneededActivatableRoles( + allMembers, + beginnerRole, + beginnerActivatingRoleIds, + quarantineRoleId, + cancellationToken); + } + + private async Task AddMissingActivatableRoles( + List allMembers, + DiscordRole activatableRole, + ulong[] activatingRoleIds, + ulong quarantineRoleId, + CancellationToken cancellationToken) + { + List missingRoleMembers = allMembers + .Where(m => !HasMemberRole(m) && HasActivatingRoles(m) && !HasQuarantineRole(m)) .ToList(); - if (missingMemberRoleMembers.Count != 0) - { - int totalCount = missingMemberRoleMembers.Count; + if (missingRoleMembers.Count == 0) return; - _discordLogger.LogOperationMessage( - $"Found {missingMemberRoleMembers.Count} users which are missing the Member role."); + int totalCount = missingRoleMembers.Count; - int successCount = await ExecuteRoleChangeWithRetry( - missingMemberRoleMembers, - m => m.GrantRoleAsync(memberRole), - cancellationToken); + _discordLogger.LogOperationMessage( + $"Found {missingRoleMembers.Count} users which are missing the {activatableRole.Name} role."); - _discordLogger.LogOperationMessage($"Done adding Member role for {successCount}/{totalCount} users."); - } + int successCount = await ExecuteRoleChangeWithRetry( + missingRoleMembers, + m => m.GrantRoleAsync(activatableRole), + cancellationToken); + + _discordLogger.LogOperationMessage( + $"Done adding {activatableRole.Name} role for {successCount}/{totalCount} users."); return; - bool HasMandatoryRoles(DiscordMember m) + bool HasActivatingRoles(DiscordMember m) { - return m.Roles.Any(r => memberRoleIds.Contains(r.Id)); + return m.Roles.Any(r => activatingRoleIds.Contains(r.Id)); } bool HasMemberRole(DiscordMember m) { - return m.Roles.Any(r => r.Id == memberRole.Id); + return m.Roles.Any(r => r.Id == activatableRole.Id); } bool HasQuarantineRole(DiscordMember m) @@ -121,41 +163,42 @@ bool HasQuarantineRole(DiscordMember m) } } - private async Task RemoveUnneededMemberRoles( + private async Task RemoveUnneededActivatableRoles( List allMembers, - ulong[] memberRoleIds, - DiscordRole memberRole, + DiscordRole activatableRole, + ulong[] activatingRoleIds, ulong quarantineRoleId, CancellationToken cancellationToken) { List memberRoleCandidates = allMembers - .Where(m => HasMemberRole(m) && (!HasMandatoryRoles(m) || HasQuarantineRole(m))) + .Where(m => HasMemberRole(m) && (!HasActivatingRoles(m) || HasQuarantineRole(m))) .ToList(); - if (memberRoleCandidates.Count != 0) - { - int totalCount = memberRoleCandidates.Count; + if (memberRoleCandidates.Count == 0) return; - _discordLogger.LogOperationMessage($"Found {memberRoleCandidates.Count} users with unneeded Member role."); + int totalCount = memberRoleCandidates.Count; - int successCount = await ExecuteRoleChangeWithRetry( - memberRoleCandidates, - m => m.RevokeRoleAsync(memberRole), - cancellationToken); + _discordLogger.LogOperationMessage( + $"Found {memberRoleCandidates.Count} users with unneeded {activatableRole.Name} role."); - _discordLogger.LogOperationMessage($"Done removing Member role for {successCount}/{totalCount} users."); - } + int successCount = await ExecuteRoleChangeWithRetry( + memberRoleCandidates, + m => m.RevokeRoleAsync(activatableRole), + cancellationToken); + + _discordLogger.LogOperationMessage( + $"Done removing {activatableRole.Name} role for {successCount}/{totalCount} users."); return; - bool HasMandatoryRoles(DiscordMember m) + bool HasActivatingRoles(DiscordMember m) { - return m.Roles.Any(r => memberRoleIds.Contains(r.Id)); + return m.Roles.Any(r => activatingRoleIds.Contains(r.Id)); } bool HasMemberRole(DiscordMember m) { - return m.Roles.Any(r => r.Id == memberRole.Id); + return m.Roles.Any(r => r.Id == activatableRole.Id); } bool HasQuarantineRole(DiscordMember m) @@ -164,6 +207,20 @@ bool HasQuarantineRole(DiscordMember m) } } + private async Task MaintainGhostRoles( + DiscordGuild guild, + List allMembers, + CancellationToken cancellationToken) + { + ulong ghostRoleId = _options.GhostRoleId; + DiscordRole ghostRole = guild.Roles[ghostRoleId] + ?? throw new Exception($"Could not find ghost role with id {ghostRoleId}"); + + await AddMissingGhostRoles(allMembers, ghostRole, cancellationToken); + + await RemoveUnneededGhostRoles(allMembers, ghostRole, cancellationToken); + } + private async Task AddMissingGhostRoles( List allMembers, DiscordRole ghostRole, diff --git a/src/Nellebot/NotificationHandlers/MemberRoleIntegrityHandler.cs b/src/Nellebot/NotificationHandlers/MemberRoleIntegrityHandler.cs index c0c7553..e56cdc8 100644 --- a/src/Nellebot/NotificationHandlers/MemberRoleIntegrityHandler.cs +++ b/src/Nellebot/NotificationHandlers/MemberRoleIntegrityHandler.cs @@ -51,9 +51,6 @@ public async Task Handle(GuildMemberUpdatedNotification notification, Cancellati DiscordMember member = args.Member ?? throw new Exception(nameof(member)); DiscordGuild guild = args.Guild; - ulong[] memberRoleIds = _options.MemberRoleIds; - ulong memberRoleId = _options.MemberRoleId; - ulong ghostRoleId = _options.GhostRoleId; ulong quarantineRoleId = _options.QuarantineRoleId; await QuarantineIfSpammer(member, addedRoles); @@ -71,38 +68,62 @@ public async Task Handle(GuildMemberUpdatedNotification notification, Cancellati return; } - await MaintainMemberRole(guild, memberRoleId, member, memberRoleIds, quarantineRoleId); + await MaintainMemberRole(guild, member, quarantineRoleId); + + await MaintainBeginnerRole(guild, member, quarantineRoleId); - await MaintainGhostRole(guild, ghostRoleId, member); + await MaintainGhostRole(guild, member); } - /// - /// Ensures that the member role is added if the user has any of the member roles, - /// and removed if the user has none of the member roles - /// - private static async Task MaintainMemberRole( + private async Task MaintainMemberRole( DiscordGuild guild, - ulong memberRoleId, DiscordMember member, - ulong[] memberRoleIds, ulong quarantineRoleId) { + ulong memberRoleId = _options.MemberRoleId; + ulong[] memberActivatingRoleIds = _options.MemberActivatingRoleIds; DiscordRole memberRole = guild.Roles[memberRoleId] - ?? throw new Exception($"Could not find member role with id {memberRoleId}"); + ?? throw new Exception($"Could not find Member role with id {memberRoleId}"); - bool userHasMandatoryRoles = member.Roles.Any(r => memberRoleIds.Contains(r.Id)); - bool userHasMemberRole = member.Roles.Any(r => r.Id == memberRoleId); + await MaintainActivatableRole(member, memberRole, memberActivatingRoleIds, quarantineRoleId); + } + + private async Task MaintainBeginnerRole( + DiscordGuild guild, + DiscordMember member, + ulong quarantineRoleId) + { + ulong beginnerRoleId = _options.BeginnerRoleId; + ulong[] beginnerActivatingRoleIds = _options.BeginnerActivatingRoleIds; + DiscordRole beginnerRole = guild.Roles[beginnerRoleId] + ?? throw new Exception($"Could not find Beginner role with id {beginnerRoleId}"); + + await MaintainActivatableRole(member, beginnerRole, beginnerActivatingRoleIds, quarantineRoleId); + } + + /// + /// Ensures that the activatable role is added if the user has any of the activating roles, + /// and removed if the user has none of the activating roles + /// + private static async Task MaintainActivatableRole( + DiscordMember member, + DiscordRole activatableRole, + ulong[] activatingRoleIds, + ulong quarantineRoleId) + { + bool userHasActivatableRole = member.Roles.Any(r => r.Id == activatableRole.Id); + bool userHasActivatingRoles = member.Roles.Any(r => activatingRoleIds.Contains(r.Id)); bool userHasQuarantineRole = member.Roles.Any(r => r.Id == quarantineRoleId); - bool userIsEligibleForMemberRole = userHasMandatoryRoles && !userHasQuarantineRole; + bool userIsEligibleForActivatableRole = userHasActivatingRoles && !userHasQuarantineRole; - if (!userHasMemberRole && userIsEligibleForMemberRole) + if (!userHasActivatableRole && userIsEligibleForActivatableRole) { - await member.GrantRoleAsync(memberRole); + await member.GrantRoleAsync(activatableRole); } - else if (userHasMemberRole && !userIsEligibleForMemberRole) + else if (userHasActivatableRole && !userIsEligibleForActivatableRole) { - await member.RevokeRoleAsync(memberRole); + await member.RevokeRoleAsync(activatableRole); } } @@ -110,8 +131,9 @@ private static async Task MaintainMemberRole( /// Ensures that the ghost role is added if the user has no roles, /// and removed if the user has any other roles /// - private static async Task MaintainGhostRole(DiscordGuild guild, ulong ghostRoleId, DiscordMember member) + private async Task MaintainGhostRole(DiscordGuild guild, DiscordMember member) { + ulong ghostRoleId = _options.GhostRoleId; DiscordRole ghostRole = guild.Roles[ghostRoleId] ?? throw new Exception($"Could not find ghost role with id {ghostRoleId}"); diff --git a/src/Nellebot/appsettings.Development.json b/src/Nellebot/appsettings.Development.json index 5b194d2..e69ce6a 100644 --- a/src/Nellebot/appsettings.Development.json +++ b/src/Nellebot/appsettings.Development.json @@ -28,13 +28,18 @@ ], "RequiredAwardCount": 1, "MemberRoleId": 990253471691309146, - "MemberRoleIds": [ + "MemberActivatingRoleIds": [ 825149115348287533, 825149115338981405, 825149115338981404, 825149115338981403, 825149115338981401 ], + "BeginnerRoleId": 1425228938019344547, + "BeginnerActivatingRoleIds": [ + 825149115338981403, + 825149115338981404 + ], "GhostRoleId": 1267826295816060988, "QuarantineRoleId": 1412826444152967389, "QuarantineChannelId": 1412828639724175410, diff --git a/src/Nellebot/appsettings.json b/src/Nellebot/appsettings.json index 1af8d1f..1400750 100644 --- a/src/Nellebot/appsettings.json +++ b/src/Nellebot/appsettings.json @@ -36,7 +36,7 @@ ], "RequiredAwardCount": 3, "MemberRoleId": 990251983887806585, - "MemberRoleIds": [ + "MemberActivatingRoleIds": [ 622142742478323727, 622144734533648405, 622145068438126604, @@ -44,6 +44,11 @@ 622143551827869696, 622788175491891231 ], + "BeginnerRoleId": 1425238421051412572, + "BeginnerActivatingRoleIds": [ + 622143551827869696, + 622143807483281457 + ], "GhostRoleId": 1267940960126369833, "QuarantineRoleId": 1412827243516006442, "QuarantineChannelId": 1412829017903595662,