[AB-167] adding warning created events and infraction counter

adding disabling of post targets
adding some logging for message sending failure consumer
This commit is contained in:
Sheldan
2021-10-25 00:04:08 +02:00
parent 8909e8ebe5
commit 0514d355c7
69 changed files with 1197 additions and 158 deletions

View File

@@ -8,6 +8,7 @@ import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.PostTargetService; import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend; import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService; import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.logging.config.LoggingFeatureDefinition; import dev.sheldan.abstracto.logging.config.LoggingFeatureDefinition;
import dev.sheldan.abstracto.logging.config.LoggingPostTarget; import dev.sheldan.abstracto.logging.config.LoggingPostTarget;
import dev.sheldan.abstracto.logging.model.template.MemberJoinLogModel; import dev.sheldan.abstracto.logging.model.template.MemberJoinLogModel;
@@ -43,7 +44,11 @@ public class JoinLogger implements AsyncJoinListener {
.build(); .build();
log.debug("Logging join event for user {} in server {}.", listenerModel.getMember().getIdLong(), listenerModel.getServerId()); log.debug("Logging join event for user {} in server {}.", listenerModel.getMember().getIdLong(), listenerModel.getServerId());
MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_JOIN_TEMPLATE, model, listenerModel.getServerId()); MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_JOIN_TEMPLATE, model, listenerModel.getServerId());
postTargetService.sendEmbedInPostTarget(messageToSend, LoggingPostTarget.JOIN_LOG, listenerModel.getServerId()); FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, LoggingPostTarget.JOIN_LOG, listenerModel.getServerId()))
.exceptionally(throwable -> {
log.error("Failed to send member joining log.", throwable);
return null;
});
return DefaultListenerResult.PROCESSED; return DefaultListenerResult.PROCESSED;
} }

View File

@@ -8,6 +8,7 @@ import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.PostTargetService; import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend; import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService; import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.logging.config.LoggingFeatureDefinition; import dev.sheldan.abstracto.logging.config.LoggingFeatureDefinition;
import dev.sheldan.abstracto.logging.config.LoggingPostTarget; import dev.sheldan.abstracto.logging.config.LoggingPostTarget;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -46,7 +47,11 @@ public class LeaveLogger implements AsyncLeaveListener {
.build(); .build();
log.debug("Logging leave event for user {} in server {}.", listenerModel.getUser().getIdLong(), listenerModel.getServerId()); log.debug("Logging leave event for user {} in server {}.", listenerModel.getUser().getIdLong(), listenerModel.getServerId());
MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_LEAVE_TEMPLATE, model, listenerModel.getServerId()); MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_LEAVE_TEMPLATE, model, listenerModel.getServerId());
postTargetService.sendEmbedInPostTarget(messageToSend, LoggingPostTarget.LEAVE_LOG, listenerModel.getServerId()); FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, LoggingPostTarget.LEAVE_LOG, listenerModel.getServerId()))
.exceptionally(throwable -> {
log.error("Failed to send member leaving log.", throwable);
return null;
});
return DefaultListenerResult.PROCESSED; return DefaultListenerResult.PROCESSED;
} }
} }

View File

@@ -85,7 +85,8 @@ public class MessageDeleteLogListener implements AsyncMessageDeletedListener {
.member(authorMember) .member(authorMember)
.build(); .build();
MessageToSend message = templateService.renderEmbedTemplate(MESSAGE_DELETED_TEMPLATE, logModel, messageFromCache.getServerId()); MessageToSend message = templateService.renderEmbedTemplate(MESSAGE_DELETED_TEMPLATE, logModel, messageFromCache.getServerId());
FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, LoggingPostTarget.DELETE_LOG, messageFromCache.getServerId())).exceptionally(throwable -> { FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, LoggingPostTarget.DELETE_LOG, messageFromCache.getServerId()))
.exceptionally(throwable -> {
log.error("Failed to send message deleted log.", throwable); log.error("Failed to send message deleted log.", throwable);
return null; return null;
}); });

View File

@@ -11,6 +11,7 @@ import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.PostTargetService; import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend; import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService; import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.logging.config.LoggingFeatureDefinition; import dev.sheldan.abstracto.logging.config.LoggingFeatureDefinition;
import dev.sheldan.abstracto.logging.config.LoggingPostTarget; import dev.sheldan.abstracto.logging.config.LoggingPostTarget;
import dev.sheldan.abstracto.logging.model.template.MessageDeletedAttachmentLog; import dev.sheldan.abstracto.logging.model.template.MessageDeletedAttachmentLog;
@@ -65,7 +66,11 @@ public class MessageEditedListener implements AsyncMessageUpdatedListener {
.member(messageAfter.getMember()) .member(messageAfter.getMember())
.build(); .build();
MessageToSend message = templateService.renderEmbedTemplate(MESSAGE_EDITED_TEMPLATE, lodModel, model.getServerId()); MessageToSend message = templateService.renderEmbedTemplate(MESSAGE_EDITED_TEMPLATE, lodModel, model.getServerId());
postTargetService.sendEmbedInPostTarget(message, LoggingPostTarget.EDIT_LOG, model.getServerId()); FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, LoggingPostTarget.EDIT_LOG, model.getServerId()))
.exceptionally(throwable -> {
log.error("Failed to send message edited log.", throwable);
return null;
});
if(attachmentWasRemoved) { if(attachmentWasRemoved) {
log.info("Attachment count changed. Old {}, new {}.", attachmentCountBefore, attachmentCountAfter); log.info("Attachment count changed. Old {}, new {}.", attachmentCountBefore, attachmentCountAfter);
@@ -75,7 +80,7 @@ public class MessageEditedListener implements AsyncMessageUpdatedListener {
).collect(Collectors.toList()); ).collect(Collectors.toList());
log.debug("Logging deletion of {} attachments.", removedAttachments.size()); log.debug("Logging deletion of {} attachments.", removedAttachments.size());
for (int i = 0; i < removedAttachments.size(); i++) { for (int i = 0; i < removedAttachments.size(); i++) {
MessageDeletedAttachmentLog log = MessageDeletedAttachmentLog MessageDeletedAttachmentLog attachmentModel = MessageDeletedAttachmentLog
.builder() .builder()
.imageUrl(removedAttachments.get(i).getProxyUrl()) .imageUrl(removedAttachments.get(i).getProxyUrl())
.counter(i + 1) .counter(i + 1)
@@ -84,8 +89,12 @@ public class MessageEditedListener implements AsyncMessageUpdatedListener {
.member(messageAfter.getMember()) .member(messageAfter.getMember())
.build(); .build();
MessageToSend attachmentEmbed = templateService.renderEmbedTemplate(MESSAGE_EDITED_ATTACHMENT_REMOVED_TEMPLATE, MessageToSend attachmentEmbed = templateService.renderEmbedTemplate(MESSAGE_EDITED_ATTACHMENT_REMOVED_TEMPLATE,
log, messageBefore.getServerId()); attachmentModel, messageBefore.getServerId());
postTargetService.sendEmbedInPostTarget(attachmentEmbed, LoggingPostTarget.DELETE_LOG, messageBefore.getServerId()); FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(attachmentEmbed, LoggingPostTarget.DELETE_LOG, messageBefore.getServerId()))
.exceptionally(throwable -> {
log.error("Failed to send message edited attachment log.", throwable);
return null;
});
} }
} }
return DefaultListenerResult.PROCESSED; return DefaultListenerResult.PROCESSED;

View File

@@ -39,7 +39,7 @@ public class MyWarnings extends AbstractConditionableCommand {
public CommandResult execute(CommandContext commandContext) { public CommandResult execute(CommandContext commandContext) {
MyWarningsModel model = (MyWarningsModel) ContextConverter.fromCommandContext(commandContext, MyWarningsModel.class); MyWarningsModel model = (MyWarningsModel) ContextConverter.fromCommandContext(commandContext, MyWarningsModel.class);
AUserInAServer userInAServer = userInServerManagementService.loadOrCreateUser(commandContext.getAuthor()); AUserInAServer userInAServer = userInServerManagementService.loadOrCreateUser(commandContext.getAuthor());
Long currentWarnCount = warnManagementService.getActiveWarnsForUser(userInAServer); Long currentWarnCount = warnManagementService.getActiveWarnCountForUser(userInAServer);
model.setCurrentWarnCount(currentWarnCount); model.setCurrentWarnCount(currentWarnCount);
Long totalWarnCount = warnManagementService.getTotalWarnsForUser(userInAServer); Long totalWarnCount = warnManagementService.getTotalWarnsForUser(userInAServer);
model.setTotalWarnCount(totalWarnCount); model.setTotalWarnCount(totalWarnCount);

View File

@@ -0,0 +1,24 @@
package dev.sheldan.abstracto.moderation.config;
import dev.sheldan.abstracto.core.service.ExecutorService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
@Configuration
public class ModerationListenerConfig {
@Autowired
private ExecutorService executorService;
@Bean(name = "infractionLevelChangedExecutor")
public TaskExecutor infractionLevelChangedExecutor() {
return executorService.setupExecutorFor("infractionLevelChangedListener");
}
@Bean(name = "warningCreatedExecutor")
public TaskExecutor warningCreatedExecutor() {
return executorService.setupExecutorFor("warningCreatedListener");
}
}

View File

@@ -0,0 +1,48 @@
package dev.sheldan.abstracto.moderation.listener.manager;
import dev.sheldan.abstracto.core.listener.ListenerService;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.moderation.listener.InfractionLevelChangedListener;
import dev.sheldan.abstracto.moderation.model.listener.InfractionLevelChangedEventModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@Slf4j
public class InfractionLevelChangedListenerManager {
@Autowired(required = false)
private List<InfractionLevelChangedListener> listeners;
@Autowired
private ListenerService listenerService;
@Autowired
@Qualifier("infractionLevelChangedExecutor")
private TaskExecutor infractionLevelChangedExecutor;
public void sendInfractionLevelChangedEvent(Integer newLevel, Integer oldLevel, Long newPoints, Long oldPoints, ServerUser serverUser) {
if(listeners == null || listeners.isEmpty()) {
return;
}
InfractionLevelChangedEventModel model = createInfractionChangedModel(newLevel, oldLevel, oldPoints, newPoints, serverUser);
listeners.forEach(listener -> listenerService.executeFeatureAwareListener(listener, model, infractionLevelChangedExecutor));
}
private InfractionLevelChangedEventModel createInfractionChangedModel(Integer newLevel, Integer oldLevel, Long oldPoints, Long newPoints, ServerUser serverUser) {
return InfractionLevelChangedEventModel
.builder()
.newLevel(newLevel)
.oldLevel(oldLevel)
.oldPoints(oldPoints)
.newPoints(newPoints)
.userId(serverUser.getUserId())
.serverId(serverUser.getServerId())
.build();
}
}

View File

@@ -0,0 +1,53 @@
package dev.sheldan.abstracto.moderation.listener.manager;
import dev.sheldan.abstracto.core.listener.ListenerService;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.moderation.listener.WarningCreatedListener;
import dev.sheldan.abstracto.moderation.model.listener.WarningCreatedEventModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@Slf4j
public class WarningCreatedListenerManager {
@Autowired(required = false)
private List<WarningCreatedListener> listeners;
@Autowired
private ListenerService listenerService;
@Autowired
@Qualifier("warningCreatedExecutor")
private TaskExecutor warningCreatedExecutor;
public void sendWarningCreatedEvent(ServerSpecificId warnId, ServerUser warnedUser, ServerUser warningUser,
String reason, ServerChannelMessage warnCommand) {
if(listeners == null || listeners.isEmpty()) {
return;
}
WarningCreatedEventModel model = createWarningCreatedEventModel(warnId, warnedUser, warningUser, reason, warnCommand);
listeners.forEach(listener -> listenerService.executeFeatureAwareListener(listener, model, warningCreatedExecutor));
}
private WarningCreatedEventModel createWarningCreatedEventModel(ServerSpecificId warnId, ServerUser warnedUser,
ServerUser warningUser, String reason, ServerChannelMessage warnCommandMessage) {
return WarningCreatedEventModel
.builder()
.warningId(warnId.getId())
.serverId(warnId.getServerId())
.warningUserId(warningUser.getUserId())
.warnedUserId(warnedUser.getUserId())
.reason(reason)
.warningChannelId(warnCommandMessage.getChannelId())
.warningMessageId(warnCommandMessage.getMessageId())
.build();
}
}

View File

@@ -0,0 +1,13 @@
package dev.sheldan.abstracto.moderation.repository;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface InfractionRepository extends JpaRepository<Infraction, Long> {
List<Infraction> findByUserAndDecayedFalse(AUserInAServer aUserInAServer);
}

View File

@@ -23,6 +23,7 @@ public interface WarnRepository extends JpaRepository<Warning, ServerSpecificId>
Long countByWarnedUser(AUserInAServer aUserInAServer); Long countByWarnedUser(AUserInAServer aUserInAServer);
Long countByWarnedUserAndDecayedFalse(AUserInAServer aUserInAServer); Long countByWarnedUserAndDecayedFalse(AUserInAServer aUserInAServer);
List<Warning> findByWarnedUserAndDecayedFalse(AUserInAServer aUserInAServer);
List<Warning> findByWarnedUser(AUserInAServer aUserInAServer); List<Warning> findByWarnedUser(AUserInAServer aUserInAServer);

View File

@@ -17,7 +17,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.time.Duration; import java.time.Duration;
import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@Component @Component
@@ -139,20 +138,14 @@ public class BanServiceBean implements BanService {
} }
public CompletableFuture<Void> sendBanLogMessage(BanLog banLog, Long guildId, String template) { public CompletableFuture<Void> sendBanLogMessage(BanLog banLog, Long guildId, String template) {
CompletableFuture<Void> completableFuture;
MessageToSend banLogMessage = templateService.renderEmbedTemplate(template, banLog, guildId); MessageToSend banLogMessage = templateService.renderEmbedTemplate(template, banLog, guildId);
log.debug("Sending ban log message in guild {}.", guildId); log.debug("Sending ban log message in guild {}.", guildId);
List<CompletableFuture<Message>> notificationFutures = postTargetService.sendEmbedInPostTarget(banLogMessage, ModerationPostTarget.BAN_LOG, guildId); return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(banLogMessage, ModerationPostTarget.BAN_LOG, guildId));
completableFuture = FutureUtils.toSingleFutureGeneric(notificationFutures);
return completableFuture;
} }
public CompletableFuture<Void> sendUnBanLogMessage(UnBanLog banLog, Long guildId, String template) { public CompletableFuture<Void> sendUnBanLogMessage(UnBanLog banLog, Long guildId, String template) {
CompletableFuture<Void> completableFuture;
MessageToSend banLogMessage = templateService.renderEmbedTemplate(template, banLog, guildId); MessageToSend banLogMessage = templateService.renderEmbedTemplate(template, banLog, guildId);
log.debug("Sending unban log message in guild {}.", guildId); log.debug("Sending unban log message in guild {}.", guildId);
List<CompletableFuture<Message>> notificationFutures = postTargetService.sendEmbedInPostTarget(banLogMessage, ModerationPostTarget.UN_BAN_LOG, guildId); return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(banLogMessage, ModerationPostTarget.UN_BAN_LOG, guildId));
completableFuture = FutureUtils.toSingleFutureGeneric(notificationFutures);
return completableFuture;
} }
} }

