diff --git a/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs b/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs index 75cc79bd..932e67dd 100644 --- a/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs +++ b/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs @@ -177,6 +177,42 @@ namespace NadekoBot.Migrations b.ToTable("EightBallResponses"); }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.Property("GuildConfigId1"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.HasIndex("GuildConfigId1"); + + b.ToTable("FilterChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildConfigId"); + + b.Property("Word"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilteredWord"); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => { b.Property("Id") @@ -230,6 +266,10 @@ namespace NadekoBot.Migrations b.Property("ExclusiveSelfAssignedRoles"); + b.Property("FilterInvites"); + + b.Property("FilterWords"); + b.Property("GenerateCurrencyChannelId"); b.Property("GreetMessageChannelId"); @@ -576,6 +616,24 @@ namespace NadekoBot.Migrations .HasForeignKey("BotConfigId"); }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => { b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") diff --git a/src/NadekoBot/Services/CommandHandler.cs b/src/NadekoBot/Services/CommandHandler.cs index 5703eaec..579790c2 100644 --- a/src/NadekoBot/Services/CommandHandler.cs +++ b/src/NadekoBot/Services/CommandHandler.cs @@ -13,6 +13,7 @@ using NadekoBot.Services.Database.Models; using NadekoBot.Modules.Permissions; using Microsoft.Data.Sqlite; using Discord.Net; +using NadekoBot.Extensions; namespace NadekoBot.Services { @@ -33,14 +34,14 @@ namespace NadekoBot.Services _client.MessageReceived += MessageReceivedHandler; } - private Task MessageReceivedHandler(IMessage msg) + private async Task MessageReceivedHandler(IMessage msg) { var usrMsg = msg as IUserMessage; if (usrMsg == null) - return Task.CompletedTask; + return; if (usrMsg.Author.IsBot) //no bots - return Task.CompletedTask; + return; var guild = (msg.Channel as ITextChannel)?.Guild; @@ -51,83 +52,124 @@ namespace NadekoBot.Services (bi.Type == BlacklistItem.BlacklistType.User && bi.ItemId == usrMsg.Author.Id))) != null) { _log.Warn("Attempt was made to run a command by a blacklisted {0}, id: {1}", blacklistedItem.Type, blacklistedItem.ItemId); - return Task.CompletedTask; + return; } - var throwaway = Task.Run(async () => + if (guild != null) { - var sw = new Stopwatch(); - sw.Start(); - - try + if (Permissions.FilterCommands.InviteFilteringChannels.Contains(usrMsg.Channel.Id) || + Permissions.FilterCommands.InviteFilteringServers.Contains(guild.Id)) { - - bool verbose = false; - Permission rootPerm = null; - string permRole = ""; - if (guild != null) + if (usrMsg.Content.IsDiscordInvite()) { - using (var uow = DbHandler.UnitOfWork()) + try { - var config = uow.GuildConfigs.PermissionsFor(guild.Id); - verbose = config.VerbosePermissions; - rootPerm = config.RootPermission; - permRole = config.PermissionRole.Trim().ToLowerInvariant(); + await usrMsg.DeleteAsync().ConfigureAwait(false); + return; } - } - - - var t = await ExecuteCommand(usrMsg, usrMsg.Content, guild, usrMsg.Author, rootPerm, permRole, MultiMatchHandling.Best); - var command = t.Item1; - var result = t.Item2; - sw.Stop(); - var channel = (usrMsg.Channel as ITextChannel); - if (result.IsSuccess) - { - CommandExecuted(this, new CommandExecutedEventArgs(usrMsg, command)); - _log.Info("Command Executed after {4}s\n\t" + - "User: {0}\n\t" + - "Server: {1}\n\t" + - "Channel: {2}\n\t" + - "Message: {3}", - usrMsg.Author + " [" + usrMsg.Author.Id + "]", // {0} - (channel == null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]"), // {1} - (channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2} - usrMsg.Content, // {3} - sw.Elapsed.TotalSeconds // {4} - ); - } - else if (!result.IsSuccess && result.Error != CommandError.UnknownCommand) - { - _log.Warn("Command Errored after {5}s\n\t" + - "User: {0}\n\t" + - "Server: {1}\n\t" + - "Channel: {2}\n\t" + - "Message: {3}\n\t" + - "Error: {4}", - usrMsg.Author + " [" + usrMsg.Author.Id + "]", // {0} - (channel == null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]"), // {1} - (channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2} - usrMsg.Content,// {3} - result.ErrorReason, // {4} - sw.Elapsed.TotalSeconds // {5} - ); - if (guild != null && command != null && result.Error == CommandError.Exception) + catch (HttpException ex) { - if (verbose) - await msg.Channel.SendMessageAsync(":warning: " + result.ErrorReason).ConfigureAwait(false); + _log.Warn("I do not have permission to filter invites in channel with id " + usrMsg.Channel.Id, ex); } } } - catch (Exception ex) + var filteredWords = Permissions.FilterCommands.FilteredWordsForChannel(usrMsg.Channel.Id, guild.Id).Concat(Permissions.FilterCommands.FilteredWordsForServer(guild.Id)); + var wordsInMessage = usrMsg.Content.ToLowerInvariant().Split(' '); + if (filteredWords.Any()) { - _log.Warn(ex, "Error in CommandHandler"); - if(ex.InnerException != null) - _log.Warn(ex.InnerException, "Inner Exception of the error in CommandHandler"); + try + { + await usrMsg.DeleteAsync().ConfigureAwait(false); + return; + } + catch (HttpException ex) + { + _log.Warn("I do not have permission to filter words in channel with id " + usrMsg.Channel.Id, ex); + } } - }); + } - return Task.CompletedTask; + try + { + bool verbose = false; + Permission rootPerm = null; + string permRole = ""; + if (guild != null) + { + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.GuildConfigs.PermissionsFor(guild.Id); + verbose = config.VerbosePermissions; + rootPerm = config.RootPermission; + permRole = config.PermissionRole.Trim().ToLowerInvariant(); + } + + + } + + var throwaway = Task.Run(async () => + { + var sw = new Stopwatch(); + sw.Start(); + + try + { + var t = await ExecuteCommand(usrMsg, usrMsg.Content, guild, usrMsg.Author, rootPerm, permRole, MultiMatchHandling.Best); + var command = t.Item1; + var result = t.Item2; + sw.Stop(); + var channel = (usrMsg.Channel as ITextChannel); + if (result.IsSuccess) + { + CommandExecuted(this, new CommandExecutedEventArgs(usrMsg, command)); + _log.Info("Command Executed after {4}s\n\t" + + "User: {0}\n\t" + + "Server: {1}\n\t" + + "Channel: {2}\n\t" + + "Message: {3}", + usrMsg.Author + " [" + usrMsg.Author.Id + "]", // {0} + (channel == null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]"), // {1} + (channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2} + usrMsg.Content, // {3} + sw.Elapsed.TotalSeconds // {4} + ); + } + else if (!result.IsSuccess && result.Error != CommandError.UnknownCommand) + { + _log.Warn("Command Errored after {5}s\n\t" + + "User: {0}\n\t" + + "Server: {1}\n\t" + + "Channel: {2}\n\t" + + "Message: {3}\n\t" + + "Error: {4}", + usrMsg.Author + " [" + usrMsg.Author.Id + "]", // {0} + (channel == null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]"), // {1} + (channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2} + usrMsg.Content,// {3} + result.ErrorReason, // {4} + sw.Elapsed.TotalSeconds // {5} + ); + if (guild != null && command != null && result.Error == CommandError.Exception) + { + if (verbose) + await msg.Channel.SendMessageAsync(":warning: " + result.ErrorReason).ConfigureAwait(false); + } + } + } + catch (Exception ex) + { + _log.Warn(ex, "Error in CommandHandler"); + if (ex.InnerException != null) + _log.Warn(ex.InnerException, "Inner Exception of the error in CommandHandler"); + } + }); + } + catch (Exception ex) + { + _log.Error(ex, "Error in the outter scope of the commandhandler."); + if (ex.InnerException != null) + _log.Error(ex.InnerException, "Inner exception: "); + } } public async Task> ExecuteCommand(IUserMessage message, string input, IGuild guild, IUser user, Permission rootPerm, string permRole, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Best) { diff --git a/src/NadekoBot/Services/Database/Models/GuildConfig.cs b/src/NadekoBot/Services/Database/Models/GuildConfig.cs index 8f96a284..3ef24e35 100644 --- a/src/NadekoBot/Services/Database/Models/GuildConfig.cs +++ b/src/NadekoBot/Services/Database/Models/GuildConfig.cs @@ -46,5 +46,23 @@ namespace NadekoBot.Services.Database.Models public Permission RootPermission { get; set; } public bool VerbosePermissions { get; set; } public string PermissionRole { get; set; } = "Nadeko"; + + //filtering + public bool FilterInvites { get; set; } + public HashSet FilterInvitesChannelIds { get; set; } + + public bool FilterWords { get; set; } + public HashSet FilteredWords { get; set; } + public HashSet FilterWordsChannelIds { get; set; } + } + + public class FilterChannelId :DbEntity + { + public ulong ChannelId { get; set; } + } + + public class FilteredWord :DbEntity + { + public string Word { get; set; } } } diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/GuildConfigRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/GuildConfigRepository.cs index 1066397b..9a82c191 100644 --- a/src/NadekoBot/Services/Database/Repositories/Impl/GuildConfigRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/Impl/GuildConfigRepository.cs @@ -23,6 +23,9 @@ namespace NadekoBot.Services.Database.Repositories.Impl .ThenInclude(gc => gc.Previous) .Include(gc => gc.RootPermission) .ThenInclude(gc => gc.Next) + .Include(gc => gc.FilterInvitesChannelIds) + .Include(gc => gc.FilterWordsChannelIds) + .Include(gc => gc.FilteredWords) .ToList(); /// @@ -37,6 +40,9 @@ namespace NadekoBot.Services.Database.Repositories.Impl .ThenInclude(ls => ls.IgnoredChannels) .Include(gc => gc.LogSetting) .ThenInclude(ls => ls.IgnoredVoicePresenceChannelIds) + .Include(gc => gc.FilterInvitesChannelIds) + .Include(gc => gc.FilterWordsChannelIds) + .Include(gc => gc.FilteredWords) .FirstOrDefault(c => c.GuildId == guildId); if (config == null) diff --git a/src/NadekoBot/_Extensions/Extensions.cs b/src/NadekoBot/_Extensions/Extensions.cs index 98a1be46..c226e45e 100644 --- a/src/NadekoBot/_Extensions/Extensions.cs +++ b/src/NadekoBot/_Extensions/Extensions.cs @@ -298,5 +298,10 @@ namespace NadekoBot.Extensions imageStream.Position = 0; return imageStream; } + + private static readonly Regex filterRegex = new Regex(@"(?:discord(?:\.gg|app\.com\/invite)\/(?([\w]{16}|(?:[\w]+-?){3})))", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public static bool IsDiscordInvite(this string str) + => filterRegex.IsMatch(str); } } \ No newline at end of file diff --git a/src/tests/Tests.cs b/src/tests/Tests.cs index fdfa5e85..57d1e8f9 100644 --- a/src/tests/Tests.cs +++ b/src/tests/Tests.cs @@ -40,9 +40,11 @@ namespace Tests root.Prepend(new Permission() { SecondaryTargetName = "Added" }); + root = root.GetRoot(); + Assert.Equal(11, root.Count()); - Assert.Equal("Added", root.AsEnumerable().Last().SecondaryTargetName); + Assert.Equal("Added", root.AsEnumerable().First().SecondaryTargetName); } [Fact] @@ -87,15 +89,9 @@ namespace Tests Assert.Equal("3", removed.SecondaryTargetName); Assert.Equal(9, root.Count()); - - var temp = root.Next; - removed = root.RemoveAt(0); - - Assert.Equal(8, temp.Count()); - Assert.Equal(null, temp.Previous); - - Assert.Throws(typeof(IndexOutOfRangeException), () => { temp.RemoveAt(8); }); - Assert.Throws(typeof(IndexOutOfRangeException), () => { temp.RemoveAt(-1); }); + Assert.Throws(typeof(IndexOutOfRangeException), () => { root.RemoveAt(0); }); + Assert.Throws(typeof(IndexOutOfRangeException), () => { root.RemoveAt(9); }); + Assert.Throws(typeof(IndexOutOfRangeException), () => { root.RemoveAt(-1); }); } [Fact]