[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.templating.model.MessageToSend;
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.LoggingPostTarget;
import dev.sheldan.abstracto.logging.model.template.MemberJoinLogModel;
@@ -43,7 +44,11 @@ public class JoinLogger implements AsyncJoinListener {
.build();
log.debug("Logging join event for user {} in server {}.", listenerModel.getMember().getIdLong(), 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;
}

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.templating.model.MessageToSend;
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.LoggingPostTarget;
import lombok.extern.slf4j.Slf4j;
@@ -46,7 +47,11 @@ public class LeaveLogger implements AsyncLeaveListener {
.build();
log.debug("Logging leave event for user {} in server {}.", listenerModel.getUser().getIdLong(), 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;
}
}

View File

@@ -85,7 +85,8 @@ public class MessageDeleteLogListener implements AsyncMessageDeletedListener {
.member(authorMember)
.build();
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);
return null;
});
@@ -102,7 +103,7 @@ public class MessageDeleteLogListener implements AsyncMessageDeletedListener {
.build();
MessageToSend attachmentEmbed = templateService.renderEmbedTemplate(MESSAGE_DELETED_ATTACHMENT_TEMPLATE, attachmentLogModel, messageFromCache.getServerId());
FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(attachmentEmbed, LoggingPostTarget.DELETE_LOG, messageFromCache.getServerId()))
.exceptionally(throwable -> {
.exceptionally(throwable -> {
log.error("Failed to send message deleted log.", throwable);
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.templating.model.MessageToSend;
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.LoggingPostTarget;
import dev.sheldan.abstracto.logging.model.template.MessageDeletedAttachmentLog;
@@ -65,7 +66,11 @@ public class MessageEditedListener implements AsyncMessageUpdatedListener {
.member(messageAfter.getMember())
.build();
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) {
log.info("Attachment count changed. Old {}, new {}.", attachmentCountBefore, attachmentCountAfter);
@@ -75,7 +80,7 @@ public class MessageEditedListener implements AsyncMessageUpdatedListener {
).collect(Collectors.toList());
log.debug("Logging deletion of {} attachments.", removedAttachments.size());
for (int i = 0; i < removedAttachments.size(); i++) {
MessageDeletedAttachmentLog log = MessageDeletedAttachmentLog
MessageDeletedAttachmentLog attachmentModel = MessageDeletedAttachmentLog
.builder()
.imageUrl(removedAttachments.get(i).getProxyUrl())
.counter(i + 1)
@@ -84,8 +89,12 @@ public class MessageEditedListener implements AsyncMessageUpdatedListener {
.member(messageAfter.getMember())
.build();
MessageToSend attachmentEmbed = templateService.renderEmbedTemplate(MESSAGE_EDITED_ATTACHMENT_REMOVED_TEMPLATE,
log, messageBefore.getServerId());
postTargetService.sendEmbedInPostTarget(attachmentEmbed, LoggingPostTarget.DELETE_LOG, messageBefore.getServerId());
attachmentModel, 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;

View File

@@ -39,7 +39,7 @@ public class MyWarnings extends AbstractConditionableCommand {
public CommandResult execute(CommandContext commandContext) {
MyWarningsModel model = (MyWarningsModel) ContextConverter.fromCommandContext(commandContext, MyWarningsModel.class);
AUserInAServer userInAServer = userInServerManagementService.loadOrCreateUser(commandContext.getAuthor());
Long currentWarnCount = warnManagementService.getActiveWarnsForUser(userInAServer);
Long currentWarnCount = warnManagementService.getActiveWarnCountForUser(userInAServer);
model.setCurrentWarnCount(currentWarnCount);
Long totalWarnCount = warnManagementService.getTotalWarnsForUser(userInAServer);
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 countByWarnedUserAndDecayedFalse(AUserInAServer aUserInAServer);
List<Warning> findByWarnedUserAndDecayedFalse(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 java.time.Duration;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
@@ -139,20 +138,14 @@ public class BanServiceBean implements BanService {
}
public CompletableFuture<Void> sendBanLogMessage(BanLog banLog, Long guildId, String template) {
CompletableFuture<Void> completableFuture;
MessageToSend banLogMessage = templateService.renderEmbedTemplate(template, banLog, guildId);
log.debug("Sending ban log message in guild {}.", guildId);
List<CompletableFuture<Message>> notificationFutures = postTargetService.sendEmbedInPostTarget(banLogMessage, ModerationPostTarget.BAN_LOG, guildId);
completableFuture = FutureUtils.toSingleFutureGeneric(notificationFutures);
return completableFuture;
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(banLogMessage, ModerationPostTarget.BAN_LOG, guildId));
}
public CompletableFuture<Void> sendUnBanLogMessage(UnBanLog banLog, Long guildId, String template) {
CompletableFuture<Void> completableFuture;
MessageToSend banLogMessage = templateService.renderEmbedTemplate(template, banLog, guildId);
log.debug("Sending unban log message in guild {}.", guildId);
List<CompletableFuture<Message>> notificationFutures = postTargetService.sendEmbedInPostTarget(banLogMessage, ModerationPostTarget.UN_BAN_LOG, guildId);
completableFuture = FutureUtils.toSingleFutureGeneric(notificationFutures);
return completableFuture;
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(banLogMessage, ModerationPostTarget.UN_BAN_LOG, guildId));
}
}

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) {
CompletableFuture<Void> completableFuture;
MessageToSend warnLogMessage = templateService.renderEmbedTemplate(KICK_LOG_TEMPLATE, kickLogModel, 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 completableFuture;
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(warnLogMessage, ModerationPostTarget.KICK_LOG, kickLogModel.getGuild().getIdLong()));
}
}

View File

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

View File

@@ -100,9 +100,13 @@ public class ReactionReportServiceBean implements ReactionReportService {
@Transactional
public void createReactionReportInDb(CachedMessage cachedMessage, Message reportMessage, ServerUser reporter) {
log.info("Creation reaction report in message {} about message {} in database.", reportMessage.getIdLong(), cachedMessage.getMessageId());
reactionReportManagementService.createReactionReport(cachedMessage, reportMessage);
updateModerationUserReportCooldown(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());
reactionReportManagementService.createReactionReport(cachedMessage, reportMessage);
updateModerationUserReportCooldown(reporter);
}
}
@Transactional

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.moderation.service;
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.ServerUser;
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.moderation.config.feature.ModerationFeatureDefinition;
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.WarningMode;
import dev.sheldan.abstracto.moderation.config.posttarget.WarnDecayPostTarget;
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.template.command.WarnContext;
import dev.sheldan.abstracto.moderation.model.template.command.WarnNotification;
import dev.sheldan.abstracto.moderation.model.template.job.WarnDecayLogModel;
import dev.sheldan.abstracto.moderation.model.template.job.WarnDecayWarning;
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.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
@@ -85,6 +90,18 @@ public class WarnServiceBean implements WarnService {
@Autowired
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_NOTIFICATION_TEMPLATE = "warn_notification";
public static final String WARNINGS_COUNTER_KEY = "WARNINGS";
@@ -100,22 +117,34 @@ public class WarnServiceBean implements WarnService {
Member warningMember = context.getMember();
Guild guild = warnedMember.getGuild();
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());
List<CompletableFuture<Message>> futures = new ArrayList<>();
List<CompletableFuture> futures = new ArrayList<>();
futures.add(messageService.sendMessageToUser(warnedMember.getUser(), warnNotificationMessage));
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());
futures.addAll(postTargetService.sendEmbedInPostTarget(message, WarningPostTarget.WARN_LOG, context.getGuild().getIdLong()));
return FutureUtils.toSingleFutureGeneric(futures);
return FutureUtils.toSingleFuture(futures);
}
@Override
public CompletableFuture<Void> warnUserWithLog(WarnContext context) {
return notifyAndLogFullUserWarning(context).thenAccept(aVoid ->
self.persistWarning(context)
);
return notifyAndLogFullUserWarning(context)
.thenAccept(aVoid -> self.persistWarning(context));
}
@Transactional
@@ -124,8 +153,15 @@ public class WarnServiceBean implements WarnService {
context.getWarnId(), context.getGuild().getId(), context.getWarnedMember().getId(), context.getMember().getId());
AUserInAServer warnedUser = userInServerManagementService.loadOrCreateUser(context.getWarnedMember());
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
@@ -235,10 +271,13 @@ public class WarnServiceBean implements WarnService {
}
@Override
public void decayWarning(Warning warning, Instant now) {
log.debug("Decaying warning {} in server {} with date {}.", warning.getWarnId().getId(), warning.getWarnId().getServerId(), now);
warning.setDecayDate(now);
public void decayWarning(Warning warning, Instant decayDate) {
log.debug("Decaying warning {} in server {} with date {}.", warning.getWarnId().getId(), warning.getWarnId().getServerId(), decayDate);
warning.setDecayDate(decayDate);
warning.setDecayed(true);
if(warning.getInfraction() != null) {
infractionService.decayInfraction(warning.getInfraction());
}
}
private CompletableFuture<Void> logDecayedWarnings(AServer server, List<Warning> warningsToDecay) {
@@ -307,8 +346,7 @@ public class WarnServiceBean implements WarnService {
.warnings(warnDecayWarnings)
.build();
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(messageFutures);
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, WarnDecayPostTarget.DECAY_LOG, server.getId()));
}
@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
public Long getActiveWarnsForUser(AUserInAServer aUserInAServer) {
public Long getActiveWarnCountForUser(AUserInAServer aUserInAServer) {
return warnRepository.countByWarnedUserAndDecayedFalse(aUserInAServer);
}
@Override
public List<Warning> getActiveWarnsForUser(AUserInAServer aUserInAServer) {
return warnRepository.findByWarnedUserAndDecayedFalse(aUserInAServer);
}
@Override
public Optional<Warning> findByIdOptional(Long id, Long 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
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="warn-decay-job-update_schedule">
<update tableName="scheduler_job">
<column name="cron_expression" value="0 0 0 * * ?"/>
<where>name='warnDecayJob'</where>
</update>
<changeSet author="Sheldan" id="infraction_feature-insertion">
<insert tableName="feature">
<column name="key" value="infractions"/>
</insert>
</changeSet>
</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.16/collection.xml" relativeToChangelogFile="true"/>
<include file="1.3.4/collection.xml" relativeToChangelogFile="true"/>
<include file="1.3.9/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -7,6 +7,9 @@ abstracto.systemConfigs.reactionReportCooldownSeconds.longValue=300
abstracto.featureFlags.moderation.featureName=moderation
abstracto.featureFlags.moderation.enabled=false
abstracto.featureFlags.infractions.featureName=infractions
abstracto.featureFlags.infractions.enabled=false
abstracto.featureFlags.reportReactions.featureName=reportReactions
abstracto.featureFlags.reportReactions.enabled=false
@@ -42,6 +45,31 @@ abstracto.featureModes.warnDecayLogging.featureName=warnings
abstracto.featureModes.warnDecayLogging.mode=warnDecayLogging
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.mode=automaticWarnDecayLogging
abstracto.featureModes.automaticWarnDecayLogging.enabled=true

View File

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

View File

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

View File

@@ -97,7 +97,7 @@ public class WarnManagementServiceBeanTest {
public void testActiveWarnCountOfUser() {
Long count = 5L;
when(warnRepository.countByWarnedUserAndDecayedFalse(warnedUser)).thenReturn(count);
Long activeWarnsForUserCount = testUnit.getActiveWarnsForUser(warnedUser);
Long activeWarnsForUserCount = testUnit.getActiveWarnCountForUser(warnedUser);
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"),
USER_NOTES("userNotes"),
INVITE_FILTER("inviteFilter"),
REPORT_REACTIONS("reportReactions")
REPORT_REACTIONS("reportReactions"),
INFRACTIONS("infractions")
;
private final String key;

View File

@@ -15,6 +15,8 @@ import java.util.List;
@Component
public class WarningFeatureConfig implements FeatureConfig {
public static final String WARN_INFRACTION_POINTS = "warnInfractionPoints";
@Autowired
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
private ServerSpecificId warnId;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@MapsId("serverId")
@JoinColumn(name = "server_id", referencedColumnName = "id", nullable = false)
private AServer server;
@@ -90,4 +90,10 @@ public class Warning implements Serializable {
@Column(name = "updated", insertable = false, updatable = false)
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
*/
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);
List<Warning> getAllWarnsForUser(AUserInAServer aUserInAServer);
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);
Warning findById(Long id, Long serverId);
List<Warning> getWarningsViaId(List<Long> warnIds, Long serverId);

View File

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

View File

@@ -115,7 +115,12 @@ public class ProfanityFilterServiceBean implements ProfanityFilterService {
Long profanityRegexId = foundProfanityRegex.getId();
return FutureUtils.toSingleFutureGeneric(messageFutures).thenCompose(aVoid -> {
Message createdMessage = messageFutures.get(0).join();
return self.afterReportCreation(message, serverId, profanityRegexId, createdMessage);
if(createdMessage != null) {
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.models.ServerChannelMessage;
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.model.StarboardPostCreatedModel;
import dev.sheldan.abstracto.starboard.model.database.StarboardPost;
@@ -28,15 +26,6 @@ public class StarboardPostCreatedListenerManager {
@Qualifier("starboardCreatedListenerExecutor")
private TaskExecutor starboardCreatedExecutor;
@Autowired
private StarboardPostCreatedListenerManager self;
@Autowired
private FeatureConfigService featureConfigService;
@Autowired
private FeatureFlagService featureFlagService;
@Autowired
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.models.ServerChannelMessage;
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.model.StarboardPostDeletedModel;
import dev.sheldan.abstracto.starboard.model.database.StarboardPost;
@@ -28,15 +26,6 @@ public class StarboardPostDeletedListenerManager {
@Qualifier("starboardDeletedListenerExecutor")
private TaskExecutor starboardDeletedExecutor;
@Autowired
private StarboardPostDeletedListenerManager self;
@Autowired
private FeatureConfigService featureConfigService;
@Autowired
private FeatureFlagService featureFlagService;
@Autowired
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.starboard.config.StarboardFeatureConfig;
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.template.*;
import dev.sheldan.abstracto.starboard.service.management.StarboardPostManagementService;
@@ -102,6 +104,7 @@ public class StarboardServiceBean implements StarboardService {
@Override
public CompletableFuture<Void> createStarboardPost(CachedMessage message, List<AUserInAServer> userExceptAuthor, AUserInAServer userReacting, AUserInAServer starredUser) {
postTargetService.validatePostTarget(StarboardPostTarget.STARBOARD, message.getServerId());
Long starredUserId = starredUser.getUserInServerId();
List<Long> userExceptAuthorIds = userExceptAuthor.stream().map(AUserInAServer::getUserInServerId).collect(Collectors.toList());
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.starboard.config.StarboardFeatureConfig;
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.StarboardPostReaction;
import dev.sheldan.abstracto.starboard.model.template.*;
import dev.sheldan.abstracto.starboard.service.management.StarboardPostManagementService;
import dev.sheldan.abstracto.starboard.service.management.StarboardPostReactorManagementService;

View File

@@ -114,13 +114,14 @@ public class SuggestionServiceBean implements SuggestionService {
@Override
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())
.thenCompose(suggester -> self.createMessageWithSuggester(commandMessage, text, suggester));
}
@Transactional
public CompletableFuture<Void> createMessageWithSuggester(Message commandMessage, String text, Member suggester) {
postTargetService.validatePostTarget(SuggestionPostTarget.SUGGESTION, suggester.getGuild().getIdLong());
Long serverId = suggester.getGuild().getIdLong();
AServer server = serverManagementService.loadServer(serverId);
AUserInAServer userSuggester = userInServerManagementService.loadOrCreateUser(suggester);
@@ -223,6 +224,7 @@ public class SuggestionServiceBean implements SuggestionService {
@Transactional
public CompletableFuture<Void> setSuggestionToFinalState(Member executingMember, Long suggestionId, Message commandMessage, String text, SuggestionState state) {
Long serverId = commandMessage.getGuild().getIdLong();
postTargetService.validatePostTarget(SuggestionPostTarget.SUGGESTION, serverId);
Suggestion suggestion = suggestionManagementService.getSuggestion(serverId, suggestionId);
suggestionManagementService.setSuggestionState(suggestion, state);
cancelSuggestionReminder(suggestion);
@@ -340,6 +342,7 @@ public class SuggestionServiceBean implements SuggestionService {
@Transactional
public CompletableFuture<Void> remindAboutSuggestion(ServerSpecificId suggestionId) {
Long serverId = suggestionId.getServerId();
postTargetService.validatePostTarget(SuggestionPostTarget.SUGGESTION_REMINDER, serverId);
Suggestion suggestion = suggestionManagementService.getSuggestion(serverId, suggestionId.getId());
ServerChannelMessage suggestionServerChannelMessage = ServerChannelMessage
.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();
postTargets.forEach(target -> {
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);
});
List<String> postTargetConfigs = postTargetService.getPostTargetsOfEnabledFeatures(server);
postTargetConfigs.forEach(postTargetName -> {
if(postTargetEntries.stream().noneMatch(postTargetModelEntry -> postTargetModelEntry.getPostTarget().getName().equalsIgnoreCase(postTargetName))) {
PostTarget fakeEntry = PostTarget.builder().name(postTargetName).build();
PostTargetModelEntry postTargetEntry = PostTargetModelEntry.builder().postTarget(fakeEntry).build();
PostTarget fakeEntry = PostTarget
.builder()
.name(postTargetName)
.build();
PostTargetModelEntry postTargetEntry = PostTargetModelEntry
.builder()
.postTarget(fakeEntry)
.disabled(false)
.build();
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.exception.ChannelNotInGuildException;
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.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.PostTarget;
@@ -22,6 +23,7 @@ import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@@ -52,15 +54,25 @@ public class PostTargetServiceBean implements PostTargetService {
@Override
public CompletableFuture<Message> sendTextInPostTarget(String text, PostTarget target) {
log.debug("Sending text to post target {}.", target.getName());
return channelService.sendTextToAChannel(text, target.getChannelReference());
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());
return channelService.sendTextToAChannel(text, target.getChannelReference());
}
}
@Override
public CompletableFuture<Message> sendEmbedInPostTarget(MessageEmbed embed, PostTarget target) {
TextChannel textChannelForPostTarget = getTextChannelForPostTarget(target);
log.debug("Sending message embed to post target {}.", target.getName());
return channelService.sendEmbedToChannel(embed, textChannelForPostTarget);
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);
log.debug("Sending message embed to post target {}.", target.getName());
return channelService.sendEmbedToChannel(embed, textChannelForPostTarget);
}
}
private TextChannel getTextChannelForPostTarget(PostTarget target) {
@@ -81,110 +93,145 @@ public class PostTargetServiceBean implements PostTargetService {
@Override
public CompletableFuture<Message> sendTextInPostTarget(String text, PostTargetEnum postTargetEnum, Long serverId) {
PostTarget postTarget = getPostTarget(postTargetEnum, serverId);
return this.sendTextInPostTarget(text, postTarget);
Optional<PostTarget> postTargetOptional = getPostTarget(postTargetEnum, serverId);
if(!postTargetOptional.isPresent()) {
return CompletableFuture.completedFuture(null);
}
return this.sendTextInPostTarget(text, postTargetOptional.get());
}
@Override
public CompletableFuture<Message> sendEmbedInPostTarget(MessageEmbed embed, PostTargetEnum postTargetName, Long serverId) {
PostTarget postTarget = getPostTarget(postTargetName, serverId);
return this.sendEmbedInPostTarget(embed, postTarget);
Optional<PostTarget> postTargetOptional = getPostTarget(postTargetName, serverId);
if(!postTargetOptional.isPresent()) {
return CompletableFuture.completedFuture(null);
}
return this.sendEmbedInPostTarget(embed, postTargetOptional.get());
}
@Override
public CompletableFuture<Message> sendMessageInPostTarget(Message message, PostTargetEnum postTargetName, Long serverId) {
PostTarget postTarget = getPostTarget(postTargetName, serverId);
return sendMessageInPostTarget(message, postTarget);
Optional<PostTarget> postTargetOptional = getPostTarget(postTargetName, serverId);
if(!postTargetOptional.isPresent()) {
return CompletableFuture.completedFuture(null);
}
return sendMessageInPostTarget(message, postTargetOptional.get());
}
@Override
public CompletableFuture<Message> sendMessageInPostTarget(Message message, PostTarget target) {
log.debug("Send message {} towards post target {}.", message.getId(), target.getName());
return channelService.sendMessageToAChannel(message, target.getChannelReference());
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());
return channelService.sendMessageToAChannel(message, target.getChannelReference());
}
}
@Override
public List<CompletableFuture<Message>> sendEmbedInPostTarget(MessageToSend message, PostTargetEnum postTargetName, Long serverId) {
PostTarget postTarget = getPostTarget(postTargetName, serverId);
return this.sendEmbedInPostTarget(message, postTarget);
Optional<PostTarget> postTargetOptional = getPostTarget(postTargetName, serverId);
if(!postTargetOptional.isPresent()) {
return Arrays.asList(CompletableFuture.completedFuture(null));
}
return this.sendEmbedInPostTarget(message, postTargetOptional.get());
}
@Override
public List<CompletableFuture<Message>> sendEmbedInPostTarget(MessageToSend message, PostTarget target) {
TextChannel textChannelForPostTarget = getTextChannelForPostTarget(target);
log.debug("Send messageToSend towards post target {}.", target.getName());
return channelService.sendMessageToSendToChannel(message, textChannelForPostTarget);
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);
log.debug("Send messageToSend towards post target {}.", target.getName());
return channelService.sendMessageToSendToChannel(message, textChannelForPostTarget);
}
}
@Override
public List<CompletableFuture<Message>> editEmbedInPostTarget(Long messageId, MessageToSend message, PostTarget target) {
TextChannel textChannelForPostTarget = getTextChannelForPostTarget(target);
// always takes the first one, only applicable for this scenario
String messageText = message.getMessages().get(0);
if(StringUtils.isBlank(messageText)) {
log.debug("Editing embeds of message {} in post target {}.", messageId, target.getName());
return Arrays.asList(channelService.editEmbedMessageInAChannel(message.getEmbeds().get(0), textChannelForPostTarget, messageId));
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 {
log.debug("Editing message text and potentially text for message {} in post target {}.", messageId, target.getName());
return Arrays.asList(channelService.editTextMessageInAChannel(messageText, message.getEmbeds().get(0), textChannelForPostTarget, messageId));
TextChannel textChannelForPostTarget = getTextChannelForPostTarget(target);
// always takes the first one, only applicable for this scenario
String messageText = message.getMessages().get(0);
if(StringUtils.isBlank(messageText)) {
log.debug("Editing embeds of message {} in post target {}.", messageId, target.getName());
return Arrays.asList(channelService.editEmbedMessageInAChannel(message.getEmbeds().get(0), textChannelForPostTarget, messageId));
} else {
log.debug("Editing message text and potentially text for message {} in post target {}.", messageId, target.getName());
return Arrays.asList(channelService.editTextMessageInAChannel(messageText, message.getEmbeds().get(0), textChannelForPostTarget, messageId));
}
}
}
@Override
public List<CompletableFuture<Message>> editOrCreatedInPostTarget(Long messageId, MessageToSend messageToSend, PostTarget target) {
List<CompletableFuture<Message>> futures = new ArrayList<>();
TextChannel textChannelForPostTarget = getTextChannelForPostTarget(target);
CompletableFuture<Message> messageEditFuture = new CompletableFuture<>();
futures.add(messageEditFuture);
if(StringUtils.isBlank(messageToSend.getMessages().get(0).trim())) {
channelService.retrieveMessageInChannel(textChannelForPostTarget, messageId).thenAccept(message -> {
log.debug("Editing existing message {} when upserting message embeds in channel {} in server {}.",
messageId, textChannelForPostTarget.getIdLong(), textChannelForPostTarget.getGuild().getId());
messageService.editMessage(message, messageToSend.getEmbeds().get(0))
.queue(messageEditFuture::complete, messageEditFuture::completeExceptionally);
}).exceptionally(throwable -> {
log.debug("Creating new message when upserting message embeds for message {} in channel {} in server {}.",
messageId, textChannelForPostTarget.getIdLong(), textChannelForPostTarget.getGuild().getId());
sendEmbedInPostTarget(messageToSend, target).get(0)
.thenAccept(messageEditFuture::complete).exceptionally(innerThrowable -> {
log.error("Failed to send message to create a message.", innerThrowable);
messageEditFuture.completeExceptionally(innerThrowable);
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<>();
TextChannel textChannelForPostTarget = getTextChannelForPostTarget(target);
CompletableFuture<Message> messageEditFuture = new CompletableFuture<>();
futures.add(messageEditFuture);
if (StringUtils.isBlank(messageToSend.getMessages().get(0).trim())) {
channelService.retrieveMessageInChannel(textChannelForPostTarget, messageId).thenAccept(message -> {
log.debug("Editing existing message {} when upserting message embeds in channel {} in server {}.",
messageId, textChannelForPostTarget.getIdLong(), textChannelForPostTarget.getGuild().getId());
messageService.editMessage(message, messageToSend.getEmbeds().get(0))
.queue(messageEditFuture::complete, messageEditFuture::completeExceptionally);
}).exceptionally(throwable -> {
log.debug("Creating new message when upserting message embeds for message {} in channel {} in server {}.",
messageId, textChannelForPostTarget.getIdLong(), textChannelForPostTarget.getGuild().getId());
sendEmbedInPostTarget(messageToSend, target).get(0)
.thenAccept(messageEditFuture::complete).exceptionally(innerThrowable -> {
log.error("Failed to send message to create a message.", innerThrowable);
messageEditFuture.completeExceptionally(innerThrowable);
return null;
});
return null;
});
return null;
});
} else {
channelService.retrieveMessageInChannel(textChannelForPostTarget, messageId).thenAccept(message -> {
} else {
channelService.retrieveMessageInChannel(textChannelForPostTarget, messageId).thenAccept(message -> {
log.debug("Editing existing message {} when upserting message in channel {} in server {}.",
messageId, textChannelForPostTarget.getIdLong(), textChannelForPostTarget.getGuild().getId());
messageService.editMessage(message, messageToSend.getMessages().get(0), messageToSend.getEmbeds().get(0))
.queue(messageEditFuture::complete, messageEditFuture::completeExceptionally);
}).exceptionally(throwable -> {
log.debug("Creating new message when trying to upsert a message {} in channel {} in server {}.",
messageId, textChannelForPostTarget.getIdLong(), textChannelForPostTarget.getGuild().getId());
sendEmbedInPostTarget(messageToSend, target).get(0)
.thenAccept(messageEditFuture::complete).exceptionally(innerThrowable -> {
log.error("Failed to send message to create a message.", innerThrowable);
messageEditFuture.completeExceptionally(innerThrowable);
}).exceptionally(throwable -> {
log.debug("Creating new message when trying to upsert a message {} in channel {} in server {}.",
messageId, textChannelForPostTarget.getIdLong(), textChannelForPostTarget.getGuild().getId());
sendEmbedInPostTarget(messageToSend, target).get(0)
.thenAccept(messageEditFuture::complete).exceptionally(innerThrowable -> {
log.error("Failed to send message to create a message.", innerThrowable);
messageEditFuture.completeExceptionally(innerThrowable);
return null;
});
return null;
});
return null;
});
}
}
return futures;
return futures;
}
}
@Override
public List<CompletableFuture<Message>> editOrCreatedInPostTarget(Long messageId, MessageToSend messageToSend, PostTargetEnum postTargetName, Long serverId) {
PostTarget postTarget = getPostTarget(postTargetName, serverId);
return this.editOrCreatedInPostTarget(messageId, messageToSend, postTarget);
Optional<PostTarget> postTargetOptional = getPostTarget(postTargetName, serverId);
if(!postTargetOptional.isPresent()) {
return Arrays.asList(CompletableFuture.completedFuture(null));
}
return this.editOrCreatedInPostTarget(messageId, messageToSend, postTargetOptional.get());
}
@Override
public void throwIfPostTargetIsNotDefined(PostTargetEnum name, Long serverId) {
PostTarget postTarget = getPostTarget(name, serverId);
if(postTarget == null) {
Optional<PostTarget> postTargetOptional = getPostTarget(name, serverId);
if(!postTargetOptional.isPresent()) {
throw new PostTargetNotValidException(name.getKey(), defaultPostTargetManagementService.getDefaultPostTargetKeys());
}
}
@@ -196,12 +243,15 @@ public class PostTargetServiceBean implements PostTargetService {
@Override
public List<CompletableFuture<Message>> editEmbedInPostTarget(Long messageId, MessageToSend message, PostTargetEnum postTargetName, Long serverId) {
PostTarget postTarget = getPostTarget(postTargetName, serverId);
return editEmbedInPostTarget(messageId, message, postTarget);
Optional<PostTarget> postTargetOptional = getPostTarget(postTargetName, serverId);
if(!postTargetOptional.isPresent()) {
return Arrays.asList(CompletableFuture.completedFuture(null));
}
return editEmbedInPostTarget(messageId, message, postTargetOptional.get());
}
private PostTarget getPostTarget(PostTargetEnum postTargetEnum, Long serverId) {
return postTargetManagement.getPostTarget(postTargetEnum.getKey(), serverId);
private Optional<PostTarget> getPostTarget(PostTargetEnum postTargetEnum, Long serverId) {
return postTargetManagement.getPostTargetOptional(postTargetEnum.getKey(), serverId);
}
@Override
@@ -210,6 +260,19 @@ public class PostTargetServiceBean implements PostTargetService {
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
public List<PostTarget> getPostTargets(AServer server) {
return postTargetManagement.getPostTargetsInServer(server);
@@ -231,4 +294,16 @@ public class PostTargetServiceBean implements PostTargetService {
});
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());
}
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);
}
@@ -75,7 +81,8 @@ public class PostTargetManagementBean implements PostTargetManagement {
@Override
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

View File

@@ -7,4 +7,5 @@
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,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/pro dbchangelog.xsd" >
<include file="confirmationCleanupJob.xml" relativeToChangelogFile="true"/>
<include file="command.xml" relativeToChangelogFile="true"/>
</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
private AServer serverReference;
@Column(name = "disabled")
@Getter
@Setter
private Boolean disabled;
@Column(name = "created", nullable = false, insertable = false, updatable = false)
private Instant created;

View File

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

View File

@@ -1,5 +1,7 @@
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.Getter;
import lombok.Setter;
@@ -21,4 +23,13 @@ public class MemberDisplay {
.userId(member.getIdLong())
.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);
boolean postTargetDefinedInServer(PostTargetEnum name, Long serverId);
boolean validPostTarget(String name);
void validatePostTarget(PostTargetEnum name, Long serverId);
boolean postTargetUsableInServer(PostTargetEnum name, Long serverId);
List<PostTarget> getPostTargets(AServer server);
List<String> getAvailablePostTargets();
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.
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.
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::
* 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.

View File

@@ -251,4 +251,17 @@ This functionality will automatically mute a member who mentions more than a con
`massPingLog`:: target for notifications of automatic mutes
==== 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.