Example #1
0
        // TODO: IRC library needs... some improvements.
        public async void OnIrcEventAsync(MessageData data, IrcClient client)
        {
            var responses = new List <string>();
            var settings  = new Settings();

            if (data.Verb == ReplyCode.RPL_ENDOFMOTD || data.Verb == ReplyCode.RPL_NOMOTD) //  motd end or motd missing
            {
                foreach (string channel in this.Config.Irc.Servers.Where(s => s.Host == client.Id).First().Channels)
                {
                    serverData[client.Host].Channels[channel.ToLowerInvariant()] = new ChannelData();
                    client.Command("JOIN", channel, string.Empty);
                }
            }

            if (data.Verb == "PRIVMSG")
            {
                string query = string.Empty;

                var props = new Dictionary <string, string> {
                    { "server", client.Host.ToLowerInvariant() },
                    { "channel", data.Target.ToLowerInvariant() },
                };

                this.TrackEvent("messageReceived", props);

                // TODO: put this in config
                // twitch parses "." prefix as internal commands; so we have to remap it :(
                if (client.Host == "irc.chat.twitch.tv")
                {
                    settings.Prefix = "^";
                }

                var messageData = BotMessageData.Create(data, client, settings);

                await this.PreProcessMessage(messageData, settings);

                responses.AddRange((await this.ProcessMessageAsync(messageData, settings)).Responses);

                foreach (string response in responses)
                {
                    client.Command("PRIVMSG", data.Target, response);
                    this.TrackEvent("messageSent", props);
                }
            }
            // TODO: This stuff should be handled by the IRC library...
            else if (data.Verb == "353")
            {
                var namesMatch = namesRegex.Match(data.Text);
                var channel    = namesMatch.Groups[1].ToString().ToLowerInvariant();
                var userList   = namesMatch.Groups[2].ToString();
                var users      = userList.Split(new[] { ' ' });

                if (!serverData[client.Host].Channels.ContainsKey(channel))
                {
                    this.serverData[client.Host].Channels[channel] = new ChannelData();
                }

                this.serverData[client.Host].Channels[channel].Users = new HashSet <string>(users);
            }
        }
Example #2
0
        /// <summary>
        /// Sends farewells and mod log messages, if configured.
        /// </summary>
        private async Task HandleUserLeftAsync(SocketGuildUser guildUser)
        {
            var settings = SettingsConfig.GetSettings(guildUser.Guild.Id);

            if (!string.IsNullOrEmpty(settings.Farewell) && settings.FarewellId != 0)
            {
                var farewell = settings.Farewell.Replace("%user%", guildUser.Mention);
                farewell = farewell.Replace("%username%", $"{guildUser}");

                farewell = Consts.ChannelRegex.Replace(farewell, new MatchEvaluator((Match chanMatch) =>
                {
                    string channelName = chanMatch.Groups[1].Value;
                    var channel        = guildUser.Guild.Channels.Where(c => c is ITextChannel && c.Name.Equals(channelName, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
                    return((channel as ITextChannel)?.Mention ?? $"#{channelName}");
                }));

                var farewellChannel = this.Client.GetChannel(settings.FarewellId) as ITextChannel ?? guildUser.Guild.DefaultChannel;
                if (farewellChannel.GetCurrentUserPermissions().SendMessages)
                {
                    await farewellChannel.SendMessageAsync(farewell);
                }
            }

            // mod log
            if (settings.HasFlag(ModOptions.Mod_LogUserLeave) && this.Client.GetChannel(settings.Mod_LogId) is ITextChannel modLogChannel && modLogChannel.GetCurrentUserPermissions().SendMessages)
            {
                this.BatchSendMessageAsync(modLogChannel, $"{guildUser.Mention} ({guildUser}) left.");
            }

            var messageData = BotMessageData.Create(guildUser, settings);

            messageData.Content = ".timer clear";
            messageData.Prefix  = ".";
            await this.BotApi.IssueRequestAsync(messageData);
        }
Example #3
0
 public DiscordBotContext(DiscordSocketClient client, SocketInteraction interaction, IUserMessage message)
 {
     this.Client      = client;
     this.Interaction = interaction;
     this.MessageData = BotMessageData.Create(interaction, message, this.Settings);
     this.Message     = message;
 }
Example #4
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);
            }
        }
