From 1edf9ccd2d4a3f0a58f829c9b9352d4d0dfb4e70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 08:53:30 +0200 Subject: [PATCH 1/4] Extend the this-is-scam action command to handle scams and job ads --- .../moderation/ThisIsScamCommand.java | 278 +++++++++++------- build.gradle | 2 +- 2 files changed, 174 insertions(+), 106 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/ThisIsScamCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/ThisIsScamCommand.java index 3180b61c57..d6bcf1a7cb 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/ThisIsScamCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/ThisIsScamCommand.java @@ -14,9 +14,9 @@ import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent; import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; import net.dv8tion.jda.api.exceptions.ErrorHandler; +import net.dv8tion.jda.api.interactions.InteractionHook; import net.dv8tion.jda.api.interactions.commands.build.Commands; import net.dv8tion.jda.api.interactions.components.buttons.Button; -import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; import net.dv8tion.jda.api.requests.ErrorResponse; import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.requests.restaction.MessageCreateAction; @@ -39,29 +39,39 @@ import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; import java.util.ArrayList; +import java.util.EnumSet; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Collectors; /** - * Allows users to report a message as potential scam. Moderators can confirm the report from the - * audit log, causing the author to be quarantined plus message history getting deleted. + * Allows users to alert moderators about a message that violates the rules. Moderators then decide + * how to handle it: + * + * The ban and unban only serves to delete the messages, the user is free to join back afterward. */ public final class ThisIsScamCommand extends BotCommandAdapter implements MessageContextCommand { private static final Logger logger = LoggerFactory.getLogger(ThisIsScamCommand.class); - private static final String COMMAND_NAME = "this-is-scam"; + private static final String COMMAND_NAME = "this-is-against-rules"; - private static final String ACTION_TITLE = "Quarantine"; - private static final String ACTION_REASON = "Message was reported and confirmed as scam"; + private static final String SCAM_REASON = "Message was reported and confirmed as scam"; + private static final String JOB_AD_REASON = + "Message was reported and confirmed as a job advertisement"; private static final String FAILED_MESSAGE = - "Sorry, there was an issue forwarding your scam report to the moderators. We are investigating."; + "Sorry, there was an issue forwarding your report to the moderators. We are investigating."; + private static final Duration USER_COMMAND_COOLDOWN = Duration.ofMinutes(1); private final Config config; @@ -70,6 +80,7 @@ public final class ThisIsScamCommand extends BotCommandAdapter implements Messag private final Cache reportedMessageToTimestamp = Caffeine.newBuilder().maximumSize(10_000).expireAfterWrite(Duration.ofDays(1)).build(); + private final Cache userToLastCommandUse = Caffeine.newBuilder() .maximumSize(10_000) .expireAfterWrite(USER_COMMAND_COOLDOWN) @@ -78,8 +89,8 @@ public final class ThisIsScamCommand extends BotCommandAdapter implements Messag /** * Creates a new instance. * - * @param config to resolve the moderation audit log channel and quarantined role - * @param actionsStore used to store issued quarantine actions + * @param config to resolve the mod mail channel and quarantined role + * @param actionsStore used to store issued quarantine and warn actions */ public ThisIsScamCommand(Config config, ModerationActionsStore actionsStore) { super(Commands.message(COMMAND_NAME), CommandVisibility.GUILD); @@ -98,16 +109,18 @@ public void onMessageContext(MessageContextInteractionEvent event) { return; } - Optional modAuditLog = findModMailChannel(event); - if (modAuditLog.isEmpty()) { + Message message = event.getTarget(); + Optional modMailChannel = findModMailChannel(event); + if (modMailChannel.isEmpty()) { + reportedMessageToTimestamp.invalidate(message.getIdLong()); event.reply(FAILED_MESSAGE).setEphemeral(true).queue(); return; } - Message message = event.getTarget(); - reportToMods(message, modAuditLog.orElseThrow()).mapToResult().map(result -> { + alertMods(message, modMailChannel.orElseThrow()).mapToResult().map(result -> { if (result.isFailure()) { - logger.warn("Unable to forward a scam report to the mod audit log channel.", + reportedMessageToTimestamp.invalidate(message.getIdLong()); + logger.warn("Unable to forward a report to the mod mail channel.", result.getFailure()); return FAILED_MESSAGE; } @@ -130,18 +143,14 @@ private boolean handleIsOnCooldown(MessageContextInteractionEvent event) { return false; } - event.reply("You just reported a message as scam, please wait a bit.") - .setEphemeral(true) - .queue(); + event.reply("You just reported a message, please wait a bit.").setEphemeral(true).queue(); return true; } private boolean handleWasAlreadyReportedMessage(MessageContextInteractionEvent event) { long messageId = event.getTarget().getIdLong(); if (reportedMessageToTimestamp.getIfPresent(messageId) != null) { - event.reply("This message was already reported as potential scam.") - .setEphemeral(true) - .queue(); + event.reply("This message was already reported.").setEphemeral(true).queue(); return true; } @@ -154,34 +163,35 @@ private Optional findModMailChannel(MessageContextInteractionEvent Optional modMailChannel = Guilds.findTextChannel(guild, isModMailChannel); if (modMailChannel.isEmpty()) { logger.warn( - "Cannot find the designated mod audit log channel in guild '{}' with the pattern '{}'", - guild.getId(), config.getModAuditLogChannelPattern()); + "Cannot find the designated mod mail channel in guild '{}' with the pattern '{}'", + guild.getId(), config.getModMailChannelPattern()); } return modMailChannel; } - private MessageCreateAction reportToMods(Message message, TextChannel auditChannel) { + private MessageCreateAction alertMods(Message message, TextChannel modMailChannel) { User author = message.getAuthor(); - String description = createDescription(message); long accountAgeDays = ChronoUnit.DAYS.between(author.getTimeCreated(), OffsetDateTime.now(ZoneOffset.UTC)); - MessageEmbed reportEmbed = new EmbedBuilder().setTitle("Is this Scam?") - .setDescription( - MessageUtils.abbreviate(description, MessageEmbed.DESCRIPTION_MAX_LENGTH)) + MessageEmbed alertEmbed = new EmbedBuilder().setTitle("Rule Violation") + .setDescription(MessageUtils.abbreviate(createDescription(message), + MessageEmbed.DESCRIPTION_MAX_LENGTH)) .setAuthor(author.getName(), null, author.getEffectiveAvatarUrl()) .setTimestamp(message.getTimeCreated()) .setColor(AmbientColors.MODERATION_SCAM) .setFooter("%s - account age %d days".formatted(author.getId(), accountAgeDays)) .build(); - long guildId = message.getGuild().getIdLong(); - long authorId = author.getIdLong(); - String[] args = {String.valueOf(guildId), String.valueOf(authorId)}; + return modMailChannel.sendMessageEmbeds(alertEmbed) + .addActionRow(Button.success(generateComponentId(AlertAction.SCAM, message), "Scam"), + Button.success(generateComponentId(AlertAction.JOB_AD, message), "Job-Ad"), + Button.danger(generateComponentId(AlertAction.DISMISS, message), "Dismiss")); + } - return auditChannel.sendMessageEmbeds(reportEmbed) - .addActionRow(Button.success(generateComponentId(args), "Yes"), - Button.danger(generateComponentId(args), "No")); + private String generateComponentId(AlertAction action, Message message) { + return super.generateComponentId(String.valueOf(message.getGuild().getIdLong()), + String.valueOf(message.getAuthor().getIdLong()), action.name()); } private static String createDescription(Message target) { @@ -207,74 +217,89 @@ private static String createDescription(Message target) { @Override public void onButtonClick(ButtonInteractionEvent event, List args) { - long guildId = Long.parseLong(args.get(0)); - long targetId = Long.parseLong(args.get(1)); - - ButtonStyle clickedStyle = event.getButton().getStyle(); - boolean isScam = clickedStyle == ButtonStyle.SUCCESS; - - MessageEmbed resultEmbed = new EmbedBuilder() - .setDescription( - isScam ? "This is scam. The user was quarantined and messages were deleted." - : "This is not scam, no action executed.") - .setColor(isScam ? Color.GREEN : Color.RED) - .build(); - - List embeds = new ArrayList<>(event.getMessage().getEmbeds()); - embeds.add(resultEmbed); - - event.editMessageEmbeds(embeds).setComponents().queue(); - - if (!isScam) { + Member moderator = Objects.requireNonNull(event.getMember()); + List alertEmbeds = new ArrayList<>(event.getMessage().getEmbeds()); + AlertAction action = AlertAction.valueOf(args.get(2)); + + if (action == AlertAction.DISMISS) { + String text = + "Dismissed by %s, no action executed.".formatted(moderator.getAsMention()); + event.editMessageEmbeds(withResult(alertEmbeds, text, Color.RED)) + .setComponents() + .queue(); return; } - Guild guild = Objects.requireNonNull(event.getJDA().getGuildById(guildId)); - Member moderator = Objects.requireNonNull(event.getMember()); + ErrorHandler authorGoneHandler = new ErrorHandler().handle( + EnumSet.of(ErrorResponse.UNKNOWN_USER, ErrorResponse.UNKNOWN_MEMBER), + _ -> event.reply("The user is not a member of this server anymore, nothing to do.") + .setEphemeral(true) + .queue()); - ErrorHandler errorHandler = new ErrorHandler() - .handle(ErrorResponse.UNKNOWN_USER, failure -> logger.debug(LogMarkers.SENSITIVE, - "Attempted to handle user-reported scam, but user '{}' does not exist anymore.", - targetId)) - .handle(ErrorResponse.UNKNOWN_MEMBER, failure -> logger.debug(LogMarkers.SENSITIVE, - "Attempted to handle user-reported scam, but user '{}' is not a member of guild '{}' anymore.", - targetId, guildId)); + long guildId = Long.parseLong(args.get(0)); + long authorId = Long.parseLong(args.get(1)); + Guild guild = Objects.requireNonNull(event.getJDA().getGuildById(guildId)); - guild.retrieveMemberById(targetId) - .queue(target -> handleConfirmedScam(guild, target, moderator, event), errorHandler); + guild.retrieveMemberById(authorId) + .queue(target -> handleConfirmedViolation(event, action, guild, moderator, target, + alertEmbeds), authorGoneHandler); } - private void handleConfirmedScam(Guild guild, Member target, Member moderator, - ButtonInteractionEvent event) { - if (!handleCanQuarantineAndBan(guild, target, event)) { - return; - } + /** + * The checks reply to the event when they fail, so they have to run before the event is + * acknowledged. On failure the buttons stay, so the moderator can fix the issue and retry. + */ + private void handleConfirmedViolation(ButtonInteractionEvent event, AlertAction action, + Guild guild, Member moderator, Member target, List alertEmbeds) { - Consumer onSuccess = _ -> { - }; - Consumer onFailure = failure -> logger.warn(LogMarkers.SENSITIVE, - "Failed to finish user-reported scam handling for user '{}' in guild '{}'.", - target.getId(), guild.getId(), failure); + Member bot = guild.getSelfMember(); - sendQuarantineDm(target.getUser(), guild) - .flatMap(hasSentDm -> quarantineUser(guild, target, moderator)) - .flatMap(_ -> deleteMessagesByBanAndUnban(guild, target.getUser())) - .queue(onSuccess, onFailure); - } + boolean canDo; + if (action == AlertAction.SCAM) { + Role quarantinedRole = ModerationUtils.getQuarantinedRole(guild, config).orElse(null); + canDo = ModerationUtils.handleRoleChangeChecks(quarantinedRole, "quarantine", target, + bot, moderator, guild, SCAM_REASON, event); + } else { + canDo = ModerationUtils.handleCanInteractWithTarget("ban", bot, moderator, target, + event); + } - private boolean handleCanQuarantineAndBan(Guild guild, Member target, - ButtonInteractionEvent event) { - Member bot = guild.getSelfMember(); - Member moderator = Objects.requireNonNull(event.getMember()); - Role quarantinedRole = ModerationUtils.getQuarantinedRole(guild, config).orElse(null); + canDo &= ModerationUtils.handleHasBotPermissions("ban", Permission.BAN_MEMBERS, bot, guild, + event); - return ModerationUtils.handleRoleChangeChecks(quarantinedRole, "quarantine", target, bot, - moderator, guild, ACTION_REASON, event) - && ModerationUtils.handleHasBotPermissions("ban", Permission.BAN_MEMBERS, bot, - guild, event); + if (!canDo) { + logger.warn(LogMarkers.SENSITIVE, + "Moderator '{}' tried to handle alert for user '{}' in guild '{}', but checks failed.", + moderator.getId(), target.getId(), guild.getId()); + return; + } + + boolean isScam = action == AlertAction.SCAM; + + RestAction handling = isScam ? quarantineAndRemove(guild, target, moderator) + : warnAndRemove(guild, target, moderator); + + String successText = + isScam ? "This is scam. The user was quarantined and messages were deleted." + : "This is a job-ad. The user was warned and messages were deleted."; + + String pendingText = "%s is handling this alert...".formatted(moderator.getAsMention()); + + event.editMessageEmbeds(withResult(alertEmbeds, pendingText, Color.ORANGE)) + .setComponents() + .queue(hook -> handling.queue(_ -> showResult(hook, alertEmbeds, + "%s (handled by %s)".formatted(successText, moderator.getAsMention()), + Color.GREEN), failure -> { + logger.warn(LogMarkers.SENSITIVE, + "Failed to handle alert for user '{}' in guild '{}'.", + target.getId(), guild.getId(), failure); + showResult(hook, alertEmbeds, + "Handling this alert failed, please check the logs and handle the user manually.", + Color.RED); + })); } - private RestAction sendQuarantineDm(User target, Guild guild) { + private RestAction quarantineAndRemove(Guild guild, Member target, Member moderator) { String description = """ Hey there, sorry to tell you but unfortunately you have been put under quarantine after sending scam. @@ -284,28 +309,71 @@ private RestAction sendQuarantineDm(User target, Guild guild) { After you regained control of your account make sure you secure it properly, for example by using 2FA. You can then join back our server and contact the mods to get unquarantined."""; - return ModerationUtils.sendModActionDm(ModerationUtils.getModActionEmbed(guild, - ACTION_TITLE, description, ACTION_REASON, true), target); + return sendDm(guild, target.getUser(), "Quarantine", description, SCAM_REASON) + .flatMap(_ -> { + recordAction(guild, target, moderator, ModerationAction.QUARANTINE, SCAM_REASON); + return guild + .addRoleToMember(target, + ModerationUtils.getQuarantinedRole(guild, config).orElseThrow()) + .reason(SCAM_REASON); + }) + .flatMap(_ -> deleteMessagesByBanAndUnban(guild, target.getUser(), SCAM_REASON)); + } + + private RestAction warnAndRemove(Guild guild, Member target, Member moderator) { + String description = + """ + Hey there, sorry to tell you but unfortunately you have been warned in the server for posting a job advertisement. + To delete your messages you were briefly removed from the server, you are welcome to join back."""; + + return sendDm(guild, target.getUser(), "Warning", description, JOB_AD_REASON).flatMap(_ -> { + recordAction(guild, target, moderator, ModerationAction.WARN, JOB_AD_REASON); + return deleteMessagesByBanAndUnban(guild, target.getUser(), JOB_AD_REASON); + }); } - private RestAction quarantineUser(Guild guild, Member target, Member moderator) { + private static RestAction deleteMessagesByBanAndUnban(Guild guild, User target, + String reason) { + return guild.ban(target, 1, TimeUnit.DAYS) + .reason(reason) + .flatMap(_ -> guild.unban(target).reason(reason)); + } + + private static RestAction sendDm(Guild guild, User target, String title, + String description, String reason) { + return ModerationUtils.sendModActionDm( + ModerationUtils.getModActionEmbed(guild, title, description, reason, true), target); + } + + private void recordAction(Guild guild, Member target, Member moderator, ModerationAction action, + String reason) { logger.info(LogMarkers.SENSITIVE, - "'{}' ({}) quarantined the user '{}' ({}) in guild '{}' for reason '{}'.", - moderator.getUser().getName(), moderator.getId(), target.getUser().getName(), - target.getId(), guild.getName(), ACTION_REASON); + "'{}' ({}) executed '{}' against the user '{}' ({}) in guild '{}' for reason '{}'.", + moderator.getUser().getName(), moderator.getId(), action, + target.getUser().getName(), target.getId(), guild.getName(), reason); + + actionsStore.addAction(guild.getIdLong(), moderator.getIdLong(), target.getIdLong(), action, + null, reason); + } + + private static List withResult(List alertEmbeds, String text, + Color color) { + MessageEmbed resultEmbed = new EmbedBuilder().setDescription(text).setColor(color).build(); + + List embeds = new ArrayList<>(alertEmbeds); + embeds.add(resultEmbed); - actionsStore.addAction(guild.getIdLong(), moderator.getIdLong(), target.getIdLong(), - ModerationAction.QUARANTINE, null, ACTION_REASON); + return embeds; + } - return guild - .addRoleToMember(target, - ModerationUtils.getQuarantinedRole(guild, config).orElseThrow()) - .reason(ACTION_REASON); + private static void showResult(InteractionHook hook, List alertEmbeds, + String text, Color color) { + hook.editOriginalEmbeds(withResult(alertEmbeds, text, color)).queue(); } - private RestAction deleteMessagesByBanAndUnban(Guild guild, User target) { - return guild.ban(target, 1, TimeUnit.DAYS) - .reason(ACTION_REASON) - .flatMap(_ -> guild.unban(target).reason(ACTION_REASON)); + private enum AlertAction { + SCAM, + JOB_AD, + DISMISS } } diff --git a/build.gradle b/build.gradle index 84e5316af6..d87a6c3b86 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ version '1.0-SNAPSHOT' ext { jooqVersion = '3.21.2' jacksonVersion = '2.19.1' - chatGPTVersion = '4.57.0' + chatGPTVersion = '4.58.0' junitVersion = '6.1.0' } From b1b8b59f1250074c72bc1bc27996f475e074020f Mon Sep 17 00:00:00 2001 From: Firas Regaieg Date: Sat, 10 Oct 2026 00:56:34 +0100 Subject: [PATCH 2/4] Rolling back build.gradle (was added by mistake) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d87a6c3b86..84e5316af6 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ version '1.0-SNAPSHOT' ext { jooqVersion = '3.21.2' jacksonVersion = '2.19.1' - chatGPTVersion = '4.58.0' + chatGPTVersion = '4.57.0' junitVersion = '6.1.0' } From 9e34386f58d1b03617e3136cdf2959d13ee26ad8 Mon Sep 17 00:00:00 2001 From: Firas Regaieg Date: Sat, 10 Oct 2026 15:34:24 +0100 Subject: [PATCH 3/4] Applies adjustments and changes for CR1 --- .../moderation/ThisIsScamCommand.java | 179 ++++++++++-------- 1 file changed, 95 insertions(+), 84 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/ThisIsScamCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/ThisIsScamCommand.java index d6bcf1a7cb..3a343842ed 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/ThisIsScamCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/ThisIsScamCommand.java @@ -39,11 +39,11 @@ import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; import java.util.ArrayList; -import java.util.EnumSet; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -80,7 +80,6 @@ public final class ThisIsScamCommand extends BotCommandAdapter implements Messag private final Cache reportedMessageToTimestamp = Caffeine.newBuilder().maximumSize(10_000).expireAfterWrite(Duration.ofDays(1)).build(); - private final Cache userToLastCommandUse = Caffeine.newBuilder() .maximumSize(10_000) .expireAfterWrite(USER_COMMAND_COOLDOWN) @@ -89,8 +88,8 @@ public final class ThisIsScamCommand extends BotCommandAdapter implements Messag /** * Creates a new instance. * - * @param config to resolve the mod mail channel and quarantined role - * @param actionsStore used to store issued quarantine and warn actions + * @param config resolve the mod channel + * @param actionsStore used to store issued moderative actions */ public ThisIsScamCommand(Config config, ModerationActionsStore actionsStore) { super(Commands.message(COMMAND_NAME), CommandVisibility.GUILD); @@ -110,14 +109,14 @@ public void onMessageContext(MessageContextInteractionEvent event) { } Message message = event.getTarget(); - Optional modMailChannel = findModMailChannel(event); - if (modMailChannel.isEmpty()) { + Optional modChannel = findModChannel(event); + if (modChannel.isEmpty()) { reportedMessageToTimestamp.invalidate(message.getIdLong()); event.reply(FAILED_MESSAGE).setEphemeral(true).queue(); return; } - alertMods(message, modMailChannel.orElseThrow()).mapToResult().map(result -> { + reportToMods(message, modChannel.orElseThrow()).mapToResult().map(result -> { if (result.isFailure()) { reportedMessageToTimestamp.invalidate(message.getIdLong()); logger.warn("Unable to forward a report to the mod mail channel.", @@ -158,7 +157,7 @@ private boolean handleWasAlreadyReportedMessage(MessageContextInteractionEvent e return false; } - private Optional findModMailChannel(MessageContextInteractionEvent event) { + private Optional findModChannel(MessageContextInteractionEvent event) { Guild guild = Objects.requireNonNull(event.getGuild()); Optional modMailChannel = Guilds.findTextChannel(guild, isModMailChannel); if (modMailChannel.isEmpty()) { @@ -169,27 +168,28 @@ private Optional findModMailChannel(MessageContextInteractionEvent return modMailChannel; } - private MessageCreateAction alertMods(Message message, TextChannel modMailChannel) { + private MessageCreateAction reportToMods(Message message, TextChannel modMailChannel) { User author = message.getAuthor(); + String description = createDescription(message); long accountAgeDays = ChronoUnit.DAYS.between(author.getTimeCreated(), OffsetDateTime.now(ZoneOffset.UTC)); - MessageEmbed alertEmbed = new EmbedBuilder().setTitle("Rule Violation") - .setDescription(MessageUtils.abbreviate(createDescription(message), - MessageEmbed.DESCRIPTION_MAX_LENGTH)) + MessageEmbed reportEmbed = new EmbedBuilder().setTitle("Rule Violation") + .setDescription( + MessageUtils.abbreviate(description, MessageEmbed.DESCRIPTION_MAX_LENGTH)) .setAuthor(author.getName(), null, author.getEffectiveAvatarUrl()) .setTimestamp(message.getTimeCreated()) .setColor(AmbientColors.MODERATION_SCAM) .setFooter("%s - account age %d days".formatted(author.getId(), accountAgeDays)) .build(); - return modMailChannel.sendMessageEmbeds(alertEmbed) - .addActionRow(Button.success(generateComponentId(AlertAction.SCAM, message), "Scam"), - Button.success(generateComponentId(AlertAction.JOB_AD, message), "Job-Ad"), - Button.danger(generateComponentId(AlertAction.DISMISS, message), "Dismiss")); + return modMailChannel.sendMessageEmbeds(reportEmbed) + .addActionRow(Button.success(generateComponentId(ReportAction.SCAM, message), "Scam"), + Button.success(generateComponentId(ReportAction.JOB_AD, message), "Job-Ad"), + Button.danger(generateComponentId(ReportAction.DISMISS, message), "Dismiss")); } - private String generateComponentId(AlertAction action, Message message) { + private String generateComponentId(ReportAction action, Message message) { return super.generateComponentId(String.valueOf(message.getGuild().getIdLong()), String.valueOf(message.getAuthor().getIdLong()), action.name()); } @@ -217,86 +217,97 @@ private static String createDescription(Message target) { @Override public void onButtonClick(ButtonInteractionEvent event, List args) { + long guildId = Long.parseLong(args.get(0)); + long targetId = Long.parseLong(args.get(1)); + ReportAction action = ReportAction.valueOf(args.get(2)); + Member moderator = Objects.requireNonNull(event.getMember()); - List alertEmbeds = new ArrayList<>(event.getMessage().getEmbeds()); - AlertAction action = AlertAction.valueOf(args.get(2)); + List existingEmbeds = new ArrayList<>(event.getMessage().getEmbeds()); - if (action == AlertAction.DISMISS) { - String text = - "Dismissed by %s, no action executed.".formatted(moderator.getAsMention()); - event.editMessageEmbeds(withResult(alertEmbeds, text, Color.RED)) + if (action == ReportAction.DISMISS) { + event + .editMessageEmbeds(createResponseEmbeds(existingEmbeds, + "Dismissed, no action executed.", Color.RED)) .setComponents() .queue(); return; } - ErrorHandler authorGoneHandler = new ErrorHandler().handle( - EnumSet.of(ErrorResponse.UNKNOWN_USER, ErrorResponse.UNKNOWN_MEMBER), - _ -> event.reply("The user is not a member of this server anymore, nothing to do.") - .setEphemeral(true) - .queue()); + ErrorHandler errorHandler = new ErrorHandler() + .handle(ErrorResponse.UNKNOWN_USER, failure -> logger.debug(LogMarkers.SENSITIVE, + "Attempted to handle user-reported scam, but user '{}' does not exist anymore.", + targetId)) + .handle(ErrorResponse.UNKNOWN_MEMBER, failure -> logger.debug(LogMarkers.SENSITIVE, + "Attempted to handle user-reported scam, but user '{}' is not a member of guild '{}' anymore.", + targetId, guildId)); - long guildId = Long.parseLong(args.get(0)); - long authorId = Long.parseLong(args.get(1)); Guild guild = Objects.requireNonNull(event.getJDA().getGuildById(guildId)); - guild.retrieveMemberById(authorId) + guild.retrieveMemberById(targetId) .queue(target -> handleConfirmedViolation(event, action, guild, moderator, target, - alertEmbeds), authorGoneHandler); + existingEmbeds), errorHandler); } - /** - * The checks reply to the event when they fail, so they have to run before the event is - * acknowledged. On failure the buttons stay, so the moderator can fix the issue and retry. - */ - private void handleConfirmedViolation(ButtonInteractionEvent event, AlertAction action, + private void handleConfirmedViolation(ButtonInteractionEvent event, ReportAction action, Guild guild, Member moderator, Member target, List alertEmbeds) { Member bot = guild.getSelfMember(); - boolean canDo; - if (action == AlertAction.SCAM) { - Role quarantinedRole = ModerationUtils.getQuarantinedRole(guild, config).orElse(null); - canDo = ModerationUtils.handleRoleChangeChecks(quarantinedRole, "quarantine", target, - bot, moderator, guild, SCAM_REASON, event); - } else { - canDo = ModerationUtils.handleCanInteractWithTarget("ban", bot, moderator, target, - event); - } + boolean canExecute = switch (action) { + case SCAM -> { + Role quarantinedRole = + ModerationUtils.getQuarantinedRole(guild, config).orElse(null); + yield ModerationUtils.handleRoleChangeChecks(quarantinedRole, "quarantine", target, + bot, moderator, guild, SCAM_REASON, event); + } + case JOB_AD -> + ModerationUtils.handleCanInteractWithTarget("ban", bot, moderator, target, event); + default -> false; + }; - canDo &= ModerationUtils.handleHasBotPermissions("ban", Permission.BAN_MEMBERS, bot, guild, - event); + canExecute &= ModerationUtils.handleHasBotPermissions("ban", Permission.BAN_MEMBERS, bot, + guild, event); - if (!canDo) { + if (!canExecute) { logger.warn(LogMarkers.SENSITIVE, - "Moderator '{}' tried to handle alert for user '{}' in guild '{}', but checks failed.", + "Moderator '{}' tried to execute report for user '{}' in guild '{}', but checks failed.", moderator.getId(), target.getId(), guild.getId()); return; } - boolean isScam = action == AlertAction.SCAM; - - RestAction handling = isScam ? quarantineAndRemove(guild, target, moderator) - : warnAndRemove(guild, target, moderator); - - String successText = - isScam ? "This is scam. The user was quarantined and messages were deleted." - : "This is a job-ad. The user was warned and messages were deleted."; + String successText; + RestAction handling = switch (action) { + case SCAM -> { + successText = "This is scam. The user was quarantined and messages were deleted."; + yield quarantineAndRemove(guild, target, moderator); + } + case JOB_AD -> { + successText = "This is a job-ad. The user was warned and messages were deleted."; + yield warnAndRemove(guild, target, moderator); + } + default -> throw new IllegalStateException("Unknown action provided: " + action); + }; String pendingText = "%s is handling this alert...".formatted(moderator.getAsMention()); - event.editMessageEmbeds(withResult(alertEmbeds, pendingText, Color.ORANGE)) + event.editMessageEmbeds(createResponseEmbeds(alertEmbeds, pendingText, Color.ORANGE)) .setComponents() - .queue(hook -> handling.queue(_ -> showResult(hook, alertEmbeds, - "%s (handled by %s)".formatted(successText, moderator.getAsMention()), - Color.GREEN), failure -> { - logger.warn(LogMarkers.SENSITIVE, - "Failed to handle alert for user '{}' in guild '{}'.", - target.getId(), guild.getId(), failure); - showResult(hook, alertEmbeds, - "Handling this alert failed, please check the logs and handle the user manually.", - Color.RED); - })); + .queue(hook -> { + Consumer onSuccess = ignored -> showResult(hook, alertEmbeds, + "%s (handled by %s)".formatted(successText, moderator.getAsMention()), + Color.GREEN); + + Consumer onFailure = failure -> { + logger.warn(LogMarkers.SENSITIVE, + "Failed to handle alert for user '{}' in guild '{}'.", target.getId(), + guild.getId(), failure); + showResult(hook, alertEmbeds, + "Handling this alert failed, please check the logs and handle the user manually.", + Color.RED); + }; + + handling.queue(onSuccess, onFailure); + }); } private RestAction quarantineAndRemove(Guild guild, Member target, Member moderator) { @@ -317,7 +328,7 @@ private RestAction quarantineAndRemove(Guild guild, Member target, Member ModerationUtils.getQuarantinedRole(guild, config).orElseThrow()) .reason(SCAM_REASON); }) - .flatMap(_ -> deleteMessagesByBanAndUnban(guild, target.getUser(), SCAM_REASON)); + .flatMap(_ -> deleteMessagesByBanThenUnban(guild, target.getUser(), SCAM_REASON)); } private RestAction warnAndRemove(Guild guild, Member target, Member moderator) { @@ -328,15 +339,8 @@ private RestAction warnAndRemove(Guild guild, Member target, Member modera return sendDm(guild, target.getUser(), "Warning", description, JOB_AD_REASON).flatMap(_ -> { recordAction(guild, target, moderator, ModerationAction.WARN, JOB_AD_REASON); - return deleteMessagesByBanAndUnban(guild, target.getUser(), JOB_AD_REASON); - }); - } - - private static RestAction deleteMessagesByBanAndUnban(Guild guild, User target, - String reason) { - return guild.ban(target, 1, TimeUnit.DAYS) - .reason(reason) - .flatMap(_ -> guild.unban(target).reason(reason)); + return deleteMessagesByBanThenUnban(guild, target.getUser(), JOB_AD_REASON); + }).flatMap(_ -> deleteMessagesByBanThenUnban(guild, target.getUser(), JOB_AD_REASON)); } private static RestAction sendDm(Guild guild, User target, String title, @@ -356,11 +360,18 @@ private void recordAction(Guild guild, Member target, Member moderator, Moderati null, reason); } - private static List withResult(List alertEmbeds, String text, - Color color) { + private static RestAction deleteMessagesByBanThenUnban(Guild guild, User target, + String reason) { + return guild.ban(target, 1, TimeUnit.DAYS) + .reason(reason) + .flatMap(_ -> guild.unban(target).reason(reason)); + } + + private static List createResponseEmbeds(List stagedEmbeds, + String text, Color color) { MessageEmbed resultEmbed = new EmbedBuilder().setDescription(text).setColor(color).build(); - List embeds = new ArrayList<>(alertEmbeds); + List embeds = new ArrayList<>(stagedEmbeds); embeds.add(resultEmbed); return embeds; @@ -368,10 +379,10 @@ private static List withResult(List alertEmbeds, Str private static void showResult(InteractionHook hook, List alertEmbeds, String text, Color color) { - hook.editOriginalEmbeds(withResult(alertEmbeds, text, color)).queue(); + hook.editOriginalEmbeds(createResponseEmbeds(alertEmbeds, text, color)).queue(); } - private enum AlertAction { + private enum ReportAction { SCAM, JOB_AD, DISMISS From a2a6af70251b7dd2d246949ce84305541ba0563c Mon Sep 17 00:00:00 2001 From: Firas Regaieg Date: Sat, 10 Oct 2026 15:54:53 +0100 Subject: [PATCH 4/4] CR1: remove the custom generateComponentId method; --- .../moderation/ThisIsScamCommand.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/ThisIsScamCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/ThisIsScamCommand.java index 3a343842ed..9becd612d6 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/ThisIsScamCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/ThisIsScamCommand.java @@ -183,15 +183,19 @@ private MessageCreateAction reportToMods(Message message, TextChannel modMailCha .setFooter("%s - account age %d days".formatted(author.getId(), accountAgeDays)) .build(); - return modMailChannel.sendMessageEmbeds(reportEmbed) - .addActionRow(Button.success(generateComponentId(ReportAction.SCAM, message), "Scam"), - Button.success(generateComponentId(ReportAction.JOB_AD, message), "Job-Ad"), - Button.danger(generateComponentId(ReportAction.DISMISS, message), "Dismiss")); - } + String guildId = String.valueOf(message.getGuild().getIdLong()); + String authorId = String.valueOf(author.getIdLong()); - private String generateComponentId(ReportAction action, Message message) { - return super.generateComponentId(String.valueOf(message.getGuild().getIdLong()), - String.valueOf(message.getAuthor().getIdLong()), action.name()); + return modMailChannel.sendMessageEmbeds(reportEmbed) + .addActionRow( + Button.success(generateComponentId(guildId, authorId, ReportAction.SCAM.name()), + "Scam"), + Button.success( + generateComponentId(guildId, authorId, ReportAction.JOB_AD.name()), + "Job-Ad"), + Button.danger( + generateComponentId(guildId, authorId, ReportAction.DISMISS.name()), + "Dismiss")); } private static String createDescription(Message target) {