View File

@@ -0,0 +1,144 @@
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;
import dev.sheldan.abstracto.core.service.management.ConfigManagementService;
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.config.feature.InfractionFeatureConfig;
import dev.sheldan.abstracto.moderation.config.posttarget.InfractionPostTarget;
import dev.sheldan.abstracto.moderation.listener.manager.InfractionLevelChangedListenerManager;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.model.template.InfractionLevelChangeModel;
import dev.sheldan.abstracto.moderation.service.management.InfractionManagementService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.util.Pair;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@Component
@Slf4j
public class InfractionServiceBean implements InfractionService {
@Autowired
private InfractionManagementService infractionManagementService;
@Autowired
private FeatureFlagService featureFlagService;
@Autowired
private ConfigService configService;
@Autowired
private ConfigManagementService configManagementService;
@Autowired
private TemplateService templateService;
@Autowired
private PostTargetService postTargetService;
@Autowired
private InfractionServiceBean self;
@Autowired
private InfractionLevelChangedListenerManager infractionLevelChangedListenerManager;
private static final String INFRACTION_NOTIFICATION_TEMPLATE_KEY = "infraction_level_notification";
@Override
public void decayInfraction(Infraction infraction) {
log.info("Decaying infraction {}", infraction.getId());
infraction.setDecayed(true);
infraction.setDecayedDate(Instant.now());
}
@Override
public Long getActiveInfractionPointsForUser(AUserInAServer aUserInAServer) {
List<Infraction> infractions = infractionManagementService.getActiveInfractionsForUser(aUserInAServer);
log.info("Calculating points for user {} in server {} with {} infractions.",
aUserInAServer.getUserReference().getId(), aUserInAServer.getServerReference().getId(), infractions.size());
return infractions.stream().collect(Collectors.summarizingLong(Infraction::getPoints)).getCount();
}
@Override
public CompletableFuture<Infraction> createInfractionWithNotification(AUserInAServer aUserInAServer, Long points) {
Infraction createdInfraction = infractionManagementService.createInfraction(aUserInAServer, points);
Long infractionId = createdInfraction.getId();
return createInfractionNotification(aUserInAServer, points)
.thenApply(aBoolean -> self.reloadInfraction(infractionId));
}
@Override
public CompletableFuture<Void> createInfractionNotification(AUserInAServer aUserInAServer, Long points) {
Long serverId = aUserInAServer.getServerReference().getId();
Long currentPoints = getActiveInfractionPointsForUser(aUserInAServer);
Long newPoints = currentPoints + points;
Pair<Integer, Integer> levelChange = infractionLevelChanged(serverId, newPoints, currentPoints);
Integer oldLevel = levelChange.getFirst();
Integer newLevel = levelChange.getSecond();
if(!oldLevel.equals(newLevel)) {
InfractionLevelChangeModel model = InfractionLevelChangeModel
.builder()
.member(MemberDisplay.fromAUserInAServer(aUserInAServer))
.newLevel(newLevel)
.oldLevel(oldLevel)
.oldPoints(currentPoints)
.newPoints(newPoints)
.build();
infractionLevelChangedListenerManager.sendInfractionLevelChangedEvent(newLevel, oldLevel, newPoints, currentPoints, ServerUser.fromAUserInAServer(aUserInAServer));
MessageToSend messageToSend = templateService.renderEmbedTemplate(INFRACTION_NOTIFICATION_TEMPLATE_KEY, model, serverId);
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, InfractionPostTarget.INFRACTION_NOTIFICATION, serverId));
} else {
return CompletableFuture.completedFuture(null);
}
}
@Transactional
public Infraction reloadInfraction(Long infractionId) {
return infractionManagementService.loadInfraction(infractionId);
}
private Pair<Integer, Integer> infractionLevelChanged(Long serverId, Long newPoints, Long oldPoints) {
List<Long> levelConfig = loadInfractionConfig(serverId);
Integer newLevel = getInfractionLevel(newPoints, levelConfig);
Integer oldLevel = getInfractionLevel(oldPoints, levelConfig);
return Pair.of(oldLevel, newLevel);
}
private List<Long> loadInfractionConfig(Long serverId) {
Long levelAmount = configService.getLongValueOrConfigDefault(InfractionFeatureConfig.INFRACTION_LEVELS, serverId);
List<Long> levelConfig = new ArrayList<>();
for (long i = 1; i <= levelAmount; i++) {
String levelKey = InfractionFeatureConfig.INFRACTION_LEVEL_PREFIX + i;
if(configManagementService.configExists(serverId, levelKey)) {
levelConfig.add(configService.getLongValue(levelKey, serverId));
}
}
return levelConfig;
}
private Integer getInfractionLevel(Long points, List<Long> levelConfig) {
for (int i = 0; i < levelConfig.size(); i++) {
if(points >= levelConfig.get(i)) {
return i;
}
}
return 0;
}
}

View File

@@ -40,10 +40,8 @@ public class KickServiceBean implements KickService {
} }
private CompletableFuture<Void> sendKickLog(KickLogModel kickLogModel) { private CompletableFuture<Void> sendKickLog(KickLogModel kickLogModel) {
CompletableFuture<Void> completableFuture;
MessageToSend warnLogMessage = templateService.renderEmbedTemplate(KICK_LOG_TEMPLATE, kickLogModel, kickLogModel.getGuild().getIdLong()); MessageToSend warnLogMessage = templateService.renderEmbedTemplate(KICK_LOG_TEMPLATE, kickLogModel, kickLogModel.getGuild().getIdLong());
log.debug("Sending kick log message in guild {}.", kickLogModel.getGuild().getIdLong()); log.debug("Sending kick log message in guild {}.", kickLogModel.getGuild().getIdLong());
completableFuture = FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(warnLogMessage, ModerationPostTarget.KICK_LOG, kickLogModel.getGuild().getIdLong())); return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(warnLogMessage, ModerationPostTarget.KICK_LOG, kickLogModel.getGuild().getIdLong()));
return completableFuture;
} }
} }

View File

@@ -244,21 +244,15 @@ public class MuteServiceBean implements MuteService {
} }
private CompletableFuture<Void> sendMuteLog(MuteContext muteLogModel, AServer server) { private CompletableFuture<Void> sendMuteLog(MuteContext muteLogModel, AServer server) {
CompletableFuture<Void> completableFuture;
log.debug("Sending mute log to the mute post target."); log.debug("Sending mute log to the mute post target.");
MessageToSend message = templateService.renderEmbedTemplate(MUTE_LOG_TEMPLATE, muteLogModel, server.getId()); MessageToSend message = templateService.renderEmbedTemplate(MUTE_LOG_TEMPLATE, muteLogModel, server.getId());
List<CompletableFuture<Message>> completableFutures = postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, muteLogModel.getContext().getServerId()); return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, muteLogModel.getContext().getServerId()));
completableFuture = FutureUtils.toSingleFutureGeneric(completableFutures);
return completableFuture;
} }
private CompletableFuture<Void> sendUnMuteLogMessage(UnMuteLog muteLogModel, AServer server) { private CompletableFuture<Void> sendUnMuteLogMessage(UnMuteLog muteLogModel, AServer server) {
CompletableFuture<Void> completableFuture;
log.debug("Sending unMute log for mute {} to the mute posttarget in server {}", muteLogModel.getMute().getMuteId().getId(), server.getId()); log.debug("Sending unMute log for mute {} to the mute posttarget in server {}", muteLogModel.getMute().getMuteId().getId(), server.getId());
MessageToSend message = templateService.renderEmbedTemplate(UN_MUTE_LOG_TEMPLATE, muteLogModel, server.getId()); MessageToSend message = templateService.renderEmbedTemplate(UN_MUTE_LOG_TEMPLATE, muteLogModel, server.getId());
List<CompletableFuture<Message>> completableFutures = postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, server.getId()); return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, server.getId()));
completableFuture = FutureUtils.toSingleFutureGeneric(completableFutures);
return completableFuture;
} }
@Override @Override

