2121package com .mcmoddev .mmdbot .thelistener .events ;
2222
2323import club .minnced .discord .webhook .send .AllowedMentions ;
24+ import com .google .errorprone .annotations .CheckReturnValue ;
2425import com .mcmoddev .mmdbot .core .event .moderation .WarningEvent ;
2526import com .mcmoddev .mmdbot .core .util .webhook .WebhookManager ;
2627import com .mcmoddev .mmdbot .thelistener .util .LoggingType ;
27- import com .mcmoddev .mmdbot .thelistener .util .Utils ;
2828import io .github .matyrobbrt .eventdispatcher .SubscribeEvent ;
2929import net .dv8tion .jda .api .EmbedBuilder ;
3030import net .dv8tion .jda .api .JDA ;
3131import net .dv8tion .jda .api .audit .ActionType ;
32+ import net .dv8tion .jda .api .audit .AuditLogChange ;
33+ import net .dv8tion .jda .api .audit .AuditLogEntry ;
3234import net .dv8tion .jda .api .audit .AuditLogKey ;
33- import net .dv8tion .jda .api .entities .Message ;
3435import net .dv8tion .jda .api .entities .MessageEmbed ;
3536import net .dv8tion .jda .api .entities .User ;
3637import net .dv8tion .jda .api .entities .channel .middleman .MessageChannel ;
3738import 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 ;
4340import 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 ;
4444import net .dv8tion .jda .api .utils .TimeFormat ;
4545import 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 ;
4851import java .time .Instant ;
49- import java .time .temporal .ChronoUnit ;
50- import java .util .Optional ;
52+ import java .time .OffsetDateTime ;
5153
5254import static com .mcmoddev .mmdbot .thelistener .TheListener .getInstance ;
55+ import static java .util .Objects .requireNonNullElse ;
5356
5457public 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