Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,39 +21,43 @@
package com.mcmoddev.mmdbot.thelistener.events;

import club.minnced.discord.webhook.send.AllowedMentions;
import com.google.errorprone.annotations.CheckReturnValue;
import com.mcmoddev.mmdbot.core.event.moderation.WarningEvent;
import com.mcmoddev.mmdbot.core.util.webhook.WebhookManager;
import com.mcmoddev.mmdbot.thelistener.util.LoggingType;
import com.mcmoddev.mmdbot.thelistener.util.Utils;
import io.github.matyrobbrt.eventdispatcher.SubscribeEvent;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.audit.ActionType;
import net.dv8tion.jda.api.audit.AuditLogChange;
import net.dv8tion.jda.api.audit.AuditLogEntry;
import net.dv8tion.jda.api.audit.AuditLogKey;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.entities.channel.middleman.StandardGuildMessageChannel;
import net.dv8tion.jda.api.events.guild.GuildBanEvent;
import net.dv8tion.jda.api.events.guild.GuildUnbanEvent;
import net.dv8tion.jda.api.events.guild.member.GuildMemberRemoveEvent;
import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateNicknameEvent;
import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateTimeOutEvent;
import net.dv8tion.jda.api.events.guild.GuildAuditLogEntryCreateEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.requests.ErrorResponse;
import net.dv8tion.jda.api.requests.RestAction;
import net.dv8tion.jda.api.utils.MarkdownSanitizer;
import net.dv8tion.jda.api.utils.TimeFormat;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.*;
import java.awt.Color;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Optional;
import java.time.OffsetDateTime;

import static com.mcmoddev.mmdbot.thelistener.TheListener.getInstance;
import static java.util.Objects.requireNonNullElse;