View File

@@ -100,10 +100,14 @@ public class ReactionReportServiceBean implements ReactionReportService {
@Transactional @Transactional
public void createReactionReportInDb(CachedMessage cachedMessage, Message reportMessage, ServerUser reporter) { public void createReactionReportInDb(CachedMessage cachedMessage, Message reportMessage, ServerUser reporter) {
if(reportMessage == null) {
log.info("Creation reaction report about message {} was not sent - post target might be disabled in server {}.", cachedMessage.getMessageId(), cachedMessage.getServerId());
} else {
log.info("Creation reaction report in message {} about message {} in database.", reportMessage.getIdLong(), cachedMessage.getMessageId()); log.info("Creation reaction report in message {} about message {} in database.", reportMessage.getIdLong(), cachedMessage.getMessageId());
reactionReportManagementService.createReactionReport(cachedMessage, reportMessage); reactionReportManagementService.createReactionReport(cachedMessage, reportMessage);
updateModerationUserReportCooldown(reporter); updateModerationUserReportCooldown(reporter);
} }
}
@Transactional @Transactional
public void updateModerationUserReportCooldown(ServerUser reporter) { public void updateModerationUserReportCooldown(ServerUser reporter) {

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.moderation.service; package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.FutureMemberPair; import dev.sheldan.abstracto.core.models.FutureMemberPair;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.ServerSpecificId; import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.core.models.database.AServer;
@@ -12,16 +13,20 @@ import dev.sheldan.abstracto.core.service.management.UserInServerManagementServi
import dev.sheldan.abstracto.core.utils.FutureUtils; import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.config.feature.WarningDecayFeatureConfig; import dev.sheldan.abstracto.moderation.config.feature.WarningDecayFeatureConfig;
import dev.sheldan.abstracto.moderation.config.feature.WarningFeatureConfig;
import dev.sheldan.abstracto.moderation.config.feature.mode.WarnDecayMode; import dev.sheldan.abstracto.moderation.config.feature.mode.WarnDecayMode;
import dev.sheldan.abstracto.moderation.config.feature.mode.WarningMode; import dev.sheldan.abstracto.moderation.config.feature.mode.WarningMode;
import dev.sheldan.abstracto.moderation.config.posttarget.WarnDecayPostTarget; import dev.sheldan.abstracto.moderation.config.posttarget.WarnDecayPostTarget;
import dev.sheldan.abstracto.moderation.config.posttarget.WarningPostTarget; import dev.sheldan.abstracto.moderation.config.posttarget.WarningPostTarget;
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.database.Warning;
import dev.sheldan.abstracto.moderation.model.template.command.WarnContext; import dev.sheldan.abstracto.moderation.model.template.command.WarnContext;
import dev.sheldan.abstracto.moderation.model.template.command.WarnNotification; 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.WarnDecayLogModel;
import dev.sheldan.abstracto.moderation.model.template.job.WarnDecayWarning; import dev.sheldan.abstracto.moderation.model.template.job.WarnDecayWarning;
import dev.sheldan.abstracto.moderation.model.template.listener.WarnDecayMemberNotificationModel; import dev.sheldan.abstracto.moderation.model.template.listener.WarnDecayMemberNotificationModel;
import dev.sheldan.abstracto.moderation.service.management.InfractionManagementService;
import dev.sheldan.abstracto.moderation.service.management.WarnManagementService; import dev.sheldan.abstracto.moderation.service.management.WarnManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend; import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService; import dev.sheldan.abstracto.core.templating.service.TemplateService;
@@ -85,6 +90,18 @@ public class WarnServiceBean implements WarnService {
@Autowired @Autowired
private WarnServiceBean self; private WarnServiceBean self;
@Autowired
private InfractionService infractionService;
@Autowired
private FeatureFlagService featureFlagService;
@Autowired
private InfractionManagementService infractionManagementService;
@Autowired
private WarningCreatedListenerManager warningCreatedListenerManager;
public static final String WARN_LOG_TEMPLATE = "warn_log"; public static final String WARN_LOG_TEMPLATE = "warn_log";
public static final String WARN_NOTIFICATION_TEMPLATE = "warn_notification"; public static final String WARN_NOTIFICATION_TEMPLATE = "warn_notification";
public static final String WARNINGS_COUNTER_KEY = "WARNINGS"; public static final String WARNINGS_COUNTER_KEY = "WARNINGS";
@@ -100,22 +117,34 @@ public class WarnServiceBean implements WarnService {
Member warningMember = context.getMember(); Member warningMember = context.getMember();
Guild guild = warnedMember.getGuild(); Guild guild = warnedMember.getGuild();
log.info("User {} is warning {} in server {}", warnedMember.getId(), warningMember.getId(), guild.getIdLong()); log.info("User {} is warning {} in server {}", warnedMember.getId(), warningMember.getId(), guild.getIdLong());
WarnNotification warnNotification = WarnNotification.builder().reason(context.getReason()).warnId(warningId).serverName(guild.getName()).build(); WarnNotification warnNotification = WarnNotification
.builder()
.reason(context.getReason())
.warnId(warningId)
.serverName(guild.getName())
.build();
Long serverId = server.getId();
String warnNotificationMessage = templateService.renderTemplate(WARN_NOTIFICATION_TEMPLATE, warnNotification, server.getId()); String warnNotificationMessage = templateService.renderTemplate(WARN_NOTIFICATION_TEMPLATE, warnNotification, server.getId());
List<CompletableFuture<Message>> futures = new ArrayList<>(); List<CompletableFuture> futures = new ArrayList<>();
futures.add(messageService.sendMessageToUser(warnedMember.getUser(), warnNotificationMessage)); futures.add(messageService.sendMessageToUser(warnedMember.getUser(), warnNotificationMessage));
log.debug("Logging warning for server {}.", server.getId()); log.debug("Logging warning for server {}.", server.getId());
if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, serverId)) {
Long infractionPoints = configService.getLongValueOrConfigDefault(WarningFeatureConfig.WARN_INFRACTION_POINTS, serverId);
AUserInAServer warnedUser = userInServerManagementService.loadOrCreateUser(warnedMember);
warnedUser.setServerReference(server);
futures.add(infractionService.createInfractionWithNotification(warnedUser, infractionPoints)
.thenAccept(infraction -> context.setInfractionId(infraction.getId())));
}
MessageToSend message = templateService.renderEmbedTemplate(WARN_LOG_TEMPLATE, context, server.getId()); MessageToSend message = templateService.renderEmbedTemplate(WARN_LOG_TEMPLATE, context, server.getId());
futures.addAll(postTargetService.sendEmbedInPostTarget(message, WarningPostTarget.WARN_LOG, context.getGuild().getIdLong())); futures.addAll(postTargetService.sendEmbedInPostTarget(message, WarningPostTarget.WARN_LOG, context.getGuild().getIdLong()));
return FutureUtils.toSingleFutureGeneric(futures); return FutureUtils.toSingleFuture(futures);
} }
@Override @Override
public CompletableFuture<Void> warnUserWithLog(WarnContext context) { public CompletableFuture<Void> warnUserWithLog(WarnContext context) {
return notifyAndLogFullUserWarning(context).thenAccept(aVoid -> return notifyAndLogFullUserWarning(context)
self.persistWarning(context) .thenAccept(aVoid -> self.persistWarning(context));
);
} }
@Transactional @Transactional
@@ -124,8 +153,15 @@ public class WarnServiceBean implements WarnService {
context.getWarnId(), context.getGuild().getId(), context.getWarnedMember().getId(), context.getMember().getId()); context.getWarnId(), context.getGuild().getId(), context.getWarnedMember().getId(), context.getMember().getId());
AUserInAServer warnedUser = userInServerManagementService.loadOrCreateUser(context.getWarnedMember()); AUserInAServer warnedUser = userInServerManagementService.loadOrCreateUser(context.getWarnedMember());
AUserInAServer warningUser = userInServerManagementService.loadOrCreateUser(context.getMember()); AUserInAServer warningUser = userInServerManagementService.loadOrCreateUser(context.getMember());
warnManagementService.createWarning(warnedUser, warningUser, context.getReason(), context.getWarnId()); Warning createdWarning = warnManagementService.createWarning(warnedUser, warningUser, context.getReason(), context.getWarnId());
if(context.getInfractionId() != null) {
Infraction infraction = infractionManagementService.loadInfraction(context.getInfractionId());
createdWarning.setInfraction(infraction);
}
ServerUser warnedServerUser = ServerUser.fromAUserInAServer(warnedUser);
ServerUser warningServerUser = ServerUser.fromAUserInAServer(warnedUser);
ServerChannelMessage commandMessage = ServerChannelMessage.fromMessage(context.getMessage());
warningCreatedListenerManager.sendWarningCreatedEvent(createdWarning.getWarnId(), warnedServerUser, warningServerUser, context.getReason(), commandMessage);
} }
@Override @Override
@@ -235,10 +271,13 @@ public class WarnServiceBean implements WarnService {
} }
@Override @Override
public void decayWarning(Warning warning, Instant now) { public void decayWarning(Warning warning, Instant decayDate) {
log.debug("Decaying warning {} in server {} with date {}.", warning.getWarnId().getId(), warning.getWarnId().getServerId(), now); log.debug("Decaying warning {} in server {} with date {}.", warning.getWarnId().getId(), warning.getWarnId().getServerId(), decayDate);
warning.setDecayDate(now); warning.setDecayDate(decayDate);
warning.setDecayed(true); warning.setDecayed(true);
if(warning.getInfraction() != null) {
infractionService.decayInfraction(warning.getInfraction());
}
} }
private CompletableFuture<Void> logDecayedWarnings(AServer server, List<Warning> warningsToDecay) { private CompletableFuture<Void> logDecayedWarnings(AServer server, List<Warning> warningsToDecay) {
@@ -307,8 +346,7 @@ public class WarnServiceBean implements WarnService {
.warnings(warnDecayWarnings) .warnings(warnDecayWarnings)
.build(); .build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(WARN_DECAY_LOG_TEMPLATE_KEY, warnDecayLogModel, serverId); MessageToSend messageToSend = templateService.renderEmbedTemplate(WARN_DECAY_LOG_TEMPLATE_KEY, warnDecayLogModel, serverId);
List<CompletableFuture<Message>> messageFutures = postTargetService.sendEmbedInPostTarget(messageToSend, WarnDecayPostTarget.DECAY_LOG, server.getId()); return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, WarnDecayPostTarget.DECAY_LOG, server.getId()));
return FutureUtils.toSingleFutureGeneric(messageFutures);
} }
@Override @Override

View File

@@ -0,0 +1,38 @@
package dev.sheldan.abstracto.moderation.service.management;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.repository.InfractionRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class InfractionManagementServiceBean implements InfractionManagementService{
@Autowired
private InfractionRepository infractionRepository;
@Override
public Infraction createInfraction(AUserInAServer aUserInAServer, Long points) {
Infraction infraction = Infraction
.builder()
.user(aUserInAServer)
.server(aUserInAServer.getServerReference())
.points(points)
.build();
return infractionRepository.save(infraction);
}
@Override
public List<Infraction> getActiveInfractionsForUser(AUserInAServer aUserInAServer) {
return infractionRepository.findByUserAndDecayedFalse(aUserInAServer);
}
@Override
public Infraction loadInfraction(Long infraction) {
return infractionRepository.getOne(infraction);
}
}

