diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/HoneyPotBan.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/HoneyPotBan.java index 7eacd01d0..cd467f6f4 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/HoneyPotBan.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/HoneyPotBan.java @@ -18,7 +18,7 @@ import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition; import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames; import dev.sheldan.abstracto.moderation.config.feature.HoneyPotFeatureConfig; import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; -import dev.sheldan.abstracto.moderation.listener.HoneyPotServiceBean; +import dev.sheldan.abstracto.moderation.service.HoneyPotServiceBean; import dev.sheldan.abstracto.moderation.model.template.command.HoneyPotBanResponseModel; import java.time.Duration; import java.time.Instant; @@ -98,7 +98,7 @@ public class HoneyPotBan extends AbstractConditionableCommand { .toList(); Role honeyPotRole = guild.getRoleById(honeyPotServiceBean.getHoneyPotRoleId(guild.getIdLong())); List> futures = currentMembersWithHoneypotRole.stream().map(member -> - honeyPotServiceBean.banForHoneyPot(member, honeyPotRole) + honeyPotServiceBean.banForHoneyPotRole(member, honeyPotRole) ).toList(); Integer memberCount = currentMembersWithHoneypotRole.size(); CompletableFutureList futureList = new CompletableFutureList<>(futures); diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/HoneyPotMessageListener.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/HoneyPotMessageListener.java new file mode 100644 index 000000000..399b8badf --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/HoneyPotMessageListener.java @@ -0,0 +1,61 @@ +package dev.sheldan.abstracto.moderation.listener; + +import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.config.FeatureMode; +import dev.sheldan.abstracto.core.listener.DefaultListenerResult; +import dev.sheldan.abstracto.core.listener.async.jda.AsyncMessageReceivedListener; +import dev.sheldan.abstracto.core.models.listener.MessageReceivedModel; +import dev.sheldan.abstracto.core.service.ConfigService; +import dev.sheldan.abstracto.core.service.MessageService; +import dev.sheldan.abstracto.moderation.config.feature.HoneyPotFeatureConfig; +import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; +import dev.sheldan.abstracto.moderation.config.feature.mode.HoneypotMode; +import dev.sheldan.abstracto.moderation.service.HoneyPotServiceBean; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class HoneyPotMessageListener implements AsyncMessageReceivedListener { + + @Autowired + private ConfigService configService; + + @Autowired + private HoneyPotServiceBean honeyPotServiceBean; + + @Autowired + private MessageService messageService; + + @Override + public DefaultListenerResult execute(MessageReceivedModel model) { + Long honeyPotChannel = configService.getLongValueOrConfigDefault(HoneyPotFeatureConfig.HONEYPOT_CHANNEL, model.getServerId()); + if(honeyPotChannel == 0) { + return DefaultListenerResult.IGNORED; + } + if(model.getMessage().getChannelId().equals(honeyPotChannel.toString())) { + boolean honeyPotActivated = honeyPotServiceBean.fellIntoHoneyPotIgnoringJoinDate(model.getServerId(), model.getMessage().getMember()); + if(honeyPotActivated) { + log.info("Banning user {} in guild {} due to a message in channel {}.", model.getMessage().getAuthor().getIdLong(), model.getServerId(), honeyPotChannel); + honeyPotServiceBean.banForHoneyPotMessage(model.getMessage().getMember(), honeyPotChannel); + } else { + log.info("NOT banning user {} in guild {} due to a message in honeypot channel {}.", model.getMessage().getAuthor().getIdLong(), model.getServerId(), honeyPotChannel); + messageService.deleteMessage(model.getMessage()); + } + return DefaultListenerResult.PROCESSED; + } + return DefaultListenerResult.IGNORED; + } + + @Override + public FeatureDefinition getFeature() { + return ModerationFeatureDefinition.HONEYPOT; + } + + @Override + public List getFeatureModeLimitations() { + return List.of(HoneypotMode.MESSAGE); + } +} 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 2a0540dd5..4f305bccc 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 @@ -1,12 +1,15 @@ package dev.sheldan.abstracto.moderation.listener; import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.config.FeatureMode; import dev.sheldan.abstracto.core.config.ListenerPriority; import dev.sheldan.abstracto.core.listener.DefaultListenerResult; import dev.sheldan.abstracto.core.listener.sync.jda.RoleAddedListener; import dev.sheldan.abstracto.core.models.listener.RoleAddedModel; import dev.sheldan.abstracto.core.service.RoleService; import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; +import dev.sheldan.abstracto.moderation.config.feature.mode.HoneypotMode; +import dev.sheldan.abstracto.moderation.service.HoneyPotServiceBean; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -37,7 +40,7 @@ public class HoneyPotRoleAddedListener implements RoleAddedListener { boolean fellIntoHoneyPot = honeyPotServiceBean.fellIntoHoneyPot(model.getServerId(), model.getTargetMember()); if (fellIntoHoneyPot) { log.info("Banning user {} in guild {} due to role {}.", model.getTargetUser().getUserId(), model.getTargetUser().getServerId(), model.getRoleId()); - honeyPotServiceBean.banForHoneyPot(model.getTargetMember(), model.getRole()); + honeyPotServiceBean.banForHoneyPotRole(model.getTargetMember(), model.getRole()); } else { log.info("User {} in server {} will not get banned by honeypot. All existing roles besides {} will be removed.", model.getTargetUser().getUserId(), model.getTargetUser().getServerId(), honeyPotRoleId); @@ -83,4 +86,8 @@ public class HoneyPotRoleAddedListener implements RoleAddedListener { return ListenerPriority.MEDIUM; } + @Override + public List getFeatureModeLimitations() { + return List.of(HoneypotMode.ROLE); + } } diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/HoneyPotServiceBean.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/HoneyPotServiceBean.java similarity index 76% rename from abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/HoneyPotServiceBean.java rename to abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/HoneyPotServiceBean.java index 8468059ad..aed3ec756 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/listener/HoneyPotServiceBean.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/HoneyPotServiceBean.java @@ -1,4 +1,4 @@ -package dev.sheldan.abstracto.moderation.listener; +package dev.sheldan.abstracto.moderation.service; import dev.sheldan.abstracto.core.models.ConditionContextInstance; import dev.sheldan.abstracto.core.models.ServerUser; @@ -13,7 +13,6 @@ import dev.sheldan.abstracto.core.service.management.UserInServerManagementServi import dev.sheldan.abstracto.core.templating.service.TemplateService; import dev.sheldan.abstracto.moderation.config.feature.HoneyPotFeatureConfig; import dev.sheldan.abstracto.moderation.model.listener.HoneyPotReasonModel; -import dev.sheldan.abstracto.moderation.service.BanService; import java.time.Duration; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -68,11 +67,33 @@ public class HoneyPotServiceBean { return !allowed; } + public boolean fellIntoHoneyPotIgnoringJoinDate(Long serverId, Member member) { + Integer levelToSkipBan = configService.getLongValueOrConfigDefault(HoneyPotFeatureConfig.HONEYPOT_IGNORED_LEVEL, serverId).intValue(); + boolean allowed = userHasLevel(member, levelToSkipBan); + return !allowed; + } + public List getCurrentMembersWithHoneypotRole(Guild guild) { return memberService.getMembersWithRole(guild.getIdLong(), getHoneyPotRoleId(guild.getIdLong())); } - public CompletableFuture banForHoneyPot(Member targetMember, Role role) { + public CompletableFuture banForHoneyPotMessage(Member targetMember, Long channelId) { + HoneyPotReasonModel reasonModel = HoneyPotReasonModel + .builder() + .memberDisplay(MemberDisplay.fromMember(targetMember)) + .build(); + ServerUser bannedUser = ServerUser.fromMember(targetMember); + String banReason = templateService.renderTemplate(HONEYPOT_BAN_REASON_TEMPLATE, reasonModel, bannedUser.getServerId()); + return banService.banUserWithNotification(bannedUser, banReason, ServerUser.fromMember(targetMember.getGuild().getSelfMember()), + targetMember.getGuild(), Duration.ofDays(7)).thenAccept(banResult -> { + log.info("Banned user {} in guild {} due to a message in channel {}.", bannedUser.getUserId(), bannedUser.getServerId(), channelId); + }).exceptionally(throwable -> { + log.error("Failed to ban user {} in guild {} due to a message in channel {}.", bannedUser.getUserId(), bannedUser.getServerId(), channelId, throwable); + return null; + }); + } + + public CompletableFuture banForHoneyPotRole(Member targetMember, Role role) { HoneyPotReasonModel reasonModel = HoneyPotReasonModel .builder() .memberDisplay(MemberDisplay.fromMember(targetMember)) @@ -91,7 +112,7 @@ public class HoneyPotServiceBean { } private boolean userHasLevel(Member member, Integer level) { - log.info("Checking if member {} is ignored to click on the honeypot in server {}.", member.getIdLong(),member.getGuild().getIdLong()); + log.info("Checking if member {} is ignored by the honeypot in server {}.", member.getIdLong(),member.getGuild().getIdLong()); Map parameters = new HashMap<>(); AUserInAServer userInAServer = userInServerManagementService.loadOrCreateUser(member); parameters.put(LEVEL_CONDITION_USER_ID_PARAMETER, userInAServer.getUserInServerId()); 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 5408108bc..05b738f36 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 @@ -99,3 +99,14 @@ abstracto.systemConfigs.honeypotIgnoredJoinDurationSeconds.longValue=86400 abstracto.featureFlags.honeypot.featureName=honeypot abstracto.featureFlags.honeypot.enabled=false + +abstracto.featureModes.honeypotRole.featureName=honeypot +abstracto.featureModes.honeypotRole.mode=honeypotRole +abstracto.featureModes.honeypotRole.enabled=false + +abstracto.featureModes.honeypotMessage.featureName=honeypot +abstracto.featureModes.honeypotMessage.mode=honeypotMessage +abstracto.featureModes.honeypotMessage.enabled=false + +abstracto.systemConfigs.honeypotChannel.name=honeypotChannel +abstracto.systemConfigs.honeypotChannel.longValue=0 \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/config/feature/HoneyPotFeatureConfig.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/config/feature/HoneyPotFeatureConfig.java index 0cb1b6ee9..dfcad137b 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/config/feature/HoneyPotFeatureConfig.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/config/feature/HoneyPotFeatureConfig.java @@ -2,6 +2,8 @@ package dev.sheldan.abstracto.moderation.config.feature; import dev.sheldan.abstracto.core.config.FeatureConfig; import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.config.FeatureMode; +import dev.sheldan.abstracto.moderation.config.feature.mode.HoneypotMode; import org.springframework.stereotype.Component; import java.util.Arrays; @@ -12,6 +14,7 @@ public class HoneyPotFeatureConfig implements FeatureConfig { public static final String HONEYPOT_ROLE_ID = "honeypotRoleId"; public static final String HONEYPOT_IGNORED_LEVEL = "honeypotIgnoredLevel"; + public static final String HONEYPOT_CHANNEL = "honeypotChannel"; public static final String HONEYPOT_IGNORED_JOIN_DURATION_SECONDS = "honeypotIgnoredJoinDurationSeconds"; @Override @@ -21,6 +24,11 @@ public class HoneyPotFeatureConfig implements FeatureConfig { @Override public List getRequiredSystemConfigKeys() { - return Arrays.asList(HONEYPOT_ROLE_ID, HONEYPOT_IGNORED_LEVEL, HONEYPOT_IGNORED_JOIN_DURATION_SECONDS); + return Arrays.asList(HONEYPOT_ROLE_ID, HONEYPOT_IGNORED_LEVEL, HONEYPOT_IGNORED_JOIN_DURATION_SECONDS, HONEYPOT_CHANNEL); + } + + @Override + public List getAvailableModes() { + return Arrays.asList(HoneypotMode.values()); } } diff --git a/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/config/feature/mode/HoneypotMode.java b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/config/feature/mode/HoneypotMode.java new file mode 100644 index 000000000..fcb72867d --- /dev/null +++ b/abstracto-application/abstracto-modules/moderation/moderation-int/src/main/java/dev/sheldan/abstracto/moderation/config/feature/mode/HoneypotMode.java @@ -0,0 +1,16 @@ +package dev.sheldan.abstracto.moderation.config.feature.mode; + +import dev.sheldan.abstracto.core.config.FeatureMode; +import lombok.Getter; + +@Getter +public enum HoneypotMode implements FeatureMode { + ROLE("honeypotRole"), + MESSAGE("honeypotMessage"); + + private final String key; + + HoneypotMode(String key) { + this.key = key; + } +}