static async Task NotifyActionLogsMentionsFound(Dictionary <ulong, List <DiscordMessage> > warnDict, DiscordChannel channel) { foreach (ulong snowflake in warnDict.Keys) { // Iterate through each user snowflake logged in the dictionary. // Only make an embed if there are actually mentions. if (warnDict[snowflake].Count > 0) { var mentionList = warnDict[snowflake]; var embed = new DiscordEmbedBuilder(); var stringBuilder = new StringBuilder(); // Build header. stringBuilder.Append($"__**{Generics.GetMention(snowflake)} has had {mentionList.Count + 1} total mention{(mentionList.Count == 1 ? String.Empty : @"s")} (including the most recent one)" + $"in {channel.Mention} in the last {Program.Settings.MaxActionAgeMonths} months:**__\n"); // Build link list. stringBuilder.AppendJoin(' ', Generics.BuildLimitedLinkList( links: mentionList .Select(a => Generics.GetMessageUrl(a)) .ToArray(), endMessage: @"... Too many to display...", maxLength: 2000 - stringBuilder.Length)); embed.WithDescription(stringBuilder.ToString()); embed.WithTitle($"Previous mention{(mentionList.Count == 1 ? String.Empty : @"s")} found"); embed.WithColor(DiscordColor.Red); await channel.SendMessageAsync(embed : embed); } // end if } // end foreach }
private static async Task FilterSystem_FilterTriggered(Filter.FilterEventArgs e) { var stringBuilder = new StringBuilder(); // Append all the found bad words to the string builder. foreach (string str in e.BadWords) { stringBuilder.Append(str); stringBuilder.Append(' '); } // Create the Discord Embed var deb = new DiscordEmbedBuilder() { Title = "Filter: Word Detected", Color = DiscordColor.Red }; deb.WithDescription(String.Format("Filter Trigger(s):```{0}```Excerpt:```{1}```", stringBuilder.ToString(), e.NotatedMessage)); deb.AddField(@"Author ID", e.User.Id.ToString(), inline: true); deb.AddField(@"Author Username", $"{e.User.Username}#{e.User.Discriminator}", inline: true); deb.AddField(@"Author Mention", e.User.Mention, inline: true); deb.AddField(@"Channel", e.Channel.Mention, inline: true); deb.AddField(@"Timestamp (UTC)", e.Message.CreationTimestamp.UtcDateTime.ToString(Generics.DateFormat), inline: true); deb.AddField(@"Link", Generics.GetMessageUrl(e.Message)); deb.WithThumbnail(Generics.URL_FILTER_BUBBLE); // Notify the filter channel. await NotifyFilterChannel(deb.Build()); }
public static async Task SeekWarns(CommandContext ctx, ulong[] memberIds) { const int MAX_FIELDS = 10; DiscordChannel actionLogChannel = await Program.BotClient.GetChannelAsync(Program.Settings.ActionChannelId); Dictionary <ulong, List <DiscordMessage> > warnDict = await QueryMemberMentions(memberIds.Distinct().ToList(), actionLogChannel, Program.Settings.MaxActionAgeMonths, ctx.Message); // Let's start paginating. var pages = new Page[warnDict.Keys.Count]; int page = 0; if (warnDict.Keys.Count > 0) { // Want to generate a page for each member. foreach (var member in warnDict.Keys) { // We want a boolean to check first because if there's no key, we'll get an exception trying to get the count. bool warnsFound = warnDict.ContainsKey(member) && warnDict[member].Count > 0; var deb = new DiscordEmbedBuilder { Title = $"Mentions found in action logs", Description = Generics.NeutralDirectResponseTemplate(mention: ctx.Member.Mention, body: warnsFound ? // Warning, really f*****g long string ahead: $"I found {warnDict[member].Count} mention{(warnDict[member].Count == 1 ? String.Empty : @"s")} for " + $"{Generics.GetMention(member)} in {actionLogChannel.Mention} in the last {Program.Settings.MaxActionAgeMonths} months. " + $"{(warnDict[member].Count > MAX_FIELDS ? $"There are over {MAX_FIELDS}. I will only show the most recent." : String.Empty)}" : $"{ctx.Member.Mention}, I did not find any mentions for {Generics.GetMention(member)}. Good for them..."), Color = warnsFound ? Generics.NegativeColor : Generics.NeutralColor }; if (warnsFound) { // Only continue here if there are actually warns, otherwise just slap a footer on. foreach (var message in warnDict[member]) { // Generate a field for each detected message. if (deb.Fields.Count < MAX_FIELDS) { // Only continue if we have less than MAX_FIELDS fields. // This SB is for all the content. var stringBuilder = new StringBuilder(); // This SB is for all the misc information. var stringBuilderFooter = new StringBuilder(); stringBuilder.Append($"{ChatObjects.Generics.GetMention(message.Author.Id)}: "); stringBuilder.Append(message.Content); if (message.Attachments.Count > 0) { stringBuilderFooter.Append($"\n\n{Formatter.Bold(@"There is an image attached:")} "); stringBuilderFooter.Append(Formatter.MaskedUrl(@"Image", new Uri(message.Attachments[0].Url))); } // end if stringBuilderFooter.Append("\n\n"); stringBuilderFooter.Append(Formatter.MaskedUrl(@"Link", new Uri(Generics.GetMessageUrl(message)))); // We want to prefer the footer's information over the content. So let's figure out how much of the content we // need to trim out. var finalStringBuilder = new StringBuilder(); if (stringBuilder.Length + stringBuilderFooter.Length > 1000) { // We need to do some trimming. if (stringBuilder.Length > 0) { // Let's get the content in there. finalStringBuilder.Append(Generics.BuildLimitedString( originalString: stringBuilder.ToString(), endMessage: @". . . Unable to preview long message...", maxLength: 1000 - stringBuilderFooter.Length)); } if (stringBuilderFooter.Length > 0) { // Let's get the footer in there. finalStringBuilder.Append(stringBuilderFooter); } } else { // We don't need to do any trimming. if (stringBuilder.Length > 0) { // Let's get the content in there. finalStringBuilder.Append(stringBuilder); } if (stringBuilderFooter.Length > 0) { // Let's get the footer in there. finalStringBuilder.Append(stringBuilderFooter); } } deb.AddField($"Action on {message.Timestamp.ToString(Generics.DateFormat)}", finalStringBuilder.ToString()); } else { // Stop the loop if we have MAX_FIELDS fields. break; // NON-SESE ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! } // end else } // end foreach } // end if deb.WithFooter($"Page {page + 1}/{warnDict.Keys.Count}"); pages[page++] = new Page(embed: deb); } // end foreach } // end if // Delete the message if it's the action channel so it's kind of out of the way and doesn't get logged again in the future. if (ctx.Message.ChannelId == Program.Settings.ActionChannelId) { await ctx.Message.DeleteAsync(); } if (pages.Length > 1) { // More than 1 page. var interactivity = Program.BotClient.GetInteractivity(); await interactivity.SendPaginatedMessageAsync ( c : ctx.Channel, u : ctx.User, pages : pages, emojis : Generics.DefaultPaginationEmojis ); } else { // Only one page, we want to send it as a regular embed instead. var anotherDeb = new DiscordEmbedBuilder(pages[0].Embed); // Clear the footer. We don't want the page count. anotherDeb.WithFooter(null, null); await ctx.Channel.SendMessageAsync(embed : anotherDeb); } }
internal static async Task BotClientMessageReactionAdded(MessageReactionAddEventArgs e) { // We don't want the cached version of this message because if it was sent during downtime, the bot won't be able to do // anything with it. var message_noCache = await e.Channel.GetMessageAsync(e.Message.Id); // Before anything, let's make sure that... // 1) This is not the rimboard channel // 2) Rimboard is enabled. // 3) The Rimboard webhook is not default. // 4) This was not sent by the bot (requires nocache). if (e.Channel.Id != Program.Settings.RimboardChannelId && e.Channel.Id != 401672277692776448 && // Temporary patch to ignore the announcements channel and e.Channel.Id != 220997547345313793 && // mod-updates channel while I work on the next Program.Settings.RimboardEnabled && // update. Please, please, PLEASE get a proper Rimboard-channel-ignore method at some point. Program.Settings.RimboardWebhookId != BotSettings.Default.RimboardWebhookId && // De-cache the message so we can get its author. !(message_noCache.Author.IsBot)) { DiscordEmoji emoji = GetReactionEmoji(e.Client); // This contains a list of the reactions that have rimboardEmoji. It's only ever really going to be be 1 long. var pinReactionsList = message_noCache.Reactions.Where(a => a.Emoji.Name == emoji.Name).ToArray(); if (pinReactionsList.Length == 0) { return; // NON-SESE ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! } int reactCount = pinReactionsList.FirstOrDefault().Count; // Let's now try to get the Rimboard message. PinInfo pinInfo = await QueryDatabaseForOriginalMessage(e.Message.Id, reactCount); bool validPinInfo = !pinInfo.Equals(PinInfo.Invalid); // Right now we want to check if the message has been posted already and react according to its reaction count. if (!validPinInfo && reactCount >= Program.Settings.RimboardReactionsNeeded) { // We don't have stuff in the database, so it probably hasn't been pinned. Let's pin it. bool file = false; // DEB! var deb = new DiscordEmbedBuilder(); deb.WithColor(DiscordColor.Gold); UriBuilder avatarUri = new UriBuilder(message_noCache.Author.AvatarUrl) { Query = "?size=64" }; deb.WithThumbnail(avatarUri.ToString()); deb.WithDescription(message_noCache.Content); deb.AddField(@"Colonist", $"{message_noCache.Author.Mention}", true); deb.AddField(@"Link", Generics.GetMessageUrl(message_noCache), true); if (message_noCache.Attachments.Count > 0) { file = true; } // Let's send this shit already. List <DiscordEmbed> embeds = new List <DiscordEmbed> { deb.Build() }; if (message_noCache.Embeds.Count > 0) { // We only want to have up to 10 embeds. Keep in mind we alread have an embed, so we can only take up to 9. embeds.AddRange(message_noCache.Embeds .Take(message_noCache.Embeds.Count >= 9 ? 9 : message_noCache.Embeds.Count)); } DiscordMessage rimboardMessage; #pragma warning disable IDE0063 if (file) { // Send a message with a file. using (WebClient webclient = new WebClient()) { string fileName = Path.Combine( path1: Program.Files.RimboardTempFileDirectory, path2: Path.ChangeExtension( path: Guid.NewGuid().ToString(), extension: Path.GetExtension(message_noCache.Attachments[0].FileName))); await webclient.DownloadFileTaskAsync(new Uri(message_noCache.Attachments[0].Url), fileName); using (FileStream fs = new FileStream(fileName, FileMode.Open)) { // Send the file paired with the embed! rimboardMessage = await WebhookDelegator.GetWebhook(webhookId : Program.Settings.RimboardWebhookId) .SendWebhookMessage( embeds: embeds.ToArray(), fileStream: fs, fileName: fileName); } if (File.Exists(fileName)) { // Delete it now that we're done with it. File.Delete(fileName); } // end if } // end using } #pragma warning restore IDE0063 else { // Send a message with no file. rimboardMessage = await WebhookDelegator.GetWebhook(webhookId : Program.Settings.RimboardWebhookId) .SendWebhookMessage(embeds: embeds.ToArray()); } pinInfo = new PinInfo( pinnedMessageId: rimboardMessage.Id, pinnedChannelId: rimboardMessage.Channel.Id, originalMessageId: message_noCache.Id, originalChannelId: message_noCache.Channel.Id, originalReactCount: reactCount); validPinInfo = true; // The pin info has been validated. await AddPinToDatabase(pinInfo); } else if (validPinInfo && reactCount >= Program.Settings.RimboardPinReactionsNeeded) { // The pin is valid and is equal to or over the threshold to be actually pinned in the Rimboard channel. var rimboardChannel = e.Guild.GetChannel(pinInfo.PinnedChannelId); var b = rimboardChannel.GetPinnedMessagesAsync(); var c = rimboardChannel.GetMessageAsync(pinInfo.PinnedMessageId); await Task.WhenAll(b, c); var pinnedMessages = b.Result; var messageToPin = c.Result; // Check if we need to get rid of the last pin. if (pinnedMessages.Count() == 50) { await pinnedMessages.Last().UnpinAsync(); } // Pin the message and react to it. var f = messageToPin.PinAsync(); var g = messageToPin.CreateReactionAsync(GetPinEmoji(e.Client)); await Task.WhenAll(f, g); } // end else if } // end if } // end method