View File

@@ -70,10 +70,15 @@ public class WarnManagementServiceBean implements WarnManagementService {
} }
@Override @Override
public Long getActiveWarnsForUser(AUserInAServer aUserInAServer) { public Long getActiveWarnCountForUser(AUserInAServer aUserInAServer) {
return warnRepository.countByWarnedUserAndDecayedFalse(aUserInAServer); return warnRepository.countByWarnedUserAndDecayedFalse(aUserInAServer);
} }
@Override
public List<Warning> getActiveWarnsForUser(AUserInAServer aUserInAServer) {
return warnRepository.findByWarnedUserAndDecayedFalse(aUserInAServer);
}
@Override @Override
public Optional<Warning> findByIdOptional(Long id, Long serverId) { public Optional<Warning> findByIdOptional(Long id, Long serverId) {
return warnRepository.findByWarnId_IdAndWarnId_ServerId(id, serverId); return warnRepository.findByWarnId_IdAndWarnId_ServerId(id, serverId);

View File

@@ -0,0 +1,11 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="seedData/data.xml" relativeToChangelogFile="true"/>
<include file="tables/tables.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,10 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="feature.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -6,10 +6,9 @@
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" > http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="warn-decay-job-update_schedule"> <changeSet author="Sheldan" id="infraction_feature-insertion">
<update tableName="scheduler_job"> <insert tableName="feature">
<column name="cron_expression" value="0 0 0 * * ?"/> <column name="key" value="infractions"/>
<where>name='warnDecayJob'</where> </insert>
</update>
</changeSet> </changeSet>
</databaseChangeLog> </databaseChangeLog>

View File

@@ -0,0 +1,46 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="infraction-table">
<createTable tableName="infraction">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="pk_infraction"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE">
<constraints nullable="false"/>
</column>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="points" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="decayed_date" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="decayed" type="BOOLEAN"/>
<column name="infraction_user_in_server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
</createTable>
<addForeignKeyConstraint baseColumnNames="server_id" baseTableName="infraction" constraintName="fk_infraction_server"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id"
referencedTableName="server" validate="true"/>
<addForeignKeyConstraint baseColumnNames="infraction_user_in_server_id" baseTableName="infraction" constraintName="fk_infraction_user_in_server"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="user_in_server_id" referencedTableName="user_in_server" validate="true"/>
<sql>
DROP TRIGGER IF EXISTS infraction_update_trigger ON infraction;
CREATE TRIGGER infraction_update_trigger BEFORE UPDATE ON infraction FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure();
</sql>
<sql>
DROP TRIGGER IF EXISTS infraction_insert_trigger ON infraction;
CREATE TRIGGER infraction_insert_trigger BEFORE INSERT ON infraction FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure();
</sql>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,11 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="infraction.xml" relativeToChangelogFile="true"/>
<include file="warning.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,17 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="warning-infraction_id">
<addColumn tableName="warning">
<column name="infraction_id" type="BIGINT" />
</addColumn>
<addForeignKeyConstraint baseColumnNames="infraction_id" baseTableName="warning" constraintName="fk_warning_infraction"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id" referencedTableName="infraction" validate="true"/>
</changeSet>
</databaseChangeLog>

View File

@@ -11,4 +11,5 @@
<include file="1.2.15/collection.xml" relativeToChangelogFile="true"/> <include file="1.2.15/collection.xml" relativeToChangelogFile="true"/>
<include file="1.2.16/collection.xml" relativeToChangelogFile="true"/> <include file="1.2.16/collection.xml" relativeToChangelogFile="true"/>
<include file="1.3.4/collection.xml" relativeToChangelogFile="true"/> <include file="1.3.4/collection.xml" relativeToChangelogFile="true"/>
<include file="1.3.9/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog> </databaseChangeLog>

View File

@@ -7,6 +7,9 @@ abstracto.systemConfigs.reactionReportCooldownSeconds.longValue=300
abstracto.featureFlags.moderation.featureName=moderation abstracto.featureFlags.moderation.featureName=moderation
abstracto.featureFlags.moderation.enabled=false abstracto.featureFlags.moderation.enabled=false
abstracto.featureFlags.infractions.featureName=infractions
abstracto.featureFlags.infractions.enabled=false
abstracto.featureFlags.reportReactions.featureName=reportReactions abstracto.featureFlags.reportReactions.featureName=reportReactions
abstracto.featureFlags.reportReactions.enabled=false abstracto.featureFlags.reportReactions.enabled=false
@@ -42,6 +45,31 @@ abstracto.featureModes.warnDecayLogging.featureName=warnings
abstracto.featureModes.warnDecayLogging.mode=warnDecayLogging abstracto.featureModes.warnDecayLogging.mode=warnDecayLogging
abstracto.featureModes.warnDecayLogging.enabled=true abstracto.featureModes.warnDecayLogging.enabled=true
abstracto.featureModes.infractionReporting.featureName=infractions
abstracto.featureModes.infractionReporting.mode=infractionReporting
abstracto.featureModes.infractionReporting.enabled=true
abstracto.systemConfigs.infractionLvl1.name=infractionLvl1
abstracto.systemConfigs.infractionLvl1.longValue=10
abstracto.systemConfigs.infractionLvl2.name=infractionLvl2
abstracto.systemConfigs.infractionLvl2.longValue=20
abstracto.systemConfigs.infractionLvl3.name=infractionLvl3
abstracto.systemConfigs.infractionLvl3.longValue=30
abstracto.systemConfigs.infractionLvl4.name=infractionLvl4
abstracto.systemConfigs.infractionLvl4.longValue=40
abstracto.systemConfigs.infractionLvl5.name=infractionLvl5
abstracto.systemConfigs.infractionLvl5.longValue=50
abstracto.systemConfigs.infractionLevels.name=infractionLevels
abstracto.systemConfigs.infractionLevels.longValue=5
abstracto.systemConfigs.warnInfractionPoints.name=warnInfractionPoints
abstracto.systemConfigs.warnInfractionPoints.longValue=0
abstracto.featureModes.automaticWarnDecayLogging.featureName=warnDecay abstracto.featureModes.automaticWarnDecayLogging.featureName=warnDecay
abstracto.featureModes.automaticWarnDecayLogging.mode=automaticWarnDecayLogging abstracto.featureModes.automaticWarnDecayLogging.mode=automaticWarnDecayLogging
abstracto.featureModes.automaticWarnDecayLogging.enabled=true abstracto.featureModes.automaticWarnDecayLogging.enabled=true

View File

@@ -49,7 +49,7 @@ public class MyWarningsTest {
Long activeWarnCount = 8L; Long activeWarnCount = 8L;
AUserInAServer aUserInAServer = Mockito.mock(AUserInAServer.class); AUserInAServer aUserInAServer = Mockito.mock(AUserInAServer.class);
when(userInServerManagementService.loadOrCreateUser(noParameter.getAuthor())).thenReturn(aUserInAServer); when(userInServerManagementService.loadOrCreateUser(noParameter.getAuthor())).thenReturn(aUserInAServer);
when(warnManagementService.getActiveWarnsForUser(aUserInAServer)).thenReturn(activeWarnCount); when(warnManagementService.getActiveWarnCountForUser(aUserInAServer)).thenReturn(activeWarnCount);
Long totalWarnCount = 10L; Long totalWarnCount = 10L;
when(warnManagementService.getTotalWarnsForUser(aUserInAServer)).thenReturn(totalWarnCount); when(warnManagementService.getTotalWarnsForUser(aUserInAServer)).thenReturn(totalWarnCount);
CommandResult result = testUnit.execute(noParameter); CommandResult result = testUnit.execute(noParameter);

View File

@@ -29,6 +29,7 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.*; import org.mockito.*;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.beans.factory.annotation.Autowired;
import java.time.Instant; import java.time.Instant;
import java.util.Arrays; import java.util.Arrays;
@@ -134,6 +135,9 @@ public class WarnServiceBeanTest {
@Mock @Mock
private DefaultConfigManagementService defaultConfigManagementService; private DefaultConfigManagementService defaultConfigManagementService;
@Mock
private FeatureFlagService featureFlagService;
private static final String NOTIFICATION_TEXT = "text"; private static final String NOTIFICATION_TEXT = "text";
private static final String GUILD_NAME = "guild"; private static final String GUILD_NAME = "guild";
private static final Long SERVER_ID = 4L; private static final Long SERVER_ID = 4L;
@@ -204,6 +208,7 @@ public class WarnServiceBeanTest {
public void testWarnFullUser() { public void testWarnFullUser() {
setupWarnContext(); setupWarnContext();
setupMocksForWarning(); setupMocksForWarning();
when(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, SERVER_ID)).thenReturn(false);
CompletableFuture<Void> future = testUnit.notifyAndLogFullUserWarning(context); CompletableFuture<Void> future = testUnit.notifyAndLogFullUserWarning(context);
future.join(); future.join();
Assert.assertFalse(future.isCompletedExceptionally()); Assert.assertFalse(future.isCompletedExceptionally());

View File

@@ -97,7 +97,7 @@ public class WarnManagementServiceBeanTest {
public void testActiveWarnCountOfUser() { public void testActiveWarnCountOfUser() {
Long count = 5L; Long count = 5L;
when(warnRepository.countByWarnedUserAndDecayedFalse(warnedUser)).thenReturn(count); when(warnRepository.countByWarnedUserAndDecayedFalse(warnedUser)).thenReturn(count);
Long activeWarnsForUserCount = testUnit.getActiveWarnsForUser(warnedUser); Long activeWarnsForUserCount = testUnit.getActiveWarnCountForUser(warnedUser);
Assert.assertEquals(count, activeWarnsForUserCount); Assert.assertEquals(count, activeWarnsForUserCount);
} }

View File

@@ -0,0 +1,32 @@
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.PostTargetEnum;
import dev.sheldan.abstracto.moderation.config.posttarget.InfractionPostTarget;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class InfractionFeatureConfig implements FeatureConfig {
public static final String INFRACTION_LEVELS = "infractionLevels";
public static final String INFRACTION_LEVEL_PREFIX = "infractionLevel";
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.INFRACTIONS;
}
@Override
public List<String> getRequiredSystemConfigKeys() {
return Arrays.asList(INFRACTION_LEVELS);
}
@Override
public List<PostTargetEnum> getRequiredPostTargets() {
return Arrays.asList(InfractionPostTarget.INFRACTION_NOTIFICATION);
}
}

View File

@@ -11,7 +11,8 @@ public enum ModerationFeatureDefinition implements FeatureDefinition {
AUTOMATIC_WARN_DECAY("warnDecay"), AUTOMATIC_WARN_DECAY("warnDecay"),
USER_NOTES("userNotes"), USER_NOTES("userNotes"),
INVITE_FILTER("inviteFilter"), INVITE_FILTER("inviteFilter"),
REPORT_REACTIONS("reportReactions") REPORT_REACTIONS("reportReactions"),
INFRACTIONS("infractions")
; ;
private final String key; private final String key;

View File

@@ -15,6 +15,8 @@ import java.util.List;
@Component @Component
public class WarningFeatureConfig implements FeatureConfig { public class WarningFeatureConfig implements FeatureConfig {
public static final String WARN_INFRACTION_POINTS = "warnInfractionPoints";
@Autowired @Autowired
private WarningDecayFeatureConfig warningDecayFeatureConfig; private WarningDecayFeatureConfig warningDecayFeatureConfig;

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.moderation.config.posttarget;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import lombok.Getter;
@Getter
public enum InfractionPostTarget implements PostTargetEnum {
INFRACTION_NOTIFICATION("infractionNotification");
private String key;
InfractionPostTarget(String key) {
this.key = key;
}
}

View File

@@ -0,0 +1,8 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.FeatureAwareListener;
import dev.sheldan.abstracto.moderation.model.listener.InfractionLevelChangedEventModel;
public interface InfractionLevelChangedListener extends FeatureAwareListener<InfractionLevelChangedEventModel, DefaultListenerResult> {
}

View File

@@ -0,0 +1,8 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.FeatureAwareListener;
import dev.sheldan.abstracto.moderation.model.listener.WarningCreatedEventModel;
public interface WarningCreatedListener extends FeatureAwareListener<WarningCreatedEventModel, DefaultListenerResult> {
}

View File

@@ -0,0 +1,47 @@
package dev.sheldan.abstracto.moderation.model.database;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import lombok.*;
import javax.persistence.*;
import java.time.Instant;
@Entity
@Table(name="infraction")
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@EqualsAndHashCode
public class Infraction {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "infraction_user_in_server_id", nullable = false)
private AUserInAServer user;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@JoinColumn(name = "server_id", referencedColumnName = "id", nullable = false)
private AServer server;
@Column(name = "points")
private Long points;
@Column(name = "decayed")
private Boolean decayed;
@Column(name = "decayed_date")
private Instant decayedDate;
@Column(name = "created", nullable = false, insertable = false, updatable = false)
private Instant created;
@Column(name = "updated", insertable = false, updatable = false)
private Instant updated;
}

View File

@@ -28,7 +28,7 @@ public class Warning implements Serializable {
@Setter @Setter
private ServerSpecificId warnId; private ServerSpecificId warnId;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@MapsId("serverId") @MapsId("serverId")
@JoinColumn(name = "server_id", referencedColumnName = "id", nullable = false) @JoinColumn(name = "server_id", referencedColumnName = "id", nullable = false)
private AServer server; private AServer server;
@@ -90,4 +90,10 @@ public class Warning implements Serializable {
@Column(name = "updated", insertable = false, updatable = false) @Column(name = "updated", insertable = false, updatable = false)
private Instant updated; private Instant updated;
@Getter
@Setter
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "infraction_id")
private Infraction infraction;
} }

View File

@@ -0,0 +1,21 @@
package dev.sheldan.abstracto.moderation.model.listener;
import dev.sheldan.abstracto.core.listener.FeatureAwareListenerModel;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class InfractionLevelChangedEventModel implements FeatureAwareListenerModel {
private Integer oldLevel;
private Long oldPoints;
private Integer newLevel;
private Long newPoints;
private Long userId;
private Long serverId;
@Override
public Long getServerId() {
return serverId;
}
}

View File

@@ -0,0 +1,22 @@
package dev.sheldan.abstracto.moderation.model.listener;
import dev.sheldan.abstracto.core.listener.FeatureAwareListenerModel;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class WarningCreatedEventModel implements FeatureAwareListenerModel {
private Long warningId;
private Long warnedUserId;
private Long serverId;
private Long warningUserId;
private Long warningChannelId;
private Long warningMessageId;
private String reason;
@Override
public Long getServerId() {
return serverId;
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.moderation.model.template;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class InfractionLevelChangeModel {
private Long newPoints;
private Long oldPoints;
private Integer newLevel;
private Integer oldLevel;
private MemberDisplay member;
}

View File

@@ -26,4 +26,5 @@ public class WarnContext extends SlimUserInitiatedServerContext {
* The persisted {@link Warning} object from the database containing the information about the warning * The persisted {@link Warning} object from the database containing the information about the warning
*/ */
private Long warnId; private Long warnId;
private Long infractionId;
} }

View File

@@ -0,0 +1,13 @@
package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import java.util.concurrent.CompletableFuture;
public interface InfractionService {
void decayInfraction(Infraction infraction);
Long getActiveInfractionPointsForUser(AUserInAServer aUserInAServer);
CompletableFuture<Infraction> createInfractionWithNotification(AUserInAServer aUserInAServer, Long points);
CompletableFuture<Void> createInfractionNotification(AUserInAServer aUserInAServer, Long points);
}

View File

@@ -0,0 +1,12 @@
package dev.sheldan.abstracto.moderation.service.management;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import java.util.List;
public interface InfractionManagementService {
Infraction createInfraction(AUserInAServer aUserInAServer, Long points);
List<Infraction> getActiveInfractionsForUser(AUserInAServer aUserInAServer);
Infraction loadInfraction(Long infraction);
}

View File

@@ -16,7 +16,8 @@ public interface WarnManagementService {
Long getTotalWarnsForUser(AUserInAServer aUserInAServer); Long getTotalWarnsForUser(AUserInAServer aUserInAServer);
List<Warning> getAllWarnsForUser(AUserInAServer aUserInAServer); List<Warning> getAllWarnsForUser(AUserInAServer aUserInAServer);
List<Warning> getAllWarningsOfServer(AServer server); List<Warning> getAllWarningsOfServer(AServer server);
Long getActiveWarnsForUser(AUserInAServer aUserInAServer); Long getActiveWarnCountForUser(AUserInAServer aUserInAServer);
List<Warning> getActiveWarnsForUser(AUserInAServer aUserInAServer);
Optional<Warning> findByIdOptional(Long id, Long serverId); Optional<Warning> findByIdOptional(Long id, Long serverId);
Warning findById(Long id, Long serverId); Warning findById(Long id, Long serverId);
List<Warning> getWarningsViaId(List<Long> warnIds, Long serverId); List<Warning> getWarningsViaId(List<Long> warnIds, Long serverId);

View File

@@ -286,8 +286,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
.channel(channel) .channel(channel)
.build(); .build();
MessageToSend messageToSend = templateService.renderEmbedTemplate("modmail_notification_message", modMailNotificationModel, channel.getGuild().getIdLong()); MessageToSend messageToSend = templateService.renderEmbedTemplate("modmail_notification_message", modMailNotificationModel, channel.getGuild().getIdLong());
List<CompletableFuture<Message>> modmailping = postTargetService.sendEmbedInPostTarget(messageToSend, ModMailPostTargets.MOD_MAIL_PING, serverId); return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, ModMailPostTargets.MOD_MAIL_PING, serverId));
return CompletableFuture.allOf(modmailping.toArray(new CompletableFuture[0]));
} }
/** /**
@@ -660,7 +659,9 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
.thenCompose(list -> list.getMainFuture().thenCompose(unused -> { .thenCompose(list -> list.getMainFuture().thenCompose(unused -> {
list.getFutures().forEach(messageCompletableFuture -> { list.getFutures().forEach(messageCompletableFuture -> {
Message message = messageCompletableFuture.join(); Message message = messageCompletableFuture.join();
if(message != null) {
undoActions.add(UndoActionInstance.getMessageDeleteAction(message.getGuild().getIdLong(), message.getChannel().getIdLong(), message.getIdLong())); undoActions.add(UndoActionInstance.getMessageDeleteAction(message.getGuild().getIdLong(), message.getChannel().getIdLong(), message.getIdLong()));
}
}); });
return memberService.getMemberInServerAsync(serverId, userId).thenCompose(member -> return memberService.getMemberInServerAsync(serverId, userId).thenCompose(member ->
self.afterSuccessfulLog(modMailThreadId, closingContext.getNotifyUser(), member, undoActions) self.afterSuccessfulLog(modMailThreadId, closingContext.getNotifyUser(), member, undoActions)

View File

@@ -115,7 +115,12 @@ public class ProfanityFilterServiceBean implements ProfanityFilterService {
Long profanityRegexId = foundProfanityRegex.getId(); Long profanityRegexId = foundProfanityRegex.getId();
return FutureUtils.toSingleFutureGeneric(messageFutures).thenCompose(aVoid -> { return FutureUtils.toSingleFutureGeneric(messageFutures).thenCompose(aVoid -> {
Message createdMessage = messageFutures.get(0).join(); Message createdMessage = messageFutures.get(0).join();
if(createdMessage != null) {
return self.afterReportCreation(message, serverId, profanityRegexId, createdMessage); return self.afterReportCreation(message, serverId, profanityRegexId, createdMessage);
} else {
log.warn("No profanity report message created about message {} in server {} - possibly post target disabled.", message.getIdLong(), message.getGuild().getIdLong());
return CompletableFuture.completedFuture(null);
}
}); });
} }

View File

@@ -1,10 +1,8 @@
package dev.sheldan.abstracto.starboard.service; package dev.sheldan.abstracto.starboard.listener.manager;
import dev.sheldan.abstracto.core.listener.ListenerService; import dev.sheldan.abstracto.core.listener.ListenerService;
import dev.sheldan.abstracto.core.models.ServerChannelMessage; import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.starboard.listener.StarboardPostCreatedListener; import dev.sheldan.abstracto.starboard.listener.StarboardPostCreatedListener;
import dev.sheldan.abstracto.starboard.model.StarboardPostCreatedModel; import dev.sheldan.abstracto.starboard.model.StarboardPostCreatedModel;
import dev.sheldan.abstracto.starboard.model.database.StarboardPost; import dev.sheldan.abstracto.starboard.model.database.StarboardPost;
@@ -28,15 +26,6 @@ public class StarboardPostCreatedListenerManager {
@Qualifier("starboardCreatedListenerExecutor") @Qualifier("starboardCreatedListenerExecutor")
private TaskExecutor starboardCreatedExecutor; private TaskExecutor starboardCreatedExecutor;
@Autowired
private StarboardPostCreatedListenerManager self;
@Autowired
private FeatureConfigService featureConfigService;
@Autowired
private FeatureFlagService featureFlagService;
@Autowired @Autowired
private ListenerService listenerService; private ListenerService listenerService;

View File

@@ -1,10 +1,8 @@
package dev.sheldan.abstracto.starboard.service; package dev.sheldan.abstracto.starboard.listener.manager;
import dev.sheldan.abstracto.core.listener.ListenerService; import dev.sheldan.abstracto.core.listener.ListenerService;
import dev.sheldan.abstracto.core.models.ServerChannelMessage; import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.starboard.listener.StarboardPostDeletedListener; import dev.sheldan.abstracto.starboard.listener.StarboardPostDeletedListener;
import dev.sheldan.abstracto.starboard.model.StarboardPostDeletedModel; import dev.sheldan.abstracto.starboard.model.StarboardPostDeletedModel;
import dev.sheldan.abstracto.starboard.model.database.StarboardPost; import dev.sheldan.abstracto.starboard.model.database.StarboardPost;
@@ -28,15 +26,6 @@ public class StarboardPostDeletedListenerManager {
@Qualifier("starboardDeletedListenerExecutor") @Qualifier("starboardDeletedListenerExecutor")
private TaskExecutor starboardDeletedExecutor; private TaskExecutor starboardDeletedExecutor;
@Autowired
private StarboardPostDeletedListenerManager self;
@Autowired
private FeatureConfigService featureConfigService;
@Autowired
private FeatureFlagService featureFlagService;
@Autowired @Autowired
private ListenerService listenerService; private ListenerService listenerService;

View File

@@ -18,6 +18,8 @@ import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils; import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.starboard.config.StarboardFeatureConfig; import dev.sheldan.abstracto.starboard.config.StarboardFeatureConfig;
import dev.sheldan.abstracto.starboard.config.StarboardPostTarget; import dev.sheldan.abstracto.starboard.config.StarboardPostTarget;
import dev.sheldan.abstracto.starboard.listener.manager.StarboardPostCreatedListenerManager;
import dev.sheldan.abstracto.starboard.listener.manager.StarboardPostDeletedListenerManager;
import dev.sheldan.abstracto.starboard.model.database.StarboardPost; import dev.sheldan.abstracto.starboard.model.database.StarboardPost;
import dev.sheldan.abstracto.starboard.model.template.*; import dev.sheldan.abstracto.starboard.model.template.*;
import dev.sheldan.abstracto.starboard.service.management.StarboardPostManagementService; import dev.sheldan.abstracto.starboard.service.management.StarboardPostManagementService;
@@ -102,6 +104,7 @@ public class StarboardServiceBean implements StarboardService {
@Override @Override
public CompletableFuture<Void> createStarboardPost(CachedMessage message, List<AUserInAServer> userExceptAuthor, AUserInAServer userReacting, AUserInAServer starredUser) { public CompletableFuture<Void> createStarboardPost(CachedMessage message, List<AUserInAServer> userExceptAuthor, AUserInAServer userReacting, AUserInAServer starredUser) {
postTargetService.validatePostTarget(StarboardPostTarget.STARBOARD, message.getServerId());
Long starredUserId = starredUser.getUserInServerId(); Long starredUserId = starredUser.getUserInServerId();
List<Long> userExceptAuthorIds = userExceptAuthor.stream().map(AUserInAServer::getUserInServerId).collect(Collectors.toList()); List<Long> userExceptAuthorIds = userExceptAuthor.stream().map(AUserInAServer::getUserInServerId).collect(Collectors.toList());
Long userReactingId = userReacting.getUserReference().getId(); Long userReactingId = userReacting.getUserReference().getId();

View File

@@ -15,8 +15,8 @@ import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService; import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.starboard.config.StarboardFeatureConfig; import dev.sheldan.abstracto.starboard.config.StarboardFeatureConfig;
import dev.sheldan.abstracto.starboard.config.StarboardPostTarget; import dev.sheldan.abstracto.starboard.config.StarboardPostTarget;
import dev.sheldan.abstracto.starboard.listener.manager.StarboardPostCreatedListenerManager;
import dev.sheldan.abstracto.starboard.model.database.StarboardPost; import dev.sheldan.abstracto.starboard.model.database.StarboardPost;
import dev.sheldan.abstracto.starboard.model.database.StarboardPostReaction;
import dev.sheldan.abstracto.starboard.model.template.*; import dev.sheldan.abstracto.starboard.model.template.*;
import dev.sheldan.abstracto.starboard.service.management.StarboardPostManagementService; import dev.sheldan.abstracto.starboard.service.management.StarboardPostManagementService;
import dev.sheldan.abstracto.starboard.service.management.StarboardPostReactorManagementService; import dev.sheldan.abstracto.starboard.service.management.StarboardPostReactorManagementService;

View File

@@ -114,13 +114,14 @@ public class SuggestionServiceBean implements SuggestionService {
@Override @Override
public CompletableFuture<Void> createSuggestionMessage(Message commandMessage, String text) { public CompletableFuture<Void> createSuggestionMessage(Message commandMessage, String text) {
// it is done that way, because we cannot always be sure, that the message containsn the member // it is done that way, because we cannot always be sure, that the message contains the member
return memberService.getMemberInServerAsync(commandMessage.getGuild().getIdLong(), commandMessage.getAuthor().getIdLong()) return memberService.getMemberInServerAsync(commandMessage.getGuild().getIdLong(), commandMessage.getAuthor().getIdLong())
.thenCompose(suggester -> self.createMessageWithSuggester(commandMessage, text, suggester)); .thenCompose(suggester -> self.createMessageWithSuggester(commandMessage, text, suggester));
} }
@Transactional @Transactional
public CompletableFuture<Void> createMessageWithSuggester(Message commandMessage, String text, Member suggester) { public CompletableFuture<Void> createMessageWithSuggester(Message commandMessage, String text, Member suggester) {
postTargetService.validatePostTarget(SuggestionPostTarget.SUGGESTION, suggester.getGuild().getIdLong());
Long serverId = suggester.getGuild().getIdLong(); Long serverId = suggester.getGuild().getIdLong();
AServer server = serverManagementService.loadServer(serverId); AServer server = serverManagementService.loadServer(serverId);
AUserInAServer userSuggester = userInServerManagementService.loadOrCreateUser(suggester); AUserInAServer userSuggester = userInServerManagementService.loadOrCreateUser(suggester);
@@ -223,6 +224,7 @@ public class SuggestionServiceBean implements SuggestionService {
@Transactional @Transactional
public CompletableFuture<Void> setSuggestionToFinalState(Member executingMember, Long suggestionId, Message commandMessage, String text, SuggestionState state) { public CompletableFuture<Void> setSuggestionToFinalState(Member executingMember, Long suggestionId, Message commandMessage, String text, SuggestionState state) {
Long serverId = commandMessage.getGuild().getIdLong(); Long serverId = commandMessage.getGuild().getIdLong();
postTargetService.validatePostTarget(SuggestionPostTarget.SUGGESTION, serverId);
Suggestion suggestion = suggestionManagementService.getSuggestion(serverId, suggestionId); Suggestion suggestion = suggestionManagementService.getSuggestion(serverId, suggestionId);
suggestionManagementService.setSuggestionState(suggestion, state); suggestionManagementService.setSuggestionState(suggestion, state);
cancelSuggestionReminder(suggestion); cancelSuggestionReminder(suggestion);
@@ -340,6 +342,7 @@ public class SuggestionServiceBean implements SuggestionService {
@Transactional @Transactional
public CompletableFuture<Void> remindAboutSuggestion(ServerSpecificId suggestionId) { public CompletableFuture<Void> remindAboutSuggestion(ServerSpecificId suggestionId) {
Long serverId = suggestionId.getServerId(); Long serverId = suggestionId.getServerId();
postTargetService.validatePostTarget(SuggestionPostTarget.SUGGESTION_REMINDER, serverId);
Suggestion suggestion = suggestionManagementService.getSuggestion(serverId, suggestionId.getId()); Suggestion suggestion = suggestionManagementService.getSuggestion(serverId, suggestionId.getId());
ServerChannelMessage suggestionServerChannelMessage = ServerChannelMessage ServerChannelMessage suggestionServerChannelMessage = ServerChannelMessage
.builder() .builder()

View File

@@ -0,0 +1,63 @@
package dev.sheldan.abstracto.core.commands.channels;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatureDefinition;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.PostTargetNotValidException;
import dev.sheldan.abstracto.core.service.PostTargetService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class DisablePostTarget extends AbstractConditionableCommand {
@Autowired
private PostTargetService postTargetService;
@Override
public CommandResult execute(CommandContext commandContext) {
String targetName = (String) commandContext.getParameters().getParameters().get(0);
if(!postTargetService.validPostTarget(targetName)) {
throw new PostTargetNotValidException(targetName, postTargetService.getAvailablePostTargets());
}
postTargetService.disablePostTarget(targetName, commandContext.getGuild().getIdLong());
return CommandResult.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
Parameter postTargetName = Parameter
.builder()
.name("name")
.type(String.class)
.templated(true)
.build();
List<Parameter> parameters = Arrays.asList(postTargetName);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
return CommandConfiguration.builder()
.name("disablePosttarget")
.module(ChannelsModuleDefinition.CHANNELS)
.parameters(parameters)
.supportsEmbedException(true)
.help(helpInfo)
.templated(true)
.causesReaction(true)
.build();
}
@Override
public FeatureDefinition getFeature() {
return CoreFeatureDefinition.CORE_FEATURE;
}
}

View File

@@ -0,0 +1,63 @@
package dev.sheldan.abstracto.core.commands.channels;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatureDefinition;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.PostTargetNotValidException;
import dev.sheldan.abstracto.core.service.PostTargetService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class EnablePostTarget extends AbstractConditionableCommand {
@Autowired
private PostTargetService postTargetService;
@Override
public CommandResult execute(CommandContext commandContext) {
String targetName = (String) commandContext.getParameters().getParameters().get(0);
if(!postTargetService.validPostTarget(targetName)) {
throw new PostTargetNotValidException(targetName, postTargetService.getAvailablePostTargets());
}
postTargetService.enablePostTarget(targetName, commandContext.getGuild().getIdLong());
return CommandResult.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
Parameter postTargetName = Parameter
.builder()
.name("name")
.type(String.class)
.templated(true)
.build();
List<Parameter> parameters = Arrays.asList(postTargetName);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
return CommandConfiguration.builder()
.name("enablePosttarget")
.module(ChannelsModuleDefinition.CHANNELS)
.parameters(parameters)
.supportsEmbedException(true)
.help(helpInfo)
.templated(true)
.causesReaction(true)
.build();
}
@Override
public FeatureDefinition getFeature() {
return CoreFeatureDefinition.CORE_FEATURE;
}
}

View File

@@ -62,14 +62,25 @@ public class PostTargetCommand extends AbstractConditionableCommand {
List<PostTargetModelEntry> postTargetEntries = posttargetDisplayModel.getPostTargets(); List<PostTargetModelEntry> postTargetEntries = posttargetDisplayModel.getPostTargets();
postTargets.forEach(target -> { postTargets.forEach(target -> {
Optional<TextChannel> channelFromAChannel = channelService.getChannelFromAChannel(target.getChannelReference()); Optional<TextChannel> channelFromAChannel = channelService.getChannelFromAChannel(target.getChannelReference());
PostTargetModelEntry targetEntry = PostTargetModelEntry.builder().channel(channelFromAChannel.orElse(null)).postTarget(target).build(); PostTargetModelEntry targetEntry = PostTargetModelEntry
.builder()
.channel(channelFromAChannel.orElse(null))
.disabled(target.getDisabled())
.postTarget(target).build();
postTargetEntries.add(targetEntry); postTargetEntries.add(targetEntry);
}); });
List<String> postTargetConfigs = postTargetService.getPostTargetsOfEnabledFeatures(server); List<String> postTargetConfigs = postTargetService.getPostTargetsOfEnabledFeatures(server);
postTargetConfigs.forEach(postTargetName -> { postTargetConfigs.forEach(postTargetName -> {
if(postTargetEntries.stream().noneMatch(postTargetModelEntry -> postTargetModelEntry.getPostTarget().getName().equalsIgnoreCase(postTargetName))) { if(postTargetEntries.stream().noneMatch(postTargetModelEntry -> postTargetModelEntry.getPostTarget().getName().equalsIgnoreCase(postTargetName))) {
PostTarget fakeEntry = PostTarget.builder().name(postTargetName).build(); PostTarget fakeEntry = PostTarget
PostTargetModelEntry postTargetEntry = PostTargetModelEntry.builder().postTarget(fakeEntry).build(); .builder()
.name(postTargetName)
.build();
PostTargetModelEntry postTargetEntry = PostTargetModelEntry
.builder()
.postTarget(fakeEntry)
.disabled(false)
.build();
postTargetEntries.add(postTargetEntry); postTargetEntries.add(postTargetEntry);
} }
}); });

View File

@@ -4,6 +4,7 @@ import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.PostTargetEnum; import dev.sheldan.abstracto.core.config.PostTargetEnum;
import dev.sheldan.abstracto.core.exception.ChannelNotInGuildException; import dev.sheldan.abstracto.core.exception.ChannelNotInGuildException;
import dev.sheldan.abstracto.core.exception.GuildNotFoundException; import dev.sheldan.abstracto.core.exception.GuildNotFoundException;
import dev.sheldan.abstracto.core.exception.PostTargetNotUsableException;
import dev.sheldan.abstracto.core.exception.PostTargetNotValidException; import dev.sheldan.abstracto.core.exception.PostTargetNotValidException;
import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.PostTarget; import dev.sheldan.abstracto.core.models.database.PostTarget;
@@ -22,6 +23,7 @@ import org.springframework.stereotype.Component;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -52,16 +54,26 @@ public class PostTargetServiceBean implements PostTargetService {
@Override @Override
public CompletableFuture<Message> sendTextInPostTarget(String text, PostTarget target) { public CompletableFuture<Message> sendTextInPostTarget(String text, PostTarget target) {
if(target.getDisabled()) {
log.info("Post target {} has been disabled in server {} - not sending message.", target.getName(), target.getServerReference().getId());
return CompletableFuture.completedFuture(null);
} else {
log.debug("Sending text to post target {}.", target.getName()); log.debug("Sending text to post target {}.", target.getName());
return channelService.sendTextToAChannel(text, target.getChannelReference()); return channelService.sendTextToAChannel(text, target.getChannelReference());
} }
}
@Override @Override
public CompletableFuture<Message> sendEmbedInPostTarget(MessageEmbed embed, PostTarget target) { public CompletableFuture<Message> sendEmbedInPostTarget(MessageEmbed embed, PostTarget target) {
if(target.getDisabled()) {
log.info("Post target {} has been disabled in server {} - not sending message.", target.getName(), target.getServerReference().getId());
return CompletableFuture.completedFuture(null);
} else {
TextChannel textChannelForPostTarget = getTextChannelForPostTarget(target); TextChannel textChannelForPostTarget = getTextChannelForPostTarget(target);
log.debug("Sending message embed to post target {}.", target.getName()); log.debug("Sending message embed to post target {}.", target.getName());
return channelService.sendEmbedToChannel(embed, textChannelForPostTarget); return channelService.sendEmbedToChannel(embed, textChannelForPostTarget);
} }
}
private TextChannel getTextChannelForPostTarget(PostTarget target) { private TextChannel getTextChannelForPostTarget(PostTarget target) {
Guild guild = botService.getInstance().getGuildById(target.getServerReference().getId()); Guild guild = botService.getInstance().getGuildById(target.getServerReference().getId());
@@ -81,43 +93,69 @@ public class PostTargetServiceBean implements PostTargetService {
@Override @Override
public CompletableFuture<Message> sendTextInPostTarget(String text, PostTargetEnum postTargetEnum, Long serverId) { public CompletableFuture<Message> sendTextInPostTarget(String text, PostTargetEnum postTargetEnum, Long serverId) {
PostTarget postTarget = getPostTarget(postTargetEnum, serverId); Optional<PostTarget> postTargetOptional = getPostTarget(postTargetEnum, serverId);
return this.sendTextInPostTarget(text, postTarget); if(!postTargetOptional.isPresent()) {
return CompletableFuture.completedFuture(null);
}
return this.sendTextInPostTarget(text, postTargetOptional.get());
} }
@Override @Override
public CompletableFuture<Message> sendEmbedInPostTarget(MessageEmbed embed, PostTargetEnum postTargetName, Long serverId) { public CompletableFuture<Message> sendEmbedInPostTarget(MessageEmbed embed, PostTargetEnum postTargetName, Long serverId) {
PostTarget postTarget = getPostTarget(postTargetName, serverId); Optional<PostTarget> postTargetOptional = getPostTarget(postTargetName, serverId);
return this.sendEmbedInPostTarget(embed, postTarget); if(!postTargetOptional.isPresent()) {
return CompletableFuture.completedFuture(null);
}
return this.sendEmbedInPostTarget(embed, postTargetOptional.get());
} }
@Override @Override
public CompletableFuture<Message> sendMessageInPostTarget(Message message, PostTargetEnum postTargetName, Long serverId) { public CompletableFuture<Message> sendMessageInPostTarget(Message message, PostTargetEnum postTargetName, Long serverId) {
PostTarget postTarget = getPostTarget(postTargetName, serverId); Optional<PostTarget> postTargetOptional = getPostTarget(postTargetName, serverId);
return sendMessageInPostTarget(message, postTarget); if(!postTargetOptional.isPresent()) {
return CompletableFuture.completedFuture(null);
}
return sendMessageInPostTarget(message, postTargetOptional.get());
} }
@Override @Override
public CompletableFuture<Message> sendMessageInPostTarget(Message message, PostTarget target) { public CompletableFuture<Message> sendMessageInPostTarget(Message message, PostTarget target) {
if(target.getDisabled()) {
log.info("Post target {} has been disabled in server {} - not sending message.", target.getName(), target.getServerReference().getId());
return CompletableFuture.completedFuture(null);
} else {
log.debug("Send message {} towards post target {}.", message.getId(), target.getName()); log.debug("Send message {} towards post target {}.", message.getId(), target.getName());
return channelService.sendMessageToAChannel(message, target.getChannelReference()); return channelService.sendMessageToAChannel(message, target.getChannelReference());
} }
}
@Override @Override
public List<CompletableFuture<Message>> sendEmbedInPostTarget(MessageToSend message, PostTargetEnum postTargetName, Long serverId) { public List<CompletableFuture<Message>> sendEmbedInPostTarget(MessageToSend message, PostTargetEnum postTargetName, Long serverId) {
PostTarget postTarget = getPostTarget(postTargetName, serverId); Optional<PostTarget> postTargetOptional = getPostTarget(postTargetName, serverId);
return this.sendEmbedInPostTarget(message, postTarget); if(!postTargetOptional.isPresent()) {
return Arrays.asList(CompletableFuture.completedFuture(null));
}
return this.sendEmbedInPostTarget(message, postTargetOptional.get());
} }
@Override @Override
public List<CompletableFuture<Message>> sendEmbedInPostTarget(MessageToSend message, PostTarget target) { public List<CompletableFuture<Message>> sendEmbedInPostTarget(MessageToSend message, PostTarget target) {
if(target.getDisabled()) {
log.info("Post target {} has been disabled in server {} - not sending message.", target.getName(), target.getServerReference().getId());
return Arrays.asList(CompletableFuture.completedFuture(null));
} else {
TextChannel textChannelForPostTarget = getTextChannelForPostTarget(target); TextChannel textChannelForPostTarget = getTextChannelForPostTarget(target);
log.debug("Send messageToSend towards post target {}.", target.getName()); log.debug("Send messageToSend towards post target {}.", target.getName());
return channelService.sendMessageToSendToChannel(message, textChannelForPostTarget); return channelService.sendMessageToSendToChannel(message, textChannelForPostTarget);
} }
}
@Override @Override
public List<CompletableFuture<Message>> editEmbedInPostTarget(Long messageId, MessageToSend message, PostTarget target) { public List<CompletableFuture<Message>> editEmbedInPostTarget(Long messageId, MessageToSend message, PostTarget target) {
if(target.getDisabled()) {
log.info("Post target {} has been disabled in server {} - not sending message.", target.getName(), target.getServerReference().getId());
return Arrays.asList(CompletableFuture.completedFuture(null));
} else {
TextChannel textChannelForPostTarget = getTextChannelForPostTarget(target); TextChannel textChannelForPostTarget = getTextChannelForPostTarget(target);
// always takes the first one, only applicable for this scenario // always takes the first one, only applicable for this scenario
String messageText = message.getMessages().get(0); String messageText = message.getMessages().get(0);
@@ -129,14 +167,19 @@ public class PostTargetServiceBean implements PostTargetService {
return Arrays.asList(channelService.editTextMessageInAChannel(messageText, message.getEmbeds().get(0), textChannelForPostTarget, messageId)); return Arrays.asList(channelService.editTextMessageInAChannel(messageText, message.getEmbeds().get(0), textChannelForPostTarget, messageId));
} }
} }
}
@Override @Override
public List<CompletableFuture<Message>> editOrCreatedInPostTarget(Long messageId, MessageToSend messageToSend, PostTarget target) { public List<CompletableFuture<Message>> editOrCreatedInPostTarget(Long messageId, MessageToSend messageToSend, PostTarget target) {
if(target.getDisabled()) {
log.info("Post target {} has been disabled in server {} - not sending message.", target.getName(), target.getServerReference().getId());
return Arrays.asList(CompletableFuture.completedFuture(null));
} else {
List<CompletableFuture<Message>> futures = new ArrayList<>(); List<CompletableFuture<Message>> futures = new ArrayList<>();
TextChannel textChannelForPostTarget = getTextChannelForPostTarget(target); TextChannel textChannelForPostTarget = getTextChannelForPostTarget(target);
CompletableFuture<Message> messageEditFuture = new CompletableFuture<>(); CompletableFuture<Message> messageEditFuture = new CompletableFuture<>();
futures.add(messageEditFuture); futures.add(messageEditFuture);
if(StringUtils.isBlank(messageToSend.getMessages().get(0).trim())) { if (StringUtils.isBlank(messageToSend.getMessages().get(0).trim())) {
channelService.retrieveMessageInChannel(textChannelForPostTarget, messageId).thenAccept(message -> { channelService.retrieveMessageInChannel(textChannelForPostTarget, messageId).thenAccept(message -> {
log.debug("Editing existing message {} when upserting message embeds in channel {} in server {}.", log.debug("Editing existing message {} when upserting message embeds in channel {} in server {}.",
messageId, textChannelForPostTarget.getIdLong(), textChannelForPostTarget.getGuild().getId()); messageId, textChannelForPostTarget.getIdLong(), textChannelForPostTarget.getGuild().getId());
@@ -174,17 +217,21 @@ public class PostTargetServiceBean implements PostTargetService {
return futures; return futures;
} }
}
@Override @Override
public List<CompletableFuture<Message>> editOrCreatedInPostTarget(Long messageId, MessageToSend messageToSend, PostTargetEnum postTargetName, Long serverId) { public List<CompletableFuture<Message>> editOrCreatedInPostTarget(Long messageId, MessageToSend messageToSend, PostTargetEnum postTargetName, Long serverId) {
PostTarget postTarget = getPostTarget(postTargetName, serverId); Optional<PostTarget> postTargetOptional = getPostTarget(postTargetName, serverId);
return this.editOrCreatedInPostTarget(messageId, messageToSend, postTarget); if(!postTargetOptional.isPresent()) {
return Arrays.asList(CompletableFuture.completedFuture(null));
}
return this.editOrCreatedInPostTarget(messageId, messageToSend, postTargetOptional.get());
} }
@Override @Override
public void throwIfPostTargetIsNotDefined(PostTargetEnum name, Long serverId) { public void throwIfPostTargetIsNotDefined(PostTargetEnum name, Long serverId) {
PostTarget postTarget = getPostTarget(name, serverId); Optional<PostTarget> postTargetOptional = getPostTarget(name, serverId);
if(postTarget == null) { if(!postTargetOptional.isPresent()) {
throw new PostTargetNotValidException(name.getKey(), defaultPostTargetManagementService.getDefaultPostTargetKeys()); throw new PostTargetNotValidException(name.getKey(), defaultPostTargetManagementService.getDefaultPostTargetKeys());
} }
} }
@@ -196,12 +243,15 @@ public class PostTargetServiceBean implements PostTargetService {
@Override @Override
public List<CompletableFuture<Message>> editEmbedInPostTarget(Long messageId, MessageToSend message, PostTargetEnum postTargetName, Long serverId) { public List<CompletableFuture<Message>> editEmbedInPostTarget(Long messageId, MessageToSend message, PostTargetEnum postTargetName, Long serverId) {
PostTarget postTarget = getPostTarget(postTargetName, serverId); Optional<PostTarget> postTargetOptional = getPostTarget(postTargetName, serverId);
return editEmbedInPostTarget(messageId, message, postTarget); if(!postTargetOptional.isPresent()) {
return Arrays.asList(CompletableFuture.completedFuture(null));
}
return editEmbedInPostTarget(messageId, message, postTargetOptional.get());
} }
private PostTarget getPostTarget(PostTargetEnum postTargetEnum, Long serverId) { private Optional<PostTarget> getPostTarget(PostTargetEnum postTargetEnum, Long serverId) {
return postTargetManagement.getPostTarget(postTargetEnum.getKey(), serverId); return postTargetManagement.getPostTargetOptional(postTargetEnum.getKey(), serverId);
} }
@Override @Override
@@ -210,6 +260,19 @@ public class PostTargetServiceBean implements PostTargetService {
return possiblePostTargets.contains(name); return possiblePostTargets.contains(name);
} }
@Override
public void validatePostTarget(PostTargetEnum name, Long serverId) {
if(!postTargetUsableInServer(name, serverId)) {
throw new PostTargetNotUsableException(name.getKey());
}
}
@Override
public boolean postTargetUsableInServer(PostTargetEnum name, Long serverId) {
Optional<PostTarget> postTargetOptional = getPostTarget(name, serverId);
return postTargetOptional.isPresent() && !postTargetOptional.get().getDisabled();
}
@Override @Override
public List<PostTarget> getPostTargets(AServer server) { public List<PostTarget> getPostTargets(AServer server) {
return postTargetManagement.getPostTargetsInServer(server); return postTargetManagement.getPostTargetsInServer(server);
@@ -231,4 +294,16 @@ public class PostTargetServiceBean implements PostTargetService {
}); });
return postTargets; return postTargets;
} }
@Override
public void disablePostTarget(String name, Long serverId) {
PostTarget postTarget = postTargetManagement.getPostTarget(name, serverId);
postTarget.setDisabled(true);
}
@Override
public void enablePostTarget(String name, Long serverId) {
PostTarget postTarget = postTargetManagement.getPostTarget(name, serverId);
postTarget.setDisabled(false);
}
} }

View File

@@ -41,7 +41,13 @@ public class PostTargetManagementBean implements PostTargetManagement {
throw new PostTargetNotValidException(name, defaultPostTargetManagementService.getDefaultPostTargetKeys()); throw new PostTargetNotValidException(name, defaultPostTargetManagementService.getDefaultPostTargetKeys());
} }
log.info("Creating post target {} pointing towards {} on server {}.", name, targetChannel.getId(), targetChannel.getServer().getId()); log.info("Creating post target {} pointing towards {} on server {}.", name, targetChannel.getId(), targetChannel.getServer().getId());
PostTarget createdPostTarget = PostTarget.builder().name(name).channelReference(targetChannel).serverReference(targetChannel.getServer()).build(); PostTarget createdPostTarget = PostTarget
.builder()
.name(name)
.channelReference(targetChannel)
.serverReference(targetChannel.getServer())
.disabled(false)
.build();
return postTargetRepository.save(createdPostTarget); return postTargetRepository.save(createdPostTarget);
} }
@@ -75,7 +81,8 @@ public class PostTargetManagementBean implements PostTargetManagement {
@Override @Override
public PostTarget getPostTarget(String name, AServer server) { public PostTarget getPostTarget(String name, AServer server) {
return postTargetRepository.findPostTargetByNameAndServerReference(name, server).orElseThrow(() -> new PostTargetNotFoundException(name)); return postTargetRepository.findPostTargetByNameAndServerReference(name, server)
.orElseThrow(() -> new PostTargetNotFoundException(name));
} }
@Override @Override

View File

@@ -7,4 +7,5 @@
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd"> http://www.liquibase.org/xml/ns/pro dbchangelog.xsd">
<include file="seedData/data.xml" relativeToChangelogFile="true"/> <include file="seedData/data.xml" relativeToChangelogFile="true"/>
<include file="tables/tables.xml" relativeToChangelogFile="true"/>
</databaseChangeLog> </databaseChangeLog>

View File

@@ -0,0 +1,23 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<property name="coreFeature" value="(SELECT id FROM feature WHERE key = 'core')"/>
<property name="channelsModule" value="(SELECT id FROM module WHERE name = 'channels')"/>
<changeSet author="Sheldan" id="disable_posttarget-command" >
<insert tableName="command">
<column name="name" value="disablePosttarget"/>
<column name="module_id" valueComputed="${channelsModule}"/>
<column name="feature_id" valueComputed="${coreFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="enablePosttarget"/>
<column name="module_id" valueComputed="${channelsModule}"/>
<column name="feature_id" valueComputed="${coreFeature}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -7,4 +7,5 @@
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" > http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="confirmationCleanupJob.xml" relativeToChangelogFile="true"/> <include file="confirmationCleanupJob.xml" relativeToChangelogFile="true"/>
<include file="command.xml" relativeToChangelogFile="true"/>
</databaseChangeLog> </databaseChangeLog>

View File

@@ -0,0 +1,14 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="post_target-addDisabled">
<addColumn tableName="posttarget">
<column name="disabled" type="BOOLEAN" defaultValueBoolean="false"/>
</addColumn>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,10 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="post_target.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,27 @@
package dev.sheldan.abstracto.core.exception;
import dev.sheldan.abstracto.core.models.exception.PostTargetNotValidExceptionModel;
import dev.sheldan.abstracto.core.templating.Templatable;
public class PostTargetNotUsableException extends AbstractoRunTimeException implements Templatable {
private final PostTargetNotValidExceptionModel model;
public PostTargetNotUsableException(String key) {
super("The post target is not setup or has been disabled.");
this.model = PostTargetNotValidExceptionModel
.builder()
.postTargetKey(key)
.build();
}
@Override
public String getTemplateName() {
return "post_target_not_usable_exception";
}
@Override
public Object getTemplateModel() {
return model;
}
}

View File

@@ -36,6 +36,11 @@ public class PostTarget implements Serializable {
@Setter @Setter
private AServer serverReference; private AServer serverReference;
@Column(name = "disabled")
@Getter
@Setter
private Boolean disabled;
@Column(name = "created", nullable = false, insertable = false, updatable = false) @Column(name = "created", nullable = false, insertable = false, updatable = false)
private Instant created; private Instant created;

View File

@@ -12,4 +12,5 @@ import net.dv8tion.jda.api.entities.TextChannel;
public class PostTargetModelEntry { public class PostTargetModelEntry {
private PostTarget postTarget; private PostTarget postTarget;
private TextChannel channel; private TextChannel channel;
private Boolean disabled;
} }

View File

@@ -1,5 +1,7 @@
package dev.sheldan.abstracto.core.models.template.display; package dev.sheldan.abstracto.core.models.template.display;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.utils.MemberUtils;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@@ -21,4 +23,13 @@ public class MemberDisplay {
.userId(member.getIdLong()) .userId(member.getIdLong())
.build(); .build();
} }
public static MemberDisplay fromAUserInAServer(AUserInAServer aUserInAServer) {
return MemberDisplay
.builder()
.memberMention(MemberUtils.getAUserInAServerAsMention(aUserInAServer))
.serverId(aUserInAServer.getServerReference().getId())
.userId(aUserInAServer.getUserReference().getId())
.build();
}
} }

View File

@@ -26,7 +26,11 @@ public interface PostTargetService {
void throwIfPostTargetIsNotDefined(PostTargetEnum name, Long serverId); void throwIfPostTargetIsNotDefined(PostTargetEnum name, Long serverId);
boolean postTargetDefinedInServer(PostTargetEnum name, Long serverId); boolean postTargetDefinedInServer(PostTargetEnum name, Long serverId);
boolean validPostTarget(String name); boolean validPostTarget(String name);
void validatePostTarget(PostTargetEnum name, Long serverId);
boolean postTargetUsableInServer(PostTargetEnum name, Long serverId);
List<PostTarget> getPostTargets(AServer server); List<PostTarget> getPostTargets(AServer server);
List<String> getAvailablePostTargets(); List<String> getAvailablePostTargets();
List<String> getPostTargetsOfEnabledFeatures(AServer server); List<String> getPostTargetsOfEnabledFeatures(AServer server);
void disablePostTarget(String name, Long serverId);
void enablePostTarget(String name, Long serverId);
} }

View File

@@ -0,0 +1,9 @@
package dev.sheldan.abstracto.core.utils;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
public class MemberUtils {
public static String getAUserInAServerAsMention(AUserInAServer aUserInAServer) {
return "<@" + aUserInAServer.getUserReference().getId() + ">";
}
}

View File

@@ -49,6 +49,12 @@ Changing a post target::
* Description: Changes the given post target identified by `key` to `channel`. All messages using this post target will be sent to this channel from now on. * Description: Changes the given post target identified by `key` to `channel`. All messages using this post target will be sent to this channel from now on.
If neither `key` nor `channel` is given, this will print the currently available post targets and the channels they point to, if set. If neither `key` nor `channel` is given, this will print the currently available post targets and the channels they point to, if set.
* Example: `posttarget banLog #general` to log the bans in the #general channel. * Example: `posttarget banLog #general` to log the bans in the #general channel.
Disabling a post target::
* Usage: `disablePostTarget <key>`
* Description: Disables the post target identified by `key` to not send any messages towards. Some features require a post target to be enabled, and have the option to throw an exception, others might just ignore it.
Enabling a post target::
* Usage: `enablePostTarget <key>`
* Description: Enables the post target identified by `key` to not send any messages towards.
Changing admin mode:: Changing admin mode::
* Usage: `setAdminMode <true/false>` * Usage: `setAdminMode <true/false>`
* Description: Changes the admin modes on this server to the given value. Admin mode means, that **all** commands in the current server, can only be executed by members who have the ADMINISTRATOR permission. * Description: Changes the admin modes on this server to the given value. Admin mode means, that **all** commands in the current server, can only be executed by members who have the ADMINISTRATOR permission.

View File

@@ -252,3 +252,16 @@ This functionality will automatically mute a member who mentions more than a con
==== Relevant system configuration ==== Relevant system configuration
`massPingMinLevel`:: The level at which members are allowed to mass ping and not get muted. `massPingMinLevel`:: The level at which members are allowed to mass ping and not get muted.
=== Tracking general infractions
Feature key `infractions`
This functionality just behaves to track general infractions of users, be it through the means of warnings or mutes. Currently, its very limited and only can be used to configure levels of infractions and certain points for various infractions, which will be tracked and stored.
==== Post targets
`infractionNotification`:: target for notifications of infraction level changes
==== Relevant system configuration
`infractionLevels`:: The amount of infraction levels which should be possible to configure
`infractionLevel`:: This system config key acts as a prefix up until the amount of infraction levels. With this you can configure the amount of points necessary to reach the given level: For example `infractionLevel2` would be the amount of points necessary to reach level 2. These levels are not enforced to be ordered nor if all levels have a value assigned to it. Any level evaluation will stop at the first level not defined.