Esempio n. 1
0
        /// <summary>
        /// Handles responses for messages.
        /// TODO: This method is way too huge.
        /// TODO: read prior todo, IT'S GETTING WORSe, STAHP
        /// </summary>
        private async Task HandleMessageReceivedAsync(SocketMessage socketMessage, string reactionType = null, IUser reactionUser = null)
        {
            var props = new Dictionary <string, string> {
                { "server", (socketMessage.Channel as SocketGuildChannel)?.Guild.Id.ToString() ?? "private" },
                { "channel", socketMessage.Channel.Id.ToString() },
            };

            this.TrackEvent("messageReceived", props);

            // Ignore system and our own messages.
            var  message    = socketMessage as SocketUserMessage;
            bool isOutbound = false;

            // replicate to webhook, if configured
            this.CallOutgoingWebhookAsync(message).Forget();

            if (message == null || (isOutbound = message.Author.Id == this.Client.CurrentUser.Id))
            {
                if (isOutbound && this.Config.LogOutgoing)
                {
                    var logMessage = message.Embeds?.Count > 0 ? $"Sending [embed content] to {message.Channel.Name}" : $"Sending to {message.Channel.Name}: {message.Content}";
                    Log.Verbose($"{{Outgoing}} {logMessage}", ">>>");
                }

                return;
            }

            // Ignore other bots unless it's an allowed webhook
            // Always ignore bot reactions
            var webhookUser = message.Author as IWebhookUser;

            if (message.Author.IsBot && !this.Config.Discord.AllowedWebhooks.Contains(webhookUser?.WebhookId ?? 0) || reactionUser?.IsBot == true)
            {
                return;
            }

            // grab the settings for this server
            var botGuildUser = (message.Channel as SocketGuildChannel)?.Guild.CurrentUser;
            var guildUser    = message.Author as SocketGuildUser;
            var guildId      = webhookUser?.GuildId ?? guildUser?.Guild.Id;
            var settings     = SettingsConfig.GetSettings(guildId?.ToString());

            // if it's a globally blocked server, ignore it unless it's the owner
            if (message.Author.Id != this.Config.Discord.OwnerId && guildId != null && this.Config.Discord.BlockedServers.Contains(guildId.Value) && !this.Config.OcrAutoIds.Contains(message.Channel.Id))
            {
                return;
            }

            // if it's a globally blocked user, ignore them
            if (this.Config.Discord.BlockedUsers.Contains(message.Author.Id))
            {
                return;
            }

            if (this.Throttler.IsThrottled(message.Author.Id.ToString(), ThrottleType.User))
            {
                Log.Debug($"messaging throttle from user: {message.Author.Id} on chan {message.Channel.Id} server {guildId}");
                return;
            }

            // Bail out with help info if it's a PM
            if (message.Channel is IDMChannel)
            {
                await this.RespondAsync(message, "Info and commands can be found at: https://ub3r-b0t.com [Commands don't work in direct messages]");

                return;
            }

            if (this.Throttler.IsThrottled(guildId.ToString(), ThrottleType.Guild))
            {
                Log.Debug($"messaging throttle from guild: {message.Author.Id} on chan {message.Channel.Id} server {guildId}");
                return;
            }

            var botContext = new DiscordBotContext(this.Client, message)
            {
                Reaction     = reactionType,
                ReactionUser = reactionUser,
                BotApi       = this.BotApi,
                AudioManager = this.audioManager,
                Bot          = this,
            };

            foreach (var module in this.modules)
            {
                var typeInfo = module.GetType().GetTypeInfo();
                var permissionChecksPassed = await this.CheckPermissions(botContext, typeInfo);

                if (!permissionChecksPassed)
                {
                    continue;
                }

                var result = await module.Process(botContext);

                if (result == ModuleResult.Stop)
                {
                    return;
                }
            }

            var textChannel = message.Channel as ITextChannel;

            if (botGuildUser != null && !botGuildUser.GetPermissions(textChannel).SendMessages)
            {
                return;
            }

            // If it's a command, match that before anything else.
            await this.PreProcessMessage(botContext.MessageData, settings);

            string command = botContext.MessageData.Command;

            if (message.Attachments.FirstOrDefault() is Attachment attachment)
            {
                this.ImageUrls[botContext.MessageData.Channel] = attachment;
            }

            // if it's a blocked command, bail
            if (settings.IsCommandDisabled(CommandsConfig.Instance, command) && !IsAuthorOwner(message))
            {
                return;
            }

            if (this.discordCommands.TryGetValue(command, out IDiscordCommand discordCommand))
            {
                var commandProps = new Dictionary <string, string> {
                    { "command", command.ToLowerInvariant() },
                    { "server", botContext.MessageData.Server },
                    { "channel", botContext.MessageData.Channel }
                };
                this.TrackEvent("commandProcessed", commandProps);

                var typeInfo = discordCommand.GetType().GetTypeInfo();
                var permissionChecksPassed = await this.CheckPermissions(botContext, typeInfo);

                if (permissionChecksPassed)
                {
                    CommandResponse response;

                    using (this.DogStats?.StartTimer("commandDuration", tags: new[] { $"shard:{this.Shard}", $"command:{command.ToLowerInvariant()}", $"{this.BotType}" }))
                    {
                        response = await discordCommand.Process(botContext);
                    }

                    if (response?.Attachment != null)
                    {
                        var sentMessage = await message.Channel.SendFileAsync(response.Attachment.Stream, response.Attachment.Name, response.Text);

                        this.botResponsesCache.Add(message.Id, sentMessage);
                    }
                    else if (response?.MultiText != null)
                    {
                        foreach (var messageText in response.MultiText)
                        {
                            var sentMessage = await this.RespondAsync(message, messageText, response.Embed, bypassEdit : true);

                            this.botResponsesCache.Add(message.Id, sentMessage);
                        }
                    }
                    else if (!string.IsNullOrEmpty(response?.Text) || response?.Embed != null)
                    {
                        var sentMessage = await this.RespondAsync(message, response.Text, response.Embed);

                        this.botResponsesCache.Add(message.Id, sentMessage);
                    }
                }
            }
            else
            {
                // Enter typing state for valid commands; skip if throttled
                IDisposable typingState = null;
                var         commandKey  = command + botContext.MessageData.Server;
                if (this.Config.Discord.TriggerTypingOnCommands &&
                    CommandsConfig.Instance.Commands.ContainsKey(command) &&
                    !this.Throttler.IsThrottled(commandKey, ThrottleType.Command))
                {
                    // possible bug with typing state
                    Log.Debug($"typing triggered by {command}");
                    typingState = message.Channel.EnterTypingState();
                }

                if (botContext.MessageData.Command == "quote" && reactionUser != null)
                {
                    botContext.MessageData.UserName = reactionUser.Username;
                }

                try
                {
                    BotResponseData responseData = await this.ProcessMessageAsync(botContext.MessageData, settings);

                    if (Uri.TryCreate(responseData.AttachmentUrl, UriKind.Absolute, out Uri attachmentUri))
                    {
                        Stream fileStream = await attachmentUri.GetStreamAsync();

                        var sentMessage = await message.Channel.SendFileAsync(fileStream, Path.GetFileName(attachmentUri.AbsolutePath));

                        this.botResponsesCache.Add(message.Id, sentMessage);
                    }
                    else if (responseData.Embed != null)
                    {
                        var sentMessage = await this.RespondAsync(message, string.Empty, responseData.Embed.CreateEmbedBuilder().Build(), bypassEdit : false, rateLimitChecked : botContext.MessageData.RateLimitChecked);

                        this.botResponsesCache.Add(message.Id, sentMessage);
                    }
                    else
                    {
                        foreach (string response in responseData.Responses)
                        {
                            if (!string.IsNullOrWhiteSpace(response))
                            {
                                // if sending a multi part message, skip the edit optimization.
                                var sentMessage = await this.RespondAsync(message, response, embedResponse : null, bypassEdit : responseData.Responses.Count > 1, rateLimitChecked : botContext.MessageData.RateLimitChecked);

                                this.botResponsesCache.Add(message.Id, sentMessage);
                            }
                        }
                    }
                }
                finally
                {
                    typingState?.Dispose();
                }
            }
        }
