From fd7fb660783131261673340d926e19ec3631cd52 Mon Sep 17 00:00:00 2001 From: Milkshake Date: Sun, 18 Aug 2024 21:32:33 -0400 Subject: [PATCH] Run AutoMod-blocked messages through autowarn filters (#217) * Run AutoMod-blocked messages through autowarn filters * Apply suggestions from code review Co-authored-by: Erisa A * Rephrase too many emoji/lines warning msgs for AutoMod-blocked msgs * Hide button to show content for automodded msgs w/ too many lines * Avoid unnecessary code --------- Co-authored-by: Erisa A --- Events/AutoModEvents.cs | 22 ++ Events/MessageEvent.cs | 575 +++++++++++++++++-------------- Events/MockDiscordMessage.cs | 76 ++++ Helpers/DiscordHelpers.cs | 4 + Helpers/InvestigationsHelpers.cs | 9 +- Program.cs | 1 + 6 files changed, 432 insertions(+), 255 deletions(-) create mode 100644 Events/AutoModEvents.cs create mode 100644 Events/MockDiscordMessage.cs diff --git a/Events/AutoModEvents.cs b/Events/AutoModEvents.cs new file mode 100644 index 00000000..944dfc7b --- /dev/null +++ b/Events/AutoModEvents.cs @@ -0,0 +1,22 @@ +namespace Cliptok.Events +{ + public class AutoModEvents + { + public static async Task AutoModerationRuleExecuted(DiscordClient client, AutoModerationRuleExecutedEventArgs e) + { + if (e.Rule.Action.Type == DiscordRuleActionType.BlockMessage) + { + // AutoMod blocked a message. Pass it to the message handler to run it through some filters anyway. + + var author = await client.GetUserAsync(e.Rule.UserId); + var channel = await client.GetChannelAsync(e.Rule.ChannelId!.Value); + + // Create a "mock" message object to pass to the message handler, since we don't have the actual message object + var message = new MockDiscordMessage(author: author, channel: channel, channelId: channel.Id, content: e.Rule.Content); + + // Pass to the message handler + await MessageEvent.MessageHandlerAsync(client, message, channel, false, true, true); + } + } + } +} \ No newline at end of file diff --git a/Events/MessageEvent.cs b/Events/MessageEvent.cs index 2f21e77f..02d46d09 100644 --- a/Events/MessageEvent.cs +++ b/Events/MessageEvent.cs @@ -101,10 +101,16 @@ public static async Task MessageDeleted(DiscordClient client, MessageDeletedEven static async Task DeleteAndWarnAsync(DiscordMessage message, string reason, DiscordClient client) { - _ = message.DeleteAsync(); + await DeleteAndWarnAsync(new MockDiscordMessage(message), reason, client); + } + + static async Task DeleteAndWarnAsync(MockDiscordMessage message, string reason, DiscordClient client, bool wasAutoModBlock = false) + { + if (!wasAutoModBlock) + _ = message.DeleteAsync(); try { - _ = InvestigationsHelpers.SendInfringingMessaageAsync("mod", message, reason, null); + _ = InvestigationsHelpers.SendInfringingMessaageAsync("mod", message, reason, null, wasAutoModBlock: wasAutoModBlock); } catch { @@ -112,126 +118,137 @@ static async Task DeleteAndWarnAsync(DiscordMessage message, string reason, Disc } DiscordMessage msg = await message.Channel.SendMessageAsync($"{Program.cfgjson.Emoji.Denied} {message.Author.Mention} was automatically warned: **{reason.Replace("`", "\\`").Replace("*", "\\*")}**"); var warning = await WarningHelpers.GiveWarningAsync(message.Author, client.CurrentUser, reason, contextMessage: msg, message.Channel, " automatically "); - await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink); + await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, wasAutoModBlock: wasAutoModBlock); + } + + public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessage message, DiscordChannel channel, bool isAnEdit = false, bool limitFilters = false, bool wasAutoModBlock = false) + { + await MessageHandlerAsync(client, new MockDiscordMessage(message), channel, isAnEdit, limitFilters); } - public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessage message, DiscordChannel channel, bool isAnEdit = false) + public static async Task MessageHandlerAsync(DiscordClient client, MockDiscordMessage message, DiscordChannel channel, bool isAnEdit = false, bool limitFilters = false, bool wasAutoModBlock = false) { try { - if (message.Timestamp.Year < (DateTime.Now.Year - 2)) + if (message.Timestamp is not null && message.Timestamp.Value.Year < (DateTime.Now.Year - 2)) return; - if (isAnEdit && (message.EditedTimestamp is null || message.EditedTimestamp == message.CreationTimestamp || message.EditedTimestamp < DateTimeOffset.Now - TimeSpan.FromDays(1))) + if (isAnEdit && message.BaseMessage is not null && (message.BaseMessage.EditedTimestamp is null || message.BaseMessage.EditedTimestamp == message.BaseMessage.CreationTimestamp || message.BaseMessage.EditedTimestamp < DateTimeOffset.Now - TimeSpan.FromDays(1))) return; if (message.Author is null || message.Author.Id == client.CurrentUser.Id) return; - if (Program.db.SetContains("trackedUsers", message.Author.Id)) + if (!limitFilters) { - DiscordThreadChannel relayThread; - - if (trackingThreadCache.ContainsKey(message.Author.Id)) + if (Program.db.SetContains("trackedUsers", message.Author.Id)) { - relayThread = trackingThreadCache[message.Author.Id]; - } - else - { - relayThread = (DiscordThreadChannel)await client.GetChannelAsync((ulong)await Program.db.HashGetAsync("trackingThreads", message.Author.Id)); - trackingThreadCache.Add(message.Author.Id, relayThread); - } - var _ = await relayThread.SendMessageAsync(await DiscordHelpers.GenerateMessageRelay(message, true, true)); + DiscordThreadChannel relayThread; - } - - if (!isAnEdit && channel.IsPrivate && Program.cfgjson.LogChannels.ContainsKey("dms")) - { - DirectMessageEvent.DirectMessageEventHandler(message); - return; - } + if (trackingThreadCache.ContainsKey(message.Author.Id)) + { + relayThread = trackingThreadCache[message.Author.Id]; + } + else + { + relayThread = (DiscordThreadChannel)await client.GetChannelAsync((ulong)await Program.db.HashGetAsync("trackingThreads", message.Author.Id)); + trackingThreadCache.Add(message.Author.Id, relayThread); + } + var _ = await relayThread.SendMessageAsync(await DiscordHelpers.GenerateMessageRelay(message.BaseMessage, true, true)); - if (!isAnEdit && message.Author.Id == Program.cfgjson.ModmailUserId && message.Content == "@here" && message.Embeds[0].Footer.Text.Contains("User ID:")) - { - Program.discord.Logger.LogDebug(Program.CliptokEventID, "Processing modmail message {message} in {channel}", message.Id, message.Channel); - var idString = modmaiL_rx.Match(message.Embeds[0].Footer.Text).Groups[1].Captures[0].Value; - DiscordMember modmailMember = default; - try - { - modmailMember = await message.Channel.Guild.GetMemberAsync(Convert.ToUInt64(idString)); } - catch (DSharpPlus.Exceptions.NotFoundException) + + if (!isAnEdit && channel.IsPrivate && Program.cfgjson.LogChannels.ContainsKey("dms")) { + DirectMessageEvent.DirectMessageEventHandler(message.BaseMessage); return; } - DiscordMessageBuilder memberWarnInfo = new(); - - DiscordRole muted = message.Channel.Guild.GetRole(Program.cfgjson.MutedRole); - if (modmailMember.Roles.Contains(muted)) + if (!isAnEdit && message.Author.Id == Program.cfgjson.ModmailUserId && message.Content == "@here" && message.Embeds[0].Footer.Text.Contains("User ID:")) { - memberWarnInfo.AddEmbed(await WarningHelpers.GenerateWarningsEmbedAsync(modmailMember)).AddEmbed(await MuteHelpers.MuteStatusEmbed(modmailMember, message.Channel.Guild)); - } + Program.discord.Logger.LogDebug(Program.CliptokEventID, "Processing modmail message {message} in {channel}", message.Id, message.Channel); + var idString = modmaiL_rx.Match(message.Embeds[0].Footer.Text).Groups[1].Captures[0].Value; + DiscordMember modmailMember = default; + try + { + modmailMember = await message.Channel.Guild.GetMemberAsync(Convert.ToUInt64(idString)); + } + catch (DSharpPlus.Exceptions.NotFoundException) + { + return; + } - // Add notes to message if any exist & are set to show on modmail + DiscordMessageBuilder memberWarnInfo = new(); - // Get user notes - var notes = Program.db.HashGetAll(modmailMember.Id.ToString()) - .Where(x => JsonConvert.DeserializeObject(x.Value).Type == WarningType.Note).ToDictionary( - x => x.Name.ToString(), - x => JsonConvert.DeserializeObject(x.Value) - ); + DiscordRole muted = message.Channel.Guild.GetRole(Program.cfgjson.MutedRole); + if (modmailMember.Roles.Contains(muted)) + { + memberWarnInfo.AddEmbed(await WarningHelpers.GenerateWarningsEmbedAsync(modmailMember)).AddEmbed(await MuteHelpers.MuteStatusEmbed(modmailMember, message.Channel.Guild)); + } - // Filter to notes set to notify on modmail - var notesToNotify = notes.Where(x => x.Value.ShowOnModmail).ToDictionary(x => x.Key, x => x.Value); + // Add notes to message if any exist & are set to show on modmail - // If there are notes, build embed and add to message - if (notesToNotify.Count != 0) - { - memberWarnInfo.AddEmbed(await UserNoteHelpers.GenerateUserNotesEmbedAsync(modmailMember, notesToUse: notesToNotify)); + // Get user notes + var notes = Program.db.HashGetAll(modmailMember.Id.ToString()) + .Where(x => JsonConvert.DeserializeObject(x.Value).Type == WarningType.Note).ToDictionary( + x => x.Name.ToString(), + x => JsonConvert.DeserializeObject(x.Value) + ); - // For any notes set to show once, show the full note content in its own embed because it will not be able to be fetched manually - foreach (var note in notesToNotify) - if (memberWarnInfo.Embeds.Count < 10) // Limit to 10 embeds; this probably won't be an issue because we probably won't have that many 'show once' notes - if (note.Value.ShowOnce) - memberWarnInfo.AddEmbed(await UserNoteHelpers.GenerateUserNoteSimpleEmbedAsync(note.Value, modmailMember)); - } + // Filter to notes set to notify on modmail + var notesToNotify = notes.Where(x => x.Value.ShowOnModmail).ToDictionary(x => x.Key, x => x.Value); - // If message was built (if user is muted OR if user has notes to show on modmail), send it - if (memberWarnInfo.Embeds.Count != 0) - await message.Channel.SendMessageAsync(memberWarnInfo); + // If there are notes, build embed and add to message + if (notesToNotify.Count != 0) + { + memberWarnInfo.AddEmbed(await UserNoteHelpers.GenerateUserNotesEmbedAsync(modmailMember, notesToUse: notesToNotify)); - // If any notes were shown & set to show only once, delete them now - foreach (var note in notesToNotify.Where(note => note.Value.ShowOnce)) - { - // Delete note - await Program.db.HashDeleteAsync(modmailMember.Id.ToString(), note.Key); + // For any notes set to show once, show the full note content in its own embed because it will not be able to be fetched manually + foreach (var note in notesToNotify) + if (memberWarnInfo.Embeds.Count < 10) // Limit to 10 embeds; this probably won't be an issue because we probably won't have that many 'show once' notes + if (note.Value.ShowOnce) + memberWarnInfo.AddEmbed(await UserNoteHelpers.GenerateUserNoteSimpleEmbedAsync(note.Value, modmailMember)); + } - // Log deletion to mod-logs channel - var embed = new DiscordEmbedBuilder(await UserNoteHelpers.GenerateUserNoteDetailEmbedAsync(note.Value, modmailMember)).WithColor(0xf03916); - await LogChannelHelper.LogMessageAsync("mod", $"{Program.cfgjson.Emoji.Deleted} Note `{note.Value.NoteId}` was automatically deleted after modmail thread creation (belonging to {modmailMember.Mention})", embed); - } - } + // If message was built (if user is muted OR if user has notes to show on modmail), send it + if (memberWarnInfo.Embeds.Count != 0) + await message.Channel.SendMessageAsync(memberWarnInfo); - // handle #giveaways - if (!isAnEdit && message.Author.Id == Program.cfgjson.GiveawayBot && message.Channel.Id == Program.cfgjson.GiveawaysChannel && message.Content == Program.cfgjson.GiveawayTriggerMessage) - { - string giveawayTitle = message.Embeds[0].Title; + // If any notes were shown & set to show only once, delete them now + foreach (var note in notesToNotify.Where(note => note.Value.ShowOnce)) + { + // Delete note + await Program.db.HashDeleteAsync(modmailMember.Id.ToString(), note.Key); - if (giveawayTitle.Length > 100) - { - giveawayTitle = StringHelpers.Truncate(giveawayTitle, 100, false); + // Log deletion to mod-logs channel + var embed = new DiscordEmbedBuilder(await UserNoteHelpers.GenerateUserNoteDetailEmbedAsync(note.Value, modmailMember)).WithColor(0xf03916); + await LogChannelHelper.LogMessageAsync("mod", $"{Program.cfgjson.Emoji.Deleted} Note `{note.Value.NoteId}` was automatically deleted after modmail thread creation (belonging to {modmailMember.Mention})", embed); + } } - await message.CreateThreadAsync(giveawayTitle, DiscordAutoArchiveDuration.ThreeDays, "Automatically creating giveaway thread."); + // handle #giveaways + if (!isAnEdit && message.Author.Id == Program.cfgjson.GiveawayBot && message.Channel.Id == Program.cfgjson.GiveawaysChannel && message.Content == Program.cfgjson.GiveawayTriggerMessage) + { + string giveawayTitle = message.Embeds[0].Title; + + if (giveawayTitle.Length > 100) + { + giveawayTitle = StringHelpers.Truncate(giveawayTitle, 100, false); + } + + await message.BaseMessage.CreateThreadAsync(giveawayTitle, DiscordAutoArchiveDuration.ThreeDays, "Automatically creating giveaway thread."); + } } // Skip DMs, external guilds, and messages from bots, beyond this point. if (message.Channel.IsPrivate || message.Channel.Guild.Id != Program.cfgjson.ServerID || message.Author.IsBot) return; - // track mentions - if (message.MentionedUsers.Any(x => x.Id == Program.discord.CurrentUser.Id)) - await LogChannelHelper.LogMessageAsync("mentions", await DiscordHelpers.GenerateMessageRelay(message, true, true, false)); + if (!limitFilters) + { + // track mentions + if (message.MentionedUsers.Any(x => x.Id == Program.discord.CurrentUser.Id)) + await LogChannelHelper.LogMessageAsync("mentions", await DiscordHelpers.GenerateMessageRelay(message.BaseMessage, true, true, false)); + } DiscordMember member; try @@ -249,23 +266,29 @@ public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessag // Skip messages from moderators beyond this point. if (GetPermLevel(member) < ServerPermLevel.TrialModerator) { - if ((channel.Id == Program.cfgjson.SupportForumIntroThreadId || Program.cfgjson.ForumIntroPosts.Contains(channel.Id)) && !member.Roles.Any(role => role.Id == Program.cfgjson.TqsRoleId)) + if (!limitFilters) { - await message.DeleteAsync(); - var msg = await message.Channel.SendMessageAsync($"{Program.cfgjson.Emoji.Error} {message.Author.Mention}, you can't send messages in this thread!\nTry creating a post on {message.Channel.Parent.Mention} instead."); - await Task.Delay(5000); - await msg.DeleteAsync(); - return; + if ((channel.Id == Program.cfgjson.SupportForumIntroThreadId || + Program.cfgjson.ForumIntroPosts.Contains(channel.Id)) && + !member.Roles.Any(role => role.Id == Program.cfgjson.TqsRoleId)) + { + await message.DeleteAsync(); + var msg = await message.Channel.SendMessageAsync($"{Program.cfgjson.Emoji.Error} {message.Author.Mention}, you can't send messages in this thread!\nTry creating a post on {message.Channel.Parent.Mention} instead."); + await Task.Delay(5000); + await msg.DeleteAsync(); + return; + } } - if (message.MentionedUsers.Count > Program.cfgjson.MassMentionBanThreshold) + if (message.MentionedUsers is not null && message.MentionedUsers.Count > Program.cfgjson.MassMentionBanThreshold) { - _ = message.DeleteAsync(); + if (!wasAutoModBlock) + _ = message.DeleteAsync(); _ = channel.Guild.BanMemberAsync(message.Author, TimeSpan.FromDays(7), $"Mentioned more than {Program.cfgjson.MassMentionBanThreshold} users in one message."); string content = $"{Program.cfgjson.Emoji.Banned} {message.Author.Mention} was automatically banned for mentioning **{message.MentionedUsers.Count}** users."; var chatMsg = await channel.SendMessageAsync(content); - _ = InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, "Mass mentions (Ban threshold)", DiscordHelpers.MessageLink(chatMsg), content: content); - _ = InvestigationsHelpers.SendInfringingMessaageAsync("mod", message, "Mass mentions (Ban threshold)", DiscordHelpers.MessageLink(chatMsg), content: 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); return; } @@ -286,8 +309,9 @@ public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessag string reason = listItem.Reason; try { - _ = message.DeleteAsync(); - await InvestigationsHelpers.SendInfringingMessaageAsync("mod", message, reason, null); + if (!wasAutoModBlock) + _ = message.DeleteAsync(); + await InvestigationsHelpers.SendInfringingMessaageAsync("mod", message, reason, null, wasAutoModBlock: wasAutoModBlock); } catch { @@ -296,7 +320,8 @@ public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessag if (listItem.Name == "autoban.txt" && GetPermLevel(member) < ServerPermLevel.Tier4) { - _ = message.DeleteAsync(); + if (!wasAutoModBlock) + _ = message.DeleteAsync(); await BanHelpers.BanFromServerAsync(message.Author.Id, reason, client.CurrentUser.Id, message.Channel.Guild, 0, message.Channel, default, true); return; } @@ -307,7 +332,7 @@ public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessag DiscordMessage msg = await message.Channel.SendMessageAsync($"{Program.cfgjson.Emoji.Denied} {message.Author.Mention} was automatically warned: **{reason.Replace("`", "\\`").Replace("*", "\\*")}**"); var warning = await WarningHelpers.GiveWarningAsync(message.Author, client.CurrentUser, reason, contextMessage: msg, message.Channel, " automatically "); - await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, extraField: ("Match", flaggedWord, true)); + await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, extraField: ("Match", flaggedWord, true), wasAutoModBlock: wasAutoModBlock); return; } } @@ -326,10 +351,11 @@ public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessag ) { string reason = "Sent an unapproved invite"; - _ = message.DeleteAsync(); + if (!wasAutoModBlock) + _ = message.DeleteAsync(); try { - _ = InvestigationsHelpers.SendInfringingMessaageAsync("mod", message, reason, null); + _ = InvestigationsHelpers.SendInfringingMessaageAsync("mod", message, reason, null, wasAutoModBlock: wasAutoModBlock); } catch { @@ -338,7 +364,7 @@ public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessag DiscordMessage msg = await message.Channel.SendMessageAsync($"{Program.cfgjson.Emoji.Denied} {message.Author.Mention} was automatically warned: **{reason.Replace("`", "\\`").Replace("*", "\\*")}**"); var warning = await WarningHelpers.GiveWarningAsync(message.Author, client.CurrentUser, reason, contextMessage: msg, message.Channel, " automatically "); - await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink); + await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, wasAutoModBlock: wasAutoModBlock); match = true; return; } @@ -348,7 +374,7 @@ public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessag if (GetPermLevel(member) < (ServerPermLevel)Program.cfgjson.InviteTierRequirement && inviteMatches.Count > 3) { string reason = "Sent too many invites"; - await DeleteAndWarnAsync(message, reason, client); + await DeleteAndWarnAsync(message, reason, client, wasAutoModBlock); match = true; return; } @@ -372,12 +398,13 @@ public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessag if (GetPermLevel(member) < (ServerPermLevel)Program.cfgjson.InviteTierRequirement && disallowedInviteCodes.Contains(code)) { - _ = message.DeleteAsync(); + if (!wasAutoModBlock) + _ = message.DeleteAsync(); //match = await InviteCheck(invite, message, client); if (!match) { string reason = "Sent an unapproved invite"; - await DeleteAndWarnAsync(message, reason, client); + await DeleteAndWarnAsync(message, reason, client, wasAutoModBlock); } break; } @@ -401,7 +428,8 @@ public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessag if (maliciousCache != default) { - _ = message.DeleteAsync(); + if (!wasAutoModBlock) + _ = message.DeleteAsync(); string reason = "Sent a malicious Discord invite"; DiscordMessage msg = await message.Channel.SendMessageAsync($"{Program.cfgjson.Emoji.Denied} {message.Author.Mention} was automatically warned: **{reason.Replace("`", "\\`").Replace("*", "\\*")}**"); @@ -410,7 +438,7 @@ public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessag 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); + await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, extraField, wasAutoModBlock: wasAutoModBlock); match = true; break; @@ -433,19 +461,20 @@ public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessag ) ) { - _ = message.DeleteAsync(); + if (!wasAutoModBlock) + _ = message.DeleteAsync(); disallowedInviteCodes.Add(code); - match = await InviteCheck(invite, message, client); + match = await InviteCheck(invite, message, client, wasAutoModBlock); if (!match) { string reason = "Sent an unapproved invite"; - await DeleteAndWarnAsync(message, reason, client); + await DeleteAndWarnAsync(message, reason, client, wasAutoModBlock); } return; } else { - match = await InviteCheck(invite, message, client); + match = await InviteCheck(invite, message, client, wasAutoModBlock); } } @@ -479,7 +508,8 @@ public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessag if (matches.Count > Program.cfgjson.MassEmojiThreshold) { string reason = "Mass emoji"; - _ = message.DeleteAsync(); + if (!wasAutoModBlock) + _ = message.DeleteAsync(); if (GetPermLevel(member) == ServerPermLevel.Nothing && !Program.db.HashExists("emojiPardoned", message.Author.Id.ToString())) { @@ -488,9 +518,12 @@ public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessag if (Program.cfgjson.UnrestrictedEmojiChannels.Count > 0) msgOut = await message.Channel.SendMessageAsync($"{Program.cfgjson.Emoji.Information} {message.Author.Mention}, if you want to play around with lots of emoji, please use <#{Program.cfgjson.UnrestrictedEmojiChannels[0]}> to avoid punishment."); else - msgOut = await message.Channel.SendMessageAsync($"{Program.cfgjson.Emoji.Information} {message.Author.Mention} Your message was automatically deleted for mass emoji."); + if (wasAutoModBlock) + msgOut = await message.Channel.SendMessageAsync($"{Program.cfgjson.Emoji.Information} {message.Author.Mention} Your message contained too many emoji."); + else + msgOut = await message.Channel.SendMessageAsync($"{Program.cfgjson.Emoji.Information} {message.Author.Mention} Your message was automatically deleted for mass emoji."); - await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, DiscordHelpers.MessageLink(msgOut)); + await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, DiscordHelpers.MessageLink(msgOut), wasAutoModBlock: wasAutoModBlock); return; } @@ -503,52 +536,56 @@ public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessag DiscordMessage msg = await message.Channel.SendMessageAsync(output); var warning = await WarningHelpers.GiveWarningAsync(message.Author, client.CurrentUser, reason, contextMessage: msg, message.Channel, " automatically "); - await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink); + await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, wasAutoModBlock: wasAutoModBlock); return; } - if (message.Channel.Id == Program.cfgjson.TechSupportChannel && message.Content.Contains($"<@&{Program.cfgjson.CommunityTechSupportRoleID}>")) + if (!limitFilters) { - if (supportRatelimit.ContainsKey(message.Author.Id)) + if (message.Channel.Id == Program.cfgjson.TechSupportChannel && + message.Content.Contains($"<@&{Program.cfgjson.CommunityTechSupportRoleID}>")) { - if (supportRatelimit[message.Author.Id] > DateTime.Now) - return; - else - supportRatelimit.Remove(message.Author.Id); - } + if (supportRatelimit.ContainsKey(message.Author.Id)) + { + if (supportRatelimit[message.Author.Id] > DateTime.Now) + return; + else + supportRatelimit.Remove(message.Author.Id); + } - supportRatelimit.Add(message.Author.Id, DateTime.Now.Add(TimeSpan.FromMinutes(Program.cfgjson.SupportRatelimitMinutes))); + supportRatelimit.Add(message.Author.Id, DateTime.Now.Add(TimeSpan.FromMinutes(Program.cfgjson.SupportRatelimitMinutes))); - var embed = new DiscordEmbedBuilder() - .WithTimestamp(DateTime.Now) - .WithAuthor(DiscordHelpers.UniqueUsername(message.Author), null, $"https://cdn.discordapp.com/avatars/{message.Author.Id}/{message.Author.AvatarHash}.png?size=128"); + var embed = new DiscordEmbedBuilder() + .WithTimestamp(DateTime.Now) + .WithAuthor(DiscordHelpers.UniqueUsername(message.Author), null, $"https://cdn.discordapp.com/avatars/{message.Author.Id}/{message.Author.AvatarHash}.png?size=128"); - var lastMsgs = await message.Channel.GetMessagesBeforeAsync(message.Id, 50).ToListAsync(); - var msgMatch = lastMsgs.FirstOrDefault(m => m.Author.Id == message.Author.Id); + var lastMsgs = await message.Channel.GetMessagesBeforeAsync(message.Id, 50).ToListAsync(); + var msgMatch = lastMsgs.FirstOrDefault(m => m.Author.Id == message.Author.Id); - if (msgMatch is not null) - { - var matchContent = StringHelpers.Truncate(string.IsNullOrWhiteSpace(msgMatch.Content) ? "`[No content]`" : msgMatch.Content, 1020, true); - embed.AddField("Previous message", matchContent); - if (msgMatch.Attachments.Count != 0) + if (msgMatch is not null) { - embed.WithImageUrl(msgMatch.Attachments[0].Url); + var matchContent = StringHelpers.Truncate(string.IsNullOrWhiteSpace(msgMatch.Content) ? "`[No content]`" : msgMatch.Content, 1020, true); + embed.AddField("Previous message", matchContent); + if (msgMatch.Attachments.Count != 0) + { + embed.WithImageUrl(msgMatch.Attachments[0].Url); + } } - } - var messageContent = StringHelpers.Truncate(string.IsNullOrWhiteSpace(message.Content) ? "`[No content]`" : message.Content, 1020, true); - embed.AddField("Current message", messageContent); - if (message.Attachments.Count != 0) - { - if (embed.ImageUrl is null) - embed.WithImageUrl(message.Attachments[0].Url); - else - embed.ImageUrl = message.Attachments[0].Url; - } + var messageContent = StringHelpers.Truncate(string.IsNullOrWhiteSpace(message.Content) ? "`[No content]`" : message.Content, 1020, true); + embed.AddField("Current message", messageContent); + if (message.Attachments.Count != 0) + { + if (embed.ImageUrl is null) + embed.WithImageUrl(message.Attachments[0].Url); + else + embed.ImageUrl = message.Attachments[0].Url; + } - embed.AddField("Message Link", $"https://discord.com/channels/{message.Channel.Guild.Id}/{message.Channel.Id}/{message.Id}"); - var logOut = await LogChannelHelper.LogMessageAsync("support", new DiscordMessageBuilder().AddEmbed(embed)); - _ = logOut.CreateReactionAsync(DiscordEmoji.FromName(client, ":CliptokAcknowledge:", true)); + embed.AddField("Message Link", $"https://discord.com/channels/{message.Channel.Guild.Id}/{message.Channel.Id}/{message.Id}"); + var logOut = await LogChannelHelper.LogMessageAsync("support", new DiscordMessageBuilder().AddEmbed(embed)); + _ = logOut.CreateReactionAsync(DiscordEmoji.FromName(client, ":CliptokAcknowledge:", true)); + } } } @@ -562,7 +599,8 @@ public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessag { if (phishingMatch) { - _ = message.DeleteAsync(); + if (!wasAutoModBlock) + _ = message.DeleteAsync(); string reason = "Sending phishing URL(s)"; DiscordMessage msg = await message.Channel.SendMessageAsync($"{Program.cfgjson.Emoji.Denied} {message.Author.Mention} was automatically warned: **{reason.Replace("`", "\\`").Replace("*", "\\*")}**"); var warning = await WarningHelpers.GiveWarningAsync(message.Author, client.CurrentUser, reason, contextMessage: msg, message.Channel, " automatically "); @@ -570,7 +608,7 @@ public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessag 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); + await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, extraField, wasAutoModBlock: wasAutoModBlock); return; } } @@ -580,21 +618,23 @@ public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessag if (Program.cfgjson.EveryoneFilter && !member.Roles.Any(role => Program.cfgjson.EveryoneExcludedRoles.Contains(role.Id)) && !Program.cfgjson.EveryoneExcludedChannels.Contains(message.Channel.Id) && (message.Content.Contains("@everyone") || message.Content.Contains("@here"))) { string reason = "Attempted to ping everyone/here"; - _ = message.DeleteAsync(); + if (!wasAutoModBlock) + _ = message.DeleteAsync(); DiscordMessage msg = await message.Channel.SendMessageAsync($"{Program.cfgjson.Emoji.Denied} {message.Author.Mention} was automatically warned: **{reason.Replace("`", "\\`").Replace("*", "\\*")}**"); var warning = await WarningHelpers.GiveWarningAsync(message.Author, client.CurrentUser, reason, contextMessage: msg, message.Channel, " automatically "); - await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink); + await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, wasAutoModBlock: wasAutoModBlock); return; } // Mass mentions - if (message.MentionedUsers.Count >= Program.cfgjson.MassMentionThreshold && GetPermLevel(member) < ServerPermLevel.Tier3) + if (message.MentionedUsers is not null && message.MentionedUsers.Count >= Program.cfgjson.MassMentionThreshold && GetPermLevel(member) < ServerPermLevel.Tier3) { string reason = "Mass mentions"; try { - _ = message.DeleteAsync(); - _ = InvestigationsHelpers.SendInfringingMessaageAsync("mod", message, reason, null); + if (!wasAutoModBlock) + _ = message.DeleteAsync(); + _ = InvestigationsHelpers.SendInfringingMessaageAsync("mod", message, reason, null, wasAutoModBlock: wasAutoModBlock); } catch { @@ -603,7 +643,7 @@ public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessag DiscordMessage msg = await message.Channel.SendMessageAsync($"{Program.cfgjson.Emoji.Denied} {message.Author.Mention} was automatically warned: **{reason.Replace("`", "\\`").Replace("*", "\\*")}**"); var warning = await WarningHelpers.GiveWarningAsync(message.Author, client.CurrentUser, reason, contextMessage: msg, message.Channel, " automatically "); - await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink); + await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, wasAutoModBlock: wasAutoModBlock); return; } @@ -616,20 +656,33 @@ public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessag || (lineCount >= Program.cfgjson.LineLimit && GetPermLevel(member) < (ServerPermLevel)Program.cfgjson.LineLimitTier))) { string reason = "Too many lines in a single message"; - _ = message.DeleteAsync(); - - var button = new DiscordButtonComponent(DiscordButtonStyle.Secondary, "line-limit-deleted-message-callback", "View message content", false, null); + if (!wasAutoModBlock) + _ = message.DeleteAsync(); if (!Program.db.HashExists("linePardoned", message.Author.Id.ToString())) { await Program.db.HashSetAsync("linePardoned", member.Id.ToString(), false); - string output = $"{Program.cfgjson.Emoji.Information} {message.Author.Mention}, your message was deleted for containing too many lines.\n" + - $"Please consider using a Pastebin-style website or <#{Program.cfgjson.UnrestrictedEmojiChannels[0]}> to avoid further punishment."; + string output; + if (wasAutoModBlock) + output = $"{Program.cfgjson.Emoji.Information} {message.Author.Mention}, your message contained too many lines.\n" + + $"Please consider using a Pastebin-style website or <#{Program.cfgjson.UnrestrictedEmojiChannels[0]}> to avoid further punishment."; + else + output = $"{Program.cfgjson.Emoji.Information} {message.Author.Mention}, your message was deleted for containing too many lines.\n" + + $"Please consider using a Pastebin-style website or <#{Program.cfgjson.UnrestrictedEmojiChannels[0]}> to avoid further punishment."; DiscordMessageBuilder messageBuilder = new(); - messageBuilder.WithContent(output).AddComponents(button); - DiscordMessage msg = await message.Channel.SendMessageAsync(messageBuilder); - await Program.db.HashSetAsync("deletedMessageReferences", msg.Id, message.Content); - await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, DiscordHelpers.MessageLink(msg)); + messageBuilder.WithContent(output); + DiscordMessage msg; + if (!wasAutoModBlock) + { + messageBuilder.AddComponents(new DiscordButtonComponent(DiscordButtonStyle.Secondary, "line-limit-deleted-message-callback", "View message content", false, null)); + msg = await message.Channel.SendMessageAsync(messageBuilder); + await Program.db.HashSetAsync("deletedMessageReferences", msg.Id, message.Content); + } + else + { + msg = await message.Channel.SendMessageAsync(messageBuilder); + } + await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, DiscordHelpers.MessageLink(msg), wasAutoModBlock: wasAutoModBlock); return; } else @@ -637,12 +690,21 @@ public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessag string output = $"{Program.cfgjson.Emoji.Denied} {message.Author.Mention} was automatically warned: **{reason.Replace("`", "\\`").Replace("*", "\\*")}**\n" + $"Please consider using a Pastebin-style website or <#{Program.cfgjson.UnrestrictedEmojiChannels[0]}> to avoid punishment."; DiscordMessageBuilder messageBuilder = new(); - messageBuilder.WithContent(output).AddComponents(button); + messageBuilder.WithContent(output); + DiscordMessage msg; + if (!wasAutoModBlock) + { + messageBuilder.AddComponents(new DiscordButtonComponent(DiscordButtonStyle.Secondary, "line-limit-deleted-message-callback", "View message content", false, null)); + msg = await message.Channel.SendMessageAsync(messageBuilder); + await Program.db.HashSetAsync("deletedMessageReferences", msg.Id, message.Content); + } + else + { + msg = await message.Channel.SendMessageAsync(messageBuilder); + } - DiscordMessage msg = await message.Channel.SendMessageAsync(messageBuilder); var warning = await WarningHelpers.GiveWarningAsync(message.Author, client.CurrentUser, reason, contextMessage: msg, message.Channel, " automatically "); - await Program.db.HashSetAsync("deletedMessageReferences", msg.Id, message.Content); - await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink); + await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, wasAutoModBlock: wasAutoModBlock); return; } @@ -650,115 +712,119 @@ public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessag } } - // feedback hub forum - if (GetPermLevel(member) < ServerPermLevel.TrialModerator && !isAnEdit && message.Channel.IsThread && message.Channel.ParentId == Program.cfgjson.FeedbackHubForum && !Program.db.SetContains("processedFeedbackHubThreads", message.Channel.Id)) + if (!limitFilters) { - var thread = (DiscordThreadChannel)message.Channel; - Program.db.SetAdd("processedFeedbackHubThreads", thread.Id); - - // we need to make sure this is the first message in the channel - if ((await thread.GetMessagesBeforeAsync(message.Id).ToListAsync()).Count == 0) + // feedback hub forum + if (GetPermLevel(member) < ServerPermLevel.TrialModerator && !isAnEdit && message.Channel.IsThread && message.Channel.ParentId == Program.cfgjson.FeedbackHubForum && !Program.db.SetContains("processedFeedbackHubThreads", message.Channel.Id)) { - // lock thread if there is no possible feedback hub link - if (!message.Content.Contains("aka.ms/") && !message.Content.Contains("feedback-hub:")) + var thread = (DiscordThreadChannel)message.Channel; + Program.db.SetAdd("processedFeedbackHubThreads", thread.Id); + + // we need to make sure this is the first message in the channel + if ((await thread.GetMessagesBeforeAsync(message.Id).ToListAsync()).Count == 0) { - await message.RespondAsync($"{Program.cfgjson.Emoji.Error} Your {message.Channel.Parent.Mention} submission must include a Feedback Hub link!\nThis post will be automatically deleted shortly."); - await thread.ModifyAsync(thread => + // lock thread if there is no possible feedback hub link + if (!message.Content.Contains("aka.ms/") && !message.Content.Contains("feedback-hub:")) { - thread.IsArchived = true; - thread.Locked = true; - }); - await Task.Delay(30000); - await LogChannelHelper.LogMessageAsync("messages", - new DiscordMessageBuilder() - .WithContent($"{Program.cfgjson.Emoji.Deleted} Deleted non-feedback post from {message.Author.Mention} in {message.Channel.Parent.Mention}:") - .AddEmbed(new DiscordEmbedBuilder() - .WithAuthor( - $"{DiscordHelpers.UniqueUsername(message.Author)} in #{message.Channel.Parent.Name}", - null, await LykosAvatarMethods.UserOrMemberAvatarURL(message.Author, message.Channel.Guild)) - .WithTitle(thread.Name) - .WithDescription(message.Content) - .WithColor(DiscordColor.Red) - ) + await message.BaseMessage.RespondAsync($"{Program.cfgjson.Emoji.Error} Your {message.Channel.Parent.Mention} submission must include a Feedback Hub link!\nThis post will be automatically deleted shortly."); + await thread.ModifyAsync(thread => + { + thread.IsArchived = true; + thread.Locked = true; + }); + await Task.Delay(30000); + await LogChannelHelper.LogMessageAsync("messages", + new DiscordMessageBuilder() + .WithContent($"{Program.cfgjson.Emoji.Deleted} Deleted non-feedback post from {message.Author.Mention} in {message.Channel.Parent.Mention}:") + .AddEmbed(new DiscordEmbedBuilder() + .WithAuthor( + $"{DiscordHelpers.UniqueUsername(message.Author)} in #{message.Channel.Parent.Name}", + null, await LykosAvatarMethods.UserOrMemberAvatarURL(message.Author, message.Channel.Guild)) + .WithTitle(thread.Name) + .WithDescription(message.Content) + .WithColor(DiscordColor.Red) + ) ); - await thread.DeleteAsync(); - return; - } - else - { - await Task.Delay(2000); - await message.ModifyEmbedSuppressionAsync(true); + await thread.DeleteAsync(); + return; + } + else + { + await Task.Delay(2000); + await message.BaseMessage.ModifyEmbedSuppressionAsync(true); + } } } - } - // feedback hub text channel - if (!isAnEdit && message.Channel.Id == Program.cfgjson.FeedbackHubChannelId) - { - var captures = bold_rx.Match(message.Content).Groups[1].Captures; - - if (captures is null || captures.Count == 0 || (!message.Content.Contains("aka.ms/") && !message.Content.Contains("feedback-hub:"))) + // feedback hub text channel + if (!isAnEdit && message.Channel.Id == Program.cfgjson.FeedbackHubChannelId) { - if (GetPermLevel(member) >= ServerPermLevel.TrialModerator) + var captures = bold_rx.Match(message.Content).Groups[1].Captures; + + if (captures is null || captures.Count == 0 || (!message.Content.Contains("aka.ms/") && !message.Content.Contains("feedback-hub:"))) { - return; - } + if (GetPermLevel(member) >= ServerPermLevel.TrialModerator) + { + return; + } - await message.DeleteAsync(); - var msg = await message.Channel.SendMessageAsync($"{Program.cfgjson.Emoji.Error} {message.Author.Mention}, please read the pinned messages in this channel and follow the message format given."); - await Task.Delay(5000); - await msg.DeleteAsync(); - } - else - { - var title = captures[0].Value; + await message.DeleteAsync(); + var msg = await message.Channel.SendMessageAsync($"{Program.cfgjson.Emoji.Error} {message.Author.Mention}, please read the pinned messages in this channel and follow the message format given."); + await Task.Delay(5000); + await msg.DeleteAsync(); + } + else + { + var title = captures[0].Value; - if (title.Length > 100) - title = StringHelpers.Truncate(title, 100, false); + if (title.Length > 100) + title = StringHelpers.Truncate(title, 100, false); - await message.CreateThreadAsync(title, DiscordAutoArchiveDuration.Week, "Automatically creating feedback hub thread."); + await message.BaseMessage.CreateThreadAsync(title, DiscordAutoArchiveDuration.Week,"Automatically creating feedback hub thread."); - await Task.Delay(2000); - await message.ModifyEmbedSuppressionAsync(true); + await Task.Delay(2000); + await message.BaseMessage.ModifyEmbedSuppressionAsync(true); + } } - } - // Check the passive lists AFTER all other checks. - if (GetPermLevel(member) >= ServerPermLevel.TrialModerator) - return; + // Check the passive lists AFTER all other checks. + if (GetPermLevel(member) >= ServerPermLevel.TrialModerator) + return; - foreach (var listItem in Program.cfgjson.WordListList) - { - if (!listItem.Passive) - { - continue; - } - else + foreach (var listItem in Program.cfgjson.WordListList) { - (bool success, string flaggedWord) = Checks.ListChecks.CheckForNaughtyWords(message.Content.ToLower(), listItem); - if (success) + if (!listItem.Passive) + { + continue; + } + else { - DiscordChannel logChannel = default; - if (listItem.ChannelId is not null) + (bool success, string flaggedWord) = Checks.ListChecks.CheckForNaughtyWords(message.Content.ToLower(), listItem); + if (success) { - logChannel = await Program.discord.GetChannelAsync((ulong)listItem.ChannelId); - } + DiscordChannel logChannel = default; + if (listItem.ChannelId is not null) + { + logChannel = await Program.discord.GetChannelAsync((ulong)listItem.ChannelId); + } - string content = $"{Program.cfgjson.Emoji.Warning} Detected potentially suspicious message by {message.Author.Mention} in {message.Channel.Mention}:"; + string content = $"{Program.cfgjson.Emoji.Warning} Detected potentially suspicious message by {message.Author.Mention} in {message.Channel.Mention}:"; - (string name, string value, bool inline) extraField = new("Match", flaggedWord, true); + (string name, string value, bool inline) extraField = new("Match", flaggedWord, true); - await InvestigationsHelpers.SendInfringingMessaageAsync( - "investigations", - message, - listItem.Reason, - DiscordHelpers.MessageLink(message), - content: content, - colour: new DiscordColor(0xFEC13D), - channelOverride: logChannel, - extraField: extraField - ); + await InvestigationsHelpers.SendInfringingMessaageAsync( + "investigations", + message, + listItem.Reason, + DiscordHelpers.MessageLink(message), + content: content, + colour: new DiscordColor(0xFEC13D), + channelOverride: logChannel, + extraField: extraField, + wasAutoModBlock: wasAutoModBlock + ); + } } } } @@ -812,7 +878,7 @@ public static int CountNewlines(string input) return count; } - public static async Task InviteCheck(DiscordInvite? invite, DiscordMessage message, DiscordClient client) + public static async Task InviteCheck(DiscordInvite? invite, MockDiscordMessage message, DiscordClient client, bool wasAutoModBlock = false) { if (invite is null || invite.Guild is null) return false; @@ -826,7 +892,8 @@ public static async Task InviteCheck(DiscordInvite? invite, DiscordMessage } else if (serverMatch) { - _ = message.DeleteAsync(); + if (!wasAutoModBlock) + _ = message.DeleteAsync(); string reason = "Sent a malicious Discord invite"; DiscordMessage msg = await message.Channel.SendMessageAsync($"{Program.cfgjson.Emoji.Denied} {message.Author.Mention} was automatically warned: **{reason.Replace("`", "\\`").Replace("*", "\\*")}**"); @@ -835,7 +902,7 @@ public static async Task InviteCheck(DiscordInvite? invite, DiscordMessage string responseToSend = $"```json\n{responseString}\n```"; (string name, string value, bool inline) extraField = new("API Response", responseToSend, false); - await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, extraField); + await InvestigationsHelpers.SendInfringingMessaageAsync("investigations", message, reason, warning.ContextLink, extraField, wasAutoModBlock: wasAutoModBlock); var newEntry = JsonConvert.DeserializeObject(responseString); newEntry.Invite = invite.Code; diff --git a/Events/MockDiscordMessage.cs b/Events/MockDiscordMessage.cs new file mode 100644 index 00000000..7b1137ba --- /dev/null +++ b/Events/MockDiscordMessage.cs @@ -0,0 +1,76 @@ +using System.Data.Common; +using System.Runtime.CompilerServices; + +namespace Cliptok.Events +{ + public class MockDiscordMessage + { + public MockDiscordMessage() { } + + public MockDiscordMessage(DiscordMessage baseMessage) + { + Attachments = baseMessage.Attachments; + Author = baseMessage.Author; + BaseMessage = baseMessage; + Channel = baseMessage.Channel; + ChannelId = baseMessage.ChannelId; + Content = baseMessage.Content; + Embeds = baseMessage.Embeds; + Id = baseMessage.Id; + JumpLink = baseMessage.JumpLink; + MentionedUsers = baseMessage.MentionedUsers; + 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, IReadOnlyList reactions = default, DiscordMessage referencedMessage = default, IReadOnlyList stickers = default, DateTimeOffset? timestamp = default) + { + Attachments = attachments; + Author = author; + Channel = channel; + ChannelId = channelId; + Content = content; + Embeds = embeds; + Id = id; + JumpLink = jumpLink; + MentionedUsers = mentionedUsers; + Reactions = reactions; + ReferencedMessage = referencedMessage; + Stickers = stickers; + Timestamp = timestamp; + } + + public IReadOnlyList Attachments { get; } + public DiscordUser Author { get; } + public DiscordMessage BaseMessage { get; } + public DiscordChannel Channel { get; } + public ulong ChannelId { get; } + public string Content { get; } + public IReadOnlyList Embeds { get; } + public ulong Id { get; } + public Uri JumpLink { get; set; } + public IReadOnlyList MentionedUsers { get; } + public IReadOnlyList Reactions { get; set; } + public DiscordMessage ReferencedMessage { get; set; } + public IReadOnlyList Stickers { get; set; } + public DateTimeOffset? Timestamp { get; } + + public async Task DeleteAsync() + { + // If we have a DiscordMessage to work with, call its delete method. + // Otherwise, fetch the message ourselves and delete it. + + if (BaseMessage is not null) + { + await BaseMessage.DeleteAsync(); + } + else + { + var baseMessage = await Channel.GetMessageAsync(Id); + await baseMessage.DeleteAsync(); + } + } + } +} \ No newline at end of file diff --git a/Helpers/DiscordHelpers.cs b/Helpers/DiscordHelpers.cs index a52ff5b5..77134564 100644 --- a/Helpers/DiscordHelpers.cs +++ b/Helpers/DiscordHelpers.cs @@ -25,6 +25,10 @@ public static async Task SafeTyping(DiscordChannel channel) } public static string MessageLink(DiscordMessage msg) + { + return MessageLink(new MockDiscordMessage(msg)); + } + public static string MessageLink(MockDiscordMessage msg) { return $"https://discord.com/channels/{(msg.Channel.IsPrivate ? "@me" : msg.Channel.Guild.Id)}/{msg.Channel.Id}/{msg.Id}"; } diff --git a/Helpers/InvestigationsHelpers.cs b/Helpers/InvestigationsHelpers.cs index ea8233b1..f6d8a1e8 100644 --- a/Helpers/InvestigationsHelpers.cs +++ b/Helpers/InvestigationsHelpers.cs @@ -3,6 +3,10 @@ public class InvestigationsHelpers { public static async Task SendInfringingMessaageAsync(string logChannelKey, DiscordMessage infringingMessage, string reason, string messageURL, (string name, string value, bool inline) extraField = default, string content = default, DiscordColor? colour = null, DiscordChannel channelOverride = default) + { + 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) { if (colour is null) colour = new DiscordColor(0xf03916); @@ -31,7 +35,10 @@ await LykosAvatarMethods.UserOrMemberAvatarURL(infringingMessage.Author, infring embed.AddField(extraField.name, extraField.value, extraField.inline); if (content == default) - content = $"{Program.cfgjson.Emoji.Denied} Deleted infringing message by {infringingMessage.Author.Mention} in {infringingMessage.Channel.Mention}:"; + if (wasAutoModBlock) + content = $"{Program.cfgjson.Emoji.Denied} Detected infringing AutoMod message by {infringingMessage.Author.Mention} in {infringingMessage.Channel.Mention}:"; + else + content = $"{Program.cfgjson.Emoji.Denied} Deleted infringing message by {infringingMessage.Author.Mention} in {infringingMessage.Channel.Mention}:"; if (channelOverride == default) await LogChannelHelper.LogMessageAsync(logChannelKey, content, embed); diff --git a/Program.cs b/Program.cs index b487851c..de35a2ee 100644 --- a/Program.cs +++ b/Program.cs @@ -184,6 +184,7 @@ static async Task Main(string[] _) .HandleVoiceStateUpdated(VoiceEvents.VoiceStateUpdate) .HandleChannelUpdated(ChannelEvents.ChannelUpdated) .HandleChannelDeleted(ChannelEvents.ChannelDeleted) + .HandleAutoModerationRuleExecuted(AutoModEvents.AutoModerationRuleExecuted) ); #pragma warning disable CS0618 // Type or member is obsolete