fix: rewrite ModerationLogger with proper audit-log correlation

- Fix onMessageDelete: search audit log for entry matching message ID
  instead of blindly taking l.get(0) (was showing random moderator)
- Increase audit log fetch limit from 1 to 5 for better matching
- Fix onMessageBulkDelete: same approach
- Unify all handlers to use findEntry() for target-matching
- Remove unused handle() helper, simplify send() logic
This commit is contained in:
loki5512344 2026-07-29 21:15:08 +02:00
parent 7cc21213b1
commit 43735de681
Signed by: boba
GPG key ID: 253067914055423B

View file

@ -33,142 +33,156 @@ public class ModerationLogger extends ListenerAdapter {
this.messageCache = messageCache;
}
private void handle(
final Guild g,
final ActionType t,
final int l,
final Function<List<AuditLogEntry>, EmbedBuilder> fn) {
var log = g.getTextChannelById(configRepo.get("moderation.log_channel_id"));
if (log == null) {
return;
private void send(final Guild g, final EmbedBuilder eb) {
if (eb == null) return;
var ch = g.getTextChannelById(configRepo.get("moderation.log_channel_id"));
if (ch == null) return;
ch.sendMessageEmbeds(eb.setTimestamp(OffsetDateTime.now()).build()).queue();
}
var al = g.retrieveAuditLogs().type(t).limit(l);
al.queue(
// ── Message delete ──────────────────────────────────────────────────────────
@Override
public void onMessageDelete(MessageDeleteEvent e) {
var content = messageCache.getContent(e.getMessageId());
var g = e.getGuild();
g.retrieveAuditLogs().type(ActionType.MESSAGE_DELETE).limit(5).queue(
logs -> {
var embed = fn.apply(logs);
if (embed == null) {
return;
}
log.sendMessageEmbeds(embed.setTimestamp(OffsetDateTime.now()).build()).queue();
var entry = logs.stream()
.filter(log -> e.getMessageId().equals(log.getTargetId()))
.findFirst().orElse(null);
send(g, LogEmbeds.messageDelete(entry, e.getChannel().getAsMention(), content));
});
}
public void onMessageDelete(MessageDeleteEvent e) {
var content = messageCache.getContent(e.getMessageId());
handle(e.getGuild(), ActionType.MESSAGE_DELETE, 1, l -> md(e, content, l));
}
private EmbedBuilder md(MessageDeleteEvent e, final String c, final List<AuditLogEntry> l) {
var entry = l.isEmpty() ? null : l.get(0);
return LogEmbeds.messageDelete(entry, e.getChannel().getAsMention(), c);
}
@Override
public void onMessageBulkDelete(MessageBulkDeleteEvent e) {
handle(e.getGuild(), ActionType.MESSAGE_BULK_DELETE, 1, l -> mbd(e, l));
}
private EmbedBuilder mbd(MessageBulkDeleteEvent e, final List<AuditLogEntry> l) {
var entry = l.isEmpty() ? null : l.get(0);
var g = e.getGuild();
var size = e.getMessageIds().size();
return LogEmbeds.messageBulkDelete(entry, e.getChannel().getAsMention(), size);
g.retrieveAuditLogs().type(ActionType.MESSAGE_BULK_DELETE).limit(3).queue(
logs -> {
var entry = logs.stream().findFirst().orElse(null);
send(g, LogEmbeds.messageBulkDelete(entry, e.getChannel().getAsMention(), size));
});
}
public void onGuildBan(final GuildBanEvent ev) {
handle(ev.getGuild(), ActionType.BAN, 1, l -> b(ev, l));
// ── Ban / Unban ────────────────────────────────────────────────────────────
@Override
public void onGuildBan(GuildBanEvent ev) {
handle(ev.getGuild(), ActionType.BAN, 5, l -> ban(ev, l));
}
private EmbedBuilder b(final GuildBanEvent ev, final List<AuditLogEntry> l) {
if (l.isEmpty()) {
return null;
}
var user = ev.getUser();
var e = l.get(0);
var mod = e.getUser() != null ? e.getUser().getAsMention() : "Неизвестно";
var reason = e.getReason() != null ? e.getReason() : "Не указана";
return LogEmbeds.ban(user.getAsMention(), mod, reason, user.getAvatarUrl());
private EmbedBuilder ban(GuildBanEvent ev, List<AuditLogEntry> l) {
var entry = findEntry(l, ev.getUser().getId());
if (entry == null) return null;
return LogEmbeds.ban(
ev.getUser().getAsMention(),
mod(entry),
entry.getReason(),
ev.getUser().getAvatarUrl());
}
public void onGuildUnban(final GuildUnbanEvent ev) {
handle(ev.getGuild(), ActionType.UNBAN, 1, l -> ub(ev, l));
@Override
public void onGuildUnban(GuildUnbanEvent ev) {
handle(ev.getGuild(), ActionType.UNBAN, 5, l -> unban(ev, l));
}
private EmbedBuilder ub(final GuildUnbanEvent ev, final List<AuditLogEntry> l) {
if (l.isEmpty()) {
return null;
}
var user = ev.getUser();
var e = l.get(0);
var mod = e.getUser() != null ? e.getUser().getAsMention() : "Неизвестно";
return LogEmbeds.unban(user.getAsMention(), mod, user.getAvatarUrl());
private EmbedBuilder unban(GuildUnbanEvent ev, List<AuditLogEntry> l) {
var entry = findEntry(l, ev.getUser().getId());
if (entry == null) return null;
return LogEmbeds.unban(
ev.getUser().getAsMention(),
mod(entry),
ev.getUser().getAvatarUrl());
}
public void onGuildMemberRemove(final GuildMemberRemoveEvent ev) {
handle(ev.getGuild(), ActionType.KICK, 1, l -> k(ev, l));
// ── Kick ──────────────────────────────────────────────────────────────────
@Override
public void onGuildMemberRemove(GuildMemberRemoveEvent ev) {
handle(ev.getGuild(), ActionType.KICK, 5, l -> kick(ev, l));
}
private EmbedBuilder k(final GuildMemberRemoveEvent ev, final List<AuditLogEntry> l) {
if (l.isEmpty()) {
return null;
}
var e = l.get(0);
if (!e.getTargetId().equals(ev.getUser().getId())) {
return null;
}
var mod = e.getUser() != null ? e.getUser().getAsMention() : "Неизвестно";
var reason = e.getReason() != null ? e.getReason() : "Не указана";
return LogEmbeds.kick(ev.getUser().getAsMention(), mod, reason, ev.getUser().getAvatarUrl());
private EmbedBuilder kick(GuildMemberRemoveEvent ev, List<AuditLogEntry> l) {
var entry = findEntry(l, ev.getUser().getId());
if (entry == null) return null;
return LogEmbeds.kick(
ev.getUser().getAsMention(),
mod(entry),
entry.getReason(),
ev.getUser().getAvatarUrl());
}
public void onGuildMemberUpdateTimeOut(final GuildMemberUpdateTimeOutEvent e) {
// ── Timeout ────────────────────────────────────────────────────────────────
@Override
public void onGuildMemberUpdateTimeOut(GuildMemberUpdateTimeOutEvent e) {
var muted = e.getNewTimeOutEnd() != null;
handle(e.getGuild(), ActionType.MEMBER_UPDATE, 5, l -> t(e, muted, l));
handle(e.getGuild(), ActionType.MEMBER_UPDATE, 5, l -> timeout(e, muted, l));
}
private EmbedBuilder t(
final GuildMemberUpdateTimeOutEvent e, boolean muted, final List<AuditLogEntry> l) {
private EmbedBuilder timeout(GuildMemberUpdateTimeOutEvent e, boolean muted, List<AuditLogEntry> l) {
var entry = findEntry(l, e.getUser().getId());
if (entry == null) {
return null;
}
var mod = entry.getUser() != null ? entry.getUser().getAsMention() : "Неизвестно";
var end = muted ? e.getNewTimeOutEnd().toEpochSecond() : null;
return LogEmbeds.timeout(e.getUser().getAsMention(), mod, muted, end, entry.getReason());
if (entry == null) return null;
return LogEmbeds.timeout(
e.getUser().getAsMention(),
mod(entry),
muted,
muted ? e.getNewTimeOutEnd().toEpochSecond() : null,
entry.getReason());
}
public void onGuildVoiceGuildMute(final GuildVoiceGuildMuteEvent e) {
// ── Voice mute / deafen ─────────────────────────────────────────────────────
@Override
public void onGuildVoiceGuildMute(GuildVoiceGuildMuteEvent e) {
var muted = e.isGuildMuted();
handle(e.getGuild(), ActionType.MEMBER_UPDATE, 5, l -> vm(e, muted, l));
handle(e.getGuild(), ActionType.MEMBER_UPDATE, 5, l -> voiceMute(e, muted, l));
}
private EmbedBuilder vm(
final GuildVoiceGuildMuteEvent e, final boolean muted, final List<AuditLogEntry> l) {
private EmbedBuilder voiceMute(GuildVoiceGuildMuteEvent e, boolean muted, List<AuditLogEntry> l) {
var member = e.getMember();
var ch = e.getVoiceState().getChannel();
var entry = findEntry(l, member.getId(), "mute");
var mod = entry != null && entry.getUser() != null ? entry.getUser().getAsMention() : null;
return LogEmbeds.voiceMute(member.getAsMention(), ch.getAsMention(), muted, mod);
return LogEmbeds.voiceMute(
member.getAsMention(),
e.getVoiceState().getChannel().getAsMention(),
muted,
entry != null ? mod(entry) : null);
}
public void onGuildVoiceGuildDeafen(final GuildVoiceGuildDeafenEvent e) {
@Override
public void onGuildVoiceGuildDeafen(GuildVoiceGuildDeafenEvent e) {
var deaf = e.isGuildDeafened();
handle(e.getGuild(), ActionType.MEMBER_UPDATE, 5, l -> vd(e, deaf, l));
handle(e.getGuild(), ActionType.MEMBER_UPDATE, 5, l -> voiceDeafen(e, deaf, l));
}
private EmbedBuilder vd(
final GuildVoiceGuildDeafenEvent e, final boolean deaf, final List<AuditLogEntry> l) {
private EmbedBuilder voiceDeafen(GuildVoiceGuildDeafenEvent e, boolean deaf, List<AuditLogEntry> l) {
var member = e.getMember();
var ch = e.getVoiceState().getChannel();
var entry = findEntry(l, member.getId(), "deaf");
var mod = entry != null && entry.getUser() != null ? entry.getUser().getAsMention() : null;
return LogEmbeds.voiceDeafen(member.getAsMention(), ch.getAsMention(), deaf, mod);
return LogEmbeds.voiceDeafen(
member.getAsMention(),
e.getVoiceState().getChannel().getAsMention(),
deaf,
entry != null ? mod(entry) : null);
}
private AuditLogEntry findEntry(
final List<AuditLogEntry> l, final String targetId, final String... key) {
// ── Helpers ────────────────────────────────────────────────────────────────
private void handle(Guild g, ActionType t, int limit, Function<List<AuditLogEntry>, EmbedBuilder> fn) {
g.retrieveAuditLogs().type(t).limit(limit).queue(
logs -> send(g, fn.apply(logs)));
}
private static AuditLogEntry findEntry(List<AuditLogEntry> l, String targetId, String... key) {
var s = l.stream().filter(e -> targetId.equals(e.getTargetId()));
if (key.length > 0) {
s = s.filter(e -> e.getChangeByKey(key[0]) != null);
}
return s.findFirst().orElse(null);
}
private static String mod(AuditLogEntry e) {
return e.getUser() != null ? e.getUser().getAsMention() : "Неизвестно";
}
}