From dbf5d99622a626b05994c48529624235fd3b75e2 Mon Sep 17 00:00:00 2001 From: Sheldan <5037282+Sheldan@users.noreply.github.com> Date: Sun, 17 Mar 2024 12:37:30 +0100 Subject: [PATCH] [AB-111] adding ability to perform moderation actions on various logging/report messages --- .../service/InviteLinkFilterServiceBean.java | 13 ++ .../resources/invite-filter-config.properties | 3 + .../invite-filter/invite-filter-int/pom.xml | 6 + .../config/InviteFilterFeatureConfig.java | 2 +- .../invitefilter/config/InviteFilterMode.java | 2 +- .../DeletedInvitesNotificationModel.java | 2 + .../abstracto/moderation/command/Ban.java | 6 +- .../abstracto/moderation/command/Kick.java | 50 +---- .../abstracto/moderation/command/Mute.java | 56 +++-- .../abstracto/moderation/command/UnBan.java | 5 +- .../abstracto/moderation/command/UnMute.java | 12 +- .../abstracto/moderation/command/Warn.java | 28 +-- .../listener/BanModerationActionListener.java | 114 +++++++++++ .../BanModerationActionModalListener.java | 113 +++++++++++ .../listener/HoneyPotRoleAddedListener.java | 5 +- .../KickModerationActionListener.java | 111 ++++++++++ .../KickModerationActionModalListener.java | 83 ++++++++ .../listener/MemberTimeoutListener.java | 5 +- .../MuteModerationActionListener.java | 114 +++++++++++ .../MuteModerationActionModalListener.java | 117 +++++++++++ .../WarnModerationActionListener.java | 111 ++++++++++ .../WarnModerationActionModalListener.java | 98 +++++++++ .../infraction/BanReasonUpdatedListener.java | 6 +- .../infraction/WarnReasonUpdatedListener.java | 12 +- .../moderation/service/BanServiceBean.java | 49 ++--- .../moderation/service/KickServiceBean.java | 72 +++++-- .../service/ModerationActionServiceBean.java | 95 +++++++++ .../moderation/service/MuteServiceBean.java | 191 +++++++----------- .../service/ReactionReportServiceBean.java | 13 ++ .../moderation/service/WarnServiceBean.java | 109 +++++----- .../resources/moderation-config.properties | 4 + .../moderation/command/KickTest.java | 80 -------- .../moderation/command/WarnTest.java | 78 ------- .../moderation/command/mute/MuteTest.java | 58 ------ .../feature/ReportReactionFeatureConfig.java | 2 +- .../feature/WarningDecayFeatureConfig.java | 2 +- .../config/feature/WarningFeatureConfig.java | 2 +- .../feature/mode/ReportReactionMode.java | 2 +- .../model/ModerationActionButton.java | 11 + .../moderation/model/MuteResult.java | 6 + .../ModerationActionBanPayload.java | 15 ++ .../ModerationActionKickPayload.java | 14 ++ .../ModerationActionMutePayload.java | 15 ++ .../ModerationActionWarnPayload.java | 14 ++ .../model/template/command/BanLog.java | 10 +- .../model/template/command/KickLogModel.java | 16 +- .../template/command/MuteListenerModel.java | 15 +- .../model/template/command/UnBanLog.java | 11 +- .../model/template/command/UnMuteLog.java | 9 +- .../model/template/command/WarnContext.java | 36 ---- .../model/template/command/WarnLogModel.java | 29 +++ .../ModerationActionBanModalModel.java | 12 ++ .../ModerationActionKickModalModel.java | 11 + .../ModerationActionMuteModalModel.java | 12 ++ .../ModerationActionPayloadModel.java | 23 +++ .../ModerationActionWarnModalModel.java | 11 + .../ReportReactionNotificationModel.java | 4 + .../moderation/service/BanService.java | 11 +- .../moderation/service/KickService.java | 6 +- .../service/ModerationActionService.java | 10 + .../moderation/service/MuteService.java | 19 +- .../moderation/service/WarnService.java | 7 +- .../service/ProfanityFilterServiceBean.java | 17 +- .../profanityFilter-config.properties | 4 + .../profanity-filter-int/pom.xml | 5 + .../config/ProfanityFilterFeatureConfig.java | 7 +- .../config/ProfanityFilterMode.java | 1 + .../model/template/ProfanityReportModel.java | 4 + .../ModalInteractionListenerBean.java | 2 +- .../core/service/MemberServiceBean.java | 29 ++- .../core/service/MessageServiceBean.java | 8 + .../abstracto/core/models/ServerUser.java | 11 +- .../template/display/MemberDisplay.java | 12 ++ .../models/template/display/UserDisplay.java | 41 ++++ .../abstracto/core/service/MemberService.java | 6 +- .../core/service/MessageService.java | 2 + 76 files changed, 1661 insertions(+), 646 deletions(-) create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/BanModerationActionListener.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/BanModerationActionModalListener.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/KickModerationActionListener.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/KickModerationActionModalListener.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/MuteModerationActionListener.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/MuteModerationActionModalListener.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/WarnModerationActionListener.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/WarnModerationActionModalListener.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/ModerationActionServiceBean.java delete mode 100644 abstracto-application/abstracto-modules/moderation/moderation-impl/src/test/java/dev/sheldan/abstracto/moderation/command/KickTest.java delete mode 100644 abstracto-application/abstracto-modules/moderation/moderation-impl/src/test/java/dev/sheldan/abstracto/moderation/command/WarnTest.java delete mode 100644 abstracto-application/abstracto-modules/moderation/moderation-impl/src/test/java/dev/sheldan/abstracto/moderation/command/mute/MuteTest.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/ModerationActionButton.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/MuteResult.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/interaction/ModerationActionBanPayload.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/interaction/ModerationActionKickPayload.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/interaction/ModerationActionMutePayload.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/interaction/ModerationActionWarnPayload.java delete mode 100644 abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/WarnContext.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/WarnLogModel.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/listener/ModerationActionBanModalModel.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/listener/ModerationActionKickModalModel.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/listener/ModerationActionMuteModalModel.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/listener/ModerationActionPayloadModel.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/listener/ModerationActionWarnModalModel.java create mode 100644 abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/service/ModerationActionService.java create mode 100644 abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/template/display/UserDisplay.java diff --git a/abstracto-application/abstracto-modules/invite-filter/invite-filter-impl/src/main/java/dev/sheldan/abstracto/invitefilter/service/InviteLinkFilterServiceBean.java b/abstracto-application/abstracto-modules/invite-filter/invite-filter-impl/src/main/java/dev/sheldan/abstracto/invitefilter/service/InviteLinkFilterServiceBean.java index 0fc9d7cc7..a8b203314 100644 --- a/abstracto-application/abstracto-modules/invite-filter/invite-filter-impl/src/main/java/dev/sheldan/abstracto/invitefilter/service/InviteLinkFilterServiceBean.java +++ b/abstracto-application/abstracto-modules/invite-filter/invite-filter-impl/src/main/java/dev/sheldan/abstracto/invitefilter/service/InviteLinkFilterServiceBean.java @@ -21,6 +21,8 @@ import dev.sheldan.abstracto.invitefilter.model.template.listener.DeletedInvitesNotificationModel; import dev.sheldan.abstracto.invitefilter.service.management.AllowedInviteLinkManagement; import dev.sheldan.abstracto.invitefilter.service.management.FilteredInviteLinkManagement; +import dev.sheldan.abstracto.moderation.model.ModerationActionButton; +import dev.sheldan.abstracto.moderation.service.ModerationActionService; import lombok.Builder; import lombok.Getter; import lombok.Setter; @@ -78,6 +80,9 @@ public class InviteLinkFilterServiceBean implements InviteLinkFilterService { @Autowired private RoleImmunityService roleImmunityService; + @Autowired(required = false) + private ModerationActionService moderationActionService; + private static final Pattern INVITE_CODE_PATTERN = Pattern.compile("(?[a-z0-9-]+)", Pattern.CASE_INSENSITIVE); public static final String INVITE_FILTER_METRIC = "invite.filter"; @@ -230,10 +235,18 @@ private CompletableFuture sendDeletionNotification(List co log.info("Post target {} not defined for server {} - not sending invite link deletion notification.", InviteFilterPostTarget.INVITE_DELETE_LOG.getKey(), serverId); return CompletableFuture.completedFuture(null); } + boolean moderationActionsEnabled = featureModeService.featureModeActive(InviteFilterFeatureDefinition.INVITE_FILTER, serverId, InviteFilterMode.FILTER_MODERATION_ACTIONS); + List moderationActionComponents = new ArrayList<>(); + if(moderationActionsEnabled && moderationActionService != null) { + ServerUser reportedServerUser = ServerUser.fromMember(message.getMember()); + List moderationActions = moderationActionService.getModerationActionButtons(reportedServerUser); + moderationActionComponents.addAll(moderationActions); + } DeletedInvitesNotificationModel model = DeletedInvitesNotificationModel .builder() .author(message.getMember()) .guild(message.getGuild()) + .moderationActionComponents(moderationActionComponents) .message(message) .channel(message.getChannel()) .invites(groupInvites(codes)) diff --git a/abstracto-application/abstracto-modules/invite-filter/invite-filter-impl/src/main/resources/invite-filter-config.properties b/abstracto-application/abstracto-modules/invite-filter/invite-filter-impl/src/main/resources/invite-filter-config.properties index 757577d8f..efeb0134c 100644 --- a/abstracto-application/abstracto-modules/invite-filter/invite-filter-impl/src/main/resources/invite-filter-config.properties +++ b/abstracto-application/abstracto-modules/invite-filter/invite-filter-impl/src/main/resources/invite-filter-config.properties @@ -11,3 +11,6 @@ abstracto.featureModes.filterNotifications.featureName=inviteFilter abstracto.featureModes.filterNotifications.mode=filterNotifications abstracto.featureModes.filterNotifications.enabled=true +abstracto.featureModes.filterModerationActions.featureName=inviteFilter +abstracto.featureModes.filterModerationActions.mode=filterModerationActions +abstracto.featureModes.filterModerationActions.enabled=false \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/invite-filter/invite-filter-int/pom.xml b/abstracto-application/abstracto-modules/invite-filter/invite-filter-int/pom.xml index 313cc4dfe..dc0cc7f42 100644 --- a/abstracto-application/abstracto-modules/invite-filter/invite-filter-int/pom.xml +++ b/abstracto-application/abstracto-modules/invite-filter/invite-filter-int/pom.xml @@ -15,6 +15,12 @@ core-int ${project.version} + + dev.sheldan.abstracto.modules + moderation-int + 1.5.27-SNAPSHOT + compile + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/invite-filter/invite-filter-int/src/main/java/dev/sheldan/abstracto/invitefilter/config/InviteFilterFeatureConfig.java b/abstracto-application/abstracto-modules/invite-filter/invite-filter-int/src/main/java/dev/sheldan/abstracto/invitefilter/config/InviteFilterFeatureConfig.java index 6bad9dec0..c88cc86ae 100644 --- a/abstracto-application/abstracto-modules/invite-filter/invite-filter-int/src/main/java/dev/sheldan/abstracto/invitefilter/config/InviteFilterFeatureConfig.java +++ b/abstracto-application/abstracto-modules/invite-filter/invite-filter-int/src/main/java/dev/sheldan/abstracto/invitefilter/config/InviteFilterFeatureConfig.java @@ -23,6 +23,6 @@ public List getRequiredPostTargets() { @Override public List getAvailableModes() { - return Arrays.asList(InviteFilterMode.FILTER_NOTIFICATIONS, InviteFilterMode.TRACK_USES); + return Arrays.asList(InviteFilterMode.values()); } } diff --git a/abstracto-application/abstracto-modules/invite-filter/invite-filter-int/src/main/java/dev/sheldan/abstracto/invitefilter/config/InviteFilterMode.java b/abstracto-application/abstracto-modules/invite-filter/invite-filter-int/src/main/java/dev/sheldan/abstracto/invitefilter/config/InviteFilterMode.java index f6065276e..110009497 100644 --- a/abstracto-application/abstracto-modules/invite-filter/invite-filter-int/src/main/java/dev/sheldan/abstracto/invitefilter/config/InviteFilterMode.java +++ b/abstracto-application/abstracto-modules/invite-filter/invite-filter-int/src/main/java/dev/sheldan/abstracto/invitefilter/config/InviteFilterMode.java @@ -5,7 +5,7 @@ @Getter public enum InviteFilterMode implements FeatureMode { - TRACK_USES("trackUses"), FILTER_NOTIFICATIONS("filterNotifications"); + TRACK_USES("trackUses"), FILTER_NOTIFICATIONS("filterNotifications"), FILTER_MODERATION_ACTIONS("filterModerationActions"); private final String key; diff --git a/abstracto-application/abstracto-modules/invite-filter/invite-filter-int/src/main/java/dev/sheldan/abstracto/invitefilter/model/template/listener/DeletedInvitesNotificationModel.java b/abstracto-application/abstracto-modules/invite-filter/invite-filter-int/src/main/java/dev/sheldan/abstracto/invitefilter/model/template/listener/DeletedInvitesNotificationModel.java index cb08ab33c..c7a3b82a4 100644 --- a/abstracto-application/abstracto-modules/invite-filter/invite-filter-int/src/main/java/dev/sheldan/abstracto/invitefilter/model/template/listener/DeletedInvitesNotificationModel.java +++ b/abstracto-application/abstracto-modules/invite-filter/invite-filter-int/src/main/java/dev/sheldan/abstracto/invitefilter/model/template/listener/DeletedInvitesNotificationModel.java @@ -1,5 +1,6 @@ package dev.sheldan.abstracto.invitefilter.model.template.listener; +import dev.sheldan.abstracto.moderation.model.ModerationActionButton; import lombok.Builder; import lombok.Getter; import lombok.Setter; @@ -17,4 +18,5 @@ public class DeletedInvitesNotificationModel { private Member author; private Message message; private List invites; + private List moderationActionComponents; } diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/Ban.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/Ban.java index 10089ec5e..a210a9078 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/Ban.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/Ban.java @@ -8,7 +8,7 @@ import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.interaction.InteractionService; -import dev.sheldan.abstracto.core.service.ChannelService; +import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.service.UserService; import dev.sheldan.abstracto.core.templating.service.TemplateService; import dev.sheldan.abstracto.core.utils.ParseUtils; @@ -70,7 +70,7 @@ public CompletableFuture executeSlash(SlashCommandInteractionEven } if(slashCommandParameterService.hasCommandOptionWithFullType(USER_PARAMETER, event, OptionType.USER)) { Member member = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, Member.class); - return banService.banUserWithNotification(member.getUser(), reason, event.getMember(), duration) + return banService.banUserWithNotification(ServerUser.fromMember(member), reason, ServerUser.fromMember(event.getMember()), event.getGuild(), duration) .thenCompose(banResult -> { if(banResult == NOTIFICATION_FAILED) { String errorNotification = templateService.renderSimpleTemplate(BAN_NOTIFICATION_NOT_POSSIBLE, event.getGuild().getIdLong()); @@ -84,7 +84,7 @@ public CompletableFuture executeSlash(SlashCommandInteractionEven String userIdStr = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, String.class); Long userId = Long.parseLong(userIdStr); return userService.retrieveUserForId(userId) - .thenCompose(user -> banService.banUserWithNotification(user, reason, event.getMember(), duration)) + .thenCompose(user -> banService.banUserWithNotification(ServerUser.fromId(event.getGuild().getIdLong(), userId), reason, ServerUser.fromMember(event.getMember()), event.getGuild(), duration)) .thenCompose(banResult -> { if(banResult == NOTIFICATION_FAILED) { String errorNotification = templateService.renderSimpleTemplate(BAN_NOTIFICATION_NOT_POSSIBLE, event.getGuild().getIdLong()); diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/Kick.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/Kick.java index 6ed1b5f2d..1630f1411 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/Kick.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/Kick.java @@ -2,20 +2,21 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand; import dev.sheldan.abstracto.core.command.condition.CommandCondition; -import dev.sheldan.abstracto.core.command.config.*; -import dev.sheldan.abstracto.core.command.execution.CommandContext; +import dev.sheldan.abstracto.core.command.config.CommandConfiguration; +import dev.sheldan.abstracto.core.command.config.EffectConfig; +import dev.sheldan.abstracto.core.command.config.HelpInfo; +import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.execution.CommandResult; -import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig; -import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException; import dev.sheldan.abstracto.core.interaction.InteractionService; +import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig; +import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService; +import dev.sheldan.abstracto.core.templating.service.TemplateService; import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition; import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames; import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; -import dev.sheldan.abstracto.moderation.model.template.command.KickLogModel; import dev.sheldan.abstracto.moderation.service.KickServiceBean; -import dev.sheldan.abstracto.core.templating.service.TemplateService; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import org.springframework.beans.factory.annotation.Autowired; @@ -47,30 +48,6 @@ public class Kick extends AbstractConditionableCommand { @Autowired private InteractionService interactionService; - @Override - public CompletableFuture executeAsync(CommandContext commandContext) { - List parameters = commandContext.getParameters().getParameters(); - Member member = (Member) parameters.get(0); - if(!member.getGuild().equals(commandContext.getGuild())) { - throw new EntityGuildMismatchException(); - } - String defaultReason = templateService.renderSimpleTemplate(KICK_DEFAULT_REASON_TEMPLATE, commandContext.getGuild().getIdLong()); - String reason = parameters.size() == 2 ? (String) parameters.get(1) : defaultReason; - - KickLogModel kickLogModel = KickLogModel - .builder() - .kickedUser(member) - .reason(reason) - .guild(commandContext.getGuild()) - .channel(commandContext.getChannel()) - .member(commandContext.getAuthor()) - .build(); - kickLogModel.setKickedUser(member); - kickLogModel.setReason(reason); - return kickService.kickMember(member, reason, kickLogModel) - .thenApply(aVoid -> CommandResult.fromSuccess()); - } - @Override public CompletableFuture executeSlash(SlashCommandInteractionEvent event) { Member member = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, Member.class); @@ -84,17 +61,7 @@ public CompletableFuture executeSlash(SlashCommandInteractionEven reason = templateService.renderSimpleTemplate(KICK_DEFAULT_REASON_TEMPLATE, event.getGuild().getIdLong()); } - KickLogModel kickLogModel = KickLogModel - .builder() - .kickedUser(member) - .reason(reason) - .guild(event.getGuild()) - .channel(event.getGuildChannel()) - .member(event.getMember()) - .build(); - kickLogModel.setKickedUser(member); - kickLogModel.setReason(reason); - return kickService.kickMember(member, reason, kickLogModel) + return kickService.kickMember(member, event.getMember(), reason) .thenCompose(unused -> interactionService.replyEmbed(KICK_RESPONSE, event)) .thenApply(aVoid -> CommandResult.fromSuccess()); } @@ -143,6 +110,7 @@ public CommandConfiguration getConfiguration() { .slashCommandConfig(slashCommandConfig) .supportsEmbedException(true) .async(true) + .slashCommandOnly(true) .effects(effectConfig) .causesReaction(true) .parameters(parameters) diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/Mute.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/Mute.java index 1ec92bae5..9c368b4f6 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/Mute.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/Mute.java @@ -10,12 +10,17 @@ import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException; import dev.sheldan.abstracto.core.interaction.InteractionService; +import dev.sheldan.abstracto.core.models.ServerChannelMessage; +import dev.sheldan.abstracto.core.models.ServerUser; +import dev.sheldan.abstracto.core.service.ChannelService; +import dev.sheldan.abstracto.core.templating.model.MessageToSend; import dev.sheldan.abstracto.core.templating.service.TemplateService; +import dev.sheldan.abstracto.core.utils.FutureUtils; import dev.sheldan.abstracto.core.utils.ParseUtils; +import dev.sheldan.abstracto.core.utils.SnowflakeUtils; import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition; import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames; import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; -import dev.sheldan.abstracto.moderation.model.template.command.MuteContext; import dev.sheldan.abstracto.moderation.service.MuteService; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; @@ -24,17 +29,18 @@ import org.springframework.stereotype.Component; import java.time.Duration; -import java.time.Instant; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; +import static dev.sheldan.abstracto.moderation.model.MuteResult.NOTIFICATION_FAILED; import static dev.sheldan.abstracto.moderation.service.MuteService.MUTE_EFFECT_KEY; @Component public class Mute extends AbstractConditionableCommand { private static final String MUTE_DEFAULT_REASON_TEMPLATE = "mute_default_reason"; + public static final String MUTE_NOTIFICATION_NOT_POSSIBLE_TEMPLATE_KEY = "mute_notification_not_possible"; private static final String DURATION_PARAMETER = "duration"; private static final String MUTE_COMMAND = "mute"; private static final String USER_PARAMETER = "user"; @@ -53,6 +59,9 @@ public class Mute extends AbstractConditionableCommand { @Autowired private InteractionService interactionService; + @Autowired + private ChannelService channelService; + @Override public CompletableFuture executeAsync(CommandContext commandContext) { List parameters = commandContext.getParameters().getParameters(); @@ -64,15 +73,19 @@ public CompletableFuture executeAsync(CommandContext commandConte Duration duration = (Duration) parameters.get(1); String defaultReason = templateService.renderSimpleTemplate(MUTE_DEFAULT_REASON_TEMPLATE, guild.getIdLong()); String reason = parameters.size() == 3 ? (String) parameters.get(2) : defaultReason; - MuteContext muteLogModel = MuteContext - .builder() - .muteTargetDate(Instant.now().plus(duration)) - .mutedUser(member) - .channelId(commandContext.getChannel().getIdLong()) - .reason(reason) - .mutingUser(commandContext.getAuthor()) - .build(); - return muteService.muteMemberWithLog(muteLogModel) + ServerUser userToMute = ServerUser.fromMember(member); + ServerUser mutingUser = ServerUser.fromMember(commandContext.getAuthor()); + Long serverId = commandContext.getGuild().getIdLong(); + ServerChannelMessage serverChannelMessage = ServerChannelMessage.fromMessage(commandContext.getMessage()); + return muteService.muteMemberWithLog(userToMute, mutingUser, reason, duration, commandContext.getGuild(), serverChannelMessage) + .thenCompose(muteResult -> { + if(muteResult == NOTIFICATION_FAILED) { + MessageToSend errorNotification = templateService.renderEmbedTemplate(MUTE_NOTIFICATION_NOT_POSSIBLE_TEMPLATE_KEY, new Object(), serverId); + return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(errorNotification, commandContext.getChannel())); + } else { + return CompletableFuture.completedFuture(null); + } + }) .thenApply(aVoid -> CommandResult.fromSuccess()); } @@ -88,16 +101,23 @@ public CompletableFuture executeSlash(SlashCommandInteractionEven } else { reason = templateService.renderSimpleTemplate(MUTE_DEFAULT_REASON_TEMPLATE, guild.getIdLong()); } - MuteContext muteLogModel = MuteContext + Long serverId = event.getGuild().getIdLong(); + ServerChannelMessage commandMessage = ServerChannelMessage .builder() - .muteTargetDate(Instant.now().plus(duration)) - .mutedUser(targetMember) - .reason(reason) + .serverId(serverId) .channelId(event.getChannel().getIdLong()) - .mutingUser(event.getMember()) + .messageId(SnowflakeUtils.createSnowFlake()) .build(); - return muteService.muteMemberWithLog(muteLogModel) - .thenCompose(unused -> interactionService.replyEmbed(MUTE_RESPONSE, event)) + ServerUser userToMute = ServerUser.fromMember(targetMember); + ServerUser mutingUser = ServerUser.fromMember(event.getMember()); + return muteService.muteMemberWithLog(userToMute, mutingUser, reason, duration, event.getGuild(), commandMessage) + .thenCompose(muteResult -> { + if(muteResult == NOTIFICATION_FAILED) { + return interactionService.replyEmbed(MUTE_NOTIFICATION_NOT_POSSIBLE_TEMPLATE_KEY, new Object(), event); + } else { + return interactionService.replyEmbed(MUTE_RESPONSE, event); + } + }) .thenApply(aVoid -> CommandResult.fromSuccess()); } diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/UnBan.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/UnBan.java index 790e58b12..ffa7526bc 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/UnBan.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/UnBan.java @@ -11,6 +11,7 @@ import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.interaction.InteractionService; +import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.service.UserService; import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition; import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames; @@ -50,7 +51,7 @@ public CompletableFuture executeAsync(CommandContext commandConte String userIdStr = (String) parameters.get(0); Long userId = Long.parseLong(userIdStr); return userService.retrieveUserForId(userId) - .thenCompose(user -> banService.unBanUserWithNotification(user, commandContext.getAuthor())) + .thenCompose(user -> banService.unBanUserWithNotification(userId, ServerUser.fromMember(commandContext.getAuthor()), commandContext.getGuild())) .thenApply(aVoid -> CommandResult.fromSuccess()); } @@ -59,7 +60,7 @@ public CompletableFuture executeSlash(SlashCommandInteractionEven String userIdStr = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, String.class); Long userId = Long.parseLong(userIdStr); return userService.retrieveUserForId(userId) - .thenCompose(user -> banService.unBanUserWithNotification(user, event.getMember())) + .thenCompose(user -> banService.unBanUserWithNotification(userId, ServerUser.fromMember(event.getMember()), event.getGuild())) .thenCompose(unused -> interactionService.replyEmbed(UN_BAN_RESPONSE, event)) .thenApply(interactionHook -> CommandResult.fromSuccess()); } diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/UnMute.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/UnMute.java index 189d243f1..7d070fd6c 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/UnMute.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/UnMute.java @@ -11,7 +11,7 @@ import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException; import dev.sheldan.abstracto.core.interaction.InteractionService; -import dev.sheldan.abstracto.core.models.database.AUserInAServer; +import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.service.management.UserInServerManagementService; import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition; import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames; @@ -52,8 +52,9 @@ public CompletableFuture executeAsync(CommandContext commandConte if(!member.getGuild().equals(commandContext.getGuild())) { throw new EntityGuildMismatchException(); } - AUserInAServer userToUnMute = userInServerManagementService.loadOrCreateUser(member); - return muteService.unMuteUser(userToUnMute, commandContext.getAuthor()).thenApply(aVoid -> + ServerUser userToUnmute = ServerUser.fromMember(member); + ServerUser unMutingMember = ServerUser.fromMember(commandContext.getAuthor()); + return muteService.unMuteUser(userToUnmute, unMutingMember, commandContext.getGuild()).thenApply(aVoid -> CommandResult.fromSuccess() ); } @@ -64,8 +65,9 @@ public CompletableFuture executeSlash(SlashCommandInteractionEven if(!targetMember.getGuild().equals(event.getGuild())) { throw new EntityGuildMismatchException(); } - AUserInAServer userToUnMute = userInServerManagementService.loadOrCreateUser(targetMember); - return muteService.unMuteUser(userToUnMute, event.getMember()) + ServerUser userToUnmute = ServerUser.fromMember(targetMember); + ServerUser unMutingMember = ServerUser.fromMember(event.getMember()); + return muteService.unMuteUser(userToUnmute, unMutingMember, event.getGuild()) .thenCompose(unused -> interactionService.replyEmbed(UN_MUTE_RESPONSE, event)) .thenApply(interactionHook -> CommandResult.fromSuccess()); } diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/Warn.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/Warn.java index b91bc81aa..6c62604e9 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/Warn.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/Warn.java @@ -10,10 +10,12 @@ import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException; import dev.sheldan.abstracto.core.interaction.InteractionService; +import dev.sheldan.abstracto.core.models.ServerChannelMessage; +import dev.sheldan.abstracto.core.models.ServerUser; +import dev.sheldan.abstracto.core.utils.SnowflakeUtils; import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition; import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames; import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; -import dev.sheldan.abstracto.moderation.model.template.command.WarnContext; import dev.sheldan.abstracto.moderation.service.WarnService; import dev.sheldan.abstracto.core.templating.service.TemplateService; import lombok.extern.slf4j.Slf4j; @@ -59,16 +61,8 @@ public CompletableFuture executeAsync(CommandContext commandConte } String defaultReason = templateService.renderSimpleTemplate(WARN_DEFAULT_REASON_TEMPLATE, commandContext.getGuild().getIdLong()); String reason = parameters.size() == 2 ? (String) parameters.get(1) : defaultReason; - WarnContext warnLogModel = WarnContext - .builder() - .reason(reason) - .warnedMember(member) - .channel(commandContext.getChannel()) - .member(commandContext.getAuthor()) - .guild(commandContext.getGuild()) - .message(commandContext.getMessage()) - .build(); - return warnService.warnUserWithLog(warnLogModel) + ServerChannelMessage commandMessage = ServerChannelMessage.fromMessage(commandContext.getMessage()); + return warnService.warnUserWithLog(commandContext.getGuild(), ServerUser.fromMember(member), ServerUser.fromMember(commandContext.getAuthor()), reason, commandMessage) .thenApply(warning -> CommandResult.fromSuccess()); } @@ -84,15 +78,13 @@ public CompletableFuture executeSlash(SlashCommandInteractionEven } else { reason = templateService.renderSimpleTemplate(WARN_DEFAULT_REASON_TEMPLATE, event.getGuild().getIdLong()); } - WarnContext warnLogModel = WarnContext + ServerChannelMessage commandMessage = ServerChannelMessage .builder() - .reason(reason) - .warnedMember(member) - .member(event.getMember()) - .channel(event.getGuildChannel()) - .guild(event.getGuild()) + .serverId(event.getGuild().getIdLong()) + .channelId(event.getChannel().getIdLong()) + .messageId(SnowflakeUtils.createSnowFlake()) .build(); - return warnService.warnUserWithLog(warnLogModel) + return warnService.warnUserWithLog(event.getGuild(), ServerUser.fromMember(member), ServerUser.fromMember(event.getMember()), reason, commandMessage) .thenCompose(unused -> interactionService.replyEmbed(WARN_RESPONSE, event)) .thenApply(warning -> CommandResult.fromSuccess()); } diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/BanModerationActionListener.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/BanModerationActionListener.java new file mode 100644 index 000000000..937c4ac23 --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/BanModerationActionListener.java @@ -0,0 +1,114 @@ +package dev.sheldan.abstracto.moderation.listener; + +import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.config.ListenerPriority; +import dev.sheldan.abstracto.core.interaction.ComponentPayloadManagementService; +import dev.sheldan.abstracto.core.interaction.ComponentService; +import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListener; +import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerModel; +import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerResult; +import dev.sheldan.abstracto.core.interaction.modal.ModalConfigPayload; +import dev.sheldan.abstracto.core.interaction.modal.ModalService; +import dev.sheldan.abstracto.core.models.ServerUser; +import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; +import dev.sheldan.abstracto.moderation.model.interaction.ModerationActionBanPayload; +import dev.sheldan.abstracto.moderation.model.template.listener.ModerationActionBanModalModel; +import dev.sheldan.abstracto.moderation.model.template.listener.ModerationActionPayloadModel; +import dev.sheldan.abstracto.moderation.service.ModerationActionServiceBean; +import jakarta.transaction.Transactional; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class BanModerationActionListener implements ButtonClickedListener { + + @Autowired + private ModalService modalService; + + @Autowired + private ComponentService componentService; + + @Autowired + private ComponentPayloadManagementService componentPayloadManagementService; + + @Autowired + private BanModerationActionListener self; + + private static final String BAN_REASON_MODERATION_ACTION_MODAL = "moderationAction_ban"; + public static final String BAN_MODAL_ORIGIN = "BAN_MODERATION_ACTION_ORIGIN"; + + @Override + public ButtonClickedListenerResult execute(ButtonClickedListenerModel model) { + ModerationActionPayloadModel payload = (ModerationActionPayloadModel) model.getDeserializedPayload(); + if(ModerationActionServiceBean.BAN_ACTION.equals(payload.getAction())) { + log.info("Handling ban button interaction by user {} in server {} for user {}.", + payload.getUser().getUserId(), payload.getUser().getServerId(), model.getEvent().getMember().getIdLong()); + String modalId = componentService.generateComponentId(); + String reasonInputId = componentService.generateComponentId(); + String durationInputId = componentService.generateComponentId(); + ModerationActionBanModalModel modalModel = ModerationActionBanModalModel + .builder() + .modalId(modalId) + .durationComponentId(durationInputId) + .reasonComponentId(reasonInputId) + .build(); + modalService.replyModal(model.getEvent(), BAN_REASON_MODERATION_ACTION_MODAL, modalModel).thenAccept(unused -> { + log.info("Returned ban reason moderation action modal for user {} towards user {} in server {}.", + payload.getUser().getUserId(), model.getEvent().getMember().getIdLong(), model.getServerId()); + self.persistBanModerationActionPayload(payload.getUser(), reasonInputId, modalId); + }).exceptionally(throwable -> { + log.error("Failed to show modal for ban moderation action.", throwable); + return null; + }); + return ButtonClickedListenerResult.ACKNOWLEDGED; + } else { + return ButtonClickedListenerResult.IGNORED; + + } + } + + @Transactional + public void persistBanModerationActionPayload(ServerUser userToBan, String reasonInput, String modalId) { + ModerationActionBanPayload payload = ModerationActionBanPayload + .builder() + .bannedUserId(userToBan.getUserId()) + .serverId(userToBan.getServerId()) + .reasonInputId(reasonInput) + .modalId(modalId) + .build(); + ModalConfigPayload payloadConfig = ModalConfigPayload + .builder() + .modalPayload(payload) + .origin(BAN_MODAL_ORIGIN) + .payloadType(payload.getClass()) + .modalId(modalId) + .build(); + componentPayloadManagementService.createModalPayload(payloadConfig, userToBan.getServerId()); + } + + @Override + public Boolean handlesEvent(ButtonClickedListenerModel model) { + if(ModerationActionServiceBean.MODERATION_ACTION_ORIGIN.equals(model.getOrigin())){ + ModerationActionPayloadModel payload = (ModerationActionPayloadModel) model.getDeserializedPayload(); + return ModerationActionServiceBean.BAN_ACTION.equals(payload.getAction()); + } + return false; + } + + @Override + public FeatureDefinition getFeature() { + return ModerationFeatureDefinition.MODERATION; + } + + @Override + public Integer getPriority() { + return ListenerPriority.MEDIUM; + } + + @Override + public Boolean autoAcknowledgeEvent() { + return false; + } +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/BanModerationActionModalListener.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/BanModerationActionModalListener.java new file mode 100644 index 000000000..088c7bd63 --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/BanModerationActionModalListener.java @@ -0,0 +1,113 @@ +package dev.sheldan.abstracto.moderation.listener; + +import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.config.ListenerPriority; +import dev.sheldan.abstracto.core.interaction.InteractionExceptionService; +import dev.sheldan.abstracto.core.interaction.InteractionService; +import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListener; +import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListenerModel; +import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListenerResult; +import dev.sheldan.abstracto.core.models.ServerUser; +import dev.sheldan.abstracto.core.templating.service.TemplateService; +import dev.sheldan.abstracto.core.utils.FutureUtils; +import dev.sheldan.abstracto.core.utils.ParseUtils; +import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; +import dev.sheldan.abstracto.moderation.model.interaction.ModerationActionBanPayload; +import dev.sheldan.abstracto.moderation.service.BanService; +import lombok.extern.slf4j.Slf4j; +import net.dv8tion.jda.api.interactions.modals.ModalMapping; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.time.Duration; + +@Component +@Slf4j +public class BanModerationActionModalListener implements ModalInteractionListener { + + private static final String KICK_MODERATION_ACTION_MODAL_RESPONSE_TEMPLATE = "moderationAction_ban_response"; + private static final String DEFAULT_BAN_REASON_TEMPLATE_KEY = "ban_default_reason"; + + @Autowired + private BanService banService; + + @Autowired + private InteractionService interactionService; + + @Autowired + private InteractionExceptionService interactionExceptionService; + + @Autowired + private TemplateService templateService; + + @Override + public Boolean handlesEvent(ModalInteractionListenerModel model) { + return BanModerationActionListener.BAN_MODAL_ORIGIN.equals(model.getOrigin()); + } + + @Override + public ModalInteractionListenerResult execute(ModalInteractionListenerModel model) { + ModerationActionBanPayload payload = (ModerationActionBanPayload) model.getDeserializedPayload(); + ServerUser userBeingBanned = ServerUser + .builder() + .userId(payload.getBannedUserId()) + .serverId(payload.getServerId()) + .build(); + + ServerUser kickingUser = ServerUser.fromMember(model.getEvent().getMember()); + + String duration = model + .getEvent() + .getValues() + .stream() + .filter(modalMapping -> modalMapping.getId().equals(payload.getDurationInputId())) + .map(ModalMapping::getAsString) + .findFirst() + .orElse(null); + Duration messageDeletionDuration; + if(duration != null) { + messageDeletionDuration = ParseUtils.parseDuration(duration.trim()); + } else { + messageDeletionDuration = null; + } + String reason; + String tempReason = model + .getEvent() + .getValues() + .stream() + .filter(modalMapping -> modalMapping.getId().equals(payload.getDurationInputId())) + .map(ModalMapping::getAsString) + .findFirst() + .orElse(null); + if(StringUtils.isBlank(tempReason)) { + reason = templateService.renderSimpleTemplate(DEFAULT_BAN_REASON_TEMPLATE_KEY); + } else { + reason = tempReason; + } + log.debug("Handling ban moderation action modal interaction by user {} in server {}.", kickingUser.getUserId(), kickingUser.getServerId()); + model.getEvent().deferReply(true).queue(interactionHook -> { + banService.banUserWithNotification(userBeingBanned, reason, kickingUser, model.getEvent().getGuild(), messageDeletionDuration) + .thenCompose((future) -> FutureUtils.toSingleFutureGeneric(interactionService.sendMessageToInteraction(KICK_MODERATION_ACTION_MODAL_RESPONSE_TEMPLATE, new Object(), model.getEvent().getInteraction().getHook()))) + .thenAccept(unused -> { + log.info("Kicked user {} from server {}. Performed by user {}.", userBeingBanned.getUserId(), kickingUser.getServerId(), kickingUser.getUserId()); + }).exceptionally(throwable -> { + interactionExceptionService.reportExceptionToInteraction(throwable, model, this); + log.error("Failed to kick user {} from server {}. Performed by user {}.", userBeingBanned.getUserId(), kickingUser.getServerId(), kickingUser.getUserId(), throwable); + return null; + }); + }); + + return ModalInteractionListenerResult.ACKNOWLEDGED; + } + + @Override + public FeatureDefinition getFeature() { + return ModerationFeatureDefinition.MODERATION; + } + + @Override + public Integer getPriority() { + return ListenerPriority.MEDIUM; + } +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/HoneyPotRoleAddedListener.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/HoneyPotRoleAddedListener.java index 0fdc755cc..f9382d2e9 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/HoneyPotRoleAddedListener.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/HoneyPotRoleAddedListener.java @@ -5,6 +5,7 @@ import dev.sheldan.abstracto.core.listener.DefaultListenerResult; import dev.sheldan.abstracto.core.listener.sync.jda.RoleAddedListener; import dev.sheldan.abstracto.core.models.ConditionContextInstance; +import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.listener.RoleAddedModel; import dev.sheldan.abstracto.core.models.template.display.MemberDisplay; @@ -23,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import java.time.Duration; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.HashMap; @@ -77,7 +79,8 @@ public DefaultListenerResult execute(RoleAddedModel model) { .roleDisplay(RoleDisplay.fromRole(model.getRole())) .build(); String banReason = templateService.renderTemplate(HONEYPOT_BAN_REASON_TEMPLATE, reasonModel); - banService.banUserWithNotification(model.getTargetMember().getUser(), banReason, model.getTargetMember().getGuild().getSelfMember(), null).thenAccept(banResult -> { + banService.banUserWithNotification(model.getTargetUser(), banReason, ServerUser.fromMember(model.getTargetMember().getGuild().getSelfMember()), + model.getTargetMember().getGuild(), Duration.ofDays(7)).thenAccept(banResult -> { log.info("Banned user {} in guild {} due to role {}.", model.getTargetUser().getUserId(), model.getTargetUser().getServerId(), model.getRoleId()); }).exceptionally(throwable -> { log.error("Failed to ban user {} in guild {} due to role {}.", model.getTargetUser().getUserId(), model.getTargetUser().getServerId(), model.getRoleId(), throwable); diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/KickModerationActionListener.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/KickModerationActionListener.java new file mode 100644 index 000000000..f08e1f838 --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/KickModerationActionListener.java @@ -0,0 +1,111 @@ +package dev.sheldan.abstracto.moderation.listener; + +import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.config.ListenerPriority; +import dev.sheldan.abstracto.core.interaction.ComponentPayloadManagementService; +import dev.sheldan.abstracto.core.interaction.ComponentService; +import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListener; +import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerModel; +import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerResult; +import dev.sheldan.abstracto.core.interaction.modal.ModalConfigPayload; +import dev.sheldan.abstracto.core.interaction.modal.ModalService; +import dev.sheldan.abstracto.core.models.ServerUser; +import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; +import dev.sheldan.abstracto.moderation.model.interaction.ModerationActionKickPayload; +import dev.sheldan.abstracto.moderation.model.template.listener.ModerationActionKickModalModel; +import dev.sheldan.abstracto.moderation.model.template.listener.ModerationActionPayloadModel; +import dev.sheldan.abstracto.moderation.service.ModerationActionServiceBean; +import jakarta.transaction.Transactional; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class KickModerationActionListener implements ButtonClickedListener { + + @Autowired + private ModalService modalService; + + @Autowired + private ComponentService componentService; + + @Autowired + private ComponentPayloadManagementService componentPayloadManagementService; + + @Autowired + private KickModerationActionListener self; + + private static final String KICK_REASON_MODERATION_ACTION_MODAL = "moderationAction_kick"; + public static final String KICK_MODAL_ORIGIN = "KICK_MODERATION_ACTION_ORIGIN"; + + @Override + public ButtonClickedListenerResult execute(ButtonClickedListenerModel model) { + ModerationActionPayloadModel payload = (ModerationActionPayloadModel) model.getDeserializedPayload(); + if(ModerationActionServiceBean.KICK_ACTION.equals(payload.getAction())) { + log.info("Handling kick button interaction by user {} in server {} for user {}.", + payload.getUser().getUserId(), payload.getUser().getServerId(), model.getEvent().getMember().getIdLong()); + String modalId = componentService.generateComponentId(); + String reasonInputId = componentService.generateComponentId(); + ModerationActionKickModalModel modalModel = ModerationActionKickModalModel + .builder() + .modalId(modalId) + .reasonComponentId(reasonInputId) + .build(); + modalService.replyModal(model.getEvent(), KICK_REASON_MODERATION_ACTION_MODAL, modalModel).thenAccept(unused -> { + log.info("Returned kick reason moderation action modal for user {} towards user {} in server {}.", + payload.getUser().getUserId(), model.getEvent().getMember().getIdLong(), model.getServerId()); + self.persistKickModerationActionPayload(payload.getUser(), reasonInputId, modalId); + }).exceptionally(throwable -> { + log.error("Failed to show modal for kick moderation action.", throwable); + return null; + }); + return ButtonClickedListenerResult.ACKNOWLEDGED; + } else { + return ButtonClickedListenerResult.IGNORED; + } + } + + @Transactional + public void persistKickModerationActionPayload(ServerUser userToKick, String reasonInput, String modalId) { + ModerationActionKickPayload payload = ModerationActionKickPayload + .builder() + .kickedUserId(userToKick.getUserId()) + .serverId(userToKick.getServerId()) + .reasonInputId(reasonInput) + .modalId(modalId) + .build(); + ModalConfigPayload payloadConfig = ModalConfigPayload + .builder() + .modalPayload(payload) + .origin(KICK_MODAL_ORIGIN) + .payloadType(payload.getClass()) + .modalId(modalId) + .build(); + componentPayloadManagementService.createModalPayload(payloadConfig, userToKick.getServerId()); + } + + @Override + public Boolean handlesEvent(ButtonClickedListenerModel model) { + if(ModerationActionServiceBean.MODERATION_ACTION_ORIGIN.equals(model.getOrigin())){ + ModerationActionPayloadModel payload = (ModerationActionPayloadModel) model.getDeserializedPayload(); + return ModerationActionServiceBean.KICK_ACTION.equals(payload.getAction()); + } + return false; + } + + @Override + public FeatureDefinition getFeature() { + return ModerationFeatureDefinition.MODERATION; + } + + @Override + public Integer getPriority() { + return ListenerPriority.MEDIUM; + } + + @Override + public Boolean autoAcknowledgeEvent() { + return false; + } +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/KickModerationActionModalListener.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/KickModerationActionModalListener.java new file mode 100644 index 000000000..35b132436 --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/KickModerationActionModalListener.java @@ -0,0 +1,83 @@ +package dev.sheldan.abstracto.moderation.listener; + +import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.config.ListenerPriority; +import dev.sheldan.abstracto.core.interaction.InteractionExceptionService; +import dev.sheldan.abstracto.core.interaction.InteractionService; +import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListener; +import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListenerModel; +import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListenerResult; +import dev.sheldan.abstracto.core.models.ServerUser; +import dev.sheldan.abstracto.core.utils.FutureUtils; +import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; +import dev.sheldan.abstracto.moderation.model.interaction.ModerationActionKickPayload; +import dev.sheldan.abstracto.moderation.service.KickService; +import lombok.extern.slf4j.Slf4j; +import net.dv8tion.jda.api.interactions.modals.ModalMapping; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class KickModerationActionModalListener implements ModalInteractionListener { + + private static final String KICK_MODERATION_ACTION_MODAL_RESPONSE_TEMPLATE = "moderationAction_kick_response"; + + @Autowired + private KickService kickService; + + @Autowired + private InteractionService interactionService; + + @Autowired + private InteractionExceptionService interactionExceptionService; + + @Override + public Boolean handlesEvent(ModalInteractionListenerModel model) { + return KickModerationActionListener.KICK_MODAL_ORIGIN.equals(model.getOrigin()); + } + + @Override + public ModalInteractionListenerResult execute(ModalInteractionListenerModel model) { + ModerationActionKickPayload payload = (ModerationActionKickPayload) model.getDeserializedPayload(); + ServerUser userBeingKicked = ServerUser + .builder() + .userId(payload.getKickedUserId()) + .serverId(payload.getServerId()) + .build(); + + ServerUser kickingUser = ServerUser.fromMember(model.getEvent().getMember()); + String reason = model + .getEvent() + .getValues() + .stream() + .filter(modalMapping -> modalMapping.getId().equals(payload.getReasonInputId())) + .map(ModalMapping::getAsString) + .findFirst() + .orElse(null); + log.debug("Handling kick moderation action modal interaction by user {} in server {}.", kickingUser.getUserId(), kickingUser.getServerId()); + model.getEvent().deferReply(true).queue(interactionHook -> { + kickService.kickMember(model.getEvent().getGuild(), userBeingKicked, reason, kickingUser) + .thenCompose((future) -> FutureUtils.toSingleFutureGeneric(interactionService.sendMessageToInteraction(KICK_MODERATION_ACTION_MODAL_RESPONSE_TEMPLATE, new Object(), model.getEvent().getInteraction().getHook()))) + .thenAccept(unused -> { + log.info("Kicked user {} from server {}. Performed by user {}.", userBeingKicked.getUserId(), kickingUser.getServerId(), kickingUser.getUserId()); + }).exceptionally(throwable -> { + interactionExceptionService.reportExceptionToInteraction(throwable, model, this); + log.error("Failed to kick user {} from server {}. Performed by user {}.", userBeingKicked.getUserId(), kickingUser.getServerId(), kickingUser.getUserId(), throwable); + return null; + }); + }); + + return ModalInteractionListenerResult.ACKNOWLEDGED; + } + + @Override + public FeatureDefinition getFeature() { + return ModerationFeatureDefinition.MODERATION; + } + + @Override + public Integer getPriority() { + return ListenerPriority.MEDIUM; + } +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/MemberTimeoutListener.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/MemberTimeoutListener.java index 18ad80223..e91f9f8bc 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/MemberTimeoutListener.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/MemberTimeoutListener.java @@ -5,6 +5,7 @@ import dev.sheldan.abstracto.core.listener.async.jda.AsyncMemberTimeoutUpdatedListener; import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.models.listener.MemberTimeoutUpdatedModel; +import dev.sheldan.abstracto.core.models.template.display.MemberDisplay; import dev.sheldan.abstracto.core.service.MemberService; import dev.sheldan.abstracto.core.service.PostTargetService; import dev.sheldan.abstracto.core.templating.model.MessageToSend; @@ -116,8 +117,8 @@ public CompletableFuture sendMutingUpdateNotification(MemberTimeoutUpdated .builder() .muteTargetDate(model.getNewTimeout() != null ? model.getNewTimeout().toInstant() : null) .oldMuteTargetDate(model.getOldTimeout() != null ? model.getOldTimeout().toInstant() : null) - .mutingUser(future.isCompletedExceptionally() ? null : future.join()) - .mutedUser(model.getMember()) + .mutingUser(future.isCompletedExceptionally() ? null : MemberDisplay.fromMember(future.join())) + .mutedUser(MemberDisplay.fromMember(model.getMember())) .reason(reason) .build(); MessageToSend message = templateService.renderEmbedTemplate(MuteServiceBean.MUTE_LOG_TEMPLATE, muteLogModel, guild.getIdLong()); diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/MuteModerationActionListener.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/MuteModerationActionListener.java new file mode 100644 index 000000000..04699504d --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/MuteModerationActionListener.java @@ -0,0 +1,114 @@ +package dev.sheldan.abstracto.moderation.listener; + +import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.config.ListenerPriority; +import dev.sheldan.abstracto.core.interaction.ComponentPayloadManagementService; +import dev.sheldan.abstracto.core.interaction.ComponentService; +import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListener; +import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerModel; +import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerResult; +import dev.sheldan.abstracto.core.interaction.modal.ModalConfigPayload; +import dev.sheldan.abstracto.core.interaction.modal.ModalService; +import dev.sheldan.abstracto.core.models.ServerUser; +import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; +import dev.sheldan.abstracto.moderation.model.interaction.ModerationActionMutePayload; +import dev.sheldan.abstracto.moderation.model.template.listener.ModerationActionMuteModalModel; +import dev.sheldan.abstracto.moderation.model.template.listener.ModerationActionPayloadModel; +import dev.sheldan.abstracto.moderation.service.ModerationActionServiceBean; +import jakarta.transaction.Transactional; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class MuteModerationActionListener implements ButtonClickedListener { + + @Autowired + private ComponentService componentService; + + @Autowired + private ModalService modalService; + + @Autowired + private ComponentPayloadManagementService componentPayloadManagementService; + + @Autowired + private MuteModerationActionListener self; + + private static final String MUTE_REASON_MODERATION_ACTION_MODAL = "moderationAction_mute"; + public static final String MUTE_MODAL_ORIGIN = "MUTE_MODERATION_ACTION_ORIGIN"; + + @Override + public ButtonClickedListenerResult execute(ButtonClickedListenerModel model) { + ModerationActionPayloadModel payload = (ModerationActionPayloadModel) model.getDeserializedPayload(); + if(ModerationActionServiceBean.MUTE_ACTION.equals(payload.getAction())) { + log.info("Handling mute button interaction by user {} in server {} for user {}.", + payload.getUser().getUserId(), payload.getUser().getServerId(), model.getEvent().getMember().getIdLong()); + String modalId = componentService.generateComponentId(); + String reasonInputId = componentService.generateComponentId(); + String durationInputId = componentService.generateComponentId(); + ModerationActionMuteModalModel modalModel = ModerationActionMuteModalModel + .builder() + .modalId(modalId) + .durationComponentId(durationInputId) + .reasonComponentId(reasonInputId) + .build(); + modalService.replyModal(model.getEvent(), MUTE_REASON_MODERATION_ACTION_MODAL, modalModel).thenAccept(unused -> { + log.info("Returned mute reason moderation action modal for user {} towards user {} in server {}.", + payload.getUser().getUserId(), model.getEvent().getMember().getIdLong(), model.getServerId()); + self.persistMuteModerationActionPayload(payload.getUser(), reasonInputId, modalId, durationInputId); + }).exceptionally(throwable -> { + log.error("Failed to show modal for mute moderation action.", throwable); + return null; + }); + return ButtonClickedListenerResult.ACKNOWLEDGED; + } else { + return ButtonClickedListenerResult.IGNORED; + } + } + + @Transactional + public void persistMuteModerationActionPayload(ServerUser userToMute, String reasonInput, String modalId, String durationInputId) { + ModerationActionMutePayload payload = ModerationActionMutePayload + .builder() + .mutedUserId(userToMute.getUserId()) + .serverId(userToMute.getServerId()) + .reasonInputId(reasonInput) + .durationInputId(durationInputId) + .modalId(modalId) + .build(); + ModalConfigPayload payloadConfig = ModalConfigPayload + .builder() + .modalPayload(payload) + .origin(MUTE_MODAL_ORIGIN) + .payloadType(payload.getClass()) + .modalId(modalId) + .build(); + componentPayloadManagementService.createModalPayload(payloadConfig, userToMute.getServerId()); + } + + @Override + public Boolean handlesEvent(ButtonClickedListenerModel model) { + if(ModerationActionServiceBean.MODERATION_ACTION_ORIGIN.equals(model.getOrigin())){ + ModerationActionPayloadModel payload = (ModerationActionPayloadModel) model.getDeserializedPayload(); + return ModerationActionServiceBean.MUTE_ACTION.equals(payload.getAction()); + } + return false; + } + + @Override + public FeatureDefinition getFeature() { + return ModerationFeatureDefinition.MUTING; + } + + @Override + public Integer getPriority() { + return ListenerPriority.MEDIUM; + } + + @Override + public Boolean autoAcknowledgeEvent() { + return false; + } +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/MuteModerationActionModalListener.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/MuteModerationActionModalListener.java new file mode 100644 index 000000000..1c3a874b0 --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/MuteModerationActionModalListener.java @@ -0,0 +1,117 @@ +package dev.sheldan.abstracto.moderation.listener; + +import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.config.ListenerPriority; +import dev.sheldan.abstracto.core.interaction.InteractionExceptionService; +import dev.sheldan.abstracto.core.interaction.InteractionService; +import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListener; +import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListenerModel; +import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListenerResult; +import dev.sheldan.abstracto.core.models.ServerChannelMessage; +import dev.sheldan.abstracto.core.models.ServerUser; +import dev.sheldan.abstracto.core.templating.service.TemplateService; +import dev.sheldan.abstracto.core.utils.FutureUtils; +import dev.sheldan.abstracto.core.utils.ParseUtils; +import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; +import dev.sheldan.abstracto.moderation.model.interaction.ModerationActionMutePayload; +import dev.sheldan.abstracto.moderation.service.MuteService; +import lombok.extern.slf4j.Slf4j; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.interactions.modals.ModalMapping; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.time.Duration; + +import static dev.sheldan.abstracto.moderation.command.Mute.MUTE_NOTIFICATION_NOT_POSSIBLE_TEMPLATE_KEY; +import static dev.sheldan.abstracto.moderation.model.MuteResult.NOTIFICATION_FAILED; + +@Component +@Slf4j +public class MuteModerationActionModalListener implements ModalInteractionListener { + + private static final String MUTE_MODERATION_ACTION_MODAL_RESPONSE_TEMPLATE = "moderationAction_mute_response"; + + @Autowired + private MuteService muteService; + + @Autowired + private InteractionService interactionService; + + @Autowired + private TemplateService templateService; + + @Autowired + private InteractionExceptionService interactionExceptionService; + + @Override + public Boolean handlesEvent(ModalInteractionListenerModel model) { + return MuteModerationActionListener.MUTE_MODAL_ORIGIN.equals(model.getOrigin()); + } + + @Override + public ModalInteractionListenerResult execute(ModalInteractionListenerModel model) { + ModerationActionMutePayload payload = (ModerationActionMutePayload) model.getDeserializedPayload(); + ServerUser userBeingMuted = ServerUser + .builder() + .userId(payload.getMutedUserId()) + .serverId(payload.getServerId()) + .build(); + + ServerUser mutingUser = ServerUser.fromMember(model.getEvent().getMember()); + String reason = model + .getEvent() + .getValues() + .stream() + .filter(modalMapping -> modalMapping.getId().equals(payload.getReasonInputId())) + .map(ModalMapping::getAsString) + .findFirst() + .orElse(null); + + String duration = model + .getEvent() + .getValues() + .stream() + .filter(modalMapping -> modalMapping.getId().equals(payload.getDurationInputId())) + .map(ModalMapping::getAsString) + .findFirst() + .orElse(null); + Duration muteDuration; + if(duration != null) { + muteDuration = ParseUtils.parseDuration(duration.trim()); + } else { + muteDuration = Duration.ofDays(Member.MAX_TIME_OUT_LENGTH); + } + ServerChannelMessage serverChannelMessage = ServerChannelMessage.fromMessage(model.getEvent().getMessage()); + log.debug("Handling mute moderation action modal interaction by user {} in server {}.", mutingUser.getUserId(), mutingUser.getServerId()); + model.getEvent().deferReply(true).queue(interactionHook -> { + muteService.muteMemberWithLog(userBeingMuted, mutingUser, reason, muteDuration, model.getEvent().getGuild(), serverChannelMessage) + .thenCompose((future) -> { + if(future == NOTIFICATION_FAILED) { + return FutureUtils.toSingleFutureGeneric(interactionService.sendMessageToInteraction(MUTE_NOTIFICATION_NOT_POSSIBLE_TEMPLATE_KEY, new Object(), model.getEvent().getInteraction().getHook())); + } else { + return FutureUtils.toSingleFutureGeneric(interactionService.sendMessageToInteraction(MUTE_MODERATION_ACTION_MODAL_RESPONSE_TEMPLATE, new Object(), model.getEvent().getInteraction().getHook())); + } + }) + .thenAccept(unused -> { + log.info("Muted user {} in server {}. Performed by user {}.", userBeingMuted.getUserId(), mutingUser.getServerId(), mutingUser.getUserId()); + }).exceptionally(throwable -> { + interactionExceptionService.reportExceptionToInteraction(throwable, model, this); + log.error("Failed to mute user {} in server {}. Performed by user {}.", userBeingMuted.getUserId(), mutingUser.getServerId(), mutingUser.getUserId(), throwable); + return null; + }); + }); + + return ModalInteractionListenerResult.ACKNOWLEDGED; + } + + @Override + public FeatureDefinition getFeature() { + return ModerationFeatureDefinition.MUTING; + } + + @Override + public Integer getPriority() { + return ListenerPriority.MEDIUM; + } +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/WarnModerationActionListener.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/WarnModerationActionListener.java new file mode 100644 index 000000000..314a207bd --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/WarnModerationActionListener.java @@ -0,0 +1,111 @@ +package dev.sheldan.abstracto.moderation.listener; + +import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.config.ListenerPriority; +import dev.sheldan.abstracto.core.interaction.ComponentPayloadManagementService; +import dev.sheldan.abstracto.core.interaction.ComponentService; +import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListener; +import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerModel; +import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerResult; +import dev.sheldan.abstracto.core.interaction.modal.ModalConfigPayload; +import dev.sheldan.abstracto.core.interaction.modal.ModalService; +import dev.sheldan.abstracto.core.models.ServerUser; +import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; +import dev.sheldan.abstracto.moderation.model.interaction.ModerationActionWarnPayload; +import dev.sheldan.abstracto.moderation.model.template.listener.ModerationActionPayloadModel; +import dev.sheldan.abstracto.moderation.model.template.listener.ModerationActionWarnModalModel; +import dev.sheldan.abstracto.moderation.service.ModerationActionServiceBean; +import jakarta.transaction.Transactional; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class WarnModerationActionListener implements ButtonClickedListener { + + @Autowired + private ModalService modalService; + + @Autowired + private ComponentService componentService; + + @Autowired + private ComponentPayloadManagementService componentPayloadManagementService; + + @Autowired + private WarnModerationActionListener self; + + private static final String WARN_REASON_MODERATION_ACTION_MODAL = "moderationAction_warn"; + public static final String WARN_MODAL_ORIGIN = "WARN_MODERATION_ACTION_ORIGIN"; + + @Override + public ButtonClickedListenerResult execute(ButtonClickedListenerModel model) { + ModerationActionPayloadModel payload = (ModerationActionPayloadModel) model.getDeserializedPayload(); + if(ModerationActionServiceBean.WARN_ACTION.equals(payload.getAction())) { + log.info("Handling warn button interaction by user {} in server {} for user {}.", + payload.getUser().getUserId(), payload.getUser().getServerId(), model.getEvent().getMember().getIdLong()); + String modalId = componentService.generateComponentId(); + String reasonInputId = componentService.generateComponentId(); + ModerationActionWarnModalModel modalModel = ModerationActionWarnModalModel + .builder() + .modalId(modalId) + .reasonComponentId(reasonInputId) + .build(); + modalService.replyModal(model.getEvent(), WARN_REASON_MODERATION_ACTION_MODAL, modalModel).thenAccept(unused -> { + log.info("Returned warn reason moderation action modal for user {} towards user {} in server {}.", + payload.getUser().getUserId(), model.getEvent().getMember().getIdLong(), model.getServerId()); + self.persistWarnModerationActionPayload(payload.getUser(), reasonInputId, modalId); + }).exceptionally(throwable -> { + log.error("Failed to show modal for warn moderation action.", throwable); + return null; + }); + return ButtonClickedListenerResult.ACKNOWLEDGED; + } else { + return ButtonClickedListenerResult.IGNORED; + } + } + + @Transactional + public void persistWarnModerationActionPayload(ServerUser userToWarn, String reasonInput, String modalId) { + ModerationActionWarnPayload payload = ModerationActionWarnPayload + .builder() + .warnedUserId(userToWarn.getUserId()) + .serverId(userToWarn.getServerId()) + .reasonInputId(reasonInput) + .modalId(modalId) + .build(); + ModalConfigPayload payloadConfig = ModalConfigPayload + .builder() + .modalPayload(payload) + .origin(WARN_MODAL_ORIGIN) + .payloadType(payload.getClass()) + .modalId(modalId) + .build(); + componentPayloadManagementService.createModalPayload(payloadConfig, userToWarn.getServerId()); + } + + @Override + public Boolean handlesEvent(ButtonClickedListenerModel model) { + if(ModerationActionServiceBean.MODERATION_ACTION_ORIGIN.equals(model.getOrigin())){ + ModerationActionPayloadModel payload = (ModerationActionPayloadModel) model.getDeserializedPayload(); + return ModerationActionServiceBean.WARN_ACTION.equals(payload.getAction()); + } + return false; + } + + @Override + public FeatureDefinition getFeature() { + return ModerationFeatureDefinition.WARNING; + } + + @Override + public Integer getPriority() { + return ListenerPriority.MEDIUM; + } + + @Override + public Boolean autoAcknowledgeEvent() { + return false; + } +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/WarnModerationActionModalListener.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/WarnModerationActionModalListener.java new file mode 100644 index 000000000..0f078ac4b --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/WarnModerationActionModalListener.java @@ -0,0 +1,98 @@ +package dev.sheldan.abstracto.moderation.listener; + +import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.config.ListenerPriority; +import dev.sheldan.abstracto.core.interaction.InteractionExceptionService; +import dev.sheldan.abstracto.core.interaction.InteractionService; +import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListener; +import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListenerModel; +import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListenerResult; +import dev.sheldan.abstracto.core.models.ServerChannelMessage; +import dev.sheldan.abstracto.core.models.ServerUser; +import dev.sheldan.abstracto.core.templating.service.TemplateService; +import dev.sheldan.abstracto.core.utils.FutureUtils; +import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; +import dev.sheldan.abstracto.moderation.model.interaction.ModerationActionWarnPayload; +import dev.sheldan.abstracto.moderation.service.WarnService; +import lombok.extern.slf4j.Slf4j; +import net.dv8tion.jda.api.interactions.modals.ModalMapping; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import static dev.sheldan.abstracto.moderation.command.Warn.WARN_DEFAULT_REASON_TEMPLATE; + +@Component +@Slf4j +public class WarnModerationActionModalListener implements ModalInteractionListener { + + private static final String WARN_MODERATION_ACTION_MODAL_RESPONSE_TEMPLATE = "moderationAction_warn_response"; + + @Autowired + private WarnService warnService; + + @Autowired + private InteractionService interactionService; + + @Autowired + private TemplateService templateService; + + @Autowired + private InteractionExceptionService interactionExceptionService; + + @Override + public Boolean handlesEvent(ModalInteractionListenerModel model) { + return WarnModerationActionListener.WARN_MODAL_ORIGIN.equals(model.getOrigin()); + } + + @Override + public ModalInteractionListenerResult execute(ModalInteractionListenerModel model) { + ModerationActionWarnPayload payload = (ModerationActionWarnPayload) model.getDeserializedPayload(); + ServerUser userBeingWarned = ServerUser + .builder() + .userId(payload.getWarnedUserId()) + .serverId(payload.getServerId()) + .build(); + + ServerUser warningUser = ServerUser.fromMember(model.getEvent().getMember()); + String reason; + String tempReason = model + .getEvent() + .getValues() + .stream() + .filter(modalMapping -> modalMapping.getId().equals(payload.getReasonInputId())) + .map(ModalMapping::getAsString) + .findFirst() + .orElse(null); + if(StringUtils.isBlank(tempReason)) { + reason = templateService.renderSimpleTemplate(WARN_DEFAULT_REASON_TEMPLATE, model.getServerId()); + } else { + reason = tempReason; + } + ServerChannelMessage serverChannelMessage = ServerChannelMessage.fromMessage(model.getEvent().getMessage()); + log.debug("Handling warn moderation action modal interaction by user {} in server {}.", warningUser.getUserId(), warningUser.getServerId()); + model.getEvent().deferReply(true).queue(interactionHook -> { + warnService.warnUserWithLog(model.getEvent().getGuild(), userBeingWarned, warningUser, reason, serverChannelMessage) + .thenCompose((future) -> FutureUtils.toSingleFutureGeneric(interactionService.sendMessageToInteraction(WARN_MODERATION_ACTION_MODAL_RESPONSE_TEMPLATE, new Object(), model.getEvent().getInteraction().getHook()))) + .thenAccept(unused -> { + log.info("Warned user {} in server {}. Performed by user {}.", userBeingWarned.getUserId(), warningUser.getServerId(), warningUser.getUserId()); + }).exceptionally(throwable -> { + interactionExceptionService.reportExceptionToInteraction(throwable, model, this); + log.error("Failed to warn user {} from server {}. Performed by user {}.", userBeingWarned.getUserId(), warningUser.getServerId(), warningUser.getUserId(), throwable); + return null; + }); + }); + + return ModalInteractionListenerResult.ACKNOWLEDGED; + } + + @Override + public FeatureDefinition getFeature() { + return ModerationFeatureDefinition.WARNING; + } + + @Override + public Integer getPriority() { + return ListenerPriority.MEDIUM; + } +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/infraction/BanReasonUpdatedListener.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/infraction/BanReasonUpdatedListener.java index dec6dfc3b..95f4fa18d 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/infraction/BanReasonUpdatedListener.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/infraction/BanReasonUpdatedListener.java @@ -4,6 +4,8 @@ import dev.sheldan.abstracto.core.config.ListenerPriority; import dev.sheldan.abstracto.core.listener.DefaultListenerResult; import dev.sheldan.abstracto.core.models.ServerUser; +import dev.sheldan.abstracto.core.models.template.display.MemberDisplay; +import dev.sheldan.abstracto.core.models.template.display.UserDisplay; import dev.sheldan.abstracto.core.service.ChannelService; import dev.sheldan.abstracto.core.service.MemberService; import dev.sheldan.abstracto.core.service.MessageService; @@ -85,8 +87,8 @@ public void handleBanUpdate(InfractionDescriptionEventModel model, CompletableFu .orElse(Duration.ZERO); BanLog banLog = BanLog .builder() - .bannedUser(infractionUser.isCompletedExceptionally() ? null : infractionUser.join()) - .banningMember(infractionCreator.isCompletedExceptionally() ? null : infractionCreator.join()) + .bannedUser(infractionUser.isCompletedExceptionally() ? null : UserDisplay.fromUser(infractionUser.join())) + .banningMember(infractionCreator.isCompletedExceptionally() ? null : MemberDisplay.fromMember(infractionCreator.join())) .deletionDuration(deletionDuration) .reason(model.getNewDescription()) .build(); diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/infraction/WarnReasonUpdatedListener.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/infraction/WarnReasonUpdatedListener.java index b0601bc8a..ce7af346b 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/infraction/WarnReasonUpdatedListener.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/infraction/WarnReasonUpdatedListener.java @@ -4,6 +4,7 @@ import dev.sheldan.abstracto.core.config.ListenerPriority; import dev.sheldan.abstracto.core.listener.DefaultListenerResult; import dev.sheldan.abstracto.core.models.ServerUser; +import dev.sheldan.abstracto.core.models.template.display.MemberDisplay; import dev.sheldan.abstracto.core.service.ChannelService; import dev.sheldan.abstracto.core.service.GuildService; import dev.sheldan.abstracto.core.service.MemberService; @@ -14,7 +15,7 @@ import dev.sheldan.abstracto.moderation.model.database.Infraction; import dev.sheldan.abstracto.moderation.model.database.Warning; import dev.sheldan.abstracto.moderation.model.listener.InfractionDescriptionEventModel; -import dev.sheldan.abstracto.moderation.model.template.command.WarnContext; +import dev.sheldan.abstracto.moderation.model.template.command.WarnLogModel; import dev.sheldan.abstracto.moderation.service.WarnServiceBean; import dev.sheldan.abstracto.moderation.service.management.InfractionManagementService; import dev.sheldan.abstracto.moderation.service.management.WarnManagementService; @@ -82,15 +83,14 @@ private void handleWarnUpdate(InfractionDescriptionEventModel model, Long warnId Guild guild = guildService.getGuildById(model.getServerId()); Infraction infraction = infractionManagementService.loadInfraction(model.getInfractionId()); GuildMessageChannel messageChannel = channelService.getMessageChannelFromServer(model.getServerId(), infraction.getLogChannel().getId()); - WarnContext context = WarnContext + WarnLogModel context = WarnLogModel .builder() - .warnedMember(warnedUser.isCompletedExceptionally() ? null : warnedUser.join()) - .member(warningUser.isCompletedExceptionally() ? null : warningUser.join()) + .warnedMember(warnedUser.isCompletedExceptionally() ? null : MemberDisplay.fromMember(warnedUser.join())) + .warningMember(warningUser.isCompletedExceptionally() ? null : MemberDisplay.fromMember(warningUser.join())) .reason(model.getNewDescription()) .warnId(warnId) - .guild(guild) .build(); - MessageToSend message = warnService.renderMessageModel(context); + MessageToSend message = warnService.renderMessageModel(context, guild.getIdLong()); messageService.editMessageInChannel(messageChannel, message, infraction.getLogMessageId()) .thenAccept(unused1 -> returningFuture.complete(DefaultListenerResult.PROCESSED)) .exceptionally(throwable1 -> { diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/BanServiceBean.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/BanServiceBean.java index 7cf993eb0..5c15d7272 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/BanServiceBean.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/BanServiceBean.java @@ -1,6 +1,9 @@ package dev.sheldan.abstracto.moderation.service; +import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.models.database.AUserInAServer; +import dev.sheldan.abstracto.core.models.template.display.MemberDisplay; +import dev.sheldan.abstracto.core.models.template.display.UserDisplay; import dev.sheldan.abstracto.core.service.*; import dev.sheldan.abstracto.core.service.management.UserInServerManagementService; import dev.sheldan.abstracto.core.utils.FutureUtils; @@ -60,32 +63,31 @@ public class BanServiceBean implements BanService { private InfractionService infractionService; @Override - public CompletableFuture banUserWithNotification(User user, String reason, Member banningMember, Duration deletionDuration) { + public CompletableFuture banUserWithNotification(ServerUser userToBeBanned, String reason, ServerUser banningUser, Guild guild, Duration deletionDuration) { BanLog banLog = BanLog .builder() - .bannedUser(user) - .banningMember(banningMember) + .bannedUser(UserDisplay.fromServerUser(userToBeBanned)) + .banningMember(MemberDisplay.fromServerUser(banningUser)) .deletionDuration(deletionDuration) .reason(reason) .build(); - Guild guild = banningMember.getGuild(); BanResult[] result = {BanResult.SUCCESSFUL}; - return sendBanNotification(user, reason, guild) + return sendBanNotification(userToBeBanned, reason, guild) .exceptionally(throwable -> { result[0] = BanResult.NOTIFICATION_FAILED; return null; }) - .thenCompose(unused -> banUser(guild, user, deletionDuration, reason)) + .thenCompose(unused -> banUser(guild, userToBeBanned, deletionDuration, reason)) .thenCompose(unused -> sendBanLogMessage(banLog, guild.getIdLong())) - .thenAccept(banLogMessage -> self.evaluateAndStoreInfraction(user, guild, reason, banningMember, banLogMessage, deletionDuration)) + .thenAccept(banLogMessage -> self.evaluateAndStoreInfraction(userToBeBanned, guild, reason, banningUser, banLogMessage, deletionDuration)) .thenApply(unused -> result[0]); } @Transactional - public CompletableFuture evaluateAndStoreInfraction(User user, Guild guild, String reason, Member banningMember, Message banLogMessage, Duration deletionDuration) { + public CompletableFuture evaluateAndStoreInfraction(ServerUser user, Guild guild, String reason, ServerUser banningMember, Message banLogMessage, Duration deletionDuration) { if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, guild.getIdLong())) { Long infractionPoints = configService.getLongValueOrConfigDefault(ModerationFeatureConfig.BAN_INFRACTION_POINTS, guild.getIdLong()); - AUserInAServer bannedUser = userInServerManagementService.loadOrCreateUser(guild.getIdLong(), user.getIdLong()); + AUserInAServer bannedUser = userInServerManagementService.loadOrCreateUser(guild.getIdLong(), user.getUserId()); AUserInAServer banningUser = userInServerManagementService.loadOrCreateUser(banningMember); Map parameters = new HashMap<>(); if(deletionDuration == null) { @@ -99,47 +101,46 @@ public CompletableFuture evaluateAndStoreInfraction(User user, Guild guild } } - private CompletableFuture sendBanNotification(User user, String reason, Guild guild) { + private CompletableFuture sendBanNotification(ServerUser serverUser, String reason, Guild guild) { BanNotificationModel model = BanNotificationModel .builder() .serverName(guild.getName()) .reason(reason) .build(); String message = templateService.renderTemplate(BAN_NOTIFICATION, model, guild.getIdLong()); - return messageService.sendMessageToUser(user, message).thenAccept(message1 -> {}); + return messageService.sendMessageToUser(serverUser, message).thenAccept(message1 -> {}); } @Override - public CompletableFuture unBanUserWithNotification(User user, Member unBanningMember) { - Guild guild = unBanningMember.getGuild(); + public CompletableFuture unBanUserWithNotification(Long userId, ServerUser unBanningMember, Guild guild) { UnBanLog banLog = UnBanLog .builder() - .bannedUser(user) - .unBanningMember(unBanningMember) + .bannedUser(UserDisplay.fromId(userId)) + .unBanningMember(MemberDisplay.fromServerUser(unBanningMember)) .build(); - return unbanUser(guild, user) + return unbanUser(guild, userId) .thenCompose(unused -> self.sendUnBanLogMessage(banLog, guild.getIdLong(), UN_BAN_LOG_TEMPLATE)); } @Override - public CompletableFuture banUser(Guild guild, User user, Duration deletionDuration, String reason) { - log.info("Banning user {} in guild {}.", user.getIdLong(), guild.getId()); + public CompletableFuture banUser(Guild guild, ServerUser userToBeBanned, Duration deletionDuration, String reason) { + log.info("Banning user {} in guild {}.", userToBeBanned.getUserId(), guild.getId()); if(deletionDuration == null || deletionDuration.isNegative()) { deletionDuration = Duration.ZERO; } - return guild.ban(user, (int) deletionDuration.getSeconds(), TimeUnit.SECONDS).reason(reason).submit(); + return guild.ban(UserSnowflake.fromId(userToBeBanned.getUserId()), (int) deletionDuration.getSeconds(), TimeUnit.SECONDS).reason(reason).submit(); } @Override - public CompletableFuture unbanUser(Guild guild, User user) { - log.info("Unbanning user {} in guild {}.", user.getIdLong(), guild.getId()); - return guild.unban(user).submit(); + public CompletableFuture unbanUser(Guild guild, Long userId) { + log.info("Unbanning user {} in guild {}.", userId, guild.getId()); + return guild.unban(UserSnowflake.fromId(userId)).submit(); } @Override - public CompletableFuture softBanUser(Guild guild, User user, Duration delDays) { + public CompletableFuture softBanUser(Guild guild, ServerUser user, Duration delDays) { return banUser(guild, user, delDays, "") - .thenCompose(unused -> unbanUser(guild, user)); + .thenCompose(unused -> unbanUser(guild, user.getUserId())); } public CompletableFuture sendBanLogMessage(BanLog banLog, Long guildId) { diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/KickServiceBean.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/KickServiceBean.java index ff7705049..3343849da 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/KickServiceBean.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/KickServiceBean.java @@ -1,6 +1,8 @@ package dev.sheldan.abstracto.moderation.service; +import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.models.database.AUserInAServer; +import dev.sheldan.abstracto.core.models.template.display.MemberDisplay; import dev.sheldan.abstracto.core.service.ConfigService; import dev.sheldan.abstracto.core.service.FeatureFlagService; import dev.sheldan.abstracto.core.service.PostTargetService; @@ -17,6 +19,7 @@ import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.UserSnowflake; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -52,31 +55,72 @@ public class KickServiceBean implements KickService { private KickServiceBean self; @Override - public CompletableFuture kickMember(Member member, String reason, KickLogModel kickLogModel) { - Guild guild = member.getGuild(); - log.info("Kicking user {} from guild {}", member.getUser().getIdLong(), guild.getIdLong()); - CompletableFuture kickFuture = guild.kick(member, reason).submit(); - CompletableFuture logFuture = this.sendKickLog(kickLogModel); + public CompletableFuture kickMember(Member kickedMember, Member kickingMember, String reason) { + Guild guild = kickedMember.getGuild(); + log.info("Kicking user {} from guild {}", kickedMember.getUser().getIdLong(), guild.getIdLong()); + CompletableFuture kickFuture = guild.kick(kickedMember, reason).submit(); + CompletableFuture logFuture = sendKickLog(kickedMember, kickingMember, reason, guild.getIdLong()); return CompletableFuture.allOf(kickFuture, logFuture) - .thenAccept(unused -> self.storeInfraction(member, reason, kickLogModel, guild, logFuture.join())); + .thenAccept(unused -> self.storeInfraction(kickedMember, kickingMember, reason, logFuture.join(), guild.getIdLong())); + } + + @Override + public CompletableFuture kickMember(Guild guild, ServerUser kickedUser, String reason, ServerUser kickingUser) { + CompletableFuture kickFuture = guild.kick(UserSnowflake.fromId(kickedUser.getUserId())).submit(); + CompletableFuture logFuture = sendKickLog(kickedUser, kickingUser, reason, guild.getIdLong()); + return CompletableFuture.allOf(kickFuture, logFuture) + .thenAccept(unused -> self.storeInfraction(kickedUser, kickingUser, reason, logFuture.join(), guild.getIdLong())); } @Transactional - public CompletableFuture storeInfraction(Member member, String reason, KickLogModel kickLogModel, Guild guild, Message logMessage) { - if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, guild.getIdLong())) { - Long infractionPoints = configService.getLongValueOrConfigDefault(ModerationFeatureConfig.KICK_INFRACTION_POINTS, guild.getIdLong()); + public CompletableFuture storeInfraction(Member member, Member kickingMember, String reason, Message logMessage, Long serverId) { + if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, serverId)) { + Long infractionPoints = configService.getLongValueOrConfigDefault(ModerationFeatureConfig.KICK_INFRACTION_POINTS, serverId); AUserInAServer kickedUser = userInServerManagementService.loadOrCreateUser(member); - AUserInAServer kickingUser = userInServerManagementService.loadOrCreateUser(kickLogModel.getMember()); + AUserInAServer kickingUser = userInServerManagementService.loadOrCreateUser(kickingMember); return infractionService.createInfractionWithNotification(kickedUser, infractionPoints, KICK_INFRACTION_TYPE, reason, kickingUser, logMessage).thenApply(Infraction::getId); } else { return CompletableFuture.completedFuture(null); } } - private CompletableFuture sendKickLog(KickLogModel kickLogModel) { - MessageToSend warnLogMessage = templateService.renderEmbedTemplate(KICK_LOG_TEMPLATE, kickLogModel, kickLogModel.getGuild().getIdLong()); - log.debug("Sending kick log message in guild {}.", kickLogModel.getGuild().getIdLong()); - List> messageFutures = postTargetService.sendEmbedInPostTarget(warnLogMessage, ModerationPostTarget.KICK_LOG, kickLogModel.getGuild().getIdLong()); + @Transactional + public CompletableFuture storeInfraction(ServerUser member, ServerUser kickingMember, String reason, Message logMessage, Long serverId) { + if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, serverId)) { + Long infractionPoints = configService.getLongValueOrConfigDefault(ModerationFeatureConfig.KICK_INFRACTION_POINTS, serverId); + AUserInAServer kickedUser = userInServerManagementService.loadOrCreateUser(member); + AUserInAServer kickingUser = userInServerManagementService.loadOrCreateUser(kickingMember); + return infractionService.createInfractionWithNotification(kickedUser, infractionPoints, KICK_INFRACTION_TYPE, reason, kickingUser, logMessage).thenApply(Infraction::getId); + } else { + return CompletableFuture.completedFuture(null); + } + } + + private CompletableFuture sendKickLog(Member kickedMember, Member kickingMember, String reason, Long serverId) { + KickLogModel kickLogModel = KickLogModel + .builder() + .kickedMember(MemberDisplay.fromMember(kickedMember)) + .kickingMember(MemberDisplay.fromMember(kickingMember)) + .reason(reason) + .build(); + return sendKicklog(serverId, kickLogModel); + } + + private CompletableFuture sendKickLog(ServerUser kickedMember, ServerUser kickingMember, String reason, Long serverId) { + KickLogModel kickLogModel = KickLogModel + .builder() + .kickedMember(MemberDisplay.fromServerUser(kickedMember)) + .kickingMember(MemberDisplay.fromServerUser(kickingMember)) + .reason(reason) + .build(); + return sendKicklog(serverId, kickLogModel); + } + + private CompletableFuture sendKicklog(Long serverId, KickLogModel kickLogModel) { + MessageToSend warnLogMessage = templateService.renderEmbedTemplate(KICK_LOG_TEMPLATE, kickLogModel, serverId); + log.debug("Sending kick log message in guild {}.", serverId); + List> messageFutures = postTargetService.sendEmbedInPostTarget(warnLogMessage, ModerationPostTarget.KICK_LOG, serverId); return FutureUtils.toSingleFutureGeneric(messageFutures).thenApply(unused -> messageFutures.get(0).join()); } + } diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/ModerationActionServiceBean.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/ModerationActionServiceBean.java new file mode 100644 index 000000000..a7d6fdcf0 --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/ModerationActionServiceBean.java @@ -0,0 +1,95 @@ +package dev.sheldan.abstracto.moderation.service; + +import dev.sheldan.abstracto.core.interaction.ComponentPayloadService; +import dev.sheldan.abstracto.core.interaction.ComponentService; +import dev.sheldan.abstracto.core.models.ServerUser; +import dev.sheldan.abstracto.core.models.database.AServer; +import dev.sheldan.abstracto.core.service.FeatureFlagService; +import dev.sheldan.abstracto.core.service.management.ServerManagementService; +import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; +import dev.sheldan.abstracto.moderation.model.ModerationActionButton; +import dev.sheldan.abstracto.moderation.model.template.listener.ModerationActionPayloadModel; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@Component +@Slf4j +public class ModerationActionServiceBean implements ModerationActionService { + + public static final String WARN_ACTION = "warn"; + public static final String MUTE_ACTION = "mute"; + public static final String KICK_ACTION = "kick"; + public static final String BAN_ACTION = "ban"; + + public static final String MODERATION_ACTION_ORIGIN = "moderationAction"; + + + @Autowired + private FeatureFlagService featureFlagService; + + @Autowired + private ComponentService componentService; + + @Autowired + private ComponentPayloadService componentPayloadService; + + @Autowired + private ServerManagementService serverManagementService; + + @Override + public List getModerationActionButtons(ServerUser serverUser) { + AServer server = serverManagementService.loadServer(serverUser.getServerId()); + boolean mutingEnabled = featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.MUTING, serverUser.getServerId()); + boolean moderationEnabled = featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.MODERATION, serverUser.getServerId()); + boolean warningsEnabled = featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.WARNING, serverUser.getServerId()); + List buttons = new ArrayList<>(); + if(warningsEnabled) { + String warnButtonId = componentService.generateComponentId(); + ModerationActionPayloadModel warnPayload = ModerationActionPayloadModel.forAction(WARN_ACTION, serverUser); + componentPayloadService.createButtonPayload(warnButtonId, warnPayload, MODERATION_ACTION_ORIGIN, server); + ModerationActionButton warnAction = ModerationActionButton + .builder() + .componentId(warnButtonId) + .action(WARN_ACTION) + .build(); + buttons.add(warnAction); + } + if(mutingEnabled) { + String muteButtonId = componentService.generateComponentId(); + ModerationActionPayloadModel mutePayload = ModerationActionPayloadModel.forAction(MUTE_ACTION, serverUser); + componentPayloadService.createButtonPayload(muteButtonId, mutePayload, MODERATION_ACTION_ORIGIN, server); + ModerationActionButton muteAction = ModerationActionButton + .builder() + .componentId(muteButtonId) + .action(MUTE_ACTION) + .build(); + buttons.add(muteAction); + } + if(moderationEnabled) { + String kickButtonId = componentService.generateComponentId(); + String banButtonId = componentService.generateComponentId(); + ModerationActionPayloadModel kickPayload = ModerationActionPayloadModel.forAction(KICK_ACTION, serverUser); + ModerationActionPayloadModel banPayload = ModerationActionPayloadModel.forAction(BAN_ACTION, serverUser); + componentPayloadService.createButtonPayload(kickButtonId, kickPayload, MODERATION_ACTION_ORIGIN, server); + componentPayloadService.createButtonPayload(banButtonId, banPayload, MODERATION_ACTION_ORIGIN, server); + ModerationActionButton kickAction = ModerationActionButton + .builder() + .componentId(kickButtonId) + .action(KICK_ACTION) + .build(); + buttons.add(kickAction); + ModerationActionButton banAction = ModerationActionButton + .builder() + .componentId(banButtonId) + .action(BAN_ACTION) + .build(); + buttons.add(banAction); + } + log.info("Attaching {} buttons to moderation action for user {} in server {}.", buttons.size(), serverUser.getUserId(), serverUser.getServerId()); + return buttons; + } +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/MuteServiceBean.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/MuteServiceBean.java index f8c4e329f..b52ae25df 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/MuteServiceBean.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/MuteServiceBean.java @@ -1,11 +1,12 @@ package dev.sheldan.abstracto.moderation.service; import dev.sheldan.abstracto.core.models.AServerAChannelMessage; -import dev.sheldan.abstracto.core.models.FullUserInServer; +import dev.sheldan.abstracto.core.models.ServerChannelMessage; import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.models.database.AChannel; import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer; +import dev.sheldan.abstracto.core.models.template.display.MemberDisplay; import dev.sheldan.abstracto.core.service.*; import dev.sheldan.abstracto.core.service.management.ChannelManagementService; import dev.sheldan.abstracto.core.service.management.ServerManagementService; @@ -17,9 +18,9 @@ import dev.sheldan.abstracto.moderation.config.feature.MutingFeatureConfig; import dev.sheldan.abstracto.moderation.config.posttarget.MutingPostTarget; import dev.sheldan.abstracto.moderation.exception.NoMuteFoundException; +import dev.sheldan.abstracto.moderation.model.MuteResult; import dev.sheldan.abstracto.moderation.model.database.Infraction; import dev.sheldan.abstracto.moderation.model.database.Mute; -import dev.sheldan.abstracto.moderation.model.template.command.MuteContext; import dev.sheldan.abstracto.moderation.model.template.command.MuteListenerModel; import dev.sheldan.abstracto.moderation.model.template.command.MuteNotification; import dev.sheldan.abstracto.moderation.model.template.command.UnMuteLog; @@ -28,9 +29,7 @@ import dev.sheldan.abstracto.scheduling.service.SchedulerService; import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.entities.Guild; -import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Message; -import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @@ -87,9 +86,6 @@ public class MuteServiceBean implements MuteService { @Autowired private ServerManagementService serverManagementService; - @Autowired - private ChannelService channelService; - @Autowired private FeatureFlagService featureFlagService; @@ -104,68 +100,39 @@ public class MuteServiceBean implements MuteService { public static final String MUTE_COUNTER_KEY = "MUTES"; @Override - public CompletableFuture muteMember(Member memberToMute, String reason, Instant unMuteDate, Long channelId) { - FullUserInServer mutedUser = FullUserInServer + public CompletableFuture muteUserInServer(Guild guild, ServerUser userBeingMuted, String reason, Duration duration) { + Long serverId = guild.getIdLong(); + Instant targetDate = Instant.now().plus(duration); + MuteNotification muteNotificationModel = MuteNotification .builder() - .aUserInAServer(userInServerManagementService.loadOrCreateUser(memberToMute)) - .member(memberToMute) - .build(); - return muteUserInServer(mutedUser, reason, unMuteDate, channelId); - } - - @Override - public CompletableFuture muteUserInServer(FullUserInServer userBeingMuted, String reason, Instant unMuteDate, Long channelId) { - Member memberBeingMuted = userBeingMuted.getMember(); - List> futures = new ArrayList<>(); - - futures.add(memberService.timeoutUser(userBeingMuted.getMember(), unMuteDate)); - Guild guild = memberBeingMuted.getGuild(); - if(memberBeingMuted.getVoiceState() != null && memberBeingMuted.getVoiceState().getChannel() != null) { - futures.add(guild.kickVoiceMember(memberBeingMuted).submit()); - } - MuteNotification muteNotification = MuteNotification - .builder() - .muteTargetDate(unMuteDate) + .muteTargetDate(targetDate) .reason(reason) .serverName(guild.getName()) .build(); - futures.add(sendMuteNotification(memberBeingMuted, muteNotification, channelId)); - return FutureUtils.toSingleFutureGeneric(futures); - } - - private CompletableFuture sendMuteNotification(Member memberBeingMuted, MuteNotification muteNotification, Long channelId) { + MuteResult[] result = {MuteResult.SUCCESSFUL}; log.info("Notifying the user about the mute."); - CompletableFuture notificationFuture = new CompletableFuture<>(); - Long guildId = memberBeingMuted.getGuild().getIdLong(); - String muteNotificationMessage = templateService.renderTemplate(MUTE_NOTIFICATION_TEMPLATE, muteNotification, guildId); - CompletableFuture messageCompletableFuture = messageService.sendMessageToUser(memberBeingMuted.getUser(), muteNotificationMessage); - messageCompletableFuture.exceptionally(throwable -> { - GuildMessageChannel feedBackChannel = channelService.getMessageChannelFromServer(guildId, channelId); - channelService.sendTextToChannel(throwable.getMessage(), feedBackChannel).whenComplete((exceptionMessage, innerThrowable) -> { - notificationFuture.complete(null); - log.info("Successfully notified user {} in server {} about mute.", memberBeingMuted.getId(), memberBeingMuted.getGuild().getId()); - }).exceptionally(throwable1 -> { - notificationFuture.completeExceptionally(throwable1); - return null; - }); - return null; - }); - messageCompletableFuture.thenAccept(message1 -> - notificationFuture.complete(null) - ); - return notificationFuture; + String muteNotificationMessage = templateService.renderTemplate(MUTE_NOTIFICATION_TEMPLATE, muteNotificationModel, serverId); + return messageService.sendMessageToUser(userBeingMuted, muteNotificationMessage) + .exceptionally(throwable -> { + log.warn("Failed to notify about mute", throwable); + result[0] = MuteResult.NOTIFICATION_FAILED; + return null; + }) + .thenCompose(unused -> memberService.timeoutMember(guild, userBeingMuted, duration, reason)) + .thenApply(message -> result[0]); } - private void createMuteObject(MuteContext muteContext, String triggerKey, Long infractionId) { - AChannel channel = channelManagementService.loadChannel(muteContext.getChannelId()); + private void createMuteObject(ServerUser userToMute, ServerUser mutingUser, String reason, Instant targetDate, Long muteId, + String triggerKey, Long infractionId, ServerChannelMessage serverChannelMessage) { + AChannel channel = channelManagementService.loadChannel(serverChannelMessage.getChannelId()); AServerAChannelMessage origin = AServerAChannelMessage .builder() .channel(channel) .server(channel.getServer()) .build(); - AUserInAServer userInServerBeingMuted = userInServerManagementService.loadOrCreateUser(muteContext.getMutedUser()); - AUserInAServer userInServerMuting = userInServerManagementService.loadOrCreateUser(muteContext.getMutingUser()); - muteManagementService.createMute(userInServerBeingMuted, userInServerMuting, muteContext.getReason(), muteContext.getMuteTargetDate(), origin, triggerKey, muteContext.getMuteId(), infractionId); + AUserInAServer userInServerBeingMuted = userInServerManagementService.loadOrCreateUser(userToMute); + AUserInAServer userInServerMuting = userInServerManagementService.loadOrCreateUser(mutingUser); + muteManagementService.createMute(userInServerBeingMuted, userInServerMuting, reason, targetDate, origin, triggerKey, muteId, infractionId); } @Override @@ -200,27 +167,30 @@ public void cancelUnMuteJob(Mute mute) { } @Override - public CompletableFuture muteMemberWithLog(MuteContext context) { - log.debug("Muting member {} in server {}.", context.getMutedUser().getId(), context.getMutedUser().getGuild().getId()); - AServer server = serverManagementService.loadOrCreate(context.getMutedUser().getGuild().getIdLong()); - Long nextCounterValue = counterService.getNextCounterValue(server, MUTE_COUNTER_KEY); - context.setMuteId(nextCounterValue); - return muteMember(context.getMutedUser(), context.getReason(), context.getMuteTargetDate(), context.getChannelId()) - .thenCompose(unused -> self.sendMuteLog(context)) - .thenCompose(logMessage -> self.evaluateAndStoreInfraction(context, logMessage)) - .thenAccept(infractionId -> self.persistMute(context, infractionId)); + public CompletableFuture muteMemberWithLog(ServerUser userToMute, ServerUser mutingUser, String reason, Duration duration, Guild guild, ServerChannelMessage origin) { + Long serverId = userToMute.getServerId(); + Instant targetDate = Instant.now().plus(duration); + log.debug("Muting member {} in server {}.", userToMute.getUserId(), serverId); + AServer server = serverManagementService.loadOrCreate(serverId); + Long muteId = counterService.getNextCounterValue(server, MUTE_COUNTER_KEY); + CompletableFuture result = muteUserInServer(guild, userToMute, reason, duration); + return result + .thenCompose(unused -> self.sendMuteLog(userToMute, mutingUser, duration, reason)) + .thenCompose(logMessage -> self.evaluateAndStoreInfraction(userToMute, mutingUser, reason, targetDate, logMessage)) + .thenAccept(infractionId -> self.persistMute(userToMute, mutingUser, targetDate, muteId, reason, infractionId, origin)) + .thenApply(unused -> result.join()); } @Transactional - public CompletableFuture evaluateAndStoreInfraction(MuteContext context, Message logMessage) { - Guild guild = context.getMutedUser().getGuild(); - if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, guild.getIdLong())) { - Long infractionPoints = configService.getLongValueOrConfigDefault(MutingFeatureConfig.MUTE_INFRACTION_POINTS, guild.getIdLong()); - AUserInAServer mutedUser = userInServerManagementService.loadOrCreateUser(context.getMutedUser()); - AUserInAServer mutingUser = userInServerManagementService.loadOrCreateUser(context.getMutingUser()); + public CompletableFuture evaluateAndStoreInfraction(ServerUser userToMute, ServerUser mutingUser, String reason, Instant targetDate, Message logMessage) { + Long serverId = userToMute.getServerId(); + if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, serverId)) { + Long infractionPoints = configService.getLongValueOrConfigDefault(MutingFeatureConfig.MUTE_INFRACTION_POINTS, serverId); + AUserInAServer mutedUserInAServer = userInServerManagementService.loadOrCreateUser(userToMute); + AUserInAServer mutingUserInAServer = userInServerManagementService.loadOrCreateUser(mutingUser); Map parameters = new HashMap<>(); - parameters.put(INFRACTION_PARAMETER_DURATION_KEY, templateService.renderDuration(Duration.between(Instant.now(), context.getMuteTargetDate()), guild.getIdLong())); - return infractionService.createInfractionWithNotification(mutedUser, infractionPoints, MUTE_INFRACTION_TYPE, context.getReason(), mutingUser, parameters, logMessage) + parameters.put(INFRACTION_PARAMETER_DURATION_KEY, templateService.renderDuration(Duration.between(Instant.now(), targetDate), serverId)); + return infractionService.createInfractionWithNotification(mutedUserInAServer, infractionPoints, MUTE_INFRACTION_TYPE, reason, mutingUserInAServer, parameters, logMessage) .thenApply(Infraction::getId); } else { return CompletableFuture.completedFuture(null); @@ -228,26 +198,28 @@ public CompletableFuture evaluateAndStoreInfraction(MuteContext context, M } @Transactional - public void persistMute(MuteContext context, Long infractionId) { - completelyUnMuteMember(context.getMutedUser()); - String triggerKey = startUnMuteJobFor(context.getMuteTargetDate(), context.getMuteId(), context.getMutedUser().getGuild().getIdLong()); - createMuteObject(context, triggerKey, infractionId); + public void persistMute(ServerUser userToMute, ServerUser mutingUser, Instant targetDate, Long muteId, String reason, Long infractionId, ServerChannelMessage origin) { + completelyUnMuteMember(userToMute); + String triggerKey = startUnMuteJobFor(targetDate, muteId, userToMute.getServerId()); + createMuteObject(userToMute, mutingUser, reason, targetDate, muteId, triggerKey, infractionId, origin); } @Transactional - public CompletableFuture sendMuteLog(MuteContext muteLogModel) { + public CompletableFuture sendMuteLog(ServerUser userBeingMuted, ServerUser mutingUser, Duration duration, String reason) { + Instant targetDate = Instant.now().plus(duration); MuteListenerModel model = MuteListenerModel .builder() - .mutedUser(muteLogModel.getMutedUser()) - .mutingUser(muteLogModel.getMutingUser()) - .channelId(muteLogModel.getChannelId()) + .mutedUser(MemberDisplay.fromServerUser(userBeingMuted)) + .mutingUser(MemberDisplay.fromServerUser(mutingUser)) .oldMuteTargetDate(null) - .muteTargetDate(muteLogModel.getMuteTargetDate()) - .reason(muteLogModel.getReason()) + .duration(duration) + .muteTargetDate(targetDate) + .reason(reason) .build(); log.debug("Sending mute log to the mute post target."); - MessageToSend message = templateService.renderEmbedTemplate(MUTE_LOG_TEMPLATE, model, muteLogModel.getMutedUser().getIdLong()); - List> futures = postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, muteLogModel.getMutedUser().getGuild().getIdLong()); + Long serverId = userBeingMuted.getServerId(); + MessageToSend message = templateService.renderEmbedTemplate(MUTE_LOG_TEMPLATE, model, serverId); + List> futures = postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, serverId); return FutureUtils.toSingleFutureGeneric(futures).thenApply(unused -> futures.get(0).join()); } @@ -268,45 +240,36 @@ private CompletableFuture sendUnMuteLogMessage(UnMuteLog muteLogModel, ASe @Override @Transactional - public CompletableFuture unMuteUser(AUserInAServer userToUnmute, Member unMutingMember) { - boolean muteActive = muteManagementService.hasActiveMute(userToUnmute); + public CompletableFuture unMuteUser(ServerUser userToUnmute, ServerUser unMutingUser, Guild guild) { + AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(userToUnmute); + boolean muteActive = muteManagementService.hasActiveMute(aUserInAServer); if(!muteActive) { - CompletableFuture unMutedMemberFuture = memberService.retrieveMemberInServer(ServerUser.fromAUserInAServer(userToUnmute)); - return unMutedMemberFuture - .thenCompose(member -> memberService.removeTimeout(member)) - .thenCompose(unused -> self.sendUnmuteLog(null, unMutingMember.getGuild(), unMutedMemberFuture.join(), unMutingMember)); + return memberService.removeTimeout(guild, userToUnmute, null) + .thenCompose(unused -> self.sendUnmuteLog(null, guild, userToUnmute, unMutingUser)); } else { - Mute mute = muteManagementService.getAMuteOf(userToUnmute); - return endMute(mute); + Mute mute = muteManagementService.getAMuteOf(aUserInAServer); + return endMute(mute, guild); } } @Override - public CompletableFuture endMute(Mute mute) { + public CompletableFuture endMute(Mute mute, Guild guild) { if(mute.getMuteEnded()) { log.info("Mute {} in server {} has already ended. Not unmuting.", mute.getMuteId().getId(), mute.getMuteId().getServerId()); return CompletableFuture.completedFuture(null); } Long muteId = mute.getMuteId().getId(); - Guild guild = guildService.getGuildById(mute.getMuteId().getServerId()); AServer mutingServer = mute.getServer(); + ServerUser mutedUser = ServerUser.fromAUserInAServer(mute.getMutedUser()); + ServerUser mutingUser = ServerUser.fromAUserInAServer(mute.getMutedUser()); log.info("UnMuting {} in server {}", mute.getMutedUser().getUserReference().getId(), mutingServer.getId()); - CompletableFuture mutedMemberFuture = memberService.getMemberInServerAsync(mute.getMutedUser()); - CompletableFuture mutingMemberFuture = memberService.getMemberInServerAsync(mute.getMutingUser()); - return CompletableFuture.allOf(mutedMemberFuture, mutingMemberFuture) - .thenAccept(member -> memberService.removeTimeout(mutedMemberFuture.join())) - .thenCompose(unused -> self.sendUnmuteLog(muteId, guild, mutingMemberFuture, mutedMemberFuture)); + return memberService.removeTimeout(guild, mutedUser, null) + .thenCompose(unused -> self.sendUnmuteLog(muteId, guild, mutedUser, mutingUser)); } - @Transactional - public CompletableFuture sendUnmuteLog(Long muteId, Guild guild, CompletableFuture mutingMemberFuture, CompletableFuture mutedMemberFuture) { - Member mutingMember = !mutingMemberFuture.isCompletedExceptionally() ? mutingMemberFuture.join() : null; - Member mutedMember = !mutedMemberFuture.isCompletedExceptionally() ? mutedMemberFuture.join() : null; - return sendUnmuteLog(muteId, guild, mutedMember, mutingMember); - } @Transactional - public CompletableFuture sendUnmuteLog(Long muteId, Guild guild, Member mutedMember, Member mutingMember) { + public CompletableFuture sendUnmuteLog(Long muteId, Guild guild, ServerUser unMutedMember, ServerUser mutingMember) { Mute mute = null; if(muteId != null) { mute = muteManagementService.findMute(muteId, guild.getIdLong()); @@ -315,9 +278,8 @@ public CompletableFuture sendUnmuteLog(Long muteId, Guild guild, Member mu UnMuteLog unMuteLog = UnMuteLog .builder() .mute(mute) - .mutingUser(mutingMember) - .unMutedUser(mutedMember) - .guild(guild) + .mutingUser(MemberDisplay.fromServerUser(mutingMember)) + .unMutedUser(MemberDisplay.fromServerUser(unMutedMember)) .build(); CompletableFuture notificationFuture = sendUnMuteLogMessage(unMuteLog, mutingServer); return CompletableFuture.allOf(notificationFuture).thenAccept(aVoid -> { @@ -341,7 +303,8 @@ public CompletableFuture endMute(Long muteId, Long serverId) { log.info("UnMuting the mute {} in server {}", muteId, serverId); Optional muteOptional = muteManagementService.findMuteOptional(muteId, serverId); if(muteOptional.isPresent()) { - return endMute(muteOptional.get()); + Guild guild = guildService.getGuildById(serverId); + return endMute(muteOptional.get(), guild); } else { throw new NoMuteFoundException(); } @@ -359,8 +322,8 @@ public void completelyUnMuteUser(AUserInAServer aUserInAServer) { } @Override - public void completelyUnMuteMember(Member member) { - completelyUnMuteUser(userInServerManagementService.loadOrCreateUser(member)); + public void completelyUnMuteMember(ServerUser serverUser) { + completelyUnMuteUser(userInServerManagementService.loadOrCreateUser(serverUser)); } } diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/ReactionReportServiceBean.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/ReactionReportServiceBean.java index 7d92436fa..1bbd1cc78 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/ReactionReportServiceBean.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/ReactionReportServiceBean.java @@ -12,6 +12,7 @@ import dev.sheldan.abstracto.moderation.config.feature.mode.ReportReactionMode; import dev.sheldan.abstracto.moderation.config.posttarget.ReactionReportPostTarget; import dev.sheldan.abstracto.moderation.listener.manager.ReportMessageCreatedListenerManager; +import dev.sheldan.abstracto.moderation.model.ModerationActionButton; import dev.sheldan.abstracto.moderation.model.database.ModerationUser; import dev.sheldan.abstracto.moderation.model.database.ReactionReport; import dev.sheldan.abstracto.moderation.model.template.listener.ReportReactionNotificationModel; @@ -27,6 +28,7 @@ import java.time.Duration; import java.time.Instant; import java.time.temporal.ChronoUnit; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -68,6 +70,9 @@ public class ReactionReportServiceBean implements ReactionReportService { @Autowired private ReportMessageCreatedListenerManager reportMessageCreatedListenerManager; + @Autowired + private ModerationActionService moderationActionService; + private static final String REACTION_REPORT_TEMPLATE_KEY = "reactionReport_notification"; public static final String REACTION_REPORT_MODAL_ORIGIN = "reportMessageModal"; public static final String REACTION_REPORT_RESPONSE_TEMPLATE = "reactionReport_response"; @@ -93,11 +98,19 @@ public CompletableFuture createReactionReport(CachedMessage reportedMessag return channelService.editFieldValueInMessage(reportTextChannel, report.getReportMessageId(), 0, report.getReportCount().toString()) .thenAccept(message -> self.updateModerationUserReportCooldown(reporter)); } else { + boolean reportActionsEnabled = featureModeService.featureModeActive(ModerationFeatureDefinition.REPORT_REACTIONS, serverId, ReportReactionMode.REPORT_ACTIONS); + List moderationActionComponents = new ArrayList<>(); + if(reportActionsEnabled) { + ServerUser reportedServerUser = ServerUser.fromAUserInAServer(reportedUser); + List moderationActions = moderationActionService.getModerationActionButtons(reportedServerUser); + moderationActionComponents.addAll(moderationActions); + } ReportReactionNotificationModel model = ReportReactionNotificationModel .builder() .reportCount(1) .context(context) .singularMessage(singularMessage) + .moderationActionComponents(moderationActionComponents) .reportedMessage(reportedMessage) .build(); MessageToSend messageToSend = templateService.renderEmbedTemplate(REACTION_REPORT_TEMPLATE_KEY, model, serverId); diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/WarnServiceBean.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/WarnServiceBean.java index 402d4497a..4b7b9ce45 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/WarnServiceBean.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/WarnServiceBean.java @@ -6,12 +6,12 @@ import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer; +import dev.sheldan.abstracto.core.models.template.display.MemberDisplay; import dev.sheldan.abstracto.core.service.*; import dev.sheldan.abstracto.core.service.management.DefaultConfigManagementService; import dev.sheldan.abstracto.core.service.management.ServerManagementService; import dev.sheldan.abstracto.core.service.management.UserInServerManagementService; import dev.sheldan.abstracto.core.utils.FutureUtils; -import dev.sheldan.abstracto.core.utils.SnowflakeUtils; import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; import dev.sheldan.abstracto.moderation.config.feature.WarningDecayFeatureConfig; import dev.sheldan.abstracto.moderation.config.feature.WarningFeatureConfig; @@ -22,7 +22,7 @@ import dev.sheldan.abstracto.moderation.listener.manager.WarningCreatedListenerManager; import dev.sheldan.abstracto.moderation.model.database.Infraction; import dev.sheldan.abstracto.moderation.model.database.Warning; -import dev.sheldan.abstracto.moderation.model.template.command.WarnContext; +import dev.sheldan.abstracto.moderation.model.template.command.WarnLogModel; import dev.sheldan.abstracto.moderation.model.template.command.WarnNotification; import dev.sheldan.abstracto.moderation.model.template.job.WarnDecayLogModel; import dev.sheldan.abstracto.moderation.model.template.job.WarnDecayWarning; @@ -109,50 +109,31 @@ public class WarnServiceBean implements WarnService { public static final String WARN_DECAY_LOG_TEMPLATE_KEY = "warn_decay_log"; public static final String WARN_DECAY_NOTIFICATION_TEMPLATE_KEY = "warn_decay_member_notification"; - @Override - public CompletableFuture notifyAndLogFullUserWarning(WarnContext context) { - Long serverId = context.getGuild().getIdLong(); - Long warningId = counterService.getNextCounterValue(serverId, WARNINGS_COUNTER_KEY); - context.setWarnId(warningId); - Member warnedMember = context.getWarnedMember(); - Member warningMember = context.getMember(); - Guild guild = warnedMember.getGuild(); - log.info("User {} is warning {} in server {}", warnedMember.getId(), warningMember.getId(), guild.getIdLong()); - WarnNotification warnNotification = WarnNotification + @Transactional + public CompletableFuture sendWarningLog(Guild guild, ServerUser warnedUser, ServerUser warningUser, String reason, ServerChannelMessage serverChannelMessage, Long warningId) { + WarnLogModel warnContext = WarnLogModel .builder() - .reason(context.getReason()) + .warnedMember(MemberDisplay.fromServerUser(warnedUser)) + .warningMember(MemberDisplay.fromServerUser(warningUser)) + .channelMessage(serverChannelMessage) .warnId(warningId) - .serverName(guild.getName()) + .reason(reason) .build(); - String warnNotificationMessage = templateService.renderTemplate(WARN_NOTIFICATION_TEMPLATE, warnNotification, serverId); - return messageService.sendMessageToUser(warnedMember.getUser(), warnNotificationMessage) - .exceptionally(throwable -> { - log.warn("Failed to notify user {} of warning {} in guild {}.", warnedMember.getId(), warningId, serverId); - return null; - }) - .thenCompose(message -> self.sendWarningLog(context)) - .thenCompose(logMessage -> self.evaluateInfraction(context, logMessage)) - .thenAccept(context::setInfractionId); - } - - @Transactional - public CompletableFuture sendWarningLog(WarnContext context) { - MessageToSend message = renderMessageModel(context); - List> futures = postTargetService.sendEmbedInPostTarget(message, WarningPostTarget.WARN_LOG, context.getGuild().getIdLong()); + MessageToSend message = renderMessageModel(warnContext, guild.getIdLong()); + List> futures = postTargetService.sendEmbedInPostTarget(message, WarningPostTarget.WARN_LOG, guild.getIdLong()); return FutureUtils.toSingleFutureGeneric(futures).thenCompose(unused -> futures.get(0)); } @Transactional - public CompletableFuture evaluateInfraction(WarnContext context, Message logMessage) { - Long serverId = context.getGuild().getIdLong(); + public CompletableFuture evaluateInfraction(Guild guild, ServerUser warnedUser, ServerUser warningUser, String reason, Message logMessage) { + Long serverId = guild.getIdLong(); if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, serverId)) { Long infractionPoints = configService.getLongValueOrConfigDefault(WarningFeatureConfig.WARN_INFRACTION_POINTS, serverId); - AServer server = serverManagementService.loadServer(context.getGuild()); - AUserInAServer warnedUser = userInServerManagementService.loadOrCreateUser(server, context.getWarnedMember().getIdLong()); - AUserInAServer warningUser = userInServerManagementService.loadOrCreateUser(server, context.getMember().getIdLong()); + AUserInAServer warnedUserInAServer = userInServerManagementService.loadOrCreateUser(warnedUser); + AUserInAServer warningUserInAServer = userInServerManagementService.loadOrCreateUser(warningUser); // both user could create the server object, we need to make sure we have the same reference - warnedUser.setServerReference(warningUser.getServerReference()); - return infractionService.createInfractionWithNotification(warnedUser, infractionPoints, WARN_INFRACTION_TYPE, context.getReason(), warningUser, logMessage) + warnedUserInAServer.setServerReference(warningUserInAServer.getServerReference()); + return infractionService.createInfractionWithNotification(warnedUserInAServer, infractionPoints, WARN_INFRACTION_TYPE, reason, warningUserInAServer, logMessage) .thenApply(Infraction::getId); } else { return CompletableFuture.completedFuture(null); @@ -160,36 +141,40 @@ public CompletableFuture evaluateInfraction(WarnContext context, Message l } @Override - public CompletableFuture warnUserWithLog(WarnContext context) { - return notifyAndLogFullUserWarning(context) - .thenAccept(aVoid -> self.persistWarning(context)); + public CompletableFuture warnUserWithLog(Guild guild, ServerUser warnedUser, ServerUser warningUser, String reason, ServerChannelMessage serverChannelMessage) { + Long serverId = guild.getIdLong(); + Long warningId = counterService.getNextCounterValue(serverId, WARNINGS_COUNTER_KEY); + log.info("User {} is warning {} in server {}", warningUser.getUserId(), warnedUser.getUserId(), serverId); + WarnNotification warnNotification = WarnNotification + .builder() + .reason(reason) + .warnId(warningId) + .serverName(guild.getName()) + .build(); + String warnNotificationMessage = templateService.renderTemplate(WARN_NOTIFICATION_TEMPLATE, warnNotification, serverId); + return messageService.sendMessageToUser(warnedUser, warnNotificationMessage) + .exceptionally(throwable -> { + log.warn("Failed to notify user {} of warning {} in guild {}.", warnedUser.getUserId(), warningId, serverId, throwable); + return null; + }) + .thenCompose(message -> self.sendWarningLog(guild, warnedUser, warningUser, reason, serverChannelMessage, warningId)) + .thenCompose(logMessage -> self.evaluateInfraction(guild, warnedUser, warningUser, reason, logMessage)) + .thenAccept(infractionId -> self.persistWarning(warnedUser, warningUser, reason, serverChannelMessage, infractionId, warningId)); } @Transactional - public void persistWarning(WarnContext context) { + public void persistWarning(ServerUser warnedUser, ServerUser warningUser, String reason, ServerChannelMessage serverChannelMessage, Long infractionId, Long warningId) { + Long serverId = warnedUser.getServerId(); log.info("Persisting warning {} in server {} for user {} by user {}.", - context.getWarnId(), context.getGuild().getId(), context.getWarnedMember().getId(), context.getMember().getId()); - AUserInAServer warnedUser = userInServerManagementService.loadOrCreateUser(context.getWarnedMember()); - AUserInAServer warningUser = userInServerManagementService.loadOrCreateUser(context.getMember()); - Warning createdWarning = warnManagementService.createWarning(warnedUser, warningUser, context.getReason(), context.getWarnId()); - if(context.getInfractionId() != null) { - Infraction infraction = infractionManagementService.loadInfraction(context.getInfractionId()); + warningId, serverId, warnedUser.getUserId(), warningUser.getUserId()); + AUserInAServer warnedUserInAServer = userInServerManagementService.loadOrCreateUser(warnedUser); + AUserInAServer warningUserInAServer = userInServerManagementService.loadOrCreateUser(warningUser); + Warning createdWarning = warnManagementService.createWarning(warnedUserInAServer, warningUserInAServer, reason, warningId); + if(infractionId != null) { + Infraction infraction = infractionManagementService.loadInfraction(infractionId); createdWarning.setInfraction(infraction); } - ServerUser warnedServerUser = ServerUser.fromAUserInAServer(warnedUser); - ServerUser warningServerUser = ServerUser.fromAUserInAServer(warnedUser); - ServerChannelMessage commandMessage; - if(context.getMessage() != null) { - commandMessage = ServerChannelMessage.fromMessage(context.getMessage()); - } else { - commandMessage = ServerChannelMessage - .builder() - .serverId(context.getGuild().getIdLong()) - .channelId(context.getChannel().getIdLong()) - .messageId(SnowflakeUtils.createSnowFlake()) - .build(); - } - warningCreatedListenerManager.sendWarningCreatedEvent(createdWarning.getWarnId(), warnedServerUser, warningServerUser, context.getReason(), commandMessage); + warningCreatedListenerManager.sendWarningCreatedEvent(createdWarning.getWarnId(), warnedUser, warningUser, reason, serverChannelMessage); } @Override @@ -308,8 +293,8 @@ public void decayWarning(Warning warning, Instant decayDate) { } } - public MessageToSend renderMessageModel(WarnContext warnContext) { - return templateService.renderEmbedTemplate(WARN_LOG_TEMPLATE, warnContext, warnContext.getGuild().getIdLong()); + public MessageToSend renderMessageModel(WarnLogModel warnContext, Long serverId) { + return templateService.renderEmbedTemplate(WARN_LOG_TEMPLATE, warnContext, serverId); } private CompletableFuture logDecayedWarnings(AServer server, List warningsToDecay) { diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/resources/moderation-config.properties b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/resources/moderation-config.properties index 2aefc1849..a1ec86e1b 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/resources/moderation-config.properties +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/resources/moderation-config.properties @@ -57,6 +57,10 @@ abstracto.featureModes.singularReportReactions.featureName=reportReactions abstracto.featureModes.singularReportReactions.mode=singularReportReactions abstracto.featureModes.singularReportReactions.enabled=false +abstracto.featureModes.reactionReportActions.featureName=reportReactions +abstracto.featureModes.reactionReportActions.mode=reactionReportActions +abstracto.featureModes.reactionReportActions.enabled=false + abstracto.systemConfigs.infractionLvl1.name=infractionLvl1 abstracto.systemConfigs.infractionLvl1.longValue=10 diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/test/java/dev/sheldan/abstracto/moderation/command/KickTest.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/test/java/dev/sheldan/abstracto/moderation/command/KickTest.java deleted file mode 100644 index cde185faa..000000000 --- a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/test/java/dev/sheldan/abstracto/moderation/command/KickTest.java +++ /dev/null @@ -1,80 +0,0 @@ -package dev.sheldan.abstracto.moderation.command; - -import dev.sheldan.abstracto.core.command.execution.CommandContext; -import dev.sheldan.abstracto.core.command.execution.CommandResult; -import dev.sheldan.abstracto.core.test.command.CommandConfigValidator; -import dev.sheldan.abstracto.core.test.command.CommandTestUtilities; -import dev.sheldan.abstracto.moderation.model.template.command.KickLogModel; -import dev.sheldan.abstracto.moderation.service.KickServiceBean; -import dev.sheldan.abstracto.core.templating.service.TemplateService; -import net.dv8tion.jda.api.entities.Member; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; - -import java.util.Arrays; -import java.util.concurrent.CompletableFuture; - -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.when; - -@RunWith(MockitoJUnitRunner.class) -public class KickTest { - - @InjectMocks - private Kick testUnit; - - @Mock - private TemplateService templateService; - - @Mock - private KickServiceBean kickService; - - @Mock - private Member memberToKick; - - @Captor - private ArgumentCaptor logModelArgumentCaptor; - - private static final String REASON = "reason"; - private static final Long SERVER_ID = 1L; - - @Test - public void testKickMemberWithoutReason() { - CommandContext parameters = CommandTestUtilities.getWithParameters(Arrays.asList(memberToKick)); - when(memberToKick.getGuild()).thenReturn(parameters.getGuild()); - when(parameters.getGuild().getIdLong()).thenReturn(SERVER_ID); - when(templateService.renderSimpleTemplate(Kick.KICK_DEFAULT_REASON_TEMPLATE, SERVER_ID)).thenReturn(REASON); - when(kickService.kickMember(eq(memberToKick), eq(REASON), logModelArgumentCaptor.capture())).thenReturn(CompletableFuture.completedFuture(null)); - CompletableFuture result = testUnit.executeAsync(parameters); - KickLogModel usedLogModel = logModelArgumentCaptor.getValue(); - Assert.assertEquals(REASON, usedLogModel.getReason()); - Assert.assertEquals(memberToKick, usedLogModel.getKickedUser()); - Assert.assertEquals(parameters.getAuthor(), usedLogModel.getMember()); - CommandTestUtilities.checkSuccessfulCompletionAsync(result); - } - - @Test - public void testKickMemberWithReason() { - String customReason = "reason2"; - CommandContext parameters = CommandTestUtilities.getWithParameters(Arrays.asList(memberToKick, customReason)); - when(memberToKick.getGuild()).thenReturn(parameters.getGuild()); - when(kickService.kickMember(eq(memberToKick), eq(customReason), logModelArgumentCaptor.capture())).thenReturn(CompletableFuture.completedFuture(null)); - CompletableFuture result = testUnit.executeAsync(parameters); - KickLogModel usedLogModel = logModelArgumentCaptor.getValue(); - Assert.assertEquals(customReason, usedLogModel.getReason()); - Assert.assertEquals(memberToKick, usedLogModel.getKickedUser()); - Assert.assertEquals(parameters.getAuthor(), usedLogModel.getMember()); - CommandTestUtilities.checkSuccessfulCompletionAsync(result); - } - - @Test - public void validateCommand() { - CommandConfigValidator.validateCommandConfiguration(testUnit.getConfiguration()); - } -} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/test/java/dev/sheldan/abstracto/moderation/command/WarnTest.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/test/java/dev/sheldan/abstracto/moderation/command/WarnTest.java deleted file mode 100644 index e4b618139..000000000 --- a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/test/java/dev/sheldan/abstracto/moderation/command/WarnTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package dev.sheldan.abstracto.moderation.command; - -import dev.sheldan.abstracto.core.command.execution.CommandContext; -import dev.sheldan.abstracto.core.command.execution.CommandResult; -import dev.sheldan.abstracto.core.test.command.CommandConfigValidator; -import dev.sheldan.abstracto.core.test.command.CommandTestUtilities; -import dev.sheldan.abstracto.moderation.model.template.command.WarnContext; -import dev.sheldan.abstracto.moderation.service.WarnService; -import dev.sheldan.abstracto.core.templating.service.TemplateService; -import net.dv8tion.jda.api.entities.Member; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.*; -import org.mockito.junit.MockitoJUnitRunner; - -import java.util.Arrays; -import java.util.concurrent.CompletableFuture; - -import static org.mockito.Mockito.when; - -@RunWith(MockitoJUnitRunner.class) -public class WarnTest { - - @InjectMocks - private Warn testUnit; - - @Mock - private WarnService warnService; - - @Mock - private TemplateService templateService; - private static final String DEFAULT_REASON = "defaultReason"; - - @Captor - private ArgumentCaptor parameterCaptor; - - private static final Long SERVER_ID = 1L; - - @Test - public void testExecuteWarnCommandWithReason() { - Member warnedMember = Mockito.mock(Member.class); - String reason = "reason"; - CommandContext parameters = CommandTestUtilities.getWithParameters(Arrays.asList(warnedMember, reason)); - when(warnedMember.getGuild()).thenReturn(parameters.getGuild()); - when(parameters.getGuild().getIdLong()).thenReturn(SERVER_ID); - when(templateService.renderSimpleTemplate(Warn.WARN_DEFAULT_REASON_TEMPLATE, SERVER_ID)).thenReturn(DEFAULT_REASON); - when(warnService.warnUserWithLog(parameterCaptor.capture())).thenReturn(CompletableFuture.completedFuture(null)); - CompletableFuture result = testUnit.executeAsync(parameters); - WarnContext value = parameterCaptor.getValue(); - Assert.assertEquals(reason, value.getReason()); - Assert.assertEquals(warnedMember, value.getWarnedMember()); - Assert.assertEquals(parameters.getAuthor(), value.getMember()); - CommandTestUtilities.checkSuccessfulCompletionAsync(result); - } - - @Test - public void testExecuteWarnCommandWithDefaultReason() { - Member warnedMember = Mockito.mock(Member.class); - CommandContext parameters = CommandTestUtilities.getWithParameters(Arrays.asList(warnedMember)); - when(warnedMember.getGuild()).thenReturn(parameters.getGuild()); - when(parameters.getGuild().getIdLong()).thenReturn(SERVER_ID); - when(templateService.renderSimpleTemplate(Warn.WARN_DEFAULT_REASON_TEMPLATE, SERVER_ID)).thenReturn(DEFAULT_REASON); - when(warnService.warnUserWithLog(parameterCaptor.capture())).thenReturn(CompletableFuture.completedFuture(null)); - CompletableFuture result = testUnit.executeAsync(parameters); - WarnContext value = parameterCaptor.getValue(); - Assert.assertEquals(DEFAULT_REASON, value.getReason()); - Assert.assertEquals(warnedMember, value.getWarnedMember()); - Assert.assertEquals(parameters.getAuthor(), value.getMember()); - CommandTestUtilities.checkSuccessfulCompletionAsync(result); - } - - @Test - public void validateCommand() { - CommandConfigValidator.validateCommandConfiguration(testUnit.getConfiguration()); - } - -} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/test/java/dev/sheldan/abstracto/moderation/command/mute/MuteTest.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/test/java/dev/sheldan/abstracto/moderation/command/mute/MuteTest.java deleted file mode 100644 index 0d70d0e49..000000000 --- a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/test/java/dev/sheldan/abstracto/moderation/command/mute/MuteTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package dev.sheldan.abstracto.moderation.command.mute; - -import dev.sheldan.abstracto.core.command.execution.CommandContext; -import dev.sheldan.abstracto.core.command.execution.CommandResult; -import dev.sheldan.abstracto.core.templating.service.TemplateService; -import dev.sheldan.abstracto.core.test.command.CommandConfigValidator; -import dev.sheldan.abstracto.core.test.command.CommandTestUtilities; -import dev.sheldan.abstracto.moderation.command.Mute; -import dev.sheldan.abstracto.moderation.model.template.command.MuteContext; -import dev.sheldan.abstracto.moderation.service.MuteService; -import net.dv8tion.jda.api.entities.Member; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.*; -import org.mockito.junit.MockitoJUnitRunner; - -import java.time.Duration; -import java.util.Arrays; -import java.util.concurrent.CompletableFuture; - -import static org.mockito.Mockito.when; - -@RunWith(MockitoJUnitRunner.class) -public class MuteTest { - - @InjectMocks - private Mute testUnit; - - @Mock - private MuteService muteService; - - @Mock - private TemplateService templateService; - - @Captor - private ArgumentCaptor muteLogArgumentCaptor; - - @Test - public void testMuteMember() { - Member mutedMember = Mockito.mock(Member.class); - String reason = "reason"; - Duration duration = Duration.ofMinutes(1); - CommandContext parameters = CommandTestUtilities.getWithParameters(Arrays.asList(mutedMember, duration, reason)); - when(mutedMember.getGuild()).thenReturn(parameters.getGuild()); - when(muteService.muteMemberWithLog(muteLogArgumentCaptor.capture())).thenReturn(CompletableFuture.completedFuture(null)); - CompletableFuture result = testUnit.executeAsync(parameters); - CommandTestUtilities.checkSuccessfulCompletionAsync(result); - MuteContext muteLog = muteLogArgumentCaptor.getValue(); - Assert.assertEquals(mutedMember, muteLog.getMutedUser()); - Assert.assertEquals(parameters.getAuthor(), muteLog.getMutingUser()); - } - - @Test - public void validateCommand() { - CommandConfigValidator.validateCommandConfiguration(testUnit.getConfiguration()); - } -} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/config/feature/ReportReactionFeatureConfig.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/config/feature/ReportReactionFeatureConfig.java index 43b99931d..ab007735a 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/config/feature/ReportReactionFeatureConfig.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/config/feature/ReportReactionFeatureConfig.java @@ -38,6 +38,6 @@ public List getRequiredSystemConfigKeys() { @Override public List getAvailableModes() { - return Arrays.asList(ReportReactionMode.SINGULAR_MESSAGE, ReportReactionMode.ANONYMOUS); + return Arrays.asList(ReportReactionMode.values()); } } diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/config/feature/WarningDecayFeatureConfig.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/config/feature/WarningDecayFeatureConfig.java index 603d21c94..4102ca76b 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/config/feature/WarningDecayFeatureConfig.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/config/feature/WarningDecayFeatureConfig.java @@ -42,6 +42,6 @@ public List getRequiredPostTargets() { @Override public List getAvailableModes() { - return Arrays.asList(WarnDecayMode.AUTOMATIC_WARN_DECAY_LOG); + return Arrays.asList(WarnDecayMode.values()); } } diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/config/feature/WarningFeatureConfig.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/config/feature/WarningFeatureConfig.java index 38adddbcb..eadb90d13 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/config/feature/WarningFeatureConfig.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/config/feature/WarningFeatureConfig.java @@ -37,7 +37,7 @@ public List getRequiredPostTargets() { @Override public List getAvailableModes() { - return Arrays.asList(WarningMode.WARN_DECAY_LOG); + return Arrays.asList(WarningMode.values()); } @Override diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/config/feature/mode/ReportReactionMode.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/config/feature/mode/ReportReactionMode.java index 0b2cbbb3d..769bdfbec 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/config/feature/mode/ReportReactionMode.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/config/feature/mode/ReportReactionMode.java @@ -5,7 +5,7 @@ @Getter public enum ReportReactionMode implements FeatureMode { - SINGULAR_MESSAGE("singularReportReactions"), ANONYMOUS("anonymousReportReactions"); + SINGULAR_MESSAGE("singularReportReactions"), ANONYMOUS("anonymousReportReactions"), REPORT_ACTIONS("reactionReportActions"); private final String key; diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/ModerationActionButton.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/ModerationActionButton.java new file mode 100644 index 000000000..7020ede65 --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/ModerationActionButton.java @@ -0,0 +1,11 @@ +package dev.sheldan.abstracto.moderation.model; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class ModerationActionButton { + private String componentId; + private String action; +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/MuteResult.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/MuteResult.java new file mode 100644 index 000000000..85428a41d --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/MuteResult.java @@ -0,0 +1,6 @@ +package dev.sheldan.abstracto.moderation.model; + +public enum MuteResult { + NOTIFICATION_FAILED, SUCCESSFUL +} + diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/interaction/ModerationActionBanPayload.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/interaction/ModerationActionBanPayload.java new file mode 100644 index 000000000..fd952b6c8 --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/interaction/ModerationActionBanPayload.java @@ -0,0 +1,15 @@ +package dev.sheldan.abstracto.moderation.model.interaction; + +import dev.sheldan.abstracto.core.interaction.modal.ModalPayload; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class ModerationActionBanPayload implements ModalPayload { + private String modalId; + private String reasonInputId; + private String durationInputId; + private Long serverId; + private Long bannedUserId; +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/interaction/ModerationActionKickPayload.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/interaction/ModerationActionKickPayload.java new file mode 100644 index 000000000..163d516cc --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/interaction/ModerationActionKickPayload.java @@ -0,0 +1,14 @@ +package dev.sheldan.abstracto.moderation.model.interaction; + +import dev.sheldan.abstracto.core.interaction.modal.ModalPayload; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class ModerationActionKickPayload implements ModalPayload { + private String modalId; + private String reasonInputId; + private Long serverId; + private Long kickedUserId; +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/interaction/ModerationActionMutePayload.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/interaction/ModerationActionMutePayload.java new file mode 100644 index 000000000..68d2e229f --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/interaction/ModerationActionMutePayload.java @@ -0,0 +1,15 @@ +package dev.sheldan.abstracto.moderation.model.interaction; + +import dev.sheldan.abstracto.core.interaction.modal.ModalPayload; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class ModerationActionMutePayload implements ModalPayload { + private String modalId; + private String reasonInputId; + private String durationInputId; + private Long serverId; + private Long mutedUserId; +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/interaction/ModerationActionWarnPayload.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/interaction/ModerationActionWarnPayload.java new file mode 100644 index 000000000..14135c9dd --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/interaction/ModerationActionWarnPayload.java @@ -0,0 +1,14 @@ +package dev.sheldan.abstracto.moderation.model.interaction; + +import dev.sheldan.abstracto.core.interaction.modal.ModalPayload; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class ModerationActionWarnPayload implements ModalPayload { + private String modalId; + private String reasonInputId; + private Long serverId; + private Long warnedUserId; +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/BanLog.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/BanLog.java index f0c4c1713..fbdb24f65 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/BanLog.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/BanLog.java @@ -1,11 +1,10 @@ package dev.sheldan.abstracto.moderation.model.template.command; +import dev.sheldan.abstracto.core.models.template.display.MemberDisplay; +import dev.sheldan.abstracto.core.models.template.display.UserDisplay; import lombok.Builder; import lombok.Getter; import lombok.Setter; -import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.Message; -import net.dv8tion.jda.api.entities.User; import java.time.Duration; @@ -24,11 +23,10 @@ public class BanLog { /** * The member executing the ban */ - private Member banningMember; + private MemberDisplay banningMember; /** * The user being banned */ - private User bannedUser; - private Message commandMessage; + private UserDisplay bannedUser; private Duration deletionDuration; } diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/KickLogModel.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/KickLogModel.java index 3b97ad7b0..415be4082 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/KickLogModel.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/KickLogModel.java @@ -1,11 +1,9 @@ package dev.sheldan.abstracto.moderation.model.template.command; +import dev.sheldan.abstracto.core.models.template.display.MemberDisplay; import lombok.Builder; import lombok.Getter; import lombok.Setter; -import net.dv8tion.jda.api.entities.Guild; -import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; /** * Used when rendering the notification when a member was kicked. The template is: "kick_log_embed" @@ -14,15 +12,7 @@ @Builder @Setter public class KickLogModel { - /** - * The reason of the kick - */ private String reason; - /** - * The member being kicked - */ - private Member kickedUser; - private Member member; - private Guild guild; - private GuildMessageChannel channel; + private MemberDisplay kickedMember; + private MemberDisplay kickingMember; } diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/MuteListenerModel.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/MuteListenerModel.java index 1476ccda5..b849e096d 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/MuteListenerModel.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/MuteListenerModel.java @@ -1,5 +1,6 @@ package dev.sheldan.abstracto.moderation.model.template.command; +import dev.sheldan.abstracto.core.models.template.display.MemberDisplay; import lombok.Getter; import lombok.experimental.SuperBuilder; import net.dv8tion.jda.api.entities.Member; @@ -13,27 +14,19 @@ public class MuteListenerModel { /** * The {@link Member} being muted */ - private Member mutedUser; + private MemberDisplay mutedUser; /** * The {@link Member} executing the mute */ - private Member mutingUser; + private MemberDisplay mutingUser; /** * The persisted mute object from the database containing the information about the mute */ private Long muteId; private Instant muteTargetDate; private Instant oldMuteTargetDate; + private Duration duration; private String reason; - private Long channelId; - - /** - * The {@link Duration} of the mute between the mute was cast and and the date it should end - * @return The {@link Duration} between start and target date - */ - public Duration getMuteDuration() { - return Duration.between(Instant.now(), muteTargetDate); - } public boolean getMuteEnded() { return oldMuteTargetDate != null && muteTargetDate == null || oldMuteTargetDate == null && muteTargetDate == null; diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/UnBanLog.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/UnBanLog.java index 4975be2e7..679a5834b 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/UnBanLog.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/UnBanLog.java @@ -1,12 +1,10 @@ package dev.sheldan.abstracto.moderation.model.template.command; +import dev.sheldan.abstracto.core.models.template.display.MemberDisplay; +import dev.sheldan.abstracto.core.models.template.display.UserDisplay; import lombok.Builder; import lombok.Getter; import lombok.Setter; -import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.Message; -import net.dv8tion.jda.api.entities.User; - /** * Used when rendering the notification when a member was banned. The template is: "ban_log_embed" @@ -18,10 +16,9 @@ public class UnBanLog { /** * The member executing the unban */ - private Member unBanningMember; + private MemberDisplay unBanningMember; /** * The user being unbanned */ - private User bannedUser; - private Message commandMessage; + private UserDisplay bannedUser; } diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/UnMuteLog.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/UnMuteLog.java index ddf84c5de..5779f74e3 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/UnMuteLog.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/UnMuteLog.java @@ -1,6 +1,6 @@ package dev.sheldan.abstracto.moderation.model.template.command; -import dev.sheldan.abstracto.core.models.context.ServerContext; +import dev.sheldan.abstracto.core.models.template.display.MemberDisplay; import dev.sheldan.abstracto.core.utils.MessageUtils; import dev.sheldan.abstracto.moderation.model.database.Mute; import lombok.AllArgsConstructor; @@ -8,7 +8,6 @@ import lombok.NoArgsConstructor; import lombok.Setter; import lombok.experimental.SuperBuilder; -import net.dv8tion.jda.api.entities.Member; import java.time.Duration; import java.time.Instant; @@ -21,15 +20,15 @@ @Setter @NoArgsConstructor @AllArgsConstructor -public class UnMuteLog extends ServerContext { +public class UnMuteLog { /** * The un-muted Member, is null if the member left the server */ - private Member unMutedUser; + private MemberDisplay unMutedUser; /** * The user casting the mute, is null if the member left the server */ - private Member mutingUser; + private MemberDisplay mutingUser; /** * The persisted mute object from the database containing the information about the mute */ diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/WarnContext.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/WarnContext.java deleted file mode 100644 index 56a9ee458..000000000 --- a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/WarnContext.java +++ /dev/null @@ -1,36 +0,0 @@ -package dev.sheldan.abstracto.moderation.model.template.command; - -import dev.sheldan.abstracto.moderation.model.database.Warning; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; -import net.dv8tion.jda.api.entities.Guild; -import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.Message; -import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; - -/** - * Used when rendering the notification when a member was warned. The template is: "warn_log_embed" - */ -@Getter -@Builder -@Setter -public class WarnContext { - /** - * The reason why the warn was cast - */ - private String reason; - /** - * The {@link Member} being warned - */ - private Member warnedMember; - /** - * The persisted {@link Warning} object from the database containing the information about the warning - */ - private Long warnId; - private Long infractionId; - private Member member; - private Guild guild; - private Message message; - private GuildMessageChannel channel; -} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/WarnLogModel.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/WarnLogModel.java new file mode 100644 index 000000000..20d5ac67f --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/command/WarnLogModel.java @@ -0,0 +1,29 @@ +package dev.sheldan.abstracto.moderation.model.template.command; + +import dev.sheldan.abstracto.core.models.ServerChannelMessage; +import dev.sheldan.abstracto.core.models.template.display.MemberDisplay; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import net.dv8tion.jda.api.entities.Member; + +/** + * Used when rendering the notification when a member was warned. The template is: "warn_log_embed" + */ +@Getter +@Builder +@Setter +public class WarnLogModel { + /** + * The reason why warn was cast + */ + private String reason; + /** + * The {@link Member} being warned + */ + private MemberDisplay warnedMember; + private Long warnId; + private Long infractionId; + private MemberDisplay warningMember; + private ServerChannelMessage channelMessage; +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/listener/ModerationActionBanModalModel.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/listener/ModerationActionBanModalModel.java new file mode 100644 index 000000000..3c315956a --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/listener/ModerationActionBanModalModel.java @@ -0,0 +1,12 @@ +package dev.sheldan.abstracto.moderation.model.template.listener; + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class ModerationActionBanModalModel { + private String modalId; + private String reasonComponentId; + private String durationComponentId; +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/listener/ModerationActionKickModalModel.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/listener/ModerationActionKickModalModel.java new file mode 100644 index 000000000..185fa36d4 --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/listener/ModerationActionKickModalModel.java @@ -0,0 +1,11 @@ +package dev.sheldan.abstracto.moderation.model.template.listener; + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class ModerationActionKickModalModel { + private String modalId; + private String reasonComponentId; +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/listener/ModerationActionMuteModalModel.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/listener/ModerationActionMuteModalModel.java new file mode 100644 index 000000000..acb065856 --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/listener/ModerationActionMuteModalModel.java @@ -0,0 +1,12 @@ +package dev.sheldan.abstracto.moderation.model.template.listener; + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class ModerationActionMuteModalModel { + private String modalId; + private String reasonComponentId; + private String durationComponentId; +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/listener/ModerationActionPayloadModel.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/listener/ModerationActionPayloadModel.java new file mode 100644 index 000000000..6f578a173 --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/listener/ModerationActionPayloadModel.java @@ -0,0 +1,23 @@ +package dev.sheldan.abstracto.moderation.model.template.listener; + +import dev.sheldan.abstracto.core.interaction.button.ButtonPayload; +import dev.sheldan.abstracto.core.models.ServerUser; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder +public class ModerationActionPayloadModel implements ButtonPayload { + private String action; + private ServerUser user; + + public static ModerationActionPayloadModel forAction(String action, ServerUser user) { + return ModerationActionPayloadModel + .builder() + .user(user) + .action(action) + .build(); + } +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/listener/ModerationActionWarnModalModel.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/listener/ModerationActionWarnModalModel.java new file mode 100644 index 000000000..8116a30a4 --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/listener/ModerationActionWarnModalModel.java @@ -0,0 +1,11 @@ +package dev.sheldan.abstracto.moderation.model.template.listener; + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class ModerationActionWarnModalModel { + private String modalId; + private String reasonComponentId; +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/listener/ReportReactionNotificationModel.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/listener/ReportReactionNotificationModel.java index 235d52959..43a4b62cf 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/listener/ReportReactionNotificationModel.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/model/template/listener/ReportReactionNotificationModel.java @@ -2,10 +2,13 @@ import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.models.cache.CachedMessage; +import dev.sheldan.abstracto.moderation.model.ModerationActionButton; import lombok.Builder; import lombok.Getter; import lombok.Setter; +import java.util.List; + @Getter @Setter @Builder @@ -15,4 +18,5 @@ public class ReportReactionNotificationModel { private Integer reportCount; private String context; private Boolean singularMessage; + private List moderationActionComponents; } diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/service/BanService.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/service/BanService.java index 6bbaa99e5..2049fbcc6 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/service/BanService.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/service/BanService.java @@ -1,5 +1,6 @@ package dev.sheldan.abstracto.moderation.service; +import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.moderation.model.BanResult; import net.dv8tion.jda.api.entities.*; @@ -10,9 +11,9 @@ public interface BanService { String BAN_EFFECT_KEY = "ban"; String BAN_INFRACTION_TYPE = "ban"; String INFRACTION_PARAMETER_DELETION_DURATION_KEY = "DELETION_DURATION"; - CompletableFuture banUserWithNotification(User user, String reason, Member banningMember, Duration deletionDuration); - CompletableFuture unBanUserWithNotification(User user, Member unBanningUser); - CompletableFuture banUser(Guild guild, User user, Duration deletionDuration, String reason); - CompletableFuture unbanUser(Guild guild, User user); - CompletableFuture softBanUser(Guild guild, User user, Duration delDays); + CompletableFuture banUserWithNotification(ServerUser userToBeBanned, String reason, ServerUser banningUser, Guild guild, Duration deletionDuration); + CompletableFuture unBanUserWithNotification(Long userId, ServerUser unBanningMember, Guild guild); + CompletableFuture banUser(Guild guild, ServerUser userToBeBanned, Duration deletionDuration, String reason); + CompletableFuture unbanUser(Guild guild, Long userId); + CompletableFuture softBanUser(Guild guild, ServerUser user, Duration delDays); } diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/service/KickService.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/service/KickService.java index 67eb1a24c..bf4539a28 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/service/KickService.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/service/KickService.java @@ -1,6 +1,7 @@ package dev.sheldan.abstracto.moderation.service; -import dev.sheldan.abstracto.moderation.model.template.command.KickLogModel; +import dev.sheldan.abstracto.core.models.ServerUser; +import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; import java.util.concurrent.CompletableFuture; @@ -8,5 +9,6 @@ public interface KickService { String KICK_EFFECT_KEY = "kick"; String KICK_INFRACTION_TYPE = "kick"; - CompletableFuture kickMember(Member member, String reason, KickLogModel kickLogModel); + CompletableFuture kickMember(Member kickedMember, Member kickingMember, String reason); + CompletableFuture kickMember(Guild guild, ServerUser kickedUser, String reason, ServerUser kickingUser); } diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/service/ModerationActionService.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/service/ModerationActionService.java new file mode 100644 index 000000000..03e234c65 --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/service/ModerationActionService.java @@ -0,0 +1,10 @@ +package dev.sheldan.abstracto.moderation.service; + +import dev.sheldan.abstracto.core.models.ServerUser; +import dev.sheldan.abstracto.moderation.model.ModerationActionButton; + +import java.util.List; + +public interface ModerationActionService { + List getModerationActionButtons(ServerUser serverUser); +} diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/service/MuteService.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/service/MuteService.java index 0f0a12872..95de531d8 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/service/MuteService.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/service/MuteService.java @@ -1,11 +1,13 @@ package dev.sheldan.abstracto.moderation.service; -import dev.sheldan.abstracto.core.models.FullUserInServer; +import dev.sheldan.abstracto.core.models.ServerChannelMessage; +import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.models.database.AUserInAServer; +import dev.sheldan.abstracto.moderation.model.MuteResult; import dev.sheldan.abstracto.moderation.model.database.Mute; -import dev.sheldan.abstracto.moderation.model.template.command.MuteContext; -import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Guild; +import java.time.Duration; import java.time.Instant; import java.util.concurrent.CompletableFuture; @@ -13,14 +15,13 @@ public interface MuteService { String MUTE_EFFECT_KEY = "mute"; String MUTE_INFRACTION_TYPE = "mute"; String INFRACTION_PARAMETER_DURATION_KEY = "DURATION"; - CompletableFuture muteMember(Member memberToMute, String reason, Instant unMuteDate, Long channelId); - CompletableFuture muteUserInServer(FullUserInServer userToMute, String reason, Instant unMuteDate, Long channelId); - CompletableFuture muteMemberWithLog(MuteContext context); + CompletableFuture muteUserInServer(Guild guild, ServerUser userBeingMuted, String reason, Duration duration); + CompletableFuture muteMemberWithLog(ServerUser userToMute, ServerUser mutingUser, String reason, Duration duration, Guild guild, ServerChannelMessage origin); String startUnMuteJobFor(Instant unMuteDate, Long muteId, Long serverId); void cancelUnMuteJob(Mute mute); - CompletableFuture unMuteUser(AUserInAServer userToUnmute, Member memberUnMuting); - CompletableFuture endMute(Mute mute); + CompletableFuture unMuteUser(ServerUser userToUnMute, ServerUser memberUnMuting, Guild guild); + CompletableFuture endMute(Mute mute, Guild guild); CompletableFuture endMute(Long muteId, Long serverId); void completelyUnMuteUser(AUserInAServer aUserInAServer); - void completelyUnMuteMember(Member member); + void completelyUnMuteMember(ServerUser serverUser); } diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/service/WarnService.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/service/WarnService.java index f13fc4ed5..756023db2 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/service/WarnService.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/service/WarnService.java @@ -1,8 +1,10 @@ package dev.sheldan.abstracto.moderation.service; +import dev.sheldan.abstracto.core.models.ServerChannelMessage; +import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.moderation.model.database.Warning; -import dev.sheldan.abstracto.moderation.model.template.command.WarnContext; +import net.dv8tion.jda.api.entities.Guild; import java.time.Instant; import java.util.concurrent.CompletableFuture; @@ -11,8 +13,7 @@ public interface WarnService { String WARN_EFFECT_KEY = "warn"; String WARN_INFRACTION_TYPE = "warn"; - CompletableFuture notifyAndLogFullUserWarning(WarnContext context); - CompletableFuture warnUserWithLog(WarnContext context); + CompletableFuture warnUserWithLog(Guild guild, ServerUser warnedUser, ServerUser warningUser, String reason, ServerChannelMessage serverChannelMessage); void decayWarning(Warning warning, Instant decayDate); CompletableFuture decayWarningsForServer(AServer server); CompletableFuture decayAllWarningsForServer(AServer server); diff --git a/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-impl/src/main/java/dev/sheldan/abstracto/profanityfilter/service/ProfanityFilterServiceBean.java b/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-impl/src/main/java/dev/sheldan/abstracto/profanityfilter/service/ProfanityFilterServiceBean.java index 44e9c21af..df945ef6a 100644 --- a/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-impl/src/main/java/dev/sheldan/abstracto/profanityfilter/service/ProfanityFilterServiceBean.java +++ b/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-impl/src/main/java/dev/sheldan/abstracto/profanityfilter/service/ProfanityFilterServiceBean.java @@ -4,6 +4,7 @@ import dev.sheldan.abstracto.core.metric.service.MetricService; import dev.sheldan.abstracto.core.metric.service.MetricTag; import dev.sheldan.abstracto.core.models.ServerChannelMessage; +import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.ProfanityRegex; import dev.sheldan.abstracto.core.service.*; @@ -12,6 +13,8 @@ import dev.sheldan.abstracto.core.templating.model.MessageToSend; import dev.sheldan.abstracto.core.templating.service.TemplateService; import dev.sheldan.abstracto.core.utils.FutureUtils; +import dev.sheldan.abstracto.moderation.model.ModerationActionButton; +import dev.sheldan.abstracto.moderation.service.ModerationActionService; import dev.sheldan.abstracto.profanityfilter.config.ProfanityFilterFeatureDefinition; import dev.sheldan.abstracto.profanityfilter.config.ProfanityFilterMode; import dev.sheldan.abstracto.profanityfilter.config.ProfanityFilterPostTarget; @@ -28,6 +31,7 @@ import org.springframework.transaction.annotation.Transactional; import javax.annotation.PostConstruct; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -70,6 +74,9 @@ public class ProfanityFilterServiceBean implements ProfanityFilterService { @Autowired private RoleImmunityService roleImmunityService; + @Autowired + private ModerationActionService moderationActionService; + @Autowired private ProfanityFilterServiceBean self; @@ -102,13 +109,21 @@ public class ProfanityFilterServiceBean implements ProfanityFilterService { @Override public CompletableFuture createProfanityReport(Message message, ProfanityRegex foundProfanityRegex) { + Long serverId = message.getGuild().getIdLong(); + boolean moderationActionsEnabled = featureModeService.featureModeActive(ProfanityFilterFeatureDefinition.PROFANITY_FILTER, serverId, ProfanityFilterMode.PROFANITY_MODERATION_ACTIONS); + List moderationActionComponents = new ArrayList<>(); + if(moderationActionsEnabled && moderationActionService != null) { + ServerUser reportedServerUser = ServerUser.fromMember(message.getMember()); + List moderationActions = moderationActionService.getModerationActionButtons(reportedServerUser); + moderationActionComponents.addAll(moderationActions); + } ProfanityReportModel reportModel = ProfanityReportModel .builder() .profaneMessage(message) + .moderationActionComponents(moderationActionComponents) .profanityGroupKey(foundProfanityRegex.getGroup().getGroupName()) .profanityRegexName(foundProfanityRegex.getRegexName()) .build(); - Long serverId = message.getGuild().getIdLong(); MessageToSend messageToSend = templateService.renderEmbedTemplate(PROFANITY_REPORT_TEMPLATE_KEY, reportModel, serverId); List> messageFutures = postTargetService .sendEmbedInPostTarget(messageToSend, ProfanityFilterPostTarget.PROFANITY_FILTER_QUEUE, serverId); diff --git a/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-impl/src/main/resources/profanityFilter-config.properties b/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-impl/src/main/resources/profanityFilter-config.properties index 2aa4427a6..3a4dcb138 100644 --- a/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-impl/src/main/resources/profanityFilter-config.properties +++ b/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-impl/src/main/resources/profanityFilter-config.properties @@ -19,6 +19,10 @@ abstracto.featureModes.trackProfanities.featureName=profanityFilter abstracto.featureModes.trackProfanities.mode=trackProfanities abstracto.featureModes.trackProfanities.enabled=true +abstracto.featureModes.profanityModerationActions.featureName=profanityFilter +abstracto.featureModes.profanityModerationActions.mode=profanityModerationActions +abstracto.featureModes.profanityModerationActions.enabled=false + abstracto.featureModes.autoDeleteAfterVote.featureName=profanityFilter abstracto.featureModes.autoDeleteAfterVote.mode=autoDeleteAfterVote abstracto.featureModes.autoDeleteAfterVote.enabled=true diff --git a/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-int/pom.xml b/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-int/pom.xml index 8b2998753..2ab07019e 100644 --- a/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-int/pom.xml +++ b/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-int/pom.xml @@ -20,6 +20,11 @@ core-int ${project.version} + + dev.sheldan.abstracto.modules + moderation-int + ${project.version} + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-int/src/main/java/dev/sheldan/abstracto/profanityfilter/config/ProfanityFilterFeatureConfig.java b/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-int/src/main/java/dev/sheldan/abstracto/profanityfilter/config/ProfanityFilterFeatureConfig.java index 25ab7a5f1..c90ce8790 100644 --- a/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-int/src/main/java/dev/sheldan/abstracto/profanityfilter/config/ProfanityFilterFeatureConfig.java +++ b/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-int/src/main/java/dev/sheldan/abstracto/profanityfilter/config/ProfanityFilterFeatureConfig.java @@ -24,12 +24,7 @@ public List getRequiredPostTargets() { @Override public List getAvailableModes() { - return Arrays.asList( - ProfanityFilterMode.PROFANITY_VOTE, - ProfanityFilterMode.AUTO_DELETE_PROFANITIES, - ProfanityFilterMode.TRACK_PROFANITIES, - ProfanityFilterMode.AUTO_DELETE_AFTER_VOTE - ); + return Arrays.asList(ProfanityFilterMode.values()); } @Override diff --git a/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-int/src/main/java/dev/sheldan/abstracto/profanityfilter/config/ProfanityFilterMode.java b/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-int/src/main/java/dev/sheldan/abstracto/profanityfilter/config/ProfanityFilterMode.java index 8feb16cf1..bb4c41511 100644 --- a/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-int/src/main/java/dev/sheldan/abstracto/profanityfilter/config/ProfanityFilterMode.java +++ b/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-int/src/main/java/dev/sheldan/abstracto/profanityfilter/config/ProfanityFilterMode.java @@ -8,6 +8,7 @@ public enum ProfanityFilterMode implements FeatureMode { AUTO_DELETE_PROFANITIES("autoDeleteProfanities"), PROFANITY_VOTE("profanityVote"), PROFANITY_REPORT("profanityReport"), + PROFANITY_MODERATION_ACTIONS("profanityModerationActions"), AUTO_DELETE_AFTER_VOTE("autoDeleteAfterVote"), TRACK_PROFANITIES("trackProfanities"); diff --git a/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-int/src/main/java/dev/sheldan/abstracto/profanityfilter/model/template/ProfanityReportModel.java b/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-int/src/main/java/dev/sheldan/abstracto/profanityfilter/model/template/ProfanityReportModel.java index de78c9211..6e0b80bb0 100644 --- a/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-int/src/main/java/dev/sheldan/abstracto/profanityfilter/model/template/ProfanityReportModel.java +++ b/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-int/src/main/java/dev/sheldan/abstracto/profanityfilter/model/template/ProfanityReportModel.java @@ -1,10 +1,13 @@ package dev.sheldan.abstracto.profanityfilter.model.template; +import dev.sheldan.abstracto.moderation.model.ModerationActionButton; import lombok.Builder; import lombok.Getter; import lombok.Setter; import net.dv8tion.jda.api.entities.Message; +import java.util.List; + @Getter @Setter @Builder @@ -12,4 +15,5 @@ public class ProfanityReportModel { private String profanityGroupKey; private String profanityRegexName; private Message profaneMessage; + private List moderationActionComponents; } diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/modal/listener/ModalInteractionListenerBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/modal/listener/ModalInteractionListenerBean.java index c30bceb8d..7c5b54c1f 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/modal/listener/ModalInteractionListenerBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/modal/listener/ModalInteractionListenerBean.java @@ -87,7 +87,7 @@ public void executeListenerLogic(ModalInteractionEvent event) { postInteractionExecution.execute(model, result, listener); } } else { - log.warn("No listener found for button event for id {}.", event.getModalId()); + log.warn("No listener found for modal event for id {}.", event.getModalId()); } } else { log.warn("No callback found for id {}.", event.getModalId()); diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/MemberServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/MemberServiceBean.java index effd2f032..e4e85dfa2 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/MemberServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/MemberServiceBean.java @@ -117,13 +117,23 @@ public Member getMemberInServer(AUserInAServer aUserInAServer) { return getMemberInServer(aUserInAServer.getServerReference().getId(), aUserInAServer.getUserReference().getId()); } + @Override + public Member getMemberInServer(ServerUser serverUser) { + return getMemberInServer(serverUser.getServerId(), serverUser.getUserId()); + } + + @Override + public CompletableFuture getMemberInServerAsync(ServerUser serverUser) { + return getMemberInServerAsync(serverUser.getServerId(), serverUser.getUserId()); + } + @Override public CompletableFuture getMemberInServerAsync(AUserInAServer aUserInAServer) { return getMemberInServerAsync(aUserInAServer.getServerReference().getId(), aUserInAServer.getUserReference().getId()); } @Override - public Member getMemberInServer(AServer server, AUser member) { + public Member getMemberInServerAsync(AServer server, AUser member) { return getMemberInServer(server.getId(), member.getId()); } @@ -151,8 +161,7 @@ public CompletableFuture timeoutUser(Member member, Duration duration) { @Override public CompletableFuture timeoutUser(Member member, Duration duration, String reason) { - log.info("Applying timeout for user {} in guild {} for {}", member.getId(), member.getGuild().getIdLong(), duration); - return member.timeoutFor(duration).reason(reason).submit(); + return timeoutMember(member.getGuild(), ServerUser.fromMember(member), duration, reason); } @Override @@ -171,9 +180,19 @@ public CompletableFuture timeoutUser(Member member, Instant target, String return timeoutUser(member, muteDuration, reason); } + @Override + public CompletableFuture timeoutMember(Guild guild, ServerUser serverUser, Duration duration, String reason) { + return guild.timeoutFor(UserSnowflake.fromId(serverUser.getUserId()), duration).reason(reason).submit(); + } + + @Override + public CompletableFuture removeTimeout(Guild guild, ServerUser serverUser, String reason) { + log.info("Removing timeout for user {} in guild {}.", serverUser.getUserId(), guild.getIdLong()); + return guild.removeTimeout(UserSnowflake.fromId(serverUser.getUserId())).reason(reason).submit(); + } + @Override public CompletableFuture removeTimeout(Member member) { - log.info("Removing timeout for user {} in guild {}.", member.getId(), member.getGuild().getIdLong()); - return member.removeTimeout().submit(); + return removeTimeout(member.getGuild(), ServerUser.fromMember(member), null); } } diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/MessageServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/MessageServiceBean.java index 7491a15e2..0ea5f39c4 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/MessageServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/MessageServiceBean.java @@ -4,6 +4,7 @@ import dev.sheldan.abstracto.core.metric.service.MetricService; import dev.sheldan.abstracto.core.metric.service.MetricTag; import dev.sheldan.abstracto.core.models.ServerChannelMessage; +import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.models.cache.CachedMessage; import dev.sheldan.abstracto.core.models.database.AChannel; import dev.sheldan.abstracto.core.models.database.AUserInAServer; @@ -130,6 +131,13 @@ public CompletableFuture sendMessageToUser(AUserInAServer userInAServer ); } + @Override + public CompletableFuture sendMessageToUser(ServerUser serverUser, String text) { + return memberService.getMemberInServerAsync(serverUser).thenCompose(member -> + sendMessageToUser(member.getUser(), text) + ); + } + @Override public CompletableFuture sendSimpleTemplateToUser(Long userId, String templateKey) { String text = templateService.renderSimpleTemplate(templateKey); diff --git a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/ServerUser.java b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/ServerUser.java index 5aab074c8..fd167667a 100644 --- a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/ServerUser.java +++ b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/ServerUser.java @@ -22,7 +22,16 @@ public static ServerUser fromAUserInAServer(AUserInAServer aUserInAServer) { return ServerUser .builder() .serverId(aUserInAServer.getServerReference().getId()) - .userId(aUserInAServer.getUserReference().getId()).build(); + .userId(aUserInAServer.getUserReference().getId()) + .build(); + } + + public static ServerUser fromId(Long serverId, Long userId) { + return ServerUser + .builder() + .serverId(serverId) + .userId(userId) + .build(); } public static ServerUser fromMember(Member member) { diff --git a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/template/display/MemberDisplay.java b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/template/display/MemberDisplay.java index af4234b71..fe9757d23 100644 --- a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/template/display/MemberDisplay.java +++ b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/template/display/MemberDisplay.java @@ -1,5 +1,6 @@ package dev.sheldan.abstracto.core.models.template.display; +import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.utils.MemberUtils; import lombok.Builder; @@ -12,6 +13,7 @@ @Builder public class MemberDisplay { private String memberMention; + private String name; private Long userId; private Long serverId; @@ -19,6 +21,7 @@ public static MemberDisplay fromMember(Member member) { return MemberDisplay .builder() .memberMention(member.getAsMention()) + .name(member.getEffectiveName()) .serverId(member.getGuild().getIdLong()) .userId(member.getIdLong()) .build(); @@ -41,4 +44,13 @@ public static MemberDisplay fromIds(Long serverId, Long userId) { .userId(userId) .build(); } + + public static MemberDisplay fromServerUser(ServerUser serverUser) { + return MemberDisplay + .builder() + .memberMention(MemberUtils.getUserAsMention(serverUser.getUserId())) + .serverId(serverUser.getServerId()) + .userId(serverUser.getUserId()) + .build(); + } } diff --git a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/template/display/UserDisplay.java b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/template/display/UserDisplay.java new file mode 100644 index 000000000..9d5b1f703 --- /dev/null +++ b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/template/display/UserDisplay.java @@ -0,0 +1,41 @@ +package dev.sheldan.abstracto.core.models.template.display; + +import dev.sheldan.abstracto.core.models.ServerUser; +import dev.sheldan.abstracto.core.utils.MemberUtils; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import net.dv8tion.jda.api.entities.User; + +@Getter +@Setter +@Builder +public class UserDisplay { + private Long userId; + private String userMention; + + public static UserDisplay fromUser(User user) { + return UserDisplay + .builder() + .userMention(MemberUtils.getUserAsMention(user.getIdLong())) + .userId(user.getIdLong()) + .build(); + } + + public static UserDisplay fromServerUser(ServerUser serverUser) { + return UserDisplay + .builder() + .userMention(MemberUtils.getUserAsMention(serverUser.getUserId())) + .userId(serverUser.getUserId()) + .build(); + } + + public static UserDisplay fromId(Long id) { + return UserDisplay + .builder() + .userMention(MemberUtils.getUserAsMention(id)) + .userId(id) + .build(); + } + +} diff --git a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/service/MemberService.java b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/service/MemberService.java index fc1468785..718245d84 100644 --- a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/service/MemberService.java +++ b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/service/MemberService.java @@ -25,8 +25,10 @@ public interface MemberService { boolean isUserInGuild(AUserInAServer aUserInAServer); boolean isUserInGuild(Guild guild, AUserInAServer aUserInAServer); Member getMemberInServer(AUserInAServer aUserInAServer); + Member getMemberInServer(ServerUser serverUser); + CompletableFuture getMemberInServerAsync(ServerUser serverUser); CompletableFuture getMemberInServerAsync(AUserInAServer aUserInAServer); - Member getMemberInServer(AServer server, AUser member); + Member getMemberInServerAsync(AServer server, AUser member); CompletableFuture forceReloadMember(Member member); Member getBotInGuild(AServer server); CompletableFuture getUserViaId(Long userId); @@ -35,5 +37,7 @@ public interface MemberService { CompletableFuture timeoutUserMaxDuration(Member member); CompletableFuture timeoutUser(Member member, Instant target); CompletableFuture timeoutUser(Member member, Instant target, String reason); + CompletableFuture timeoutMember(Guild guild, ServerUser serverUser, Duration duration, String reason); + CompletableFuture removeTimeout(Guild guild, ServerUser serverUser, String reason); CompletableFuture removeTimeout(Member member); } diff --git a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/service/MessageService.java b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/service/MessageService.java index 211771946..fc5dab31b 100644 --- a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/service/MessageService.java +++ b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/service/MessageService.java @@ -1,6 +1,7 @@ package dev.sheldan.abstracto.core.service; import dev.sheldan.abstracto.core.models.ServerChannelMessage; +import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.models.cache.CachedMessage; import dev.sheldan.abstracto.core.models.database.AChannel; import dev.sheldan.abstracto.core.models.database.AUserInAServer; @@ -25,6 +26,7 @@ public interface MessageService { void updateStatusMessage(AChannel channel, Long messageId, MessageToSend messageToSend); void updateStatusMessage(MessageChannel channel, Long messageId, MessageToSend messageToSend); CompletableFuture sendMessageToUser(AUserInAServer userInAServer, String text); + CompletableFuture sendMessageToUser(ServerUser serverUser, String text); CompletableFuture sendSimpleTemplateToUser(Long userId, String templateKey); List> retrieveMessages(List messages); CompletableFuture sendTemplateToUser(User user, String template, Object model);