public static string ToInfractionString(this InfractionType infractionType) { var value = string.Empty; switch (infractionType) { case InfractionType.AutoMod: value = "Auto Moderation"; break; case InfractionType.Warning: value = "Warn"; break; case InfractionType.Mute: value = "Mute"; break; case InfractionType.Kick: value = "Kick"; break; case InfractionType.Ban: value = "Ban"; break; default: throw new ArgumentOutOfRangeException(nameof(infractionType), infractionType, null); } return(value); }
public async Task RescindInfractionAsync(InfractionType type, ulong guildId, ulong subjectId, string?reason = null) { _authorizationService.RequireAuthenticatedGuild(); _authorizationService.RequireAuthenticatedUser(); _authorizationService.RequireClaims(AuthorizationClaim.ModerationRescind); if (reason?.Length >= MaxReasonLength) { throw new ArgumentException($"Reason must be less than {MaxReasonLength} characters in length", nameof(reason)); } var infraction = (await _infractionRepository.SearchSummariesAsync( new InfractionSearchCriteria() { GuildId = _authorizationService.CurrentGuildId.Value, Types = new[] { type }, SubjectId = subjectId, IsRescinded = false, IsDeleted = false, })).FirstOrDefault(); await DoRescindInfractionAsync(type, guildId, subjectId, infraction, reason); }
/// <inheritdoc /> public async Task <ServiceResult> RescindInfractionAsync(InfractionType type, ulong subjectId) { var authResult = AuthorizationService.CheckClaims(AuthorizationClaim.ModerationRescind); if (authResult.IsFailure) { return(authResult); } var rankResult = await RequireSubjectRankLowerThanModeratorRankAsync(AuthorizationService.CurrentGuildId.Value, subjectId); if (rankResult.IsFailure) { return(rankResult); } await DoRescindInfractionAsync( (await InfractionRepository.SearchSummariesAsync( new InfractionSearchCriteria() { GuildId = AuthorizationService.CurrentGuildId.Value, Types = new [] { type }, SubjectId = subjectId, IsRescinded = false, IsDeleted = false, })) .FirstOrDefault()); return(ServiceResult.FromSuccess()); }
/// <summary> /// Añade un tipo de infracción /// </summary> /// <param name="infractiontype"></param> public void Add(InfractionType infractiontype) { using (var db = new Context()) { db.InfractionTypes.Add(infractiontype); db.SaveChanges(); } }
/// <returns>The warning made.</returns> public Infraction AddInfractionToGuild(ulong userId, ulong moderatorId, InfractionType type, DateTime?endsAt, string reason) { var infraction = new Infraction(++LastUsedModerationId, userId, moderatorId, type, endsAt, reason); Infractions.Add(infraction); return(infraction); }
} // LiteDB public Infraction(uint id, ulong infractionerId, ulong modId, InfractionType type, DateTime?endsAt, string reason) { Id = id; ModeratorId = modId; InfractionerId = infractionerId; Type = type; FinishesAt = endsAt; Reason = reason; Date = DateTime.UtcNow; }
/// <summary> /// Associate infractionType to current infraction /// </summary> /// <param name="infractionType"></param> public void SetInfractionType(InfractionType infractionType) { if (infractionType == null || infractionType.IsTransient()) { throw new ArgumentNullException(String.Format(CommonMessages.exception_CannotAssociateTransientOrNullEntity, Names.InfractionType)); } this.InfractionTypeId = infractionType.Id; this.InfractionType = infractionType; }
private string GetInfractionTypeString(InfractionType type) { return(type switch { InfractionType.Kick => "kicked", InfractionType.Mute => "muted", InfractionType.Warning => "warned", InfractionType.TemporaryMute => "temporarily muted", _ => "given an infraction", });
/// <summary> /// Añade un tipo de infracción /// </summary> /// <param name="infractiontype">Tipo de infracción</param> /// <returns></returns> public IHttpActionResult PostInfractionType(InfractionType infractiontype) { if (!ModelState.IsValid) { return(BadRequest(ModelState)); } try { new InfractionTypesLogic().Add(infractiontype); return(Ok()); } catch (Exception e) { return(BadRequest(e.Message)); } }
private Infraction AddInfractionToGuild(ulong userId, ulong modId, InfractionType type, DateTime?endsAt, string reason, GuildAccount guild) { var infraction = guild.AddInfractionToGuild(userId, modId, type, endsAt, reason); if (guild.ModLogsChannelId != 0) { var channel = _client.GetChannel(guild.ModLogsChannelId) as ITextChannel; var embed = GetMessageEmbedForLog(infraction); channel.SendMessageAsync("", false, embed).GetAwaiter().GetResult(); } return(infraction); }
public static Infraction CreateInfraction(Vehicle vehicle, Driver driver, InfractionType infractionType, DateTime date) { var infraction = new Infraction() { VehicleId = vehicle.Id, InfractionTypeId = infractionType.Id, Date = date }; infraction.SetVehicle(vehicle); infraction.SetInfractionType(infractionType); infraction.SetDriver(driver); return(infraction); }
private InfractionType MaterializeInfractionTypeFromDto(InfractionTypeDTO dto) { var it = new InfractionType() { Name = dto.Name, Points = dto.Points, Description = dto.Description }; if (dto.Id != Guid.Empty) { it.ChangeCurrentIdentity(dto.Id); } return(it); }
/// <inheritdoc /> public async Task RescindInfractionAsync(InfractionType type, ulong subjectId) { AuthorizationService.RequireAuthenticatedGuild(); AuthorizationService.RequireAuthenticatedUser(); AuthorizationService.RequireClaims(AuthorizationClaim.ModerationRescind); await DoRescindInfractionAsync( (await InfractionRepository.SearchSummariesAsync( new InfractionSearchCriteria() { GuildId = AuthorizationService.CurrentGuildId.Value, Types = new [] { type }, SubjectId = subjectId, IsRescinded = false, IsDeleted = false, })) .FirstOrDefault()); }
public static void Seed(UnitOfWork.MainBCUnitOfWork context) { var excesoVelocidad20 = new InfractionType() { Name = "Exceder la velocidad máxima permitida en un 20%", Points = 2, CreatedDate = DateTime.Now }; excesoVelocidad20.ChangeCurrentIdentity(EntityGuids.InfractionTypes.ExcesoVelocidad20); context.InfractionTypes.AddOrUpdate(excesoVelocidad20); var excesoVelocidad40 = new InfractionType() { Name = "Exceder la velocidad máxima permitida en un 40%", Points = 4, CreatedDate = DateTime.Now }; excesoVelocidad40.ChangeCurrentIdentity(EntityGuids.InfractionTypes.ExcesoVelocidad40); context.InfractionTypes.AddOrUpdate(excesoVelocidad40); var excesoVelocidad60 = new InfractionType() { Name = "Exceder la velocidad máxima permitida en un 60%", Points = 8, CreatedDate = DateTime.Now }; excesoVelocidad60.ChangeCurrentIdentity(EntityGuids.InfractionTypes.ExcesoVelocidad60); context.InfractionTypes.AddOrUpdate(excesoVelocidad60); var aparcarEnDobleFila = new InfractionType() { Name = "Aparcar en doble fila", Points = 2, CreatedDate = DateTime.Now }; aparcarEnDobleFila.ChangeCurrentIdentity(EntityGuids.InfractionTypes.DobleFila); context.InfractionTypes.AddOrUpdate(aparcarEnDobleFila); var sinCinturonSeguridad = new InfractionType() { Name = "No llevar el cinturón de seguridad", Points = 3, CreatedDate = DateTime.Now }; sinCinturonSeguridad.ChangeCurrentIdentity(EntityGuids.InfractionTypes.SinCinturonSeguridad); context.InfractionTypes.AddOrUpdate(sinCinturonSeguridad); }
private string GetInfractionTypeString(InfractionType type) { switch (type) { case InfractionType.Kick: return("Kicked"); case InfractionType.Mute: return("Muted"); case InfractionType.Warning: return("Warned"); case InfractionType.TemporaryMute: return("Temporarily Muted"); default: return("Given an Infraction"); } }
public Color GetColorFromInfractionType(InfractionType type) { switch (type) { case InfractionType.Warning: return(Color.Orange); case InfractionType.Mute: return(Color.Red); case InfractionType.Kick: return(Color.DarkOrange); case InfractionType.Ban: return(Color.DarkRed); default: return(Color.Green); } }
/// <inheritdoc /> public async Task CreateInfractionAsync(InfractionType type, ulong subjectId, string reason, TimeSpan?duration) { AuthorizationService.RequireClaims(_createInfractionClaimsByType[type]); switch (type) { case InfractionType.Mute: await DoDiscordMuteAsync(subjectId); break; case InfractionType.Ban: await DoDiscordBanAsync(subjectId); break; } var actionId = await ModerationActionRepository.CreateAsync(new ModerationActionCreationData() { Type = ModerationActionType.InfractionCreated, CreatedById = AuthorizationService.CurrentUserId.Value, Reason = reason }); var infractionId = await InfractionRepository.CreateAsync(new InfractionCreationData() { Type = type, SubjectId = subjectId, Duration = duration, CreateActionId = actionId }); await ModerationActionRepository.UpdateAsync(actionId, data => { data.InfractionId = infractionId; }); // TODO: Log action to a channel, pulled from IModerationConfigRepository. // TODO: Implement InfractionAutoExpirationBehavior (or whatever) to automatically rescind infractions, based on Duration, and notify it here that a new infraction has been created, if it has a duration. }
public async Task CreateInfraction(DiscordGuild guild, DiscordChannel channel, DiscordClient client, DiscordMember moderator, DiscordMember suspect, string reason, InfractionType infractionType) { var suspectLeft = guild.GetMemberAsync(suspect.Id).Result == null; var embed = new Embed(); var owners = client.CurrentApplication.Owners; var verb = infractionType.ToInfractionString().ToLowerInvariant(); var action = infractionType.ToActionString().ToLowerInvariant(); var isSuspectAdministrator = false; var isModerator = await redis.IsModerator(guild.Id, moderator); var isAdministrator = moderator.Roles.Any(role => role.Permissions.HasPermission(Permissions.Administrator)); if (!isModerator && isAdministrator) { isModerator = true; } if (isModerator) { if (!suspectLeft) { if (suspect.Roles.Any(role => role.Permissions.HasPermission(Permissions.Administrator))) { isSuspectAdministrator = true; } if (moderator == suspect) { await channel.SendMessageAsync($"You can not {verb} yourself!"); } else if (owners.Any(x => x.Id == suspect.Id)) { await channel.SendMessageAsync($"You can not {verb} {suspect.Username}!"); } else if (suspect.IsOwner) { await channel.SendMessageAsync($"You can not {verb} the owner!"); } else if (isSuspectAdministrator) { await channel.SendMessageAsync($"You can not {verb} a administrator!"); } else if (await redis.IsModerator(guild.Id, suspect)) { var guildData = await redis.GetAsync <Guild>(RedisKeyNaming.Guild(guild.Id)); switch (infractionType) { case InfractionType.Ban: case InfractionType.Kick: await channel.SendMessageAsync($"You can not {verb} a moderator!"); break; case InfractionType.Warning: { if (!guildData.AllowWarnModerators) { await channel.SendMessageAsync($"You can not {verb} a moderator! [Disabled]"); } break; } case InfractionType.Mute: { if (!guildData.AllowMuteModerators) { await channel.SendMessageAsync($"You can not {verb} a moderator! [Disabled]"); } break; } } } else { var userData = await redis.InitUser(suspect.Id); userData.Infractions.Add(new Infraction() { Id = ++userData.InfractionId, ModeratorId = moderator.Id, ModeratorUsername = moderator.GetUsertag(), GuildId = guild.Id, InfractionType = infractionType, Reason = reason, Date = DateTimeOffset.UtcNow }); await redis.ReplaceAsync <User>(RedisKeyNaming.User(suspect.Id), userData); var description = new StringBuilder().AppendLine($"Moderator: {moderator.GetUsertag()} {Formatter.InlineCode($"{moderator.Id}")}") .AppendLine($"Reason: {reason}"); description.AppendLine("User left the server already!"); embed = new Embed() { Title = $"{suspect.Username} has been {action}!", Description = description.ToString(), Footer = new EmbedFooter() { Text = $"Infractions: {userData.Infractions.Count}" } }; await channel.SendEmbedMessageAsync(embed); embed.Title = $"You have been {action} on {guild.Name}"; await suspect.SendEmbedMessageAsync(embed); switch (infractionType) { case InfractionType.Kick: await suspect.RemoveAsync(reason); break; case InfractionType.Ban: await guild.BanMemberAsync(suspect.Id, 7, reason); break; } } } else { var suspectedUser = await client.GetUserAsync(suspect.Id); var userData = await redis.InitUser(suspectedUser.Id); userData.Infractions.Add(new Infraction() { Id = ++userData.InfractionId, ModeratorId = moderator.Id, ModeratorUsername = moderator.GetUsertag(), GuildId = guild.Id, InfractionType = infractionType, Reason = reason, Date = DateTimeOffset.UtcNow }); await redis.ReplaceAsync <User>(RedisKeyNaming.User(suspectedUser.Id), userData); var description = new StringBuilder().AppendLine($"Moderator: {moderator.GetUsertag()} {Formatter.InlineCode($"{moderator.Id}")}") .AppendLine($"Reason: {reason}") .AppendLine("User left the server already!"); embed = new Embed() { Title = $"{suspect.Username} has been {action}!", Description = description.ToString(), Footer = new EmbedFooter() { Text = $"Infractions: {userData.Infractions.Count}" } }; await channel.SendEmbedMessageAsync(embed); switch (infractionType) { case InfractionType.Kick: await channel.SendMessageAsync("Suspect already left"); break; case InfractionType.Ban: await guild.BanMemberAsync(suspectedUser.Id); break; } } } else { await channel.SendMessageAsync("You are not a moderator of this server."); } }
/// <inheritdoc /> public async Task CreateInfractionAsync(InfractionType type, ulong subjectId, string reason, TimeSpan?duration) { AuthorizationService.RequireAuthenticatedGuild(); AuthorizationService.RequireAuthenticatedUser(); AuthorizationService.RequireClaims(_createInfractionClaimsByType[type]); var guild = await GuildService.GetGuildAsync(AuthorizationService.CurrentGuildId.Value); var subject = await UserService.GetGuildUserAsync(guild.Id, subjectId); if (reason == null) { throw new ArgumentNullException(nameof(reason)); } if (((type == InfractionType.Notice) || (type == InfractionType.Warning)) && string.IsNullOrWhiteSpace(reason)) { throw new InvalidOperationException($"{type.ToString()} infractions require a reason to be given"); } using (var transaction = await InfractionRepository.BeginCreateTransactionAsync()) { if ((type == InfractionType.Mute) || (type == InfractionType.Ban)) { if (await InfractionRepository.AnyAsync(new InfractionSearchCriteria() { GuildId = guild.Id, Types = new[] { type }, SubjectId = subject.Id, IsRescinded = false, IsDeleted = false })) { throw new InvalidOperationException($"Discord user {subjectId} already has an active {type} infraction"); } } await InfractionRepository.CreateAsync( new InfractionCreationData() { GuildId = guild.Id, Type = type, SubjectId = subjectId, Reason = reason, Duration = duration, CreatedById = AuthorizationService.CurrentUserId.Value }); transaction.Commit(); } // TODO: Implement ModerationSyncBehavior to listen for mutes and bans that happen directly in Discord, instead of through bot commands, // and to read the Discord Audit Log to check for mutes and bans that were missed during downtime, and add all such actions to // the Infractions and ModerationActions repositories. // Note that we'll need to upgrade to the latest Discord.NET version to get access to the audit log. // Assuming that our Infractions repository is always correct, regarding the state of the Discord API. switch (type) { case InfractionType.Mute: await subject.AddRoleAsync( await GetOrCreateMuteRoleInGuildAsync(guild)); break; case InfractionType.Ban: await guild.AddBanAsync(subject, reason : reason); break; } }
private async Task <IResult> RescindInfractionAsync(UserOrMessageAuthor subject, string reason, InfractionType infractionType) { var confirmationResult = await GetConfirmationIfRequiredAsync(subject); if (!confirmationResult.IsSuccess || !confirmationResult.Entity) { return(Result.FromSuccess()); } try { var reasonWithUrls = AppendUrlsFromMessage(reason); await _moderationService.RescindInfractionAsync(infractionType, _context.GuildID.Value.Value, subject.User.ID.Value, reasonWithUrls); return(await ConfirmAsync()); } catch (Exception ex) { await _channelApi.CreateMessageAsync(_context.ChannelID, ex.Message, allowedMentions : new NoAllowedMentions()); return(Result.FromError(new ExceptionError(ex))); } }
public async Task CreateInfractionAsync(ulong guildId, ulong moderatorId, InfractionType type, ulong subjectId, string reason, TimeSpan?duration) { _authorizationService.RequireClaims(_createInfractionClaimsByType[type]); if (reason is null) { throw new ArgumentNullException(nameof(reason)); } if (reason.Length >= MaxReasonLength) { throw new ArgumentException($"Reason must be less than {MaxReasonLength} characters in length", nameof(reason)); } if (((type == InfractionType.Notice) || (type == InfractionType.Warning)) && string.IsNullOrWhiteSpace(reason)) { throw new InvalidOperationException($"{type.ToString()} infractions require a reason to be given"); } var guild = await _discordClient.GetGuildAsync(guildId); var subject = await _userService.TryGetGuildUserAsync(guild, subjectId, default); await RequireSubjectRankLowerThanModeratorRankAsync(guild, moderatorId, subject); using (var transaction = await _infractionRepository.BeginCreateTransactionAsync()) { if ((type == InfractionType.Mute) || (type == InfractionType.Ban)) { if (await _infractionRepository.AnyAsync(new InfractionSearchCriteria() { GuildId = guildId, Types = new[] { type }, SubjectId = subjectId, IsRescinded = false, IsDeleted = false })) { throw new InvalidOperationException( $"Discord user {subjectId} already has an active {type} infraction"); } } await _infractionRepository.CreateAsync( new InfractionCreationData() { GuildId = guildId, Type = type, SubjectId = subjectId, Reason = reason, Duration = duration, CreatedById = moderatorId }); transaction.Commit(); try { _dogStatsd.Increment("infractions", tags: new[] { $"infraction_type:{type}", $"guild:{guild.Name}" }); } catch (Exception) { // The world mourned, but nothing of tremendous value was lost. } } // TODO: Implement ModerationSyncBehavior to listen for mutes and bans that happen directly in Discord, instead of through bot commands, // and to read the Discord Audit Log to check for mutes and bans that were missed during downtime, and add all such actions to // the Infractions and ModerationActions repositories. // Note that we'll need to upgrade to the latest Discord.NET version to get access to the audit log. // Assuming that our Infractions repository is always correct, regarding the state of the Discord API. switch (type) { case InfractionType.Mute when subject is not null: await subject.AddRoleAsync( await GetDesignatedMuteRoleAsync(guild)); break; case InfractionType.Ban: await guild.AddBanAsync(subjectId, reason : reason); break; } }
public Infraction WithType(InfractionType type) { Type = type; return(this); }
private static string GetEmojiForInfractionType(InfractionType infractionType) => infractionType switch {
public Task <OperationResult <long> > CreateInfractionAsync(InfractionType type, ulong guildId, ulong subjectId, string reason, TimeSpan?duration) => Operation.Start // Validation .Require(!string.IsNullOrWhiteSpace(reason), () => new InfractionReasonMissingError()) .Require(reason.Length <= 1000, () => new InfractionReasonTooLongError(actualLength: reason.Length, maxLength: 1000)) // Authorization .ContinueOnSuccessAsync(() => AuthorizationService .RequireClaimsAsync(_requiredClaimsByInfractionType[type])) .ContinueOnSuccessAsync(() => AuthorizationService .RequireRankOverSubjectAsync(guildId, subjectId)) // Acquire Guild API .ContinueOnSuccessAsync(() => UserService .GetGuildUser(guildId, subjectId)) // Try perform mute .DoOnSuccessWhenAsync(type == InfractionType.Mute, guildUser => DesignatedRoleService // Retrieve mute role .SearchDesignatedRolesAsync(new DesignatedRoleMappingSearchCriteria() { GuildId = guildId, Type = DesignatedRoleType.ModerationMute, IsDeleted = false }) .AsSuccessAsync() .RequireOnSuccessAsync( roleMappings => roleMappings.Any(), () => new ModerationMuteRoleNotConfiguredError()) .RequireOnSuccessAsync( roleMappings => roleMappings.Count == 1, () => new ModerationMuteRoleMultipleConfigurationsError()) .ContinueOnSuccessAsync(roleMappings => roleMappings .First() .Role.Id .AsSuccess()) // Verify user is not muted .RequireOnSuccessAsync( roleId => !guildUser.RoleIds.Contains(roleId), () => new ModerationSubjectAlreadyMutedError()) // Discord API .ContinueOnSuccessAsync(roleId => guildUser .Guild .GetRole(roleId) .AsSuccess()) // Perform mute .BranchOnSuccessAsync(role => guildUser.AddRoleAsync(role))) // Try perform ban .DoOnSuccessWhenAsync(type == InfractionType.Ban, guildUser => guildUser.Guild .AsSuccess() // Verify user is not banned .DoOnSuccessAsync(guild => guild .GetBansAsync() .AsSuccessAsync() .RequireOnSuccessAsync(bans => !bans.Any(x => x.User.Id == guildUser.Id), () => new ModerationSubjectAlreadyBannedError())) // Perform ban .BranchOnSuccessAsync(guild => guild.AddBanAsync(guildUser.Id))) // Perform database operations .ContinueOnSuccessAsync(async() => { using (var deleteTransaction = await InfractionRepository.BeginDeleteTransactionAsync()) using (var createTransaction = await InfractionRepository.BeginCreateTransactionAsync()) { return(await Operation.Start // Delete existing active Mute/Ban infractions, if any, so we can create a new one .BranchWhenAsync((type == InfractionType.Mute) || (type == InfractionType.Ban), () => Operation.Start .ContinueAsync(InfractionRepository.SearchIdsAsync(new InfractionSearchCriteria() { GuildId = guildId, Types = new[] { type }, SubjectId = subjectId, IsRescinded = false, IsDeleted = false }).AsSuccessAsync()) .BranchOnSuccessWhenAsync(bans => bans.Any(), bans => InfractionRepository .TryDeleteAsync( bans, SelfUser.Id))) // Record new infraction .ContinueOnSuccessAsync(() => InfractionRepository.CreateAsync(new InfractionCreationData() { GuildId = guildId, Type = type, Reason = reason, Duration = duration, SubjectId = subjectId, CreatedById = AuthorizationService.CurrentUserId ?? SelfUser.Id })) .BranchOnSuccess(() => { deleteTransaction.Commit(); createTransaction.Commit(); })); } });
/// <inheritdoc /> public async Task <ServiceResult> CreateInfractionAsync(InfractionType type, ulong subjectId, string reason, TimeSpan?duration) { var authResult = AuthorizationService.CheckClaims(_createInfractionClaimsByType[type]); if (authResult.IsFailure) { return(authResult); } var rankResult = await RequireSubjectRankLowerThanModeratorRankAsync(AuthorizationService.CurrentGuildId.Value, subjectId); if (rankResult.IsFailure) { return(rankResult); } var guild = await DiscordClient.GetGuildAsync(AuthorizationService.CurrentGuildId.Value); IGuildUser subject; if (!await UserService.GuildUserExistsAsync(guild.Id, subjectId)) { subject = new EphemeralUser(subjectId, "[FORCED]", guild); await UserService.TrackUserAsync(subject); } else { subject = await UserService.GetGuildUserAsync(guild.Id, subjectId); } if (type == InfractionType.Notice || type == InfractionType.Warning) { if (string.IsNullOrWhiteSpace(reason)) { return(ServiceResult.FromError($"{type.ToString()} infractions require a reason to be given")); } } var lengthResult = new InvalidLengthResult("Reason", reason.Length, maximum: 1000); if (lengthResult.IsFailure) { return(lengthResult); } using (var transaction = await InfractionRepository.BeginCreateTransactionAsync()) { if ((type == InfractionType.Mute) || (type == InfractionType.Ban)) { if (await InfractionRepository.AnyAsync(new InfractionSearchCriteria() { GuildId = guild.Id, Types = new[] { type }, SubjectId = subject.Id, IsRescinded = false, IsDeleted = false })) { return(ServiceResult.FromError($"Discord user {subjectId} already has an active {type} infraction")); } } await InfractionRepository.CreateAsync( new InfractionCreationData() { GuildId = guild.Id, Type = type, SubjectId = subjectId, Reason = reason, Duration = duration, CreatedById = AuthorizationService.CurrentUserId.Value }); transaction.Commit(); } // TODO: Implement ModerationSyncBehavior to listen for mutes and bans that happen directly in Discord, instead of through bot commands, // and to read the Discord Audit Log to check for mutes and bans that were missed during downtime, and add all such actions to // the Infractions and ModerationActions repositories. // Note that we'll need to upgrade to the latest Discord.NET version to get access to the audit log. // Assuming that our Infractions repository is always correct, regarding the state of the Discord API. switch (type) { case InfractionType.Mute: await subject.AddRoleAsync( await GetDesignatedMuteRoleAsync(guild)); break; case InfractionType.Ban: await guild.AddBanAsync(subject, reason : reason); break; } return(ServiceResult.FromSuccess()); }