public final class ModerationEvents extends ListenerAdapter {

public static final ModerationEvents INSTANCE = new ModerationEvents();
private static final Logger LOGGER = LoggerFactory.getLogger(ModerationEvents.class);

private static final String WEBHOOK_NAME = "ModerationLogs";
private static final WebhookManager WEBHOOKS = WebhookManager.of(e
Expand All @@ -66,135 +70,163 @@ private ModerationEvents() {
}

@Override
public void onGuildBan(@NotNull final GuildBanEvent event) {
Utils.getAuditLog(event.getGuild(), event.getUser().getIdLong(), log -> log
.limit(5)
.type(ActionType.BAN), log -> {
final var embed = new EmbedBuilder();
final var bannedUser = event.getUser();
final var bannedBy = Optional.ofNullable(log.getUser());
embed.setColor(Color.RED);
embed.setTitle("User Banned.");
embed.addField("**User:**", bannedUser.getAsTag(), true);
if (log.getReason() != null) {
embed.addField("**Ban reason:**", log.getReason(), false);
} else {
embed.addField("**Ban reason:**", "Reason for ban was not provided or could not be found "
+ "please contact a member of staff for more information about this ban ban.", false);
public void onGuildAuditLogEntryCreate(@NotNull final GuildAuditLogEntryCreateEvent event) {
final AuditLogEntry entry = event.getEntry();
final ActionType type = entry.getType();

switch (type) {
case BAN -> retrieveUsers(entry, this::onBan).queue();
case UNBAN -> retrieveUsers(entry, this::onUnban).queue();
case MEMBER_UPDATE -> {
final @Nullable AuditLogChange nicknameChange = entry.getChangeByKey(AuditLogKey.MEMBER_NICK);
if (nicknameChange != null) retrieveUsers(entry,
(target, actor, auditEntry) -> this.onNicknameUpdate(target, actor, auditEntry, nicknameChange)).queue();

final @Nullable AuditLogChange timeoutChange = entry.getChangeByKey(AuditLogKey.MEMBER_TIME_OUT);
if (timeoutChange != null) retrieveUsers(entry,
(target, actor, auditEntry) -> this.onTimeoutUpdate(target, actor, auditEntry, timeoutChange)).queue();
}
embed.setFooter("User ID: " + bannedUser.getId(), bannedUser.getEffectiveAvatarUrl());
embed.setTimestamp(Instant.now());
log(event.getGuild().getIdLong(), event.getJDA(), embed.build(), bannedBy);
});
case KICK -> retrieveUsers(entry, this::onKick).queue();
}
}

@Override
public void onGuildUnban(@NotNull final GuildUnbanEvent event) {
Utils.getAuditLog(event.getGuild(), event.getUser().getIdLong(), log -> log
.limit(5)
.type(ActionType.UNBAN), log -> {
final var embed = new EmbedBuilder();
final var unBannedUser = event.getUser();
final var bannedBy = Optional.ofNullable(log.getUser());
embed.setColor(Color.GREEN);
embed.setTitle("User Un-banned.");
embed.addField("**User:**", unBannedUser.getAsTag(), true);
embed.setFooter("User ID: " + unBannedUser.getId(), unBannedUser.getEffectiveAvatarUrl());
embed.setTimestamp(Instant.now());
log(event.getGuild().getIdLong(), event.getJDA(), embed.build(), bannedBy);
@FunctionalInterface
private interface AuditLogEntryHandler {
void handle(final User target, final User actor, final AuditLogEntry entry);
}

@CheckReturnValue
private RestAction<Void> retrieveUsers(final AuditLogEntry entry, final AuditLogEntryHandler handler) {
final String targetId = entry.getTargetId();
final String actorId = entry.getUserId();

final RestAction<@Nullable User> targetRestAction = entry.getJDA().retrieveUserById(targetId)
.onErrorMap(ErrorResponse.UNKNOWN_USER::test, e -> {
LOGGER.error("Could not retrieve target user for ID {}", targetId);
return null;
});
final RestAction<@Nullable User> actorRestAction = entry.getJDA().retrieveUserById(actorId)
.onErrorMap(ErrorResponse.UNKNOWN_USER::test, e -> {
LOGGER.error("Could not retrieve actor user for ID {}", actorId);
return null;
});

return targetRestAction.and(actorRestAction, (targetUser, actorUser) -> {
if (targetUser != null && actorUser != null) {
handler.handle(targetUser, actorUser, entry);
}
return null;
});
}

@Override
public void onGuildMemberUpdateNickname(@NotNull final GuildMemberUpdateNicknameEvent event) {
public void onBan(final User bannedUser, final User bannedBy, final AuditLogEntry entry) {
final var reason = requireNonNullElse(entry.getReason(),
"_Reason for ban was not provided or could not be found; "
+ "please contact a member of staff for more information about this ban._");

final var embed = new EmbedBuilder();
embed.setColor(Color.RED);
embed.setTitle("User Banned.");
embed.addField("**User:**", bannedUser.getAsTag(), true);
embed.addField("**Ban reason:**", reason, false);
embed.setFooter("User ID: " + bannedUser.getId(), bannedUser.getEffectiveAvatarUrl());
embed.setTimestamp(Instant.now());

log(entry.getGuild().getIdLong(), entry.getJDA(), embed.build(), bannedBy);
}

public void onUnban(final User unBannedUser, final User bannedBy, final AuditLogEntry entry) {
final var embed = new EmbedBuilder();
embed.setColor(Color.GREEN);
embed.setTitle("User Un-banned.");
embed.addField("**User:**", unBannedUser.getAsTag(), true);
embed.setFooter("User ID: " + unBannedUser.getId(), unBannedUser.getEffectiveAvatarUrl());
embed.setTimestamp(Instant.now());

log(entry.getGuild().getIdLong(), entry.getJDA(), embed.build(), bannedBy);
}

public void onNicknameUpdate(final User target, final User editor, final AuditLogEntry entry, final AuditLogChange nicknameChange) {
final var embed = new EmbedBuilder();
final var targetUser = event.getUser();
embed.setColor(Color.YELLOW);
embed.setTitle("Nickname Changed");
embed.addField("User:", targetUser.getAsTag(), true);
embed.addField("Old Nickname:", event.getOldNickname() == null
? "*None*" : event.getOldNickname(), true);
embed.addField("New Nickname:", event.getNewNickname() == null
? "*None*" : event.getNewNickname(), true);
embed.setFooter("User ID: " + event.getUser().getId(), event.getUser().getEffectiveAvatarUrl());
embed.addField("User:", target.getAsTag(), true);
embed.addField("Old Nickname:", wrapNicknameValue(nicknameChange.getOldValue()), true);
embed.addField("New Nickname:", wrapNicknameValue(nicknameChange.getNewValue()), true);
embed.setFooter("User ID: " + target.getId(), target.getEffectiveAvatarUrl());
embed.setTimestamp(Instant.now());
logWithWebhook(event.getGuild().getIdLong(), event.getJDA(), embed.build(), event.getUser());
log(entry.getGuild().getIdLong(), entry.getJDA(), embed.build(), editor);
}

@Override
public void onGuildMemberRemove(@NotNull final GuildMemberRemoveEvent event) {
Utils.getAuditLog(event.getGuild(), event.getUser().getIdLong(), log -> log
.type(ActionType.KICK)
.limit(5), log -> {
if (log.getTimeCreated().toInstant().isBefore(Instant.now().minus(2, ChronoUnit.MINUTES))) {
return;
}
private static String wrapNicknameValue(@Nullable String nicknameValue) {
if (nicknameValue == null) return "*None*";
return MarkdownSanitizer.escape(nicknameValue);
}

final var embed = new EmbedBuilder();
final var kicker = Optional.ofNullable(log.getUser());
final var kickedUser = event.getUser();

if (kicker.isPresent() && kicker.get().isBot()) {
var botKickMessage = kickedUser.getAsTag() + " was kicked! Kick Reason: " + log.getReason();
log(event.getGuild().getIdLong(), event.getJDA(), botKickMessage, kicker);
} else {
embed.setColor(RUBY);
embed.setTitle("User Kicked");
embed.addField("**Name:**", kickedUser.getAsTag(), true);
embed.addField("**Kick reason:**", log.getReason() != null ? log.getReason() :
("Reason for kick was not provided or could not be found, please contact "
+ "a member of staff for more information about this kick."), false);
embed.setFooter("User ID: " + kickedUser.getId(), kickedUser.getAvatarUrl());
embed.setTimestamp(Instant.now());

log(event.getGuild().getIdLong(), event.getJDA(), embed.build(), kicker);
}
});
public void onKick(final User kickedUser, final User kicker, final AuditLogEntry entry) {
final var embed = new EmbedBuilder();
if (kicker.isBot()) {
var botKickMessage = kickedUser.getAsTag() + " was kicked! Kick Reason: " + entry.getReason();
log(entry.getGuild().getIdLong(), entry.getJDA(), botKickMessage, kicker);
} else {
final var reason = requireNonNullElse(entry.getReason(), "Reason for kick was not provided or could not be found, please contact "
+ "a member of staff for more information about this kick.");

embed.setColor(RUBY);
embed.setTitle("User Kicked");
embed.addField("**Name:**", kickedUser.getAsTag(), true);
embed.addField("**Kick reason:**", reason, false);
embed.setFooter("User ID: " + kickedUser.getId(), kickedUser.getAvatarUrl());
embed.setTimestamp(Instant.now());

log(entry.getGuild().getIdLong(), entry.getJDA(), embed.build(), kicker);
}
}

@Override
public void onGuildMemberUpdateTimeOut(@NotNull final GuildMemberUpdateTimeOutEvent event) {
if (event.getOldTimeOutEnd() == null && event.getNewTimeOutEnd() != null) {
public void onTimeoutUpdate(final User user, final User moderator, final AuditLogEntry entry, final AuditLogChange timeoutChange) {
final OffsetDateTime oldTimeoutEnd = parseDateTime(timeoutChange.getOldValue());
final OffsetDateTime newTimeoutEnd = parseDateTime(timeoutChange.getNewValue());

if (oldTimeoutEnd == null && newTimeoutEnd != null) {
// Somebody was timed out!
Utils.getAuditLog(event.getGuild(), event.getUser().getIdLong(), log -> log.type(ActionType.MEMBER_UPDATE)
.limit(5), log -> {
if (log.getChangeByKey(AuditLogKey.MEMBER_TIME_OUT) == null) return;
final var embed = new EmbedBuilder();
final var moderator = Optional.ofNullable(log.getUser());
final var user = event.getUser();

embed.setColor(LIGHT_SEA_GREEN);
embed.setTitle("User Timed Out");
embed.addField("**User:**", user.getAsTag(), true);
embed.addField("**Timeout End:**", TimeFormat.RELATIVE.format(event.getNewTimeOutEnd()),
true);
embed.addField("**Reason:**", log.getReason() != null ? log.getReason() :
"Reason for timeout was not provided or could not be found, please ask a member of staff for "
+ "information about this timeout.", false);
embed.setFooter("User ID: " + user.getId(), user.getEffectiveAvatarUrl());
embed.setTimestamp(Instant.now());
log(event.getGuild().getIdLong(), event.getJDA(), embed.build(), moderator);
});
} else if (event.getOldTimeOutEnd() != null && event.getNewTimeOutEnd() == null) {

final String reason = requireNonNullElse(entry.getReason(),
"Reason for timeout was not provided or could not be found; " +
"please ask a member of staff for information about this timeout.");

final var embed = new EmbedBuilder();

embed.setColor(LIGHT_SEA_GREEN);
embed.setTitle("User Timed Out");
embed.addField("**User:**", user.getAsTag(), true);
embed.addField("**Timeout End:**", TimeFormat.RELATIVE.format(newTimeoutEnd),
true);
embed.addField("**Reason:**", reason, false);
embed.setFooter("User ID: " + user.getId(), user.getEffectiveAvatarUrl());
embed.setTimestamp(Instant.now());
log(entry.getGuild().getIdLong(), entry.getJDA(), embed.build(), moderator);

} else if (oldTimeoutEnd != null && newTimeoutEnd == null) {
// Somebody's timeout was removed
Utils.getAuditLog(event.getGuild(), event.getUser().getIdLong(), log -> log.type(ActionType.MEMBER_UPDATE)
.limit(5), log -> {
if (log.getChangeByKey(AuditLogKey.MEMBER_TIME_OUT) == null) return;
final var embed = new EmbedBuilder();
final var moderator = Optional.ofNullable(log.getUser());
final var user = event.getUser();
embed.setColor(Color.CYAN);
embed.setTitle("User Timeout Removed");
embed.addField("**User:**", user.getAsTag(), true);
embed.addField("**Old Timeout End:**", TimeFormat.RELATIVE.format(event.getOldTimeOutEnd()),
true);
embed.setFooter("User ID: " + user.getId(), user.getEffectiveAvatarUrl());
embed.setTimestamp(Instant.now());
log(event.getGuild().getIdLong(), event.getJDA(), embed.build(), moderator);
});
final var embed = new EmbedBuilder();

embed.setColor(Color.CYAN);
embed.setTitle("User Timeout Removed");
embed.addField("**User:**", user.getAsTag(), true);
embed.addField("**Old Timeout End:**", TimeFormat.RELATIVE.format(oldTimeoutEnd),
true);
embed.setFooter("User ID: " + user.getId(), user.getEffectiveAvatarUrl());
embed.setTimestamp(Instant.now());
log(entry.getGuild().getIdLong(), entry.getJDA(), embed.build(), moderator);

}
}

private static @Nullable OffsetDateTime parseDateTime(@Nullable String dateTimeString) {
if (dateTimeString == null) return null;
return OffsetDateTime.parse(dateTimeString);
}

@SubscribeEvent
public void onWarnAdd(final WarningEvent.Add event) {
if (getInstance() == null) {
Expand All @@ -211,7 +243,7 @@ public void onWarnAdd(final WarningEvent.Add event) {
.addField("Warning ID:", doc.warnId(), false)
.setFooter("User ID: " + user.getId(), user.getEffectiveAvatarUrl())
.setTimestamp(Instant.now());
logWithWebhook(event.getGuildId(), jda, embed.build(), moderator);
log(event.getGuildId(), jda, embed.build(), moderator);
return null;
}).queue();
}
Expand Down Expand Up @@ -255,15 +287,6 @@ public void onWarnClearAll(final WarningEvent.ClearAllWarns event) {
});
}

@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private void log(long guildId, JDA jda, MessageEmbed embed, Optional<User> owner) {
owner.ifPresentOrElse(user -> logWithWebhook(guildId, jda, embed, user), () -> log(guildId, jda, embed));
}

private void log(long guildId, JDA jda, String message, Optional<User> owner) {
owner.ifPresentOrElse(user -> logWithWebhook(guildId, jda, message, user), () -> log(guildId, jda, message));
}

private void log(long guildId, JDA jda, MessageEmbed embed) {
final var loggingChannels = LoggingType.MODERATION_EVENTS.getChannels(guildId);
loggingChannels
Expand All @@ -275,18 +298,7 @@ private void log(long guildId, JDA jda, MessageEmbed embed) {
});
}

private void log(long guildId, JDA jda, String message) {
final var loggingChannels = LoggingType.MODERATION_EVENTS.getChannels(guildId);
loggingChannels
.forEach(id -> {
final var ch = id.resolve(idL -> jda.getChannelById(MessageChannel.class, idL));
if (ch != null) {
ch.sendMessage(message).queue();
}
});
}

private void logWithWebhook(long guildId, JDA jda, MessageEmbed embed, User author) {
private void log(long guildId, JDA jda, MessageEmbed embed, User author) {
final var loggingChannels = LoggingType.MODERATION_EVENTS.getChannels(guildId);
loggingChannels
.forEach(id -> {
Expand All @@ -301,7 +313,7 @@ private void logWithWebhook(long guildId, JDA jda, MessageEmbed embed, User auth
});
}

private void logWithWebhook(long guildId, JDA jda, String message, User author) {
private void log(long guildId, JDA jda, String message, User author) {
final var loggingChannels = LoggingType.MODERATION_EVENTS.getChannels(guildId);
loggingChannels
.forEach(id -> {
Expand Down
Loading