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);
        }
Esempio n. 2
0
        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);
        }
Esempio n. 3
0
        /// <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());
        }
Esempio n. 4
0
 /// <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();
     }
 }
Esempio n. 5
0
        /// <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);
        }
Esempio n. 6
0
        }                      // 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;
        }
Esempio n. 7
0
        /// <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));
     }
 }
Esempio n. 10
0
        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);
        }
Esempio n. 11
0
        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);
        }
Esempio n. 12
0
        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);
        }
Esempio n. 13
0
        /// <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());
        }
Esempio n. 14
0
        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);
        }
Esempio n. 15
0
        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");
            }
        }
Esempio n. 16
0
        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);
            }
        }
Esempio n. 17
0
        /// <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.
        }
Esempio n. 18
0
        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.");
            }
        }
Esempio n. 19
0
        /// <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;
            }
        }
Esempio n. 20
0
        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)));
            }
        }
Esempio n. 21
0
        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;
            }
        }
Esempio n. 22
0
 public Infraction WithType(InfractionType type)
 {
     Type = type;
     return(this);
 }
Esempio n. 23
0
 private static string GetEmojiForInfractionType(InfractionType infractionType)
 => infractionType switch
 {
Esempio n. 24
0
 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();
             }));
         }
 });
Esempio n. 25
0
        /// <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());
        }