Exemplo n.º 1
0
        public async Task HandleMessage(MessageEvent messageEvent)
        {
            try
            {
                if (messageEvent.IsSystemMessage || messageEvent.CreatedByWebhook != null || messageEvent.CreatedBy == this.client.Me.Id || messageEvent.IsReply)
                {
                    return;
                }

                if (this.Throttler.IsThrottled(messageEvent.CreatedBy.ToString(), ThrottleType.User))
                {
                    Log.Debug($"messaging throttle from user: {messageEvent.CreatedBy} on chan {messageEvent.ChannelId} server {messageEvent.ServerId}");
                    return;
                }

                if (this.Throttler.IsThrottled(messageEvent.ServerId.ToString(), ThrottleType.Guild))
                {
                    Log.Debug($"messaging throttle from guild: {messageEvent.CreatedBy} on chan {messageEvent.ChannelId} server {messageEvent.ServerId}");
                    return;
                }

                var settings = new Settings
                {
                    FunResponsesEnabled = true,
                    AutoTitlesEnabled   = true,
                    PreferEmbeds        = true,
                };

                var messageData = BotMessageData.Create(messageEvent.Message, settings);

                await this.PreProcessMessage(messageData, settings);

                BotResponseData responseData = await this.ProcessMessageAsync(messageData, settings);

                if (responseData.Embed != null)
                {
                    await this.RespondAsync(messageEvent.Message, string.Empty, bypassEdit : true, responseData.Embed.CreateGuildedEmbed());
                }
                else
                {
                    foreach (string response in responseData.Responses)
                    {
                        await this.RespondAsync(messageEvent.Message, response, bypassEdit : responseData.Responses.Count > 1);
                    }
                }
            }
            catch (Exception ex)
            {
                Log.Warning(ex, $"Error in HandleMessage");
                this.AppInsights?.TrackException(ex);
            }
        }
