diff --git a/Events/MessageEvent.cs b/Events/MessageEvent.cs index e5a4a6d6..c2f7867c 100644 --- a/Events/MessageEvent.cs +++ b/Events/MessageEvent.cs @@ -71,12 +71,12 @@ public static async Task MessageDeleted(DiscordClient client, MessageDeletedEven await DiscordHelpers.DoEmptyThreadCleanupAsync(e.Channel, e.Message); } - static async Task DeleteAndWarnAsync(DiscordMessage message, string reason, DiscordClient client) + static async Task DeleteAndWarnAsync(DiscordMessage message, string reason, DiscordClient client, string messageContentOverride = default) { - await DeleteAndWarnAsync(new MockDiscordMessage(message), reason, client); + await DeleteAndWarnAsync(new MockDiscordMessage(message), reason, client, messageContentOverride: messageContentOverride); } - static async Task DeleteAndWarnAsync(MockDiscordMessage message, string reason, DiscordClient client, bool wasAutoModBlock = false) + static async Task DeleteAndWarnAsync(MockDiscordMessage message, string reason, DiscordClient client, bool wasAutoModBlock = false, string messageContentOverride = default) { var channel = message.Channel; DiscordMessage msg; @@ -100,7 +100,7 @@ static async Task DeleteAndWarnAsync(MockDiscordMessage message, string reason, try { - _ = InvestigationsHelpers.SendInfringingMessaageAsync("mod", message, reason, null, wasAutoModBlock: wasAutoModBlock); + _ = InvestigationsHelpers.SendInfringingMessaageAsync("mod", message, reason, null, wasAutoModBlock: wasAutoModBlock, messageContentOverride: messageContentOverride); } catch { @@ -116,6 +116,53 @@ public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessag } public static async Task MessageHandlerAsync(DiscordClient client, MockDiscordMessage message, DiscordChannel channel, bool isAnEdit = false, bool limitFilters = false, bool wasAutoModBlock = false) { + // Get forwarded msg & embeds, if any, and combine with content to evaluate + // Combined as a single long string + + string msgContentWithEmbedData = message.Content; + var embeds = new List(); + if (message.Embeds is not null) + embeds.AddRange(message.Embeds.Where(embed => embed.Type != "auto_moderation_message")); + + if (message.MessageSnapshots is not null) + foreach (var snapshot in message.MessageSnapshots) + { + msgContentWithEmbedData += $" {snapshot.Message.Content}"; + embeds.AddRange(snapshot.Message.Embeds); + } + + foreach (var embed in embeds) + { + // Add any text from the embed into the content to be checked + + if (embed.Author is not null) + { + if (embed.Author.Name is not null) + msgContentWithEmbedData += $" {embed.Author.Name}"; + + if (embed.Author.Url is not null) + msgContentWithEmbedData += $" {embed.Author.Url}"; + } + + if (embed.Title is not null) + msgContentWithEmbedData += $" {embed.Title}"; + + if (embed.Url is not null) + msgContentWithEmbedData += $" {embed.Url}"; + + if (embed.Description is not null) + msgContentWithEmbedData += $" {embed.Description}"; + + if (embed.Footer is not null && embed.Footer.Text is not null) + msgContentWithEmbedData += $" {embed.Footer.Text}"; + + if (embed.Fields is not null) + foreach (var field in embed.Fields) + { + msgContentWithEmbedData += $" {field.Name} {field.Value}"; + } + } + try { if (message.Timestamp is not null && message.Timestamp.Value.Year < (DateTime.Now.Year - 2)) @@ -301,8 +348,8 @@ public static async Task MessageHandlerAsync(DiscordClient client, MockDiscordMe var mentionCount = message.MentionedUsers is not null && message.MentionedUsers.Count > 0 ? message.MentionedUsers.Count : message.MentionedUsersCount; string content = $"{Program.cfgjson.Emoji.Banned} {message.Author.Mention} was automatically banned for mentioning **{mentionCount}** users."; var chatMsg = await channel.SendMessageAsync(content); - _ = InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, "Mass mentions (Ban threshold)", DiscordHelpers.MessageLink(chatMsg), content: content, wasAutoModBlock: wasAutoModBlock); - _ = InvestigationsHelpers.SendInfringingMessaageAsync("mod", message, "Mass mentions (Ban threshold)", DiscordHelpers.MessageLink(chatMsg), content: content, wasAutoModBlock: wasAutoModBlock); + _ = InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, "Mass mentions (Ban threshold)", DiscordHelpers.MessageLink(chatMsg), content: content, messageContentOverride: msgContentWithEmbedData, wasAutoModBlock: wasAutoModBlock); + _ = InvestigationsHelpers.SendInfringingMessaageAsync("mod", message, "Mass mentions (Ban threshold)", DiscordHelpers.MessageLink(chatMsg), content: content, messageContentOverride: msgContentWithEmbedData, wasAutoModBlock: wasAutoModBlock); return; } @@ -317,7 +364,7 @@ public static async Task MessageHandlerAsync(DiscordClient client, MockDiscordMe } else { - (bool success, string flaggedWord) = Checks.ListChecks.CheckForNaughtyWords(message.Content.ToLower(), listItem); + (bool success, string flaggedWord) = Checks.ListChecks.CheckForNaughtyWords(msgContentWithEmbedData.ToLower(), listItem); if (success) { if (wasAutoModBlock) @@ -328,7 +375,7 @@ public static async Task MessageHandlerAsync(DiscordClient client, MockDiscordMe string reason = listItem.Reason; try { - await InvestigationsHelpers.SendInfringingMessaageAsync("mod", message, reason, null, wasAutoModBlock: wasAutoModBlock); + await InvestigationsHelpers.SendInfringingMessaageAsync("mod", message, reason, null, messageContentOverride: msgContentWithEmbedData, wasAutoModBlock: wasAutoModBlock); } catch { @@ -347,7 +394,7 @@ public static async Task MessageHandlerAsync(DiscordClient client, MockDiscordMe DiscordMessage msg = await WarningHelpers.SendPublicWarningMessageAndDeleteInfringingMessageAsync(message, $"{Program.cfgjson.Emoji.Denied} {message.Author.Mention} was automatically warned: **{reason.Replace("`", "\\`").Replace("*", "\\*")}**", wasAutoModBlock, 1); var warning = await WarningHelpers.GiveWarningAsync(message.Author, client.CurrentUser, reason, contextMessage: msg, channel, " automatically "); - await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, extraField: ("Match", flaggedWord, true), wasAutoModBlock: wasAutoModBlock); + await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, extraField: ("Match", flaggedWord, true), messageContentOverride: msgContentWithEmbedData, wasAutoModBlock: wasAutoModBlock); return; } } @@ -359,7 +406,7 @@ public static async Task MessageHandlerAsync(DiscordClient client, MockDiscordMe return; // Unapproved invites - string checkedMessage = message.Content.Replace('\\', '/'); + string checkedMessage = msgContentWithEmbedData.Replace('\\', '/'); if ((await GetPermLevelAsync(member)) < (ServerPermLevel)Program.cfgjson.InviteTierRequirement && checkedMessage.Contains("dsc.gg/") || checkedMessage.Contains("invite.gg/") @@ -377,7 +424,7 @@ public static async Task MessageHandlerAsync(DiscordClient client, MockDiscordMe string reason = "Sent an unapproved invite"; try { - _ = InvestigationsHelpers.SendInfringingMessaageAsync("mod", message, reason, null, wasAutoModBlock: wasAutoModBlock); + _ = InvestigationsHelpers.SendInfringingMessaageAsync("mod", message, reason, null, messageContentOverride: msgContentWithEmbedData, wasAutoModBlock: wasAutoModBlock); } catch { @@ -386,7 +433,7 @@ public static async Task MessageHandlerAsync(DiscordClient client, MockDiscordMe DiscordMessage msg = await WarningHelpers.SendPublicWarningMessageAndDeleteInfringingMessageAsync(message, $"{Program.cfgjson.Emoji.Denied} {message.Author.Mention} was automatically warned: **{reason.Replace("`", "\\`").Replace("*", "\\*")}**", wasAutoModBlock, 1); var warning = await WarningHelpers.GiveWarningAsync(message.Author, client.CurrentUser, reason, contextMessage: msg, channel, " automatically "); - await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, wasAutoModBlock: wasAutoModBlock); + await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, messageContentOverride: msgContentWithEmbedData, wasAutoModBlock: wasAutoModBlock); match = true; return; } @@ -396,7 +443,7 @@ public static async Task MessageHandlerAsync(DiscordClient client, MockDiscordMe if ((await GetPermLevelAsync(member)) < (ServerPermLevel)Program.cfgjson.InviteTierRequirement && inviteMatches.Count > 3) { string reason = "Sent too many invites"; - await DeleteAndWarnAsync(message, reason, client, wasAutoModBlock); + await DeleteAndWarnAsync(message, reason, client, wasAutoModBlock, messageContentOverride: msgContentWithEmbedData); match = true; return; } @@ -423,7 +470,7 @@ public static async Task MessageHandlerAsync(DiscordClient client, MockDiscordMe if (!match) { string reason = "Sent an unapproved invite"; - await DeleteAndWarnAsync(message, reason, client, wasAutoModBlock); + await DeleteAndWarnAsync(message, reason, client, wasAutoModBlock, messageContentOverride: msgContentWithEmbedData); match = true; } break; @@ -456,7 +503,7 @@ public static async Task MessageHandlerAsync(DiscordClient client, MockDiscordMe string responseToSend = $"```json\n{JsonConvert.SerializeObject(maliciousCache)}\n```"; (string name, string value, bool inline) extraField = new("Cached API response", responseToSend, false); - await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, extraField, wasAutoModBlock: wasAutoModBlock); + await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, extraField, messageContentOverride: msgContentWithEmbedData, wasAutoModBlock: wasAutoModBlock); match = true; break; @@ -484,7 +531,7 @@ public static async Task MessageHandlerAsync(DiscordClient client, MockDiscordMe if (!match) { string reason = "Sent an unapproved invite"; - await DeleteAndWarnAsync(message, reason, client, wasAutoModBlock); + await DeleteAndWarnAsync(message, reason, client, wasAutoModBlock, messageContentOverride: msgContentWithEmbedData); } return; } @@ -499,9 +546,9 @@ public static async Task MessageHandlerAsync(DiscordClient client, MockDiscordMe return; // Mass emoji - if (!Program.cfgjson.UnrestrictedEmojiChannels.Contains(channel.Id) && message.Content.Length >= Program.cfgjson.MassEmojiThreshold) + if (!Program.cfgjson.UnrestrictedEmojiChannels.Contains(channel.Id) && msgContentWithEmbedData.Length >= Program.cfgjson.MassEmojiThreshold) { - char[] tempArray = message.Content.Replace("🏻", "").Replace("🏼", "").Replace("🏽", "").Replace("🏾", "").Replace("🏿", "").ToCharArray(); + char[] tempArray = msgContentWithEmbedData.Replace("🏻", "").Replace("🏼", "").Replace("🏽", "").Replace("🏾", "").Replace("🏿", "").ToCharArray(); int pos = 0; foreach (char c in tempArray) { @@ -546,7 +593,7 @@ public static async Task MessageHandlerAsync(DiscordClient client, MockDiscordMe pardonOutput = $"{Program.cfgjson.Emoji.Information} {message.Author.Mention} Your message was automatically deleted for mass emoji."; var msgOut = await WarningHelpers.SendPublicWarningMessageAndDeleteInfringingMessageAsync(message, pardonOutput, wasAutoModBlock); - await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, DiscordHelpers.MessageLink(msgOut), wasAutoModBlock: wasAutoModBlock); + await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, DiscordHelpers.MessageLink(msgOut), messageContentOverride: msgContentWithEmbedData, wasAutoModBlock: wasAutoModBlock); return; } @@ -559,7 +606,7 @@ public static async Task MessageHandlerAsync(DiscordClient client, MockDiscordMe DiscordMessage msg = await WarningHelpers.SendPublicWarningMessageAndDeleteInfringingMessageAsync(message, output, wasAutoModBlock); var warning = await WarningHelpers.GiveWarningAsync(message.Author, client.CurrentUser, reason, contextMessage: msg, channel, " automatically "); - await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, wasAutoModBlock: wasAutoModBlock); + await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, messageContentOverride: msgContentWithEmbedData, wasAutoModBlock: wasAutoModBlock); return; } @@ -613,10 +660,10 @@ public static async Task MessageHandlerAsync(DiscordClient client, MockDiscordMe } // phishing API - var urlMatches = url_rx.Matches(message.Content); + var urlMatches = url_rx.Matches(msgContentWithEmbedData); if (urlMatches.Count > 0 && Environment.GetEnvironmentVariable("CLIPTOK_ANTIPHISHING_ENDPOINT") is not null && Environment.GetEnvironmentVariable("CLIPTOK_ANTIPHISHING_ENDPOINT") != "useyourimagination") { - var (phishingMatch, httpStatus, responseText, phishingResponse) = await APIs.PhishingAPI.PhishingAPICheckAsync(message.Content); + var (phishingMatch, httpStatus, responseText, phishingResponse) = await APIs.PhishingAPI.PhishingAPICheckAsync(msgContentWithEmbedData); if (httpStatus == HttpStatusCode.OK) { @@ -638,14 +685,14 @@ public static async Task MessageHandlerAsync(DiscordClient client, MockDiscordMe string responseToSend = await StringHelpers.CodeOrHasteBinAsync(responseText, "json", 1000, true); (string name, string value, bool inline) extraField = new("API Response", responseToSend, false); - await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, extraField, wasAutoModBlock: wasAutoModBlock); + await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, extraField, messageContentOverride: msgContentWithEmbedData, wasAutoModBlock: wasAutoModBlock); return; } } } // attempted to ping @everyone/@here - var msgContent = message.Content; + var msgContent = msgContentWithEmbedData; foreach (var letter in Checks.ListChecks.lookalikeAlphabetMap) msgContent = msgContent.Replace(letter.Key, letter.Value); if (Program.cfgjson.EveryoneFilter && !member.Roles.Any(role => Program.cfgjson.EveryoneExcludedRoles.Contains(role.Id)) && !Program.cfgjson.EveryoneExcludedChannels.Contains(channel.Id) && (msgContent.Contains("@everyone") || msgContent.Contains("@here"))) @@ -662,7 +709,7 @@ public static async Task MessageHandlerAsync(DiscordClient client, MockDiscordMe string reason = "Attempted to ping everyone/here"; DiscordMessage msg = await WarningHelpers.SendPublicWarningMessageAndDeleteInfringingMessageAsync(message, $"{Program.cfgjson.Emoji.Denied} {message.Author.Mention} was automatically warned: **{reason.Replace("`", "\\`").Replace("*", "\\*")}**", wasAutoModBlock); var warning = await WarningHelpers.GiveWarningAsync(message.Author, client.CurrentUser, reason, contextMessage: msg, channel, " automatically "); - await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, wasAutoModBlock: wasAutoModBlock); + await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, messageContentOverride: msgContentWithEmbedData, wasAutoModBlock: wasAutoModBlock); return; } @@ -681,7 +728,7 @@ public static async Task MessageHandlerAsync(DiscordClient client, MockDiscordMe string reason = "Mass mentions"; try { - _ = InvestigationsHelpers.SendInfringingMessaageAsync("mod", message, reason, null, wasAutoModBlock: wasAutoModBlock); + _ = InvestigationsHelpers.SendInfringingMessaageAsync("mod", message, reason, null, messageContentOverride: msgContentWithEmbedData, wasAutoModBlock: wasAutoModBlock); } catch { @@ -690,12 +737,12 @@ public static async Task MessageHandlerAsync(DiscordClient client, MockDiscordMe DiscordMessage msg = await WarningHelpers.SendPublicWarningMessageAndDeleteInfringingMessageAsync(message, $"{Program.cfgjson.Emoji.Denied} {message.Author.Mention} was automatically warned: **{reason.Replace("`", "\\`").Replace("*", "\\*")}**", wasAutoModBlock); var warning = await WarningHelpers.GiveWarningAsync(message.Author, client.CurrentUser, reason, contextMessage: msg, channel, " automatically "); - await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, wasAutoModBlock: wasAutoModBlock); + await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, messageContentOverride: msgContentWithEmbedData, wasAutoModBlock: wasAutoModBlock); return; } // line limit - var lineCount = CountNewlines(message.Content); + var lineCount = CountNewlines(msgContentWithEmbedData); if (!Program.cfgjson.LineLimitExcludedChannels.Contains(channel.Id) && (channel.ParentId is null || !Program.cfgjson.LineLimitExcludedChannels.Contains((ulong)channel.ParentId)) @@ -737,7 +784,7 @@ public static async Task MessageHandlerAsync(DiscordClient client, MockDiscordMe { msg = await channel.SendMessageAsync(messageBuilder); } - await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, DiscordHelpers.MessageLink(msg), wasAutoModBlock: wasAutoModBlock); + await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, DiscordHelpers.MessageLink(msg), messageContentOverride: msgContentWithEmbedData, wasAutoModBlock: wasAutoModBlock); return; } else @@ -759,7 +806,7 @@ public static async Task MessageHandlerAsync(DiscordClient client, MockDiscordMe } var warning = await WarningHelpers.GiveWarningAsync(message.Author, client.CurrentUser, reason, contextMessage: msg, channel, " automatically "); - await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, wasAutoModBlock: wasAutoModBlock); + await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, messageContentOverride: msgContentWithEmbedData, wasAutoModBlock: wasAutoModBlock); return; } @@ -824,7 +871,7 @@ await LogChannelHelper.LogMessageAsync("messages", } else { - (bool success, string flaggedWord) = Checks.ListChecks.CheckForNaughtyWords(message.Content.ToLower(), listItem); + (bool success, string flaggedWord) = Checks.ListChecks.CheckForNaughtyWords(msgContentWithEmbedData.ToLower(), listItem); if (success) { DiscordChannel logChannel = default; @@ -846,6 +893,7 @@ await InvestigationsHelpers.SendInfringingMessaageAsync( colour: new DiscordColor(0xFEC13D), channelOverride: logChannel, extraField: extraField, + messageContentOverride: msgContentWithEmbedData, wasAutoModBlock: wasAutoModBlock ); } diff --git a/Events/MockDiscordMessage.cs b/Events/MockDiscordMessage.cs index bac84add..e3c346e3 100644 --- a/Events/MockDiscordMessage.cs +++ b/Events/MockDiscordMessage.cs @@ -20,13 +20,14 @@ public MockDiscordMessage(DiscordMessage baseMessage) JumpLink = baseMessage.JumpLink; MentionedUsers = baseMessage.MentionedUsers; MentionedUsersCount = baseMessage.MentionedUsers.Count; + MessageSnapshots = baseMessage.MessageSnapshots; Reactions = baseMessage.Reactions; ReferencedMessage = baseMessage.ReferencedMessage; Stickers = baseMessage.Stickers; Timestamp = baseMessage.Timestamp; } - public MockDiscordMessage(IReadOnlyList attachments = default, DiscordUser author = default, DiscordChannel channel = default, ulong channelId = default, string content = default, IReadOnlyList embeds = default, ulong id = default, Uri jumpLink = default, IReadOnlyList mentionedUsers = default, int mentionedUsersCount = default, IReadOnlyList reactions = default, DiscordMessage referencedMessage = default, IReadOnlyList stickers = default, DateTimeOffset? timestamp = default) + public MockDiscordMessage(IReadOnlyList attachments = default, DiscordUser author = default, DiscordChannel channel = default, ulong channelId = default, string content = default, IReadOnlyList embeds = default, ulong id = default, Uri jumpLink = default, IReadOnlyList mentionedUsers = default, int mentionedUsersCount = default, IReadOnlyList messageSnapshots = default, IReadOnlyList reactions = default, DiscordMessage referencedMessage = default, IReadOnlyList stickers = default, DateTimeOffset? timestamp = default) { Attachments = attachments; Author = author; @@ -38,6 +39,7 @@ public MockDiscordMessage(IReadOnlyList attachments = default JumpLink = jumpLink; MentionedUsers = mentionedUsers; MentionedUsersCount = mentionedUsersCount; + MessageSnapshots = messageSnapshots; Reactions = reactions; ReferencedMessage = referencedMessage; Stickers = stickers; @@ -55,6 +57,7 @@ public MockDiscordMessage(IReadOnlyList attachments = default public Uri JumpLink { get; set; } public IReadOnlyList MentionedUsers { get; } public int MentionedUsersCount { get; } + public IReadOnlyList MessageSnapshots { get; } public IReadOnlyList Reactions { get; set; } public DiscordMessage ReferencedMessage { get; set; } public IReadOnlyList Stickers { get; set; } diff --git a/Helpers/InvestigationsHelpers.cs b/Helpers/InvestigationsHelpers.cs index f6d8a1e8..15e41dbf 100644 --- a/Helpers/InvestigationsHelpers.cs +++ b/Helpers/InvestigationsHelpers.cs @@ -6,13 +6,13 @@ public static async Task SendInfringingMessaageAsync(string logChannelKey, Disco { await SendInfringingMessaageAsync(logChannelKey, new MockDiscordMessage(infringingMessage), reason, messageURL, extraField, content, colour, channelOverride); } - public static async Task SendInfringingMessaageAsync(string logChannelKey, MockDiscordMessage infringingMessage, string reason, string messageURL, (string name, string value, bool inline) extraField = default, string content = default, DiscordColor? colour = null, DiscordChannel channelOverride = default, bool wasAutoModBlock = false) + public static async Task SendInfringingMessaageAsync(string logChannelKey, MockDiscordMessage infringingMessage, string reason, string messageURL, (string name, string value, bool inline) extraField = default, string content = default, DiscordColor? colour = null, DiscordChannel channelOverride = default, string messageContentOverride = default, bool wasAutoModBlock = false) { if (colour is null) colour = new DiscordColor(0xf03916); var embed = new DiscordEmbedBuilder() - .WithDescription(infringingMessage.Content) + .WithDescription(string.IsNullOrWhiteSpace(messageContentOverride) ? infringingMessage.Content : messageContentOverride) .WithColor((DiscordColor)colour) .WithTimestamp(infringingMessage.Timestamp) .WithFooter(