diff --git a/README.md b/README.md index 2e83dfa..1f42f3d 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ and deliver them to a configured channel without the need to manually navigate t With support for detecting more than 72 events, it can log changes made to: AutoMod Settings, Servers, Onboarding, Invites, Members, Roles, Channels, Threads, Stages, Events, Polls, Messages, Boosts, Emojis, Stickers, Soundboard, -Integrations, Webhooks, Moderation Action and Unknown events. +Integrations, Webhooks, Moderation Action, Unusual DMs, Raids and Unknown events. > Get it from here: https://discord.com/discovery/applications/1381658412550590475 diff --git a/pom.xml b/pom.xml index 66576fb..dbfed1e 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 io.github.eggy03 papertrailbot - 4.0.0 + 4.1.0 PaperTrail Bot A simple bot for logging all server and member actions quarkus diff --git a/src/main/java/io/github/eggy03/papertrail/bot/constant/ProjectInfo.java b/src/main/java/io/github/eggy03/papertrail/bot/constant/ProjectInfo.java index c071194..73d4b8c 100644 --- a/src/main/java/io/github/eggy03/papertrail/bot/constant/ProjectInfo.java +++ b/src/main/java/io/github/eggy03/papertrail/bot/constant/ProjectInfo.java @@ -10,7 +10,7 @@ public final class ProjectInfo { public static final String APPNAME = "PaperTrail"; @NonNull - public static final String VERSION = "v4.0.0"; + public static final String VERSION = "v4.1.0"; @NonNull public static final String PROJECT_ISSUE_LINK = "https://github.com/eggy03/PaperTrailBot/issues"; diff --git a/src/main/java/io/github/eggy03/papertrail/bot/handlers/guild/GuildSecurityIncidentEventHandler.java b/src/main/java/io/github/eggy03/papertrail/bot/handlers/guild/GuildSecurityIncidentEventHandler.java new file mode 100644 index 0000000..160141e --- /dev/null +++ b/src/main/java/io/github/eggy03/papertrail/bot/handlers/guild/GuildSecurityIncidentEventHandler.java @@ -0,0 +1,182 @@ +package io.github.eggy03.papertrail.bot.handlers.guild; + +import io.github.eggy03.papertrail.sdk.client.AuditLogRegistrationClient; +import io.github.eggy03.papertrail.sdk.entity.AuditLogRegistrationEntity; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.entities.guild.SecurityIncidentActions; +import net.dv8tion.jda.api.entities.guild.SecurityIncidentDetections; +import net.dv8tion.jda.api.events.guild.update.GenericGuildUpdateEvent; +import net.dv8tion.jda.api.events.guild.update.GuildUpdateSecurityIncidentActionsEvent; +import net.dv8tion.jda.api.events.guild.update.GuildUpdateSecurityIncidentDetectionsEvent; +import net.dv8tion.jda.api.utils.MarkdownUtil; +import org.apache.commons.lang3.StringUtils; +import org.jspecify.annotations.Nullable; + +import java.awt.Color; +import java.time.Instant; +import java.time.OffsetDateTime; + +@ApplicationScoped +@Slf4j +public final class GuildSecurityIncidentEventHandler { + + private final @NonNull AuditLogRegistrationClient client; + + @Inject + public GuildSecurityIncidentEventHandler(@NonNull AuditLogRegistrationClient client) { + this.client = client; + } + + @NonNull + private String getRegisteredGuildChannel(@NonNull String guildId) { + return client.getRegisteredGuild(guildId) + .map(AuditLogRegistrationEntity::getChannelId).orElse(StringUtils.EMPTY); + + } + + private void performChecksThenBuildAndSendEmbed(@NonNull GenericGuildUpdateEvent event, @NonNull EmbedBuilder embedBuilder, @NonNull String channelIdToSendTo) { + if (!embedBuilder.isValidLength() || embedBuilder.isEmpty()) { + log.warn("Embed is empty or too long (current length: {}).", embedBuilder.length()); + return; + } + + TextChannel sendingChannel = event.getGuild().getTextChannelById(channelIdToSendTo); + if (sendingChannel != null && sendingChannel.canTalk()) { + sendingChannel.sendMessageEmbeds(embedBuilder.build()).queue(); + } + } + + private @NonNull String formatOffsetDateTime(@Nullable OffsetDateTime offsetDateTime) { + if (offsetDateTime == null) return "N/A"; + return ""; + } + + public void handleGuildUpdateSecurityIncidentDetections(@NonNull GuildUpdateSecurityIncidentDetectionsEvent event) { + String channelIdToSendTo = getRegisteredGuildChannel(event.getGuild().getId()); + if (channelIdToSendTo.isBlank()) return; + + SecurityIncidentDetections oldSID = event.getOldSecurityIncidentDetections(); + SecurityIncidentDetections newSID = event.getNewSecurityIncidentDetections(); + + OffsetDateTime oldDMSpamDetectedAt = oldSID.getTimeDetectedDmSpam(); + OffsetDateTime newDMSpamDetectedAt = newSID.getTimeDetectedDmSpam(); + + OffsetDateTime oldRaidDetectedAt = oldSID.getTimeDetectedRaid(); + OffsetDateTime newRaidDetectedAt = newSID.getTimeDetectedRaid(); + + if (oldDMSpamDetectedAt != null) { + EmbedBuilder eb = new EmbedBuilder(); + eb.setTitle("Audit Log Entry | Security Incident Resolved"); + eb.setDescription(MarkdownUtil.quoteBlock("Incident Type: DM Spam\nStatus: Resolved")); + eb.setColor(Color.GREEN); + eb.addField(MarkdownUtil.underline("DM Spam Started At"), formatOffsetDateTime(oldDMSpamDetectedAt), false); + eb.setFooter(event.getGuild().getName()); + eb.setTimestamp(Instant.now()); + + performChecksThenBuildAndSendEmbed(event, eb, channelIdToSendTo); + } + + if (newDMSpamDetectedAt != null) { + EmbedBuilder eb = new EmbedBuilder(); + eb.setTitle("Audit Log Entry | Security Incident Detected"); + eb.setDescription(MarkdownUtil.quoteBlock("Incident Type: DM Spam\nStatus: Detected")); + eb.setColor(Color.ORANGE); + eb.addField(MarkdownUtil.underline("DM Spam Detected At"), formatOffsetDateTime(newDMSpamDetectedAt), false); + eb.setFooter(event.getGuild().getName()); + eb.setTimestamp(Instant.now()); + + performChecksThenBuildAndSendEmbed(event, eb, channelIdToSendTo); + } + + if (oldRaidDetectedAt != null) { + EmbedBuilder eb = new EmbedBuilder(); + eb.setTitle("Audit Log Entry | Security Incident Resolved"); + eb.setDescription(MarkdownUtil.quoteBlock("Incident Type: RAID\nStatus: Resolved")); + eb.setColor(Color.GREEN); + eb.addField(MarkdownUtil.underline("Raid Was Detected At"), formatOffsetDateTime(oldRaidDetectedAt), false); + eb.setFooter(event.getGuild().getName()); + eb.setTimestamp(Instant.now()); + + performChecksThenBuildAndSendEmbed(event, eb, channelIdToSendTo); + } + + if (newRaidDetectedAt != null) { + EmbedBuilder eb = new EmbedBuilder(); + eb.setTitle("Audit Log Entry | Security Incident Detected"); + eb.setDescription(MarkdownUtil.quoteBlock("Incident Type: RAID\nStatus: Detected")); + eb.setColor(Color.ORANGE); + eb.addField(MarkdownUtil.underline("Raid Detected At"), formatOffsetDateTime(newRaidDetectedAt), false); + eb.setFooter(event.getGuild().getName()); + eb.setTimestamp(Instant.now()); + + performChecksThenBuildAndSendEmbed(event, eb, channelIdToSendTo); + } + } + + public void handleGuildUpdateSecurityIncidentActions(@NonNull GuildUpdateSecurityIncidentActionsEvent event) { + String channelIdToSendTo = getRegisteredGuildChannel(event.getGuild().getId()); + if (channelIdToSendTo.isBlank()) return; + + SecurityIncidentActions oldSIA = event.getOldSecurityIncidentActions(); + SecurityIncidentActions newSIA = event.getNewSecurityIncidentActions(); + + OffsetDateTime oldDMDisabledUntil = oldSIA.getDirectMessagesDisabledUntil(); + OffsetDateTime newDMDisabledUntil = newSIA.getDirectMessagesDisabledUntil(); + + OffsetDateTime oldInvitesPausedUntil = oldSIA.getInvitesDisabledUntil(); + OffsetDateTime newInvitesPausedUntil = newSIA.getInvitesDisabledUntil(); + + if (oldDMDisabledUntil != null) { + EmbedBuilder eb = new EmbedBuilder(); + eb.setTitle("Audit Log Entry | Security Action Disabled"); + eb.setDescription(MarkdownUtil.quoteBlock("Action Type: DM Pause\nStatus: Disabled")); + eb.setColor(Color.GREEN); + eb.addField(MarkdownUtil.underline("DMs Were Paused Until"), formatOffsetDateTime(oldDMDisabledUntil), false); + eb.setFooter(event.getGuild().getName()); + eb.setTimestamp(Instant.now()); + + performChecksThenBuildAndSendEmbed(event, eb, channelIdToSendTo); + } + + if (newDMDisabledUntil != null) { + EmbedBuilder eb = new EmbedBuilder(); + eb.setTitle("Audit Log Entry | Security Action Enabled"); + eb.setDescription(MarkdownUtil.quoteBlock("Action Type: DM Pause\nStatus: Enabled")); + eb.setColor(Color.ORANGE); + eb.addField(MarkdownUtil.underline("DMs Paused Until"), formatOffsetDateTime(newDMDisabledUntil), false); + eb.setFooter(event.getGuild().getName()); + eb.setTimestamp(Instant.now()); + + performChecksThenBuildAndSendEmbed(event, eb, channelIdToSendTo); + } + + if (oldInvitesPausedUntil != null) { + EmbedBuilder eb = new EmbedBuilder(); + eb.setTitle("Audit Log Entry | Security Action Disabled"); + eb.setDescription(MarkdownUtil.quoteBlock("Action Type: Invite Pause\nStatus: Disabled")); + eb.setColor(Color.GREEN); + eb.addField(MarkdownUtil.underline("Invites Were Paused Until"), formatOffsetDateTime(oldInvitesPausedUntil), false); + eb.setFooter(event.getGuild().getName()); + eb.setTimestamp(Instant.now()); + + performChecksThenBuildAndSendEmbed(event, eb, channelIdToSendTo); + } + + if (newInvitesPausedUntil != null) { + EmbedBuilder eb = new EmbedBuilder(); + eb.setTitle("Audit Log Entry | Security Action Enabled"); + eb.setDescription(MarkdownUtil.quoteBlock("Action Type: Invite Pause\nStatus: Enabled")); + eb.setColor(Color.ORANGE); + eb.addField(MarkdownUtil.underline("Invites Paused Until"), formatOffsetDateTime(newInvitesPausedUntil), false); + eb.setFooter(event.getGuild().getName()); + eb.setTimestamp(Instant.now()); + + performChecksThenBuildAndSendEmbed(event, eb, channelIdToSendTo); + } + } +} diff --git a/src/main/java/io/github/eggy03/papertrail/bot/listeners/guild/GuildSecurityIncidentEventListener.java b/src/main/java/io/github/eggy03/papertrail/bot/listeners/guild/GuildSecurityIncidentEventListener.java new file mode 100644 index 0000000..c576452 --- /dev/null +++ b/src/main/java/io/github/eggy03/papertrail/bot/listeners/guild/GuildSecurityIncidentEventListener.java @@ -0,0 +1,31 @@ +package io.github.eggy03.papertrail.bot.listeners.guild; + +import io.github.eggy03.papertrail.bot.handlers.guild.GuildSecurityIncidentEventHandler; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import lombok.NonNull; +import net.dv8tion.jda.api.events.guild.update.GuildUpdateSecurityIncidentActionsEvent; +import net.dv8tion.jda.api.events.guild.update.GuildUpdateSecurityIncidentDetectionsEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; + +@ApplicationScoped +public final class GuildSecurityIncidentEventListener extends ListenerAdapter { + + private final @NonNull GuildSecurityIncidentEventHandler handler; + + @Inject + public GuildSecurityIncidentEventListener(@NonNull GuildSecurityIncidentEventHandler handler) { + this.handler = handler; + } + + @Override + public void onGuildUpdateSecurityIncidentDetections(@NonNull GuildUpdateSecurityIncidentDetectionsEvent event) { + handler.handleGuildUpdateSecurityIncidentDetections(event); + } + + @Override + public void onGuildUpdateSecurityIncidentActions(@NonNull GuildUpdateSecurityIncidentActionsEvent event) { + handler.handleGuildUpdateSecurityIncidentActions(event); + } + +}