Example #5
0
 public DiscordBotContext(DiscordSocketClient client, SocketUserMessage message)
 {
     this.Client      = client;
     this.Message     = message;
     this.MessageData = BotMessageData.Create(Message, this.Settings);
 }
Example #6
0
        // TODO:
        // this is icky
        // it's getting worse...TODO: read the above todo and fix it
        internal DiscordCommands(DiscordSocketClient client, AudioManager audioManager, BotApi botApi)
        {
            this.client       = client;
            this.audioManager = audioManager;
            this.botApi       = botApi;
            this.Commands     = new Dictionary <string, Func <SocketUserMessage, Task <CommandResponse> > >();

            this.CreateScriptOptions();

            Commands.Add("debug", (message) =>
            {
                var serverId   = (message.Channel as IGuildChannel)?.GuildId.ToString() ?? "n/a";
                var botVersion = Assembly.GetEntryAssembly().GetName().Version.ToString();
                var response   = new CommandResponse {
                    Text = $"```Server ID: {serverId} | Channel ID: {message.Channel.Id} | Your ID: {message.Author.Id} | Shard ID: {client.ShardId} | Version: {botVersion} | Discord.NET Version: {DiscordSocketConfig.Version}```"
                };
                return(Task.FromResult(response));
            });

            Commands.Add("seen", async(message) =>
            {
                if (message.Channel is IGuildChannel guildChannel)
                {
                    var settings = SettingsConfig.GetSettings(guildChannel.GuildId.ToString());
                    if (!settings.SeenEnabled)
                    {
                        return(new CommandResponse {
                            Text = "Seen data is not being tracked for this server.  Enable it in the admin settings panel."
                        });
                    }

                    string[] parts = message.Content.Split(new[] { ' ' }, 2);
                    if (parts.Length != 2)
                    {
                        return(new CommandResponse {
                            Text = "Usage: .seen username"
                        });
                    }

                    var targetUser = (await guildChannel.Guild.GetUsersAsync()).Find(parts[1]).FirstOrDefault();
                    if (targetUser != null)
                    {
                        if (targetUser.Id == client.CurrentUser.Id)
                        {
                            return(new CommandResponse {
                                Text = $"I was last seen...wait...seriously? Ain't no one got time for your shit, {message.Author.Username}."
                            });
                        }

                        if (targetUser.Id == message.Author.Id)
                        {
                            return(new CommandResponse {
                                Text = $"You were last seen now, saying: ... god DAMN it {message.Author.Username}, quit wasting my time"
                            });
                        }

                        string query    = $"seen {targetUser.Id} {targetUser.Username}";
                        var messageData = BotMessageData.Create(message, query, settings);

                        var response = (await this.botApi.IssueRequestAsync(messageData, query)).Responses.FirstOrDefault();

                        if (response != null)
                        {
                            return(new CommandResponse {
                                Text = response
                            });
                        }
                    }

                    return(new CommandResponse {
                        Text = $"I...omg...I have not seen {parts[1]} in this channel :X I AM SOOOOOO SORRY"
                    });
                }

                return(null);
            });

            Commands.Add("remove", async(message) =>
            {
                if (message.Channel is IGuildChannel guildChannel)
                {
                    if (message.Author.Id != guildChannel.Guild.OwnerId)
                    {
                        return(new CommandResponse {
                            Text = "Restricted to server owner."
                        });
                    }

                    var settings = SettingsConfig.GetSettings(guildChannel.GuildId.ToString());

                    string[] parts = message.Content.Split(new[] { ' ' }, 3);

                    if (parts.Length != 3)
                    {
                        return(new CommandResponse {
                            Text = "Usage: .remove type #; valid types are timer and wc"
                        });
                    }

                    var type = parts[1].ToLowerInvariant();
                    var id   = parts[2];

                    string query    = $"remove {CommandsConfig.Instance.HelperKey} {type} {id}";
                    var messageData = BotMessageData.Create(message, query, settings);
                    var response    = (await this.botApi.IssueRequestAsync(messageData, query)).Responses.FirstOrDefault();

                    if (response != null)
                    {
                        return(new CommandResponse {
                            Text = response
                        });
                    }
                }

                return(null);
            });

            Commands.Add("status", async(message) =>
            {
                var serversStatus = await Utilities.GetApiResponseAsync <HeartbeatData[]>(BotConfig.Instance.HeartbeatEndpoint);

                var dataSb = new StringBuilder();
                dataSb.Append("```cs\n" +
                              "type       shard   server count      users   voice count\n");

                int serverTotal = 0;
                int userTotal   = 0;
                int voiceTotal  = 0;
                foreach (HeartbeatData heartbeat in serversStatus)
                {
                    serverTotal += heartbeat.ServerCount;
                    userTotal   += heartbeat.UserCount;
                    voiceTotal  += heartbeat.VoiceChannelCount;

                    var botType = heartbeat.BotType.PadRight(11);
                    var shard   = heartbeat.Shard.ToString().PadLeft(4);
                    var servers = heartbeat.ServerCount.ToString().PadLeft(13);
                    var users   = heartbeat.UserCount.ToString().PadLeft(10);
                    var voice   = heartbeat.VoiceChannelCount.ToString().PadLeft(13);

                    dataSb.Append($"{botType} {shard}  {servers} {users} {voice}\n");
                }

                // add up totals
                dataSb.Append($"-------\n");
                dataSb.Append($"Total:            {serverTotal.ToString().PadLeft(13)} {userTotal.ToString().PadLeft(10)} {voiceTotal.ToString().PadLeft(13)}\n");

                dataSb.Append("```");

                return(new CommandResponse {
                    Text = dataSb.ToString()
                });
            });

            Commands.Add("voice", (message) =>
            {
                var channel = (message.Author as IGuildUser)?.VoiceChannel;
                if (channel == null)
                {
                    return(Task.FromResult(new CommandResponse {
                        Text = "Join a voice channel first"
                    }));
                }

                Task.Run(async() =>
                {
                    try
                    {
                        await audioManager.JoinAudioAsync(channel);
                    }
                    catch (Exception ex)
                    {
                        // TODO: proper logging
                        Console.WriteLine(ex);
                    }
                }).Forget();

                return(Task.FromResult((CommandResponse)null));
            });

            Commands.Add("dvoice", (message) =>
            {
                if (message.Channel is IGuildChannel channel)
                {
                    Task.Run(async() =>
                    {
                        try
                        {
                            await audioManager.LeaveAudioAsync(channel);
                        }
                        catch (Exception ex)
                        {
                            // TODO: proper logging
                            Console.WriteLine(ex);
                        }
                    }).Forget();
                }

                return(Task.FromResult((CommandResponse)null));
            });

            Commands.Add("devoice", (message) =>
            {
                if (message.Channel is IGuildChannel channel)
                {
                    Task.Run(async() =>
                    {
                        try
                        {
                            await audioManager.LeaveAudioAsync(channel);
                        }
                        catch (Exception ex)
                        {
                            // TODO: proper logging
                            Console.WriteLine(ex);
                        }
                    }).Forget();
                }

                return(Task.FromResult((CommandResponse)null));
            });

            Commands.Add("clear", async(message) =>
            {
                if (message.Channel is IDMChannel)
                {
                    return(null);
                }

                var guildUser = message.Author as IGuildUser;
                if (!guildUser.GuildPermissions.ManageMessages)
                {
                    return(new CommandResponse {
                        Text = "you don't have permissions to clear messages, fartface"
                    });
                }

                var guildChannel = message.Channel as IGuildChannel;

                string[] parts = message.Content.Split(new[] { ' ' }, 3);
                if (parts.Length != 2 && parts.Length != 3)
                {
                    return(new CommandResponse {
                        Text = "Usage: .clear #; Usage for user specific messages: .clear # username"
                    });
                }

                IUser deletionUser = null;

                if (parts.Length == 3)
                {
                    if (ulong.TryParse(parts[2], out ulong userId))
                    {
                        deletionUser = await message.Channel.GetUserAsync(userId);
                    }
                    else
                    {
                        deletionUser = (await guildUser.Guild.GetUsersAsync().ConfigureAwait(false)).Find(parts[2]).FirstOrDefault();
                    }

                    if (deletionUser == null)
                    {
                        return(new CommandResponse {
                            Text = "Couldn't find the specified user. Try their ID if nick matching is struggling"
                        });
                    }
                }

                var botGuildUser = await guildChannel.GetUserAsync(client.CurrentUser.Id);
                bool botOnly     = deletionUser == botGuildUser;

                if (!botOnly && !botGuildUser.GetPermissions(guildChannel).ManageMessages)
                {
                    return(new CommandResponse {
                        Text = "yeah I don't have the permissions to delete messages, buttwad."
                    });
                }

                if (int.TryParse(parts[1], out int count))
                {
                    var textChannel = message.Channel as ITextChannel;
                    // +1 for the current .clear message
                    if (deletionUser == null)
                    {
                        count = Math.Min(99, count) + 1;
                    }
                    else
                    {
                        count = Math.Min(100, count);
                    }

                    // download messages until we've hit the limit
                    var msgsToDelete      = new List <IMessage>();
                    var msgsToDeleteCount = 0;
                    ulong?lastMsgId       = null;
                    var i = 0;
                    while (msgsToDeleteCount < count)
                    {
                        i++;
                        IEnumerable <IMessage> downloadedMsgs;
                        try
                        {
                            if (!lastMsgId.HasValue)
                            {
                                downloadedMsgs = await textChannel.GetMessagesAsync(count).Flatten();
                            }
                            else
                            {
                                downloadedMsgs = await textChannel.GetMessagesAsync(lastMsgId.Value, Direction.Before, count).Flatten();
                            }
                        }
                        catch (Exception ex)
                        {
                            downloadedMsgs = new IMessage[0];
                            Console.WriteLine(ex);
                        }

                        if (downloadedMsgs.Count() > 0)
                        {
                            lastMsgId = downloadedMsgs.Last().Id;

                            var msgs           = downloadedMsgs.Where(m => (deletionUser == null || m.Author?.Id == deletionUser.Id)).Take(count - msgsToDeleteCount);
                            msgsToDeleteCount += msgs.Count();
                            msgsToDelete.AddRange(msgs);
                        }
                        else
                        {
                            break;
                        }

                        if (i >= 5)
                        {
                            break;
                        }
                    }

                    var settings = SettingsConfig.GetSettings(guildUser.GuildId.ToString());
                    if (settings.HasFlag(ModOptions.Mod_LogDelete) && this.client.GetChannel(settings.Mod_LogId) is ITextChannel modLogChannel && modLogChannel.GetCurrentUserPermissions().SendMessages)
                    {
                        modLogChannel.SendMessageAsync($"{guildUser.Username}#{guildUser.Discriminator} cleared {msgsToDeleteCount} messages from {textChannel.Mention}").Forget();
                    }

                    try
                    {
                        await(message.Channel as ITextChannel).DeleteMessagesAsync(msgsToDelete);
                    }
                    catch (ArgumentOutOfRangeException)
                    {
                        return(new CommandResponse {
                            Text = "Bots cannot delete messages older than 2 weeks."
                        });
                    }

                    return(null);
                }
                else
                {
                    return(new CommandResponse {
                        Text = "Usage: .clear #"
                    });
                }
            });

            Commands.Add("jpeg", async(message) =>
            {
                var messageParts = message.Content.Split(new[] { ' ' }, 2);
                var fileName     = "moar.jpeg";
                var url          = string.Empty;
                if (messageParts.Length == 2 && Uri.IsWellFormedUriString(messageParts[1], UriKind.Absolute))
                {
                    url = messageParts[1];
                }
                else
                {
                    Attachment img = message.Attachments.FirstOrDefault();
                    if (img != null || DiscordBot.imageUrls.TryGetValue(message.Channel.Id.ToString(), out img))
                    {
                        url      = img.Url;
                        fileName = img.Filename;
                    }
                }

                if (!string.IsNullOrEmpty(url))
                {
                    using (var httpClient = new HttpClient())
                    {
                        var response = await httpClient.GetAsync(CommandsConfig.Instance.JpegEndpoint.AppendQueryParam("url", url));
                        var stream   = await response.Content.ReadAsStreamAsync();

                        return(new CommandResponse
                        {
                            Attachment = new FileResponse
                            {
                                Name = fileName,
                                Stream = stream,
                            }
                        });
                    }
                }

                return(null);
            });

            Commands.Add("userinfo", async(message) =>
            {
                SocketUser targetUser = message.MentionedUsers?.FirstOrDefault();

                if (targetUser == null)
                {
                    var messageParts = message.Content.Split(new[] { ' ' }, 2);

                    if (messageParts.Length == 2)
                    {
                        if (message.Channel is IGuildChannel guildChannel)
                        {
                            targetUser = (await guildChannel.Guild.GetUsersAsync()).Find(messageParts[1]).FirstOrDefault() as SocketUser;
                        }

                        if (targetUser == null)
                        {
                            return(new CommandResponse {
                                Text = "User not found. Try a direct mention."
                            });
                        }
                    }
                    else
                    {
                        targetUser = message.Author;
                    }
                }

                var guildUser = targetUser as IGuildUser;

                if (guildUser == null)
                {
                    return(null);
                }

                var userInfo = new
                {
                    Title        = $"UserInfo for {targetUser.Username}#{targetUser.Discriminator}",
                    AvatarUrl    = targetUser.GetAvatarUrl(),
                    NicknameInfo = !string.IsNullOrEmpty(guildUser.Nickname) ? $" aka {guildUser.Nickname}" : "",
                    Footnote     = CommandsConfig.Instance.UserInfoSnippets.Random(),
                    Created      = $"{targetUser.GetCreatedDate().ToString("dd MMM yyyy")} {targetUser.GetCreatedDate().ToString("hh:mm:ss tt")} UTC",
                    Joined       = guildUser.JoinedAt.HasValue ? $"{guildUser.JoinedAt.Value.ToString("dd MMM yyyy")} {guildUser.JoinedAt.Value.ToString("hh:mm:ss tt")} UTC" : "[data temporarily missing]",
                    Id           = targetUser.Id.ToString(),
                };

                EmbedBuilder embedBuilder = null;
                string text = string.Empty;

                var settings = SettingsConfig.GetSettings(guildUser.GuildId.ToString());

                if ((message.Channel as ITextChannel).GetCurrentUserPermissions().EmbedLinks&& settings.PreferEmbeds)
                {
                    embedBuilder = new EmbedBuilder
                    {
                        Title        = userInfo.Title,
                        ThumbnailUrl = userInfo.AvatarUrl,
                    };

                    if (!string.IsNullOrEmpty(userInfo.NicknameInfo))
                    {
                        embedBuilder.Description = userInfo.NicknameInfo;
                    }

                    embedBuilder.Footer = new EmbedFooterBuilder
                    {
                        Text = userInfo.Footnote,
                    };

                    embedBuilder.AddField((field) =>
                    {
                        field.IsInline = true;
                        field.Name     = "created";
                        field.Value    = userInfo.Created;
                    });

                    if (guildUser.JoinedAt.HasValue)
                    {
                        embedBuilder.AddField((field) =>
                        {
                            field.IsInline = true;
                            field.Name     = "joined";
                            field.Value    = userInfo.Joined;
                        });
                    }

                    var roles = new List <string>();
                    foreach (ulong roleId in guildUser.RoleIds)
                    {
                        roles.Add(guildUser.Guild.Roles.First(g => g.Id == roleId).Name.TrimStart('@'));
                    }

                    embedBuilder.AddField((field) =>
                    {
                        field.IsInline = false;
                        field.Name     = "roles";
                        field.Value    = string.Join(", ", roles);
                    });

                    embedBuilder.AddField((field) =>
                    {
                        field.IsInline = true;
                        field.Name     = "Id";
                        field.Value    = userInfo.Id;
                    });
                }
                else
                {
                    text = $"{userInfo.Title}{userInfo.NicknameInfo}: ID: {userInfo.Id} | Created: {userInfo.Created} | Joined: {userInfo.Joined} | word on the street: {userInfo.Footnote}";
                }

                return(new CommandResponse {
                    Text = text, Embed = embedBuilder
                });
            });

            Commands.Add("serverinfo", async(message) =>
            {
                EmbedBuilder embedBuilder = null;
                string text = string.Empty;

                if (message.Channel is IGuildChannel guildChannel && message.Channel is ITextChannel textChannel)
                {
                    var guild      = guildChannel.Guild;
                    var emojiCount = guild.Emotes.Count();
                    var emojiText  = "no custom emojis? I am ASHAMED to be here";
                    if (emojiCount > 50)
                    {
                        emojiText = $"...{emojiCount} emojis? hackers";
                    }
                    else if (emojiCount == 50)
                    {
                        emojiText = "wow 50 custom emojis! that's the max";
                    }
                    else if (emojiCount >= 40)
                    {
                        emojiText = $"{emojiCount} custom emojis in here. impressive...most impressive...";
                    }
                    else if (emojiCount > 25)
                    {
                        emojiText = $"the custom emoji force is strong with this guild. {emojiCount} is over halfway to the max.";
                    }
                    else if (emojiCount > 10)
                    {
                        emojiText = $"{emojiCount} custom emoji is...passable";
                    }
                    else if (emojiCount > 0)
                    {
                        emojiText = $"really, only {emojiCount} custom emoji? tsk tsk.";
                    }

                    await(message.Channel as SocketGuildChannel).Guild.DownloadUsersAsync();
                    var serverInfo = new
                    {
                        Title     = $"Server Info for {guild.Name}",
                        UserCount = (await guild.GetUsersAsync()).Count(),
                        Owner     = (await guild.GetOwnerAsync()).Username,
                        Created   = $"{guild.CreatedAt.ToString("dd MMM yyyy")} {guild.CreatedAt.ToString("hh:mm:ss tt")} UTC",
                        EmojiText = emojiText,
                    };

                    var settings = SettingsConfig.GetSettings(guildChannel.GuildId.ToString());

                    if (textChannel.GetCurrentUserPermissions().EmbedLinks&& settings.PreferEmbeds)
                    {
                        embedBuilder = new EmbedBuilder
                        {
                            Title        = serverInfo.Title,
                            ThumbnailUrl = guildChannel.Guild.IconUrl,
                        };

                        embedBuilder.AddField((field) =>
                        {
                            field.IsInline = true;
                            field.Name     = "Users";
                            field.Value    = serverInfo.UserCount;
                        });

                        embedBuilder.AddField((field) =>
                        {
                            field.IsInline = true;
                            field.Name     = "Owner";
                            field.Value    = serverInfo.Owner;
                        });

                        embedBuilder.AddField((field) =>
                        {
                            field.IsInline = true;
                            field.Name     = "Id";
                            field.Value    = guild.Id;
                        });

                        embedBuilder.AddField((field) =>
                        {
                            field.IsInline = true;
                            field.Name     = "created";
                            field.Value    = serverInfo.Created;
                        });

                        embedBuilder.Footer = new EmbedFooterBuilder
                        {
                            Text = serverInfo.EmojiText,
                        };

                        if (!string.IsNullOrEmpty(guild.SplashUrl))
                        {
                            embedBuilder.Footer.IconUrl = guild.SplashUrl;
                        }
                    }
                    else
                    {
                        text = $"{serverInfo.Title}: Users: {serverInfo.UserCount} | Owner: {serverInfo.Owner} | Id: {guild.Id} | Created: {serverInfo.Created} | {serverInfo.EmojiText}";
                    }
                }
        /// <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();
                }
            }
        }