Exemplo n.º 2
0
        protected async Task <BotResponseData> ProcessMessageAsync(BotMessageData messageData, Settings settings)
        {
            var responses    = new List <string>();
            var responseData = new BotResponseData {
                Responses = responses
            };

            if (this.BotApi != null)
            {
                // if an explicit command is being used, it wins out over any implicitly parsed command
                string query   = messageData.Query;
                string command = messageData.Command;

                string[] contentParts = messageData.Content.Split(new[] { ' ' });

                if (string.IsNullOrEmpty(command))
                {
                    if (messageData.Content.IContains("remind "))
                    {
                        // check for reminders
                        Match timerAtMatch  = Consts.TimerOnRegex.Match(messageData.Content);
                        Match timerAt2Match = Consts.TimerOn2Regex.Match(messageData.Content);
                        if (timerAtMatch.Success && Utilities.TryParseAbsoluteReminder(timerAtMatch, messageData, out query) ||
                            timerAt2Match.Success && Utilities.TryParseAbsoluteReminder(timerAt2Match, messageData, out query))
                        {
                            command = "timer";
                        }
                        else // try relative timers if absolute had no match
                        {
                            Match timerMatch  = Consts.TimerRegex.Match(messageData.Content);
                            Match timer2Match = Consts.Timer2Regex.Match(messageData.Content);

                            if (timerMatch.Success || timer2Match.Success)
                            {
                                Match matchToUse = timerMatch.Success && !timerMatch.Groups["prep"].Value.All(char.IsDigit) ? timerMatch : timer2Match;
                                if (Utilities.TryParseReminder(matchToUse, messageData, out query))
                                {
                                    command = "timer";
                                }
                            }
                        }
                    }

                    if (string.IsNullOrEmpty(command))
                    {
                        if (settings.AutoTitlesEnabled && (CommandsConfig.Instance.AutoTitleMatches.Any(t => messageData.Content.Contains(t)) || Consts.RedditRegex.IsMatch(messageData.Content)))
                        {
                            Match  match      = Consts.HttpRegex.Match(messageData.Content);
                            string matchValue = !string.IsNullOrEmpty(match.Value) ? match.Value : Consts.RedditRegex.Match(messageData.Content).Value;
                            if (!string.IsNullOrEmpty(matchValue))
                            {
                                command = "title";
                                query   = $"{command} {matchValue}";
                            }
                        }
                        else if ((settings.FunResponsesEnabled || IsAuthorOwner(messageData)) && contentParts.Length > 1 && contentParts[1] == "face")
                        {
                            command = "face";
                            query   = $"{command} {contentParts[0]}";
                        }
                    }
                }

                // Ignore if the command is disabled on this server
                if (settings.IsCommandDisabled(CommandsConfig.Instance, command) && !IsAuthorOwner(messageData))
                {
                    return(responseData);
                }

                if (CommandsConfig.Instance.Commands.ContainsKey(command))
                {
                    if (!messageData.RateLimitChecked)
                    {
                        // make sure we're not rate limited
                        var commandKey = command + messageData.Server;

                        if (this.Throttler.IsThrottled(commandKey, ThrottleType.Command))
                        {
                            return(responseData);
                        }

                        this.Throttler.Increment(commandKey, ThrottleType.Command);

                        // if we're now throttled after this increment, return a "rate limited" message
                        if (this.Throttler.IsThrottled(commandKey, ThrottleType.Command))
                        {
                            responses.Add("rate limited try later");
                            return(responseData);
                        }

                        // increment the user/guild throttlers as well
                        this.Throttler.Increment(messageData.UserId, ThrottleType.User);
                        this.Throttler.Increment(messageData.Server, ThrottleType.Guild);

                        messageData.RateLimitChecked = true;
                    }

                    var props = new Dictionary <string, string> {
                        { "command", command.ToLowerInvariant() },
                        { "server", messageData.Server },
                        { "channel", messageData.Channel }
                    };
                    this.TrackEvent("commandProcessed", props);

                    // extra processing on ".title" command
                    if ((command == "title" || command == "t") && messageData.Content.EndsWith(command) && urls.ContainsKey(messageData.Channel))
                    {
                        query += $" {this.urls[messageData.Channel]}";
                    }

                    using (DogStatsd.StartTimer("commandDuration", tags: new[] { $"shard:{this.Shard}", $"command:{command.ToLowerInvariant()}", $"{this.BotType}" }))
                    {
                        messageData.Content = $"{settings.Prefix}{query}";
                        responseData        = await this.BotApi.IssueRequestAsync(messageData);
                    }
                }
            }

            if (responseData.Responses.Count == 0 && responseData.Embed == null)
            {
                string response = null;
                if (messageData.MentionsBot(this.Config.Name, Convert.ToUInt64(this.UserId)))
                {
                    var responseValue = PhrasesConfig.Instance.PartialMentionPhrases.FirstOrDefault(kvp => messageData.Content.IContains(kvp.Key)).Value;
                    if (!string.IsNullOrEmpty(responseValue))
                    {
                        response = PhrasesConfig.Instance.Responses[responseValue].Random();
                    }
                }

                if (response == null && PhrasesConfig.Instance.ExactPhrases.ContainsKey(messageData.Content) && (settings.FunResponsesEnabled && new Random().Next(1, 100) <= settings.FunResponseChance || IsAuthorOwner(messageData)))
                {
                    response = PhrasesConfig.Instance.Responses[PhrasesConfig.Instance.ExactPhrases[messageData.Content]].Random();
                }

                if (response == null)
                {
                    response = settings.CustomCommands.FirstOrDefault(c => c.IsExactMatch && c.Command == messageData.Content || !c.IsExactMatch && messageData.Content.IContains(c.Command))?.Response;
                }

                if (response != null)
                {
                    response = response.Replace("%from%", messageData.UserName);
                    string[] resps = response.Split(new char[] { '|' });
                    responseData.Responses.AddRange(resps);
                }
            }

            return(responseData);
        }
Exemplo n.º 3
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();
                }
            }
        }
