From 0427642af28bc8e855bf36b56ea83a46bc8e38cf Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sat, 7 Jan 2017 22:31:42 +0100 Subject: [PATCH] CommandHandler refactor --- .../Permissions/Commands/BlacklistCommands.cs | 35 ++- src/NadekoBot/Services/CommandHandler.cs | 282 ++++++++++-------- 2 files changed, 183 insertions(+), 134 deletions(-) diff --git a/src/NadekoBot/Modules/Permissions/Commands/BlacklistCommands.cs b/src/NadekoBot/Modules/Permissions/Commands/BlacklistCommands.cs index 72c41bc3..e02dc895 100644 --- a/src/NadekoBot/Modules/Permissions/Commands/BlacklistCommands.cs +++ b/src/NadekoBot/Modules/Permissions/Commands/BlacklistCommands.cs @@ -23,13 +23,18 @@ namespace NadekoBot.Modules.Permissions [Group] public class BlacklistCommands : ModuleBase { - public static ConcurrentHashSet BlacklistedItems { get; set; } = new ConcurrentHashSet(); + public static ConcurrentHashSet BlacklistedUsers { get; set; } = new ConcurrentHashSet(); + public static ConcurrentHashSet BlacklistedGuilds { get; set; } = new ConcurrentHashSet(); + public static ConcurrentHashSet BlacklistedChannels { get; set; } = new ConcurrentHashSet(); static BlacklistCommands() { using (var uow = DbHandler.UnitOfWork()) { - BlacklistedItems = new ConcurrentHashSet(uow.BotConfig.GetOrCreate().Blacklist); + var blacklist = uow.BotConfig.GetOrCreate().Blacklist; + BlacklistedUsers = new ConcurrentHashSet(blacklist.Where(bi => bi.Type == BlacklistType.User).Select(c => c.ItemId)); + BlacklistedGuilds = new ConcurrentHashSet(blacklist.Where(bi => bi.Type == BlacklistType.Server).Select(c => c.ItemId)); + BlacklistedChannels = new ConcurrentHashSet(blacklist.Where(bi => bi.Type == BlacklistType.Channel).Select(c => c.ItemId)); } } @@ -66,12 +71,34 @@ namespace NadekoBot.Modules.Permissions { var item = new BlacklistItem { ItemId = id, Type = type }; uow.BotConfig.GetOrCreate().Blacklist.Add(item); - BlacklistedItems.Add(item); + if (type == BlacklistType.Server) + { + BlacklistedGuilds.Add(id); + } + else if (type == BlacklistType.Channel) + { + BlacklistedChannels.Add(id); + } + else if (type == BlacklistType.User) + { + BlacklistedUsers.Add(id); + } } else { uow.BotConfig.GetOrCreate().Blacklist.RemoveWhere(bi => bi.ItemId == id && bi.Type == type); - BlacklistedItems.RemoveWhere(bi => bi.ItemId == id && bi.Type == type); + if (type == BlacklistType.Server) + { + BlacklistedGuilds.TryRemove(id); + } + else if (type == BlacklistType.Channel) + { + BlacklistedChannels.TryRemove(id); + } + else if (type == BlacklistType.User) + { + BlacklistedUsers.TryRemove(id); + } } await uow.CompleteAsync().ConfigureAwait(false); } diff --git a/src/NadekoBot/Services/CommandHandler.cs b/src/NadekoBot/Services/CommandHandler.cs index 9175f363..e4e30c4d 100644 --- a/src/NadekoBot/Services/CommandHandler.cs +++ b/src/NadekoBot/Services/CommandHandler.cs @@ -18,6 +18,7 @@ using NadekoBot.Modules.CustomReactions; using NadekoBot.Modules.Games; using System.Collections.Concurrent; using System.Threading; +using NadekoBot.DataStructures; namespace NadekoBot.Services { @@ -29,6 +30,8 @@ namespace NadekoBot.Services } public class CommandHandler { + public const int GlobalCommandsCooldown = 1500; + private ShardedDiscordClient _client; private CommandService _commandService; private Logger _log; @@ -52,7 +55,7 @@ namespace NadekoBot.Services clearUsersOnShortCooldown = new Timer((_) => { UsersOnShortCooldown.Clear(); - }, null, 1500, 1500); + }, null, GlobalCommandsCooldown, GlobalCommandsCooldown); } public async Task StartHandling() { @@ -71,153 +74,187 @@ namespace NadekoBot.Services _client.MessageReceived += MessageReceivedHandler; } - private async void MessageReceivedHandler(SocketMessage msg) + private async Task TryRunCleverbot(SocketUserMessage usrMsg, IGuild guild) { + if (guild == null) + return false; try { - var usrMsg = msg as SocketUserMessage; - if (usrMsg == null) - return; - - if (!usrMsg.IsAuthor()) - UserMessagesSent.AddOrUpdate(usrMsg.Author.Id, 1, (key, old) => ++old); - - if (!UsersOnShortCooldown.Add(usrMsg.Author.Id)) - return; - - if (msg.Author.IsBot || !NadekoBot.Ready) //no bots - return; - - var guild = (msg.Channel as SocketTextChannel)?.Guild; - - if (guild != null && guild.OwnerId != msg.Author.Id) + var cleverbotExecuted = await Games.CleverBotCommands.TryAsk(usrMsg).ConfigureAwait(false); + if (cleverbotExecuted) { - //todo split checks into their own modules - if (Permissions.FilterCommands.InviteFilteringChannels.Contains(msg.Channel.Id) || - Permissions.FilterCommands.InviteFilteringServers.Contains(guild.Id)) - { - if (usrMsg.Content.IsDiscordInvite()) - { - try - { - await usrMsg.DeleteAsync().ConfigureAwait(false); - return; - } - catch (HttpException ex) - { - _log.Warn("I do not have permission to filter invites in channel with id " + msg.Channel.Id, ex); - } - } - } - - var filteredWords = Permissions.FilterCommands.FilteredWordsForChannel(msg.Channel.Id, guild.Id).Concat(Permissions.FilterCommands.FilteredWordsForServer(guild.Id)); - var wordsInMessage = usrMsg.Content.ToLowerInvariant().Split(' '); - if (filteredWords.Any(w => wordsInMessage.Contains(w))) - { - try - { - await usrMsg.DeleteAsync().ConfigureAwait(false); - return; - } - catch (HttpException ex) - { - _log.Warn("I do not have permission to filter words in channel with id " + msg.Channel.Id, ex); - } - } - } - - BlacklistItem blacklistedItem; - if ((blacklistedItem = Permissions.BlacklistCommands.BlacklistedItems.FirstOrDefault(bi => - (bi.Type == BlacklistItem.BlacklistType.Server && bi.ItemId == guild?.Id) || - (bi.Type == BlacklistItem.BlacklistType.Channel && bi.ItemId == msg.Channel.Id) || - (bi.Type == BlacklistItem.BlacklistType.User && bi.ItemId == msg.Author.Id))) != null) - { - return; - } - - try - { - var cleverbotExecuted = await Games.CleverBotCommands.TryAsk(usrMsg); - if (cleverbotExecuted) - { - _log.Info($@"CleverBot Executed + _log.Info($@"CleverBot Executed Server: {guild.Name} [{guild.Id}] Channel: {usrMsg.Channel?.Name} [{usrMsg.Channel?.Id}] UserId: {usrMsg.Author} [{usrMsg.Author.Id}] Message: {usrMsg.Content}"); - return; - } + return true; } - catch (Exception ex) { _log.Warn(ex, "Error in cleverbot"); } + } + catch (Exception ex) { _log.Warn(ex, "Error in cleverbot"); } + return false; + } - try - { - // maybe this message is a custom reaction - var crExecuted = await CustomReactions.TryExecuteCustomReaction(usrMsg).ConfigureAwait(false); + private bool IsBlacklisted(IGuild guild, SocketUserMessage usrMsg) => + (guild != null && BlacklistCommands.BlacklistedGuilds.Contains(guild.Id)) || + BlacklistCommands.BlacklistedChannels.Contains(usrMsg.Channel.Id) || + BlacklistCommands.BlacklistedUsers.Contains(usrMsg.Author.Id); - //if it was, don't execute the command - if (crExecuted) - return; - } - catch { } - string messageContent = usrMsg.Content; + private async Task LogSuccessfulExecution(SocketUserMessage usrMsg, ExecuteCommandResult exec, SocketTextChannel channel, Stopwatch sw) + { + await CommandExecuted(usrMsg, exec.CommandInfo).ConfigureAwait(false); + _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); + } - var sw = new Stopwatch(); - sw.Start(); - var exec = await ExecuteCommand(new CommandContext(_client.MainClient, usrMsg), messageContent, DependencyMap.Empty, MultiMatchHandling.Best); - var command = exec.CommandInfo; - var permCache = exec.PermissionCache; - var result = exec.Result; - sw.Stop(); - var channel = (msg.Channel as ITextChannel); - if (result.IsSuccess) - { - await CommandExecuted(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}", - msg.Author + " [" + msg.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" + + private void LogErroredExecution(SocketUserMessage usrMsg, ExecuteCommandResult exec, SocketTextChannel channel, Stopwatch sw) + { + _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}", - msg.Author + " [" + msg.Author.Id + "]", // {0} + 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} + exec.Result.ErrorReason, // {4} sw.Elapsed.TotalSeconds // {5} ); - if (guild != null && command != null && result.Error == CommandError.Exception) + } + + private async Task InviteFiltered(IGuild guild, SocketUserMessage usrMsg) + { + if ((Permissions.FilterCommands.InviteFilteringChannels.Contains(usrMsg.Channel.Id) || + Permissions.FilterCommands.InviteFilteringServers.Contains(guild.Id)) && + usrMsg.Content.IsDiscordInvite()) + { + try + { + await usrMsg.DeleteAsync().ConfigureAwait(false); + return true; + } + catch (HttpException ex) + { + _log.Warn("I do not have permission to filter invites in channel with id " + usrMsg.Channel.Id, ex); + return true; + } + } + return false; + } + + private async Task WordFiltered(IGuild guild, SocketUserMessage usrMsg) + { + var filteredChannelWords = Permissions.FilterCommands.FilteredWordsForChannel(usrMsg.Channel.Id, guild.Id); + var filteredServerWords = Permissions.FilterCommands.FilteredWordsForServer(guild.Id); + var wordsInMessage = usrMsg.Content.ToLowerInvariant().Split(' '); + if (filteredChannelWords.Count != 0 || filteredServerWords.Count != 0) + { + foreach (var word in wordsInMessage) + { + if (filteredChannelWords.Contains(word) || + filteredServerWords.Contains(word)) { - if (permCache != null && permCache.Verbose) - try { await msg.Channel.SendMessageAsync("⚠️ " + result.ErrorReason).ConfigureAwait(false); } catch { } + try + { + await usrMsg.DeleteAsync().ConfigureAwait(false); + } + catch (HttpException ex) + { + _log.Warn("I do not have permission to filter words in channel with id " + usrMsg.Channel.Id, ex); + } + return true; + } + } + } + return false; + } + + private async void MessageReceivedHandler(SocketMessage msg) + { + try + { + if (msg.Author.IsBot || !NadekoBot.Ready) //no bots, wait until bot connected and initialized + return; + + var usrMsg = msg as SocketUserMessage; + if (usrMsg == null) //has to be an user message, not system/other messages. + return; + + // track how many messagges each user is sending + UserMessagesSent.AddOrUpdate(usrMsg.Author.Id, 1, (key, old) => ++old); + + // Bot will ignore commands which are ran more often than what specified by + // GlobalCommandsCooldown constant (miliseconds) + if (!UsersOnShortCooldown.Add(usrMsg.Author.Id)) + return; + + var channel = msg.Channel as SocketTextChannel; + var guild = channel?.Guild; + + if (guild != null && guild.OwnerId != msg.Author.Id) + { + if (await InviteFiltered(guild, usrMsg).ConfigureAwait(false)) + return; + + if (await WordFiltered(guild, usrMsg).ConfigureAwait(false)) + return; + } + + if (IsBlacklisted(guild, usrMsg)) + return; + + var cleverBotRan = await TryRunCleverbot(usrMsg, guild).ConfigureAwait(false); + if (cleverBotRan) + return; + + // maybe this message is a custom reaction + var crExecuted = await CustomReactions.TryExecuteCustomReaction(usrMsg).ConfigureAwait(false); + if (crExecuted) //if it was, don't execute the command + return; + + string messageContent = usrMsg.Content; + + // execute the command and measure the time it took + var sw = Stopwatch.StartNew(); + var exec = await ExecuteCommand(new CommandContext(_client.MainClient, usrMsg), messageContent, DependencyMap.Empty, MultiMatchHandling.Best); + sw.Stop(); + + if (exec.Result.IsSuccess) + { + await LogSuccessfulExecution(usrMsg, exec, channel, sw).ConfigureAwait(false); + } + else if (!exec.Result.IsSuccess && exec.Result.Error != CommandError.UnknownCommand) + { + LogErroredExecution(usrMsg, exec, channel, sw); + if (guild != null && exec.CommandInfo != null && exec.Result.Error == CommandError.Exception) + { + if (exec.PermissionCache != null && exec.PermissionCache.Verbose) + try { await msg.Channel.SendMessageAsync("⚠️ " + exec.Result.ErrorReason).ConfigureAwait(false); } catch { } } } else { if (msg.Channel is IPrivateChannel) { - //rofl, gotta do this to prevent this message from occuring on polls + // rofl, gotta do this to prevent dm help message being sent to + // users who are voting on private polls (sending a number in a DM) int vote; if (int.TryParse(msg.Content, out vote)) return; - + await msg.Channel.SendMessageAsync(Help.DMHelpString).ConfigureAwait(false); - await DMForwardCommands.HandleDMForwarding(msg, ownerChannels); + await DMForwardCommands.HandleDMForwarding(msg, ownerChannels).ConfigureAwait(false); } } } @@ -231,9 +268,8 @@ namespace NadekoBot.Services _log.Warn(ex.InnerException); } } - - return; } + public Task ExecuteCommandAsync(CommandContext context, int argPos, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) => ExecuteCommand(context, context.Message.Content.Substring(argPos), dependencyMap, multiMatchHandling); @@ -328,19 +364,5 @@ namespace NadekoBot.Services return new ExecuteCommandResult(null, null, SearchResult.FromError(CommandError.UnknownCommand, "This input does not match any overload.")); } - - public struct ExecuteCommandResult - { - public readonly CommandInfo CommandInfo; - public readonly PermissionCache PermissionCache; - public readonly IResult Result; - - public ExecuteCommandResult(CommandInfo commandInfo, PermissionCache cache, IResult result) - { - this.CommandInfo = commandInfo; - this.PermissionCache = cache; - this.Result = result; - } - } } } \ No newline at end of file