Skip to content

Commit f785fd6

Browse files
authored
Switch moderation listeners to new audit log event (#237)
1 parent 144849e commit f785fd6

File tree

3 files changed

+234
-198
lines changed

3 files changed

+234
-198
lines changed

src/listener/java/com/mcmoddev/mmdbot/thelistener/events/ModerationEvents.java

Lines changed: 154 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -21,39 +21,43 @@
2121
package com.mcmoddev.mmdbot.thelistener.events;
2222

2323
import club.minnced.discord.webhook.send.AllowedMentions;
24+
import com.google.errorprone.annotations.CheckReturnValue;
2425
import com.mcmoddev.mmdbot.core.event.moderation.WarningEvent;
2526
import com.mcmoddev.mmdbot.core.util.webhook.WebhookManager;
2627
import com.mcmoddev.mmdbot.thelistener.util.LoggingType;
27-
import com.mcmoddev.mmdbot.thelistener.util.Utils;
2828
import io.github.matyrobbrt.eventdispatcher.SubscribeEvent;
2929
import net.dv8tion.jda.api.EmbedBuilder;
3030
import net.dv8tion.jda.api.JDA;
3131
import net.dv8tion.jda.api.audit.ActionType;
32+
import net.dv8tion.jda.api.audit.AuditLogChange;
33+
import net.dv8tion.jda.api.audit.AuditLogEntry;
3234
import net.dv8tion.jda.api.audit.AuditLogKey;
33-
import net.dv8tion.jda.api.entities.Message;
3435
import net.dv8tion.jda.api.entities.MessageEmbed;
3536
import net.dv8tion.jda.api.entities.User;
3637
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
3738
import net.dv8tion.jda.api.entities.channel.middleman.StandardGuildMessageChannel;
38-
import net.dv8tion.jda.api.events.guild.GuildBanEvent;
39-
import net.dv8tion.jda.api.events.guild.GuildUnbanEvent;
40-
import net.dv8tion.jda.api.events.guild.member.GuildMemberRemoveEvent;
41-
import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateNicknameEvent;
42-
import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateTimeOutEvent;
39+
import net.dv8tion.jda.api.events.guild.GuildAuditLogEntryCreateEvent;
4340
import net.dv8tion.jda.api.hooks.ListenerAdapter;
41+
import net.dv8tion.jda.api.requests.ErrorResponse;
42+
import net.dv8tion.jda.api.requests.RestAction;
43+
import net.dv8tion.jda.api.utils.MarkdownSanitizer;
4444
import net.dv8tion.jda.api.utils.TimeFormat;
4545
import org.jetbrains.annotations.NotNull;
46+
import org.jetbrains.annotations.Nullable;
47+
import org.slf4j.Logger;
48+
import org.slf4j.LoggerFactory;
4649

47-
import java.awt.*;
50+
import java.awt.Color;
4851
import java.time.Instant;
49-
import java.time.temporal.ChronoUnit;
50-
import java.util.Optional;
52+
import java.time.OffsetDateTime;
5153

5254
import static com.mcmoddev.mmdbot.thelistener.TheListener.getInstance;
55+
import static java.util.Objects.requireNonNullElse;
5356

5457
public final class ModerationEvents extends ListenerAdapter {
5558

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

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

6872
@Override
69-
public void onGuildBan(@NotNull final GuildBanEvent event) {
70-
Utils.getAuditLog(event.getGuild(), event.getUser().getIdLong(), log -> log
71-
.limit(5)
72-
.type(ActionType.BAN), log -> {
73-
final var embed = new EmbedBuilder();
74-
final var bannedUser = event.getUser();
75-
final var bannedBy = Optional.ofNullable(log.getUser());
76-
embed.setColor(Color.RED);
77-
embed.setTitle("User Banned.");
78-
embed.addField("**User:**", bannedUser.getAsTag(), true);
79-
if (log.getReason() != null) {
80-
embed.addField("**Ban reason:**", log.getReason(), false);
81-
} else {
82-
embed.addField("**Ban reason:**", "Reason for ban was not provided or could not be found "
83-
+ "please contact a member of staff for more information about this ban ban.", false);
73+
public void onGuildAuditLogEntryCreate(@NotNull final GuildAuditLogEntryCreateEvent event) {
74+
final AuditLogEntry entry = event.getEntry();
75+
final ActionType type = entry.getType();
76+
77+
switch (type) {
78+
case BAN -> retrieveUsers(entry, this::onBan).queue();
79+
case UNBAN -> retrieveUsers(entry, this::onUnban).queue();
80+
case MEMBER_UPDATE -> {
81+
final @Nullable AuditLogChange nicknameChange = entry.getChangeByKey(AuditLogKey.MEMBER_NICK);
82+
if (nicknameChange != null) retrieveUsers(entry,
83+
(target, actor, auditEntry) -> this.onNicknameUpdate(target, actor, auditEntry, nicknameChange)).queue();
84+
85+
final @Nullable AuditLogChange timeoutChange = entry.getChangeByKey(AuditLogKey.MEMBER_TIME_OUT);
86+
if (timeoutChange != null) retrieveUsers(entry,
87+
(target, actor, auditEntry) -> this.onTimeoutUpdate(target, actor, auditEntry, timeoutChange)).queue();
8488
}
85-
embed.setFooter("User ID: " + bannedUser.getId(), bannedUser.getEffectiveAvatarUrl());
86-
embed.setTimestamp(Instant.now());
87-
log(event.getGuild().getIdLong(), event.getJDA(), embed.build(), bannedBy);
88-
});
89+
case KICK -> retrieveUsers(entry, this::onKick).queue();
90+
}
8991
}
9092

91-
@Override
92-
public void onGuildUnban(@NotNull final GuildUnbanEvent event) {
93-
Utils.getAuditLog(event.getGuild(), event.getUser().getIdLong(), log -> log
94-
.limit(5)
95-
.type(ActionType.UNBAN), log -> {
96-
final var embed = new EmbedBuilder();
97-
final var unBannedUser = event.getUser();
98-
final var bannedBy = Optional.ofNullable(log.getUser());
99-
embed.setColor(Color.GREEN);
100-
embed.setTitle("User Un-banned.");
101-
embed.addField("**User:**", unBannedUser.getAsTag(), true);
102-
embed.setFooter("User ID: " + unBannedUser.getId(), unBannedUser.getEffectiveAvatarUrl());
103-
embed.setTimestamp(Instant.now());
104-
log(event.getGuild().getIdLong(), event.getJDA(), embed.build(), bannedBy);
93+
@FunctionalInterface
94+
private interface AuditLogEntryHandler {
95+
void handle(final User target, final User actor, final AuditLogEntry entry);
96+
}
97+
98+
@CheckReturnValue
99+
private RestAction<Void> retrieveUsers(final AuditLogEntry entry, final AuditLogEntryHandler handler) {
100+
final String targetId = entry.getTargetId();
101+
final String actorId = entry.getUserId();
102+
103+
final RestAction<@Nullable User> targetRestAction = entry.getJDA().retrieveUserById(targetId)
104+
.onErrorMap(ErrorResponse.UNKNOWN_USER::test, e -> {
105+
LOGGER.error("Could not retrieve target user for ID {}", targetId);
106+
return null;
107+
});
108+
final RestAction<@Nullable User> actorRestAction = entry.getJDA().retrieveUserById(actorId)
109+
.onErrorMap(ErrorResponse.UNKNOWN_USER::test, e -> {
110+
LOGGER.error("Could not retrieve actor user for ID {}", actorId);
111+
return null;
112+
});
113+
114+
return targetRestAction.and(actorRestAction, (targetUser, actorUser) -> {
115+
if (targetUser != null && actorUser != null) {
116+
handler.handle(targetUser, actorUser, entry);
117+
}
118+
return null;
105119
});
106120
}
107121

108-
@Override
109-
public void onGuildMemberUpdateNickname(@NotNull final GuildMemberUpdateNicknameEvent event) {
122+
public void onBan(final User bannedUser, final User bannedBy, final AuditLogEntry entry) {
123+
final var reason = requireNonNullElse(entry.getReason(),
124+
"_Reason for ban was not provided or could not be found; "
125+
+ "please contact a member of staff for more information about this ban._");
126+
127+
final var embed = new EmbedBuilder();
128+
embed.setColor(Color.RED);
129+
embed.setTitle("User Banned.");
130+
embed.addField("**User:**", bannedUser.getAsTag(), true);
131+
embed.addField("**Ban reason:**", reason, false);
132+
embed.setFooter("User ID: " + bannedUser.getId(), bannedUser.getEffectiveAvatarUrl());
133+
embed.setTimestamp(Instant.now());
134+
135+
log(entry.getGuild().getIdLong(), entry.getJDA(), embed.build(), bannedBy);
136+
}
137+
138+
public void onUnban(final User unBannedUser, final User bannedBy, final AuditLogEntry entry) {
139+
final var embed = new EmbedBuilder();
140+
embed.setColor(Color.GREEN);
141+
embed.setTitle("User Un-banned.");
142+
embed.addField("**User:**", unBannedUser.getAsTag(), true);
143+
embed.setFooter("User ID: " + unBannedUser.getId(), unBannedUser.getEffectiveAvatarUrl());
144+
embed.setTimestamp(Instant.now());
145+
146+
log(entry.getGuild().getIdLong(), entry.getJDA(), embed.build(), bannedBy);
147+
}
148+
149+
public void onNicknameUpdate(final User target, final User editor, final AuditLogEntry entry, final AuditLogChange nicknameChange) {
110150
final var embed = new EmbedBuilder();
111-
final var targetUser = event.getUser();
112151
embed.setColor(Color.YELLOW);
113152
embed.setTitle("Nickname Changed");
114-
embed.addField("User:", targetUser.getAsTag(), true);
115-
embed.addField("Old Nickname:", event.getOldNickname() == null
116-
? "*None*" : event.getOldNickname(), true);
117-
embed.addField("New Nickname:", event.getNewNickname() == null
118-
? "*None*" : event.getNewNickname(), true);
119-
embed.setFooter("User ID: " + event.getUser().getId(), event.getUser().getEffectiveAvatarUrl());
153+
embed.addField("User:", target.getAsTag(), true);
154+
embed.addField("Old Nickname:", wrapNicknameValue(nicknameChange.getOldValue()), true);
155+
embed.addField("New Nickname:", wrapNicknameValue(nicknameChange.getNewValue()), true);
156+
embed.setFooter("User ID: " + target.getId(), target.getEffectiveAvatarUrl());
120157
embed.setTimestamp(Instant.now());
121-
logWithWebhook(event.getGuild().getIdLong(), event.getJDA(), embed.build(), event.getUser());
158+
log(entry.getGuild().getIdLong(), entry.getJDA(), embed.build(), editor);
122159
}
123160

124-
@Override
125-
public void onGuildMemberRemove(@NotNull final GuildMemberRemoveEvent event) {
126-
Utils.getAuditLog(event.getGuild(), event.getUser().getIdLong(), log -> log
127-
.type(ActionType.KICK)
128-
.limit(5), log -> {
129-
if (log.getTimeCreated().toInstant().isBefore(Instant.now().minus(2, ChronoUnit.MINUTES))) {
130-
return;
131-
}
161+
private static String wrapNicknameValue(@Nullable String nicknameValue) {
162+
if (nicknameValue == null) return "*None*";
163+
return MarkdownSanitizer.escape(nicknameValue);
164+
}
132165

133-
final var embed = new EmbedBuilder();
134-
final var kicker = Optional.ofNullable(log.getUser());
135-
final var kickedUser = event.getUser();
136-
137-
if (kicker.isPresent() && kicker.get().isBot()) {
138-
var botKickMessage = kickedUser.getAsTag() + " was kicked! Kick Reason: " + log.getReason();
139-
log(event.getGuild().getIdLong(), event.getJDA(), botKickMessage, kicker);
140-
} else {
141-
embed.setColor(RUBY);
142-
embed.setTitle("User Kicked");
143-
embed.addField("**Name:**", kickedUser.getAsTag(), true);
144-
embed.addField("**Kick reason:**", log.getReason() != null ? log.getReason() :
145-
("Reason for kick was not provided or could not be found, please contact "
146-
+ "a member of staff for more information about this kick."), false);
147-
embed.setFooter("User ID: " + kickedUser.getId(), kickedUser.getAvatarUrl());
148-
embed.setTimestamp(Instant.now());
149-
150-
log(event.getGuild().getIdLong(), event.getJDA(), embed.build(), kicker);
151-
}
152-
});
166+
public void onKick(final User kickedUser, final User kicker, final AuditLogEntry entry) {
167+
final var embed = new EmbedBuilder();
168+
if (kicker.isBot()) {
169+
var botKickMessage = kickedUser.getAsTag() + " was kicked! Kick Reason: " + entry.getReason();
170+
log(entry.getGuild().getIdLong(), entry.getJDA(), botKickMessage, kicker);
171+
} else {
172+
final var reason = requireNonNullElse(entry.getReason(), "Reason for kick was not provided or could not be found, please contact "
173+
+ "a member of staff for more information about this kick.");
174+
175+
embed.setColor(RUBY);
176+
embed.setTitle("User Kicked");
177+
embed.addField("**Name:**", kickedUser.getAsTag(), true);
178+
embed.addField("**Kick reason:**", reason, false);
179+
embed.setFooter("User ID: " + kickedUser.getId(), kickedUser.getAvatarUrl());
180+
embed.setTimestamp(Instant.now());
181+
182+
log(entry.getGuild().getIdLong(), entry.getJDA(), embed.build(), kicker);
183+
}
153184
}
154185

155-
@Override
156-
public void onGuildMemberUpdateTimeOut(@NotNull final GuildMemberUpdateTimeOutEvent event) {
157-
if (event.getOldTimeOutEnd() == null && event.getNewTimeOutEnd() != null) {
186+
public void onTimeoutUpdate(final User user, final User moderator, final AuditLogEntry entry, final AuditLogChange timeoutChange) {
187+
final OffsetDateTime oldTimeoutEnd = parseDateTime(timeoutChange.getOldValue());
188+
final OffsetDateTime newTimeoutEnd = parseDateTime(timeoutChange.getNewValue());
189+
190+
if (oldTimeoutEnd == null && newTimeoutEnd != null) {
158191
// Somebody was timed out!
159-
Utils.getAuditLog(event.getGuild(), event.getUser().getIdLong(), log -> log.type(ActionType.MEMBER_UPDATE)
160-
.limit(5), log -> {
161-
if (log.getChangeByKey(AuditLogKey.MEMBER_TIME_OUT) == null) return;
162-
final var embed = new EmbedBuilder();
163-
final var moderator = Optional.ofNullable(log.getUser());
164-
final var user = event.getUser();
165-
166-
embed.setColor(LIGHT_SEA_GREEN);
167-
embed.setTitle("User Timed Out");
168-
embed.addField("**User:**", user.getAsTag(), true);
169-
embed.addField("**Timeout End:**", TimeFormat.RELATIVE.format(event.getNewTimeOutEnd()),
170-
true);
171-
embed.addField("**Reason:**", log.getReason() != null ? log.getReason() :
172-
"Reason for timeout was not provided or could not be found, please ask a member of staff for "
173-
+ "information about this timeout.", false);
174-
embed.setFooter("User ID: " + user.getId(), user.getEffectiveAvatarUrl());
175-
embed.setTimestamp(Instant.now());
176-
log(event.getGuild().getIdLong(), event.getJDA(), embed.build(), moderator);
177-
});
178-
} else if (event.getOldTimeOutEnd() != null && event.getNewTimeOutEnd() == null) {
192+
193+
final String reason = requireNonNullElse(entry.getReason(),
194+
"Reason for timeout was not provided or could not be found; " +
195+
"please ask a member of staff for information about this timeout.");
196+
197+
final var embed = new EmbedBuilder();
198+
199+
embed.setColor(LIGHT_SEA_GREEN);
200+
embed.setTitle("User Timed Out");
201+
embed.addField("**User:**", user.getAsTag(), true);
202+
embed.addField("**Timeout End:**", TimeFormat.RELATIVE.format(newTimeoutEnd),
203+
true);
204+
embed.addField("**Reason:**", reason, false);
205+
embed.setFooter("User ID: " + user.getId(), user.getEffectiveAvatarUrl());
206+
embed.setTimestamp(Instant.now());
207+
log(entry.getGuild().getIdLong(), entry.getJDA(), embed.build(), moderator);
208+
209+
} else if (oldTimeoutEnd != null && newTimeoutEnd == null) {
179210
// Somebody's timeout was removed
180-
Utils.getAuditLog(event.getGuild(), event.getUser().getIdLong(), log -> log.type(ActionType.MEMBER_UPDATE)
181-
.limit(5), log -> {
182-
if (log.getChangeByKey(AuditLogKey.MEMBER_TIME_OUT) == null) return;
183-
final var embed = new EmbedBuilder();
184-
final var moderator = Optional.ofNullable(log.getUser());
185-
final var user = event.getUser();
186-
embed.setColor(Color.CYAN);
187-
embed.setTitle("User Timeout Removed");
188-
embed.addField("**User:**", user.getAsTag(), true);
189-
embed.addField("**Old Timeout End:**", TimeFormat.RELATIVE.format(event.getOldTimeOutEnd()),
190-
true);
191-
embed.setFooter("User ID: " + user.getId(), user.getEffectiveAvatarUrl());
192-
embed.setTimestamp(Instant.now());
193-
log(event.getGuild().getIdLong(), event.getJDA(), embed.build(), moderator);
194-
});
211+
final var embed = new EmbedBuilder();
212+
213+
embed.setColor(Color.CYAN);
214+
embed.setTitle("User Timeout Removed");
215+
embed.addField("**User:**", user.getAsTag(), true);
216+
embed.addField("**Old Timeout End:**", TimeFormat.RELATIVE.format(oldTimeoutEnd),
217+
true);
218+
embed.setFooter("User ID: " + user.getId(), user.getEffectiveAvatarUrl());
219+
embed.setTimestamp(Instant.now());
220+
log(entry.getGuild().getIdLong(), entry.getJDA(), embed.build(), moderator);
221+
195222
}
196223
}
197224

225+
private static @Nullable OffsetDateTime parseDateTime(@Nullable String dateTimeString) {
226+
if (dateTimeString == null) return null;
227+
return OffsetDateTime.parse(dateTimeString);
228+
}
229+
198230
@SubscribeEvent
199231
public void onWarnAdd(final WarningEvent.Add event) {
200232
if (getInstance() == null) {
@@ -211,7 +243,7 @@ public void onWarnAdd(final WarningEvent.Add event) {
211243
.addField("Warning ID:", doc.warnId(), false)
212244
.setFooter("User ID: " + user.getId(), user.getEffectiveAvatarUrl())
213245
.setTimestamp(Instant.now());
214-
logWithWebhook(event.getGuildId(), jda, embed.build(), moderator);
246+
log(event.getGuildId(), jda, embed.build(), moderator);
215247
return null;
216248
}).queue();
217249
}
@@ -255,15 +287,6 @@ public void onWarnClearAll(final WarningEvent.ClearAllWarns event) {
255287
});
256288
}
257289

258-
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
259-
private void log(long guildId, JDA jda, MessageEmbed embed, Optional<User> owner) {
260-
owner.ifPresentOrElse(user -> logWithWebhook(guildId, jda, embed, user), () -> log(guildId, jda, embed));
261-
}
262-
263-
private void log(long guildId, JDA jda, String message, Optional<User> owner) {
264-
owner.ifPresentOrElse(user -> logWithWebhook(guildId, jda, message, user), () -> log(guildId, jda, message));
265-
}
266-
267290
private void log(long guildId, JDA jda, MessageEmbed embed) {
268291
final var loggingChannels = LoggingType.MODERATION_EVENTS.getChannels(guildId);
269292
loggingChannels
@@ -275,18 +298,7 @@ private void log(long guildId, JDA jda, MessageEmbed embed) {
275298
});
276299
}
277300

278-
private void log(long guildId, JDA jda, String message) {
279-
final var loggingChannels = LoggingType.MODERATION_EVENTS.getChannels(guildId);
280-
loggingChannels
281-
.forEach(id -> {
282-
final var ch = id.resolve(idL -> jda.getChannelById(MessageChannel.class, idL));
283-
if (ch != null) {
284-
ch.sendMessage(message).queue();
285-
}
286-
});
287-
}
288-
289-
private void logWithWebhook(long guildId, JDA jda, MessageEmbed embed, User author) {
301+
private void log(long guildId, JDA jda, MessageEmbed embed, User author) {
290302
final var loggingChannels = LoggingType.MODERATION_EVENTS.getChannels(guildId);
291303
loggingChannels
292304
.forEach(id -> {
@@ -301,7 +313,7 @@ private void logWithWebhook(long guildId, JDA jda, MessageEmbed embed, User auth
301313
});
302314
}
303315

304-
private void logWithWebhook(long guildId, JDA jda, String message, User author) {
316+
private void log(long guildId, JDA jda, String message, User author) {
305317
final var loggingChannels = LoggingType.MODERATION_EVENTS.getChannels(guildId);
306318
loggingChannels
307319
.forEach(id -> {

0 commit comments

Comments
 (0)