Esempio n. 2
0
        /// <summary>
        /// Handles responses for messages.
        /// TODO: This method is way too huge.
        /// </summary>
        private async Task HandleMessageReceivedAsync(SocketMessage socketMessage, string reactionType = null, IUser reactionUser = null)
        {
            var props = new Dictionary <string, string> {
                { "server", (socketMessage.Channel as SocketGuildChannel)?.Guild.Id.ToString() ?? "private" },
                { "channel", socketMessage.Channel.Id.ToString() },
            };

            this.TrackEvent("messageReceived", props);

            // Ignore system and our own messages.
            var  message    = socketMessage as SocketUserMessage;
            bool isOutbound = false;

            // replicate to webhook, if configured
            this.CallOutgoingWebhookAsync(message).Forget();

            if (message == null || (isOutbound = message.Author.Id == this.Client.CurrentUser.Id))
            {
                if (isOutbound)
                {
                    var logMessage = message.Embeds?.Count > 0 ? $"\tSending [embed content] to {message.Channel.Name}" : $"\tSending to {message.Channel.Name}: {message.Content}";
                    this.Logger.Log(LogType.Outgoing, logMessage);
                }

                return;
            }

            // Ignore other bots
            if (message.Author.IsBot)
            {
                return;
            }

            // grab the settings for this server
            var botGuildUser = (message.Channel as SocketGuildChannel)?.Guild.CurrentUser;
            var guildUser    = message.Author as SocketGuildUser;
            var guildId      = (guildUser != null && guildUser.IsWebhook) ? null : guildUser?.Guild.Id;
            var settings     = SettingsConfig.GetSettings(guildId?.ToString());

            // if it's a globally blocked server, ignore it unless it's the owner
            if (message.Author.Id != this.Config.Discord.OwnerId && guildId != null && this.Config.Discord.BlockedServers.Contains(guildId.Value) && !this.Config.OcrAutoIds.Contains(message.Channel.Id))
            {
                return;
            }

            // if the user is blocked based on role, return
            var botlessRoleId = guildUser?.Guild?.Roles?.FirstOrDefault(r => r.Name?.ToLowerInvariant() == "botless")?.Id;

            if ((message.Author as IGuildUser)?.RoleIds.Any(r => botlessRoleId != null && r == botlessRoleId.Value) ?? false)
            {
                return;
            }

            // Bail out with help info if it's a PM
            if (message.Channel is IDMChannel)
            {
                await this.RespondAsync(message, "Info and commands can be found at: https://ub3r-b0t.com [Commands don't work in direct messages]");

                return;
            }

            var botContext = new DiscordBotContext(this.Client, message)
            {
                Reaction     = reactionType,
                ReactionUser = reactionUser,
                BotApi       = this.BotApi,
                AudioManager = this.audioManager,
            };

            foreach (var module in this.modules)
            {
                var typeInfo = module.GetType().GetTypeInfo();
                var permissionChecksPassed = await this.CheckPermissions(botContext, typeInfo);

                if (!permissionChecksPassed)
                {
                    continue;
                }

                var result = await module.Process(botContext);

                if (result == ModuleResult.Stop)
                {
                    return;
                }
            }

            var textChannel = message.Channel as ITextChannel;

            if (botGuildUser != null && !botGuildUser.GetPermissions(textChannel).SendMessages)
            {
                return;
            }

            // If it's a command, match that before anything else.
            await this.PreProcessMessage(botContext.MessageData, settings);

            string command = botContext.MessageData.Command;

            if (message.Attachments.FirstOrDefault() is Attachment attachment)
            {
                imageUrls[botContext.MessageData.Channel] = attachment;
            }

            // if it's a blocked command, bail
            if (settings.IsCommandDisabled(CommandsConfig.Instance, command) && !IsAuthorOwner(message))
            {
                return;
            }

            if (this.discordCommands.TryGetValue(command, out IDiscordCommand discordCommand))
            {
                var typeInfo = discordCommand.GetType().GetTypeInfo();
                var permissionChecksPassed = await this.CheckPermissions(botContext, typeInfo);

                if (permissionChecksPassed)
                {
                    var response = await discordCommand.Process(botContext);

                    if (response?.Attachment != null)
                    {
                        var sentMessage = await message.Channel.SendFileAsync(response.Attachment.Stream, response.Attachment.Name, response.Text);

                        this.botResponsesCache.Add(message.Id, sentMessage);
                    }
                    else if (!string.IsNullOrEmpty(response?.Text) || response?.Embed != null)
                    {
                        var sentMessage = await this.RespondAsync(message, response.Text, response.Embed);

                        this.botResponsesCache.Add(message.Id, sentMessage);
                    }
                }
            }
            else
            {
                IDisposable typingState = null;
                if (CommandsConfig.Instance.Commands.ContainsKey(command))
                {
                    // possible bug with typing state
                    Console.WriteLine($"typing triggered by {command}");
                    typingState = message.Channel.EnterTypingState();
                }

                if (botContext.MessageData.Command == "quote" && reactionUser != null)
                {
                    botContext.MessageData.UserName = reactionUser.Username;
                }

                try
                {
                    BotResponseData responseData = await this.ProcessMessageAsync(botContext.MessageData, settings);

                    if (responseData.Embed != null)
                    {
                        var sentMessage = await this.RespondAsync(message, string.Empty, responseData.Embed.CreateEmbedBuilder());

                        this.botResponsesCache.Add(message.Id, sentMessage);
                    }
                    else
                    {
                        foreach (string response in responseData.Responses)
                        {
                            if (!string.IsNullOrEmpty(response))
                            {
                                // if sending a multi part message, skip the edit optimization.
                                var sentMessage = await this.RespondAsync(message, response, embedResponse : null, bypassEdit : responseData.Responses.Count > 1);

                                this.botResponsesCache.Add(message.Id, sentMessage);
                            }
                        }
                    }
                }
                finally
                {
                    typingState?.Dispose();
                }
            }
        }