private async Task AnnounceAutomoderationConfig(AutoModerationConfig config, IUser actor, RestAction action) { using var scope = _serviceProvider.CreateScope(); _logger.LogInformation($"Announcing automod config {config.GuildId}/{config.AutoModerationType} ({config.Id})."); GuildConfig guildConfig = await GuildConfigRepository.CreateDefault(scope.ServiceProvider).GetGuildConfig(config.GuildId); if (!string.IsNullOrEmpty(guildConfig.ModInternalNotificationWebhook)) { _logger.LogInformation($"Sending internal webhook for config {config.GuildId}/{config.AutoModerationType} ({config.Id}) to {guildConfig.ModInternalNotificationWebhook}."); try { EmbedBuilder embed = await config.CreateAutomodConfigEmbed(actor, action, _serviceProvider); await _discordAPI.ExecuteWebhook(guildConfig.ModInternalNotificationWebhook, embed.Build()); } catch (Exception e) { _logger.LogError(e, $"Error while announcing config {config.GuildId}/{config.AutoModerationType} ({config.Id}) to {guildConfig.ModInternalNotificationWebhook}."); } } }
public static bool Check(IMessage message, AutoModerationConfig config, IDiscordClient _) { if (config.Limit == null) { return(false); } if (string.IsNullOrEmpty(config.CustomWordFilter)) { return(false); } if (string.IsNullOrEmpty(message.Content)) { return(false); } int matches = 0; foreach (string word in config.CustomWordFilter.Split('\n')) { if (string.IsNullOrWhiteSpace(word)) { continue; } try { matches += Regex.Matches(message.Content, word, RegexOptions.IgnoreCase).Count; } catch { } if (matches > config.Limit) { break; } } return(matches > config.Limit); }
private async Task ExecutePunishment(IMessage message, AutoModerationConfig autoModerationConfig) { AutoModerationEvent modEvent = new() { GuildId = (message.Channel as ITextChannel).Guild.Id, AutoModerationType = autoModerationConfig.AutoModerationType, AutoModerationAction = autoModerationConfig.AutoModerationAction, UserId = message.Author.Id, MessageId = message.Id, MessageContent = message.Content }; await AutoModerationEventRepository.CreateDefault(_serviceProvider).RegisterEvent(modEvent, message.Channel as ITextChannel, message.Author); if (modEvent.AutoModerationAction == AutoModerationAction.ContentDeleted || modEvent.AutoModerationAction == AutoModerationAction.ContentDeletedAndCaseCreated) { try { RequestOptions requestOptions = new(); requestOptions.RetryMode = RetryMode.RetryRatelimit; await message.DeleteAsync(requestOptions); } catch (Exception ex) { _logger.LogError(ex, $"Error deleting message {message.Id}."); } } } }
private async Task <bool> IsProtectedByFilter(IMessage message, AutoModerationConfig autoModerationConfig) { if (_config.GetSiteAdmins().Contains(message.Author.Id)) { return(true); } IGuild guild = await _client.GetGuildAsync((message.Channel as ITextChannel).Guild.Id); IGuildUser member = await guild.GetUserAsync(message.Author.Id); if (member == null) { return(false); } if (member.Guild.OwnerId == member.Id) { return(true); } if (member.RoleIds.Any(x => _guildConfig.ModRoles.Contains(x) || _guildConfig.AdminRoles.Contains(x) || autoModerationConfig.IgnoreRoles.Contains(x))) { return(true); } return(autoModerationConfig.IgnoreChannels.Contains((message.Channel as ITextChannel).Id)); }
public async Task <IActionResult> DeleteItem([FromRoute] string guildid, [FromRoute] string type) { logger.LogInformation($"{HttpContext.Request.Method} {HttpContext.Request.Path} | Incoming request."); Identity currentIdentity = await identityManager.GetIdentity(HttpContext); User currentUser = await currentIdentity.GetCurrentDiscordUser(); if (currentUser == null) { logger.LogInformation($"{HttpContext.Request.Method} {HttpContext.Request.Path} | 401 Unauthorized."); return(Unauthorized()); } if (!await currentIdentity.HasAdminRoleOnGuild(guildid, this.database) && !config.Value.SiteAdminDiscordUserIds.Contains(currentUser.Id)) { logger.LogInformation($"{HttpContext.Request.Method} {HttpContext.Request.Path} | 401 Unauthorized."); return(Unauthorized()); } // ======================================================== GuildConfig guildConfig = await database.SelectSpecificGuildConfig(guildid); if (guildConfig == null) { logger.LogInformation($"{HttpContext.Request.Method} {HttpContext.Request.Path} | 400 Guild not registered."); return(BadRequest("Guild not registered.")); } int x = 0; try { x = Int32.Parse(type); } catch (Exception e) { logger.LogInformation($"{HttpContext.Request.Method} {HttpContext.Request.Path} | 400 Invalid type.", e); return(BadRequest("Invalid type")); } if (!Enum.IsDefined(typeof(AutoModerationType), x)) { logger.LogInformation($"{HttpContext.Request.Method} {HttpContext.Request.Path} | 400 Invalid type."); return(BadRequest("Invalid type")); } AutoModerationType eType = (AutoModerationType)x; AutoModerationConfig currentConfig = await database.SelectModerationConfigForGuildAndType(guildid, eType); if (currentConfig != null) { database.DeleteSpecificModerationConfig(currentConfig); await database.SaveChangesAsync(); return(Ok(new { id = currentConfig.Id })); } else { return(NotFound()); } }
public static bool Check(IMessage message, AutoModerationConfig config, IDiscordClient _) { if (config.Limit == null) { return(false); } if (message.Embeds == null) { return(false); } return(message.Embeds.Count > config.Limit); }
private async Task <bool> CheckMultipleEvents(IMessage message, AutoModerationConfig config) { if (config.Limit == null) { return(false); } if (config.TimeLimitMinutes == null) { return(false); } var existing = await AutoModerationEventRepository.CreateDefault(_serviceProvider).GetAllEventsForUserSinceMinutes(message.Author.Id, config.TimeLimitMinutes.Value); return(existing.Count > config.Limit.Value); }
public static bool Check(IMessage message, AutoModerationConfig config, IDiscordClient _) { if (config.Limit == null) { return(false); } if (string.IsNullOrEmpty(message.Content)) { return(false); } if (config.Limit <= 0) { return(false); } Regex regexPattern = new(@"([^0-9`])(?:\s*\1){" + config.Limit.ToString() + @",}"); return(regexPattern.Match(message.Content).Success); }
private async Task <bool> CheckAutoMod(AutoModerationType autoModerationType, IMessage message, Func <IMessage, AutoModerationConfig, Task <bool> > predicate) { AutoModerationConfig autoModerationConfig = _autoModerationConfigs.FirstOrDefault(x => x.AutoModerationType == autoModerationType); if (autoModerationConfig != null) { if (await predicate(message, autoModerationConfig)) { if (!await IsProtectedByFilter(message, autoModerationConfig)) { _logger.LogInformation($"U: {message.Author.Id} | C: {(message.Channel as ITextChannel).Id} | G: {(message.Channel as ITextChannel).Guild.Id} triggered {autoModerationConfig.AutoModerationType}."); await ExecutePunishment(message, autoModerationConfig); if (autoModerationConfig.AutoModerationType != AutoModerationType.TooManyAutoModerations) { await CheckAutoMod(AutoModerationType.TooManyAutoModerations, message, CheckMultipleEvents); } return(true); } } } return(false); }
public static async Task <EmbedBuilder> CreateAutomodConfigEmbed(this AutoModerationConfig autoModerationConfig, IUser actor, RestAction action, IServiceProvider provider) { var translator = provider.GetService <Translator>(); await translator.SetContext(autoModerationConfig.GuildId); EmbedBuilder embed = CreateBasicEmbed(action, provider, actor); if (actor != null) { embed.WithThumbnailUrl(actor.GetAvatarOrDefaultUrl()); } embed.WithTitle(translator.T().Automoderation() + ": " + translator.T().Enum(autoModerationConfig.AutoModerationType)); switch (action) { case RestAction.Created: embed.WithDescription(translator.T().NotificationAutomoderationConfigInternalCreate(translator.T().Enum(autoModerationConfig.AutoModerationType), actor)); break; case RestAction.Updated: embed.WithDescription(translator.T().NotificationAutomoderationConfigInternalUpdate(translator.T().Enum(autoModerationConfig.AutoModerationType), actor)); break; case RestAction.Deleted: return(embed.WithDescription(translator.T().NotificationAutomoderationConfigInternalDelete(translator.T().Enum(autoModerationConfig.AutoModerationType), actor))); } if (autoModerationConfig.Limit != null) { embed.AddField(translator.T().NotificationAutomoderationConfigLimit(), $"`{autoModerationConfig.Limit}`", true); } if (autoModerationConfig.TimeLimitMinutes != null) { embed.AddField(translator.T().NotificationAutomoderationConfigTimeLimit(), $"`{autoModerationConfig.TimeLimitMinutes}`", true); } if (autoModerationConfig.Limit != null || autoModerationConfig.TimeLimitMinutes != null) { embed.AddField("\u200b", "\u200b"); } if (autoModerationConfig.IgnoreRoles.Length > 0) { embed.AddField(translator.T().NotificationAutomoderationConfigIgnoredRoles(), string.Join(" ", autoModerationConfig.IgnoreRoles.Select(x => $"<@&{x}>")), true); } if (autoModerationConfig.IgnoreChannels.Length > 0) { embed.AddField(translator.T().NotificationAutomoderationConfigIgnoredChannels(), string.Join(" ", autoModerationConfig.IgnoreChannels.Select(x => $"<#{x}>")), true); } if (autoModerationConfig.IgnoreRoles.Length > 0 || autoModerationConfig.IgnoreChannels.Length > 0) { embed.AddField("\u200b", "\u200b"); } if (autoModerationConfig.PunishmentType != null && (autoModerationConfig.AutoModerationAction == AutoModerationAction.CaseCreated || autoModerationConfig.AutoModerationAction == AutoModerationAction.ContentDeletedAndCaseCreated)) { embed.AddField($"{SCALES_EMOTE} {translator.T().Punishment()}", translator.T().Enum(autoModerationConfig.PunishmentType.Value), true); if (autoModerationConfig.PunishmentDurationMinutes > 0) { embed.AddField($"{ALARM_CLOCK} {translator.T().NotificationAutomoderationConfigDuration()}", $"`{autoModerationConfig.PunishmentDurationMinutes}`", true); } embed.AddField( translator.T().NotificationAutomoderationConfigSendPublic(), autoModerationConfig.SendPublicNotification ? CHECK : X_CHECK, true); embed.AddField( translator.T().NotificationAutomoderationConfigSendDM(), autoModerationConfig.SendDmNotification ? CHECK : X_CHECK, true); } embed.AddField( translator.T().NotificationAutomoderationConfigDeleteMessage(), autoModerationConfig.AutoModerationAction == AutoModerationAction.ContentDeleted || autoModerationConfig.AutoModerationAction == AutoModerationAction.ContentDeletedAndCaseCreated ? CHECK : X_CHECK, true); return(embed); }
private async Task AnnounceAutomoderation(AutoModerationEvent modEvent, AutoModerationConfig moderationConfig, GuildConfig guildConfig, ITextChannel channel, IUser author) { using var scope = _serviceProvider.CreateScope(); var translator = scope.ServiceProvider.GetRequiredService <Translator>(); translator.SetContext(guildConfig); if (!string.IsNullOrEmpty(guildConfig.ModInternalNotificationWebhook)) { _logger.LogInformation($"Sending internal webhook for automod event {modEvent.GuildId}/{modEvent.Id} to {guildConfig.ModInternalNotificationWebhook}."); try { EmbedBuilder embed = await modEvent.CreateInternalAutomodEmbed(guildConfig, author, channel, scope.ServiceProvider, moderationConfig.PunishmentType); await _discordAPI.ExecuteWebhook(guildConfig.ModInternalNotificationWebhook, embed.Build()); } catch (Exception e) { _logger.LogError(e, $"Error while announcing automod event {modEvent.GuildId}/{modEvent.Id} to {guildConfig.ModInternalNotificationWebhook}."); } } if (moderationConfig.SendDmNotification) { _logger.LogInformation($"Sending dm notification for autmod event {modEvent.GuildId}/{modEvent.Id} to {author.Id}."); try { string reason = translator.T().Enum(modEvent.AutoModerationType); string action = translator.T().Enum(modEvent.AutoModerationAction); await _discordAPI.SendDmMessage(author.Id, translator.T().NotificationAutomoderationDM(author, channel, reason, action)); } catch (Exception e) { _logger.LogError(e, $"Error while announcing automod event {modEvent.GuildId}/{modEvent.Id} in dm to {author.Id}."); } } if ((modEvent.AutoModerationAction == AutoModerationAction.ContentDeleted || modEvent.AutoModerationAction == AutoModerationAction.ContentDeletedAndCaseCreated) && moderationConfig.ChannelNotificationBehavior != AutoModerationChannelNotificationBehavior.NoNotification) { _logger.LogInformation($"Sending channel notification to {modEvent.GuildId}/{modEvent.Id} {channel.GuildId}/{channel.Id}."); try { string reason = translator.T().Enum(modEvent.AutoModerationType); IMessage msg = await channel.SendMessageAsync(translator.T().NotificationAutomoderationChannel(author, reason)); if (moderationConfig.ChannelNotificationBehavior == AutoModerationChannelNotificationBehavior.SendNotificationAndDelete) { Task task = new(async() => { await Task.Delay(TimeSpan.FromSeconds(5)); try { _logger.LogInformation($"Deleting channel automod event notification {channel.GuildId}/{channel.Id}/{msg.Id}."); await msg.DeleteAsync(); } catch (UnauthorizedException) { } catch (Exception e) { _logger.LogError(e, $"Error while deleting message {channel.GuildId}/{channel.Id}/{msg.Id} for automod event {modEvent.GuildId}/{modEvent.Id}."); } }); task.Start(); } } catch (Exception e) { _logger.LogError(e, $"Error while announcing automod event {modEvent.GuildId}/{modEvent.Id} in channel {channel.Id}."); } } }
public void DeleteSpecificModerationConfig(AutoModerationConfig modConfig) { context.AutoModerationConfigs.Remove(modConfig); }
public void PutModerationConfig(AutoModerationConfig modConfig) { context.AutoModerationConfigs.Update(modConfig); }
public async Task <IActionResult> CreateItem([FromRoute] string guildid, [FromBody] AutoModerationEventForCreateDto dto) { logger.LogInformation($"{HttpContext.Request.Method} {HttpContext.Request.Path} | Incoming request."); if (string.IsNullOrEmpty(this.config.Value.DiscordBotToken)) { logger.LogInformation($"{HttpContext.Request.Method} {HttpContext.Request.Path} | 401 Authorization header not defined."); return(Unauthorized()); } string auth = String.Empty; try { auth = Request.Headers["Authorization"]; } catch (Exception e) { logger.LogInformation($"{HttpContext.Request.Method} {HttpContext.Request.Path} | 401 Authorization header not defined.", e); return(Unauthorized()); } if (this.config.Value.DiscordBotToken == auth) { AutoModerationConfig modConfig = await this.database.SelectModerationConfigForGuildAndType(guildid, dto.AutoModerationType); if (modConfig == null) { logger.LogInformation($"{HttpContext.Request.Method} {HttpContext.Request.Path} | 400 No config found for this type."); return(BadRequest("No config found for this type")); } AutoModerationEvent modEvent = new AutoModerationEvent(); if (modConfig.AutoModerationAction == AutoModerationAction.CaseCreated || modConfig.AutoModerationAction == AutoModerationAction.ContentDeletedAndCaseCreated) { ModCase newModCase = new ModCase(); User discordBot = await discord.FetchCurrentBotInfo(); newModCase.CaseId = await database.GetHighestCaseIdForGuild(guildid) + 1; newModCase.GuildId = guildid; newModCase.UserId = dto.UserId; newModCase.Username = dto.Username; newModCase.Discriminator = dto.Discriminator; newModCase.Nickname = dto.Nickname; newModCase.ModId = discordBot.Id; newModCase.CreatedAt = DateTime.UtcNow; newModCase.LastEditedAt = newModCase.CreatedAt; newModCase.LastEditedByModId = discordBot.Id; newModCase.OccuredAt = newModCase.CreatedAt; newModCase.Title = $"AutoModeration: {dto.AutoModerationType.ToString()}"; newModCase.Description = $"User triggered AutoModeration.\nEvent: {dto.AutoModerationType.ToString()}.\nAction: {modConfig.AutoModerationAction.ToString()}\nMessageId: {dto.MessageId}.\nMessage content: {dto.MessageContent}."; newModCase.Labels = new List <string>() { "automoderation", dto.AutoModerationType.ToString() }.ToArray(); newModCase.Valid = true; if (modConfig.PunishmentType != null && modConfig.PunishmentType != PunishmentType.None) { newModCase.PunishmentType = modConfig.PunishmentType.Value; newModCase.PunishmentActive = true; if (modConfig.PunishmentDurationMinutes == null) { newModCase.Punishment = newModCase.PunishmentType.ToString(); newModCase.PunishedUntil = null; } else { newModCase.Punishment = "Temp" + newModCase.PunishmentType.ToString(); newModCase.PunishedUntil = DateTime.UtcNow.AddMinutes(modConfig.PunishmentDurationMinutes.Value); } try { logger.LogInformation($"{HttpContext.Request.Method} {HttpContext.Request.Path} | Handling punishment."); await punishmentHandler.ExecutePunishment(newModCase, database); } catch (Exception e) { logger.LogError(e, "Failed to handle punishment for modcase."); } } else { newModCase.Punishment = "Warn"; newModCase.PunishmentType = PunishmentType.None; newModCase.PunishedUntil = null; newModCase.PunishmentActive = false; } await database.SaveModCase(newModCase); await database.SaveChangesAsync(); modEvent.AssociatedCaseId = newModCase.CaseId; logger.LogInformation($"{HttpContext.Request.Method} {HttpContext.Request.Path} | Sending notification."); try { await discordAnnouncer.AnnounceModCase(newModCase, RestAction.Created, discordBot, modConfig.SendPublicNotification); } catch (Exception e) { logger.LogError(e, "Failed to announce modcase."); } } modEvent.GuildId = guildid; modEvent.CreatedAt = DateTime.UtcNow; modEvent.UserId = dto.UserId; modEvent.Username = dto.Username; modEvent.Nickname = dto.Nickname; modEvent.Discriminator = dto.Discriminator; modEvent.MessageContent = dto.MessageContent; modEvent.MessageId = dto.MessageId; modEvent.AutoModerationType = dto.AutoModerationType; modEvent.AutoModerationAction = modConfig.AutoModerationAction; await database.SaveModerationEvent(modEvent); await database.SaveChangesAsync(); return(Ok(new { Id = modEvent.Id })); } else { logger.LogInformation($"{HttpContext.Request.Method} {HttpContext.Request.Path} | 401 Unauthorized."); return(Unauthorized()); } }
public async Task <IActionResult> SetItem([FromRoute] string guildid, [FromBody] AutoModerationConfigForPutDto dto) { logger.LogInformation($"{HttpContext.Request.Method} {HttpContext.Request.Path} | Incoming request."); Identity currentIdentity = await identityManager.GetIdentity(HttpContext); User currentUser = await currentIdentity.GetCurrentDiscordUser(); if (currentUser == null) { logger.LogInformation($"{HttpContext.Request.Method} {HttpContext.Request.Path} | 401 Unauthorized."); return(Unauthorized()); } if (!await currentIdentity.HasAdminRoleOnGuild(guildid, this.database) && !config.Value.SiteAdminDiscordUserIds.Contains(currentUser.Id)) { logger.LogInformation($"{HttpContext.Request.Method} {HttpContext.Request.Path} | 401 Unauthorized."); return(Unauthorized()); } // ======================================================== GuildConfig guildConfig = await database.SelectSpecificGuildConfig(guildid); if (guildConfig == null) { logger.LogInformation($"{HttpContext.Request.Method} {HttpContext.Request.Path} | 400 Guild not registered."); return(BadRequest("Guild not registered.")); } AutoModerationConfig currentConfig = await database.SelectModerationConfigForGuildAndType(guildid, dto.AutoModerationType); if (currentConfig == null) { currentConfig = new AutoModerationConfig(); } if (!Enum.IsDefined(typeof(AutoModerationType), dto.AutoModerationType)) { logger.LogInformation($"{HttpContext.Request.Method} {HttpContext.Request.Path} | 400 Invalid moderation type."); return(BadRequest("Invalid moderation type")); } if (!Enum.IsDefined(typeof(AutoModerationAction), dto.AutoModerationAction)) { logger.LogInformation($"{HttpContext.Request.Method} {HttpContext.Request.Path} | 400 Invalid action type."); return(BadRequest("Invalid action type")); } currentConfig.AutoModerationType = dto.AutoModerationType; currentConfig.AutoModerationAction = dto.AutoModerationAction; currentConfig.GuildId = guildid; currentConfig.IgnoreChannels = dto.IgnoreChannels.Distinct().ToArray(); currentConfig.IgnoreRoles = dto.IgnoreRoles.Distinct().ToArray(); currentConfig.Limit = dto.Limit; currentConfig.TimeLimitMinutes = dto.TimeLimitMinutes; currentConfig.PunishmentType = dto.PunishmentType; currentConfig.PunishmentDurationMinutes = dto.PunishmentDurationMinutes; currentConfig.SendPublicNotification = dto.SendPublicNotification; currentConfig.SendDmNotification = dto.SendDmNotification; database.PutModerationConfig(currentConfig); await database.SaveChangesAsync(); return(Ok(new { Id = currentConfig.Id, GuildId = currentConfig.GuildId, Type = currentConfig.AutoModerationType })); }