Exemplo n.º 4
0
        public async Task <BotResponseData> IssueRequestAsync(BotMessageData messageData, string query)
        {
            var responses    = new List <string>();
            var responseData = new BotResponseData();

            string requestUrl = string.Format("{0}?apikey={1}&nick={2}&host={3}&server={4}&channel={5}&bottype={6}&userId={7}&format={8}&query={9}",
                                              this.apiEndpoint,
                                              this.apiKey,
                                              WebUtility.UrlEncode(messageData.UserName),
                                              WebUtility.UrlEncode(messageData.UserHost ?? messageData.UserId),
                                              messageData.Server,
                                              WebUtility.UrlEncode(messageData.Channel),
                                              this.botType.ToString().ToLowerInvariant(),
                                              messageData.UserId,
                                              messageData.Format,
                                              WebUtility.UrlEncode(query));

            var botResponse = await Utilities.GetApiResponseAsync <BotApiResponse>(new Uri(requestUrl));

            if (botResponse != null)
            {
                if (botResponse.Embed != null)
                {
                    responseData.Embed = botResponse.Embed;
                }
                else
                {
                    // TODO: fix API stupidity and just return the array
                    var botResponses = botResponse.Msgs.Length > 0 ? botResponse.Msgs : new string[] { botResponse.Msg };

                    if (botResponses[0] != null)
                    {
                        char a = (char)1;
                        char b = (char)2;
                        char c = (char)3;

                        foreach (var response in botResponses)
                        {
                            if (response != null)
                            {
                                responses.Add(response.Replace("%a", a.ToString()).Replace("%b", b.ToString()).Replace("%c", c.ToString()));
                            }
                        }

                        if (this.botType == BotType.Discord)
                        {
                            string response = string.Join("\n", responses);

                            // Extra processing for figlet/cowsay on Discord
                            if (query.StartsWith("cowsay", StringComparison.OrdinalIgnoreCase) || query.StartsWith("figlet", StringComparison.OrdinalIgnoreCase))
                            {
                                // use a non printable character to force preceeding whitespace to display correctly
                                response = "```" + (char)1 + response + "```";
                            }

                            responses = new List <string> {
                                response
                            };
                        }
                    }
                }

                responseData.Responses = responses;
            }

            return(responseData);
        }
