// 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); } }
/// <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); }
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; }
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); } }
public DiscordBotContext(DiscordSocketClient client, SocketUserMessage message) { this.Client = client; this.Message = message; this.MessageData = BotMessageData.Create(Message, this.Settings); }
// 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(); } } }