Exemplo n.º 5
0
        protected async Task <BotResponseData> ProcessMessageAsync(BotMessageData messageData, Settings settings)
        {
            var responses    = new List <string>();
            var responseData = new BotResponseData {
                Responses = responses
            };

            if (this.BotApi != null)
            {
                // if an explicit command is being used, it wins out over any implicitly parsed command
                string query   = messageData.Query;
                string command = messageData.Command;

                string[] contentParts = messageData.Content.Split(new[] { ' ' });

                if (string.IsNullOrEmpty(command))
                {
                    if (messageData.Content.ToLowerInvariant().Contains("remind "))
                    {
                        // check for reminders
                        Match timerAtMatch  = Consts.TimerOnRegex.Match(messageData.Content);
                        Match timerAt2Match = Consts.TimerOn2Regex.Match(messageData.Content);
                        if (timerAtMatch.Success && Utilities.TryParseAbsoluteReminder(timerAtMatch, messageData, out query) ||
                            timerAt2Match.Success && Utilities.TryParseAbsoluteReminder(timerAt2Match, messageData, out query))
                        {
                            command = "timer";
                        }
                        else // try relative timers if absolute had no match
                        {
                            Match timerMatch  = Consts.TimerRegex.Match(messageData.Content);
                            Match timer2Match = Consts.Timer2Regex.Match(messageData.Content);

                            if (timerMatch.Success || timer2Match.Success)
                            {
                                Match matchToUse = timerMatch.Success && !timerMatch.Groups["prep"].Value.All(char.IsDigit) ? timerMatch : timer2Match;
                                if (Utilities.TryParseReminder(matchToUse, messageData, out query))
                                {
                                    command = "timer";
                                }
                            }
                        }
                    }

                    if (string.IsNullOrEmpty(command))
                    {
                        if (settings.AutoTitlesEnabled && CommandsConfig.Instance.AutoTitleMatches.Any(t => messageData.Content.Contains(t)))
                        {
                            Match  match      = Consts.HttpRegex.Match(messageData.Content);
                            string matchValue = !string.IsNullOrEmpty(match.Value) ? match.Value : Consts.RedditRegex.Match(messageData.Content).Value;
                            if (!string.IsNullOrEmpty(matchValue))
                            {
                                command = "title";
                                query   = $"{command} {matchValue}";
                            }
                        }
                        else if ((settings.FunResponsesEnabled || IsAuthorOwner(messageData)) && contentParts.Length > 1 && contentParts[1] == "face")
                        {
                            command = "face";
                            query   = $"{command} {contentParts[0]}";
                        }
                    }
                }

                // Ignore if the command is disabled on this server
                if (settings.IsCommandDisabled(CommandsConfig.Instance, command) && !IsAuthorOwner(messageData))
                {
                    return(responseData);
                }

                if (CommandsConfig.Instance.Commands.ContainsKey(command))
                {
                    if (!messageData.RateLimitChecked)
                    {
                        // make sure we're not rate limited
                        var commandKey   = command + messageData.Server;
                        var commandCount = this.commandsIssued.AddOrUpdate(commandKey, 1, (key, val) =>
                        {
                            return(val + 1);
                        });

                        if (commandCount > 12)
                        {
                            return(responseData);
                        }
                        else if (commandCount > 10)
                        {
                            responses.Add("rate limited try later");
                            return(responseData);
                        }
                    }

                    var props = new Dictionary <string, string> {
                        { "serverId", messageData.Server },
                    };
                    this.AppInsights?.TrackEvent(command.ToLowerInvariant(), props);

                    // extra processing on ".title" command
                    if ((command == "title" || command == "t") && messageData.Content.EndsWith(command) && urls.ContainsKey(messageData.Channel))
                    {
                        query += $" {this.urls[messageData.Channel]}";
                    }

                    responseData = await this.BotApi.IssueRequestAsync(messageData, query);
                }
            }

            if (responseData.Responses.Count == 0 && responseData.Embed == null)
            {
                string response = null;
                if (messageData.MentionsBot(this.Config.Name, Convert.ToUInt64(this.UserId)))
                {
                    var responseValue = PhrasesConfig.Instance.PartialMentionPhrases.Where(kvp => messageData.Content.ToLowerInvariant().Contains(kvp.Key.ToLowerInvariant())).FirstOrDefault().Value;
                    if (!string.IsNullOrEmpty(responseValue))
                    {
                        response = PhrasesConfig.Instance.Responses[responseValue].Random();
                    }
                }

                if (response == null && (settings.FunResponsesEnabled || IsAuthorOwner(messageData)) && PhrasesConfig.Instance.ExactPhrases.ContainsKey(messageData.Content) && new Random().Next(1, 100) <= settings.FunResponseChance)
                {
                    response = PhrasesConfig.Instance.Responses[PhrasesConfig.Instance.ExactPhrases[messageData.Content]].Random();
                }

                if (response != null)
                {
                    response = response.Replace("%from%", messageData.UserName);
                    string[] resps = response.Split(new char[] { '|' });
                    responseData.Responses.AddRange(resps);
                }
            }

            return(responseData);
        }
        /// <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)
        {
            // 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)
                {
                    if (message.Embeds?.Count > 0)
                    {
                        this.Logger.Log(LogType.Outgoing, $"\tSending [embed content] to {message.Channel.Name}");
                    }
                    else
                    {
                        this.Logger.Log(LogType.Outgoing, $"\tSending to {message.Channel.Name}: {message.Content}");
                    }
                }

                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 IGuildUser;
            var guildId      = (guildUser != null && guildUser.IsWebhook) ? null : guildUser?.GuildId;
            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))
            {
                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 && (message.Content.Contains("help") || message.Content.Contains("info") || message.Content.Contains("commands")))
            {
                await this.RespondAsync(message, "Info and commands can be found at: https://ub3r-b0t.com");

                return;
            }

            // check for word censors
            if (botGuildUser?.GuildPermissions.ManageMessages ?? false)
            {
                if (settings.TriggersCensor(message.Content, out string offendingWord))
                {
                    offendingWord = offendingWord != null ? $"`{offendingWord}`" : "*FANCY lanuage filters*";
                    await message.DeleteAsync();

                    var dmChannel = await message.Author.GetOrCreateDMChannelAsync();

                    await dmChannel.SendMessageAsync($"hi uh sorry but your most recent message was tripped up by {offendingWord} and thusly was deleted. complain to management, i'm just the enforcer");

                    return;
                }
            }

            var textChannel = message.Channel as ITextChannel;

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

            // special case FAQ channel
            if (message.Channel.Id == this.Config.FaqChannel && message.Content.EndsWith("?") && this.Config.FaqEndpoint != null)
            {
                string content = message.Content.Replace("<@85614143951892480>", "ub3r-b0t");
                var    result  = await this.Config.FaqEndpoint.ToString().WithHeader("Ocp-Apim-Subscription-Key", this.Config.FaqKey).PostJsonAsync(new { question = content });

                if (result.IsSuccessStatusCode)
                {
                    var response = await result.Content.ReadAsStringAsync();

                    var qnaData = JsonConvert.DeserializeObject <QnAMakerData>(response);
                    var score   = Math.Floor(qnaData.Score);
                    var answer  = WebUtility.HtmlDecode(qnaData.Answer);
                    await message.Channel.SendMessageAsync($"{answer} ({score}% match)");
                }
                else
                {
                    await message.Channel.SendMessageAsync("An error occurred while fetching data");
                }

                return;
            }

            string messageContent = message.Content;

            // OCR for fun if requested (patrons only)
            // TODO: need to drive this via config
            // TODO: Need to generalize even further due to more reaction types
            // TODO: oh my god stop writing TODOs and just make the code less awful
            if (!string.IsNullOrEmpty(reactionType))
            {
                string newMessageContent = string.Empty;

                if (reactionType == "💬" || reactionType == "🗨️")
                {
                    newMessageContent = $".quote add \"{messageContent}\" - userid:{message.Author.Id} {message.Author.Username}";
                    await message.AddReactionAsync(new Emoji("💬"));
                }
                else if (string.IsNullOrEmpty(message.Content) && message.Attachments?.FirstOrDefault()?.Url is string attachmentUrl)
                {
                    if (reactionType == "👁")
                    {
                        var result = await this.Config.OcrEndpoint.ToString()
                                     .WithHeader("Ocp-Apim-Subscription-Key", this.Config.VisionKey)
                                     .PostJsonAsync(new { url = attachmentUrl });

                        if (result.IsSuccessStatusCode)
                        {
                            var response = await result.Content.ReadAsStringAsync();

                            var ocrData = JsonConvert.DeserializeObject <OcrData>(response);
                            if (!string.IsNullOrEmpty(ocrData.GetText()))
                            {
                                newMessageContent = ocrData.GetText();
                            }
                        }
                    }
                    else if (reactionType == "🖼")
                    {
                        var analyzeResult = await this.Config.AnalyzeEndpoint.ToString()
                                            .WithHeader("Ocp-Apim-Subscription-Key", this.Config.VisionKey)
                                            .PostJsonAsync(new { url = attachmentUrl });

                        if (analyzeResult.IsSuccessStatusCode)
                        {
                            var response = await analyzeResult.Content.ReadAsStringAsync();

                            var analyzeData = JsonConvert.DeserializeObject <AnalyzeImageData>(response);
                            if (analyzeData.Description.Tags.Contains("ball"))
                            {
                                newMessageContent = ".8ball foo";
                            }
                            else if (analyzeData.Description.Tags.Contains("outdoor"))
                            {
                                newMessageContent = ".fw";
                            }
                        }
                    }
                }

                messageContent = newMessageContent ?? messageContent;
            }

            // If it's a command, match that before anything else.
            string query         = string.Empty;
            bool   hasBotMention = message.MentionedUsers.Any(u => u.Id == this.Client.CurrentUser.Id);

            int argPos = 0;

            if (message.HasMentionPrefix(this.Client.CurrentUser, ref argPos))
            {
                query = messageContent.Substring(argPos);
            }
            else if (messageContent.StartsWith(settings.Prefix))
            {
                query = messageContent.Substring(settings.Prefix.Length);
            }

            var messageData = BotMessageData.Create(message, query, settings);

            messageData.Content = messageContent;
            await this.PreProcessMessage(messageData, settings);

            string command = messageData.Command;

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

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

            // Check discord specific commands prior to general ones.
            if (discordCommands.Commands.ContainsKey(command))
            {
                var response = await discordCommands.Commands[command].Invoke(message).ConfigureAwait(false);
                if (response != null)
                {
                    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 (messageData.Command == "quote" && reactionUser != null)
                {
                    messageData.UserName = reactionUser.Username;
                }

                try
                {
                    BotResponseData responseData = await this.ProcessMessageAsync(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();
                }
            }
        }
Exemplo n.º 7
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();
